「JarvisOJ」XMANlevel0题解

day6 for XMAN level0

一、解题思路

果然level0就是要比level4简单啊。当然level0毕竟是64位的。

checksec level0查看到栈不可执行,但是NO PIE,无金丝雀值[为栈溢出攻击提供客观条件]。

checksec level0

通过ida64反汇编发现string view中有“/bin/sh”且函数列表中有“system”函数,想办法调用!就可以获得shell了.[注意64位的可执行文件是不能用IDA32反汇编的,要用64位的才可以]

“/bin/sh”字符串+“system”函数

1、缓冲区大小

level0-main反汇编结果

栈溢出漏洞

对于read(0,rbp-0x80,0x200),read可读取并放入0x80大小的缓冲区的字符串可以有0x200字节。因此可以覆盖掉返回地址等栈区域。存在栈溢出攻击。

我们只需要在退栈的时候,将system函数首地址【因为system函数位于libc库,不是静态地址不变,要write+system_GOT[ELF-GOT可得]才可以动态得到system地址,但是level0应该不会那么难的;或者可以通过PLT表得到system_plt来调用system函数=elf.plt[‘system’],这也就是ELF-symbols获得,因为此时符号表中存储的是system_plt】布置在返回地址处就可以了!

但是64位机器下”/bin/sh”作为system的参数,却不像32位系统下布置在%ebp+8的位置,而是放在%edi中。所以不能将“/bin/sh”的首地址,覆盖%ebp+8来调用system(“/bin/sh”)。

注:在64位机器上参数的传递,前6个参数[从左向右数前6个]分别保存在rdi,rsi,rdx,rcx,r8和r9寄存器中,但是从第七个参数开始就要从右向左入栈[最后一个参数先入栈,最后入栈第7个参数]

现在有几种类似的方案:

  1. ret跳转到pop %edi+ret[这个ret调用了system函数,”/bin/sh”首地址放置在栈帧中,由pop放置到%edi处]
  2. ret跳转到pop %xxx + mov %xxx,%edi + ret
  3. ret跳转到mov “/bin/sh”首地址,%edi +ret
  4. ……

总之就是先让%edi放置好“/bin/sh”的首地址,再跳转/返回到/调用system函数去执行。

2.system的参数布置

(1)先去找找pop指令和ret指令

pop指令

没有pop %edi,失落。但是有%rsi,%rbp,%rbx,%r12,%r13,%r14,%r15。现在考虑看下mov %xxx,%edi。[%xxx可以在有pop指令的寄存器中选择,前面加粗的即是]

mov指令

乍一看,好失落,怎么只有一个寄存器%r15能转存到%rdi寄存器。而且这条mov %r15d,%edi指令[设置%rdi和设置%edi是一样的,因为/bin/sh的地址高4字节都为0]还不是和pop %r15连着的。

但是好像看到了熟悉的身影!

前面在ida的strings window里面有/bin/sh的地址:

.rodata:0000000000400684 00000008 C /bin/sh

这里恰好有一个mov $0x400684,%edi。满足了设置参数,但是看看ret指令,没有和0x40059a地址[bf 84 06 40 00 mov $0x400684,%edi]相连的ret指令。

这样看了ret到0x40059a执行mov “/bin/sh”首地址,%edi+ret,也不可行了。

怎么会这么巧,正好mov $0x400684,%edi,设置了/bin/sh呢?

我们去这条指令的地址0x40059a看一下!发现:

system函数调用

函数列表中静态的callsystem函数:

.text:000000000040059A mov edi, offset command ; “/bin/sh”

.text:000000000040059F call_system

这一段不就是system参数设置+调用吗!我们只需要将第一次缓冲区溢出后ret到地址0x000000000040059A[由于NO PIE,代码段地址不随机化,这个地址位于代码段[非libc库加入的.text,是静态的.text部分],所以这个指令的地址固定],就是可以实现system(“/bin/sh”)了。

静态system调用

注:其实这不是巧合,因为64位机器传参的特殊性,对于6个参数以下的函数估计都会在call前,先将参数mov到对应的寄存器。所以非栈中传参,都可在IDA查看,是否已经在call前的几条指令布置好。【只是可能是system(其他操作),不一定是system(“/bin/sh”)】

二、解题步骤

直接上脚本,注意:缓冲区大小为0x80,返回地址是8字节,不要再用p32而是p64

#!/usr/bin/python
from pwn import *
  
context(arch="amd64",os="linux")
re = remote("pwn2.jarvisoj.com", 9881) # set remote server as 're'

junk = 'a' * 0x80 # infillings, including 8B for rbp in stack 
replace_rbp = 'a' * 0x8 #fill old %ebp
syscall = 0x0000000000400596 #前面找到的system函数参数设置+调用的地址

payload = junk + replace_rbp + **p64**(syscall)

re.recvline() 
re.send(payload) # send payload for stuff  
re.interactive()

flag