【零基础学习PWN】GDB调试缓冲区溢出程序

admin 2024年3月18日15:53:25评论21 views字数 2499阅读8分19秒阅读模式
【零基础学习PWN】GDB调试缓冲区溢出程序

免责声明

【零基础学习PWN】GDB调试缓冲区溢出程序

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。

【零基础学习PWN】GDB调试缓冲区溢出程序

前言

【零基础学习PWN】GDB调试缓冲区溢出程序

本文为B站国资社畜大佬《你想有多PWN》视频教程的学习实践笔记,感兴趣的可以点击阅读原文去看视频教程。

【零基础学习PWN】GDB调试缓冲区溢出程序

缓冲区溢出

【零基础学习PWN】GDB调试缓冲区溢出程序
缓冲区溢出(Buffer Overflow)是编程中常见的安全漏洞之一,发生在当程序向一个缓冲区中写入数据超出其容量时。
缓冲区通常是内存中连续分配的一段空间,其大小在创建时就已确定。如果没有适当的边界检查,一个程序可能会允许数据超过缓冲区的实际大小,覆盖邻近内存空间的内容。这会导致程序崩溃、数据损坏或安全漏洞,比如允许攻击者执行任意代码。
【零基础学习PWN】GDB调试缓冲区溢出程序

GDB

【零基础学习PWN】GDB调试缓冲区溢出程序
GDB,全称GNU Debugger,是一个在多种Unix系统上广泛使用的强大的开源调试工具。GDB提供了对程序执行的详细控制,允许开发者调查程序执行时的内部行为,包括:
  • 启动程序,指定任何由程序解释的参数,并运行程序指定的环境变量。
  • 让已停止的程序在开发者设定的条件下继续运行。
  • 可以在自由设定的地点停止程序的执行。
  • 检查在程序停止时什么导致了停止。
  • 更改程序,可以测试其他执行路径,而不是重新启动并重新输入数据。
【零基础学习PWN】GDB调试缓冲区溢出程序

缓冲区溢出例子

【零基础学习PWN】GDB调试缓冲区溢出程序

给出一段教程中存在缓冲区溢出问题的代码:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>char sh[]="/bin/sh";int init_func(){    setvbuf(stdin,0,2,0);    setvbuf(stdout,0,2,0);    setvbuf(stderr,0,2,0);    return 0;}int func(char *cmd){        system(cmd);        return 0;}int main(){    char a[8] = {};    char b[8] = {};    //char a[1] = {'b'};        puts("input:");        gets(a);        printf(a);        if(b[0]=='a'){                func(sh);        }    return 0;}

可以使用gcc来编译:

gcc -o vuln -g question_1.c

【零基础学习PWN】GDB调试缓冲区溢出程序

【零基础学习PWN】GDB调试缓冲区溢出程序

开启调试之旅

【零基础学习PWN】GDB调试缓冲区溢出程序
  1. 输入以下命令进入gdb调试:

gdb ./vuln

【零基础学习PWN】GDB调试缓冲区溢出程序

2. 换成intel语法:

set disassembly-flavor intel

3. 输入以下指令你个找到main函数入口点:

disassemble main

【零基础学习PWN】GDB调试缓冲区溢出程序

4. 我们重点讲讲跟漏洞利用有关的汇编指令:

   0x00000000000012af <+58>:    lea    rax,[rbp-0x18]   0x00000000000012b3 <+62>:    mov    rdi,rax   0x00000000000012b6 <+65>:    mov    eax,0x0   0x00000000000012bb <+70>:    call   0x10e0 <gets@plt>

这几行代码是程序调用 gets(a)。因为 gets() 函数不检查输入长度,所以当输入超出变量 a 的大小时,会引起栈溢出。在这种情况下,溢出数据可以覆盖栈上的其他变量,甚至是返回地址。

    1. 首先将rbp寄存器的值减去0x18然后保存到rax寄存器中,这里指本地数组a的开始地址;

    2. 将rax寄存器的值复制到rdi寄存器中,rdi在x86_64系统V ABI调用约定中用于传递函数的第一个参数;

    3. 将eax寄存器的值清零,这条指令因为gets函数没有返回值因此不是必须的;

    4. 然后调用位于gets函数;

再看gets函数:

   0x00000000000012d1 <+92>:    movzx  eax,BYTE PTR [rbp-0x10]   0x00000000000012d5 <+96>:    cmp    al,0x61   0x00000000000012d7 <+98>:    jne    0x12e8 <main+115>

这几行代码是检查变量 b[0] 是否等于 'a'(ASCII码为 0x61)。如果是,则执行条件分支内的 func(sh)。通过栈溢出,可以控制 b[0] 的值,满足条件调用 system("/bin/sh") 获取shell。

    1. 它将字节(8位)位于rbp-0x10(即数组b的第一个元素)的内容移动到eax寄存器的低字节(al),其他字节填充为零;

    2. 比较aleax的低8位)和字符'a'的ASCII值(0x61)。这是逐字节比较操作,只考虑al中的内容;

    3. jne(跳转如果不相等)指令会检查前一个cmp指令的结果。如果al中的值不等于0x61,那么执行跳转到main+115处的代码。如果比较结果是al等于0x61,程序则不跳转,继续顺序执行后面的指令。

4. 理解了漏洞利用的方法,那么我们开始打断点,在gdb中,要通过break指令来打上断点,我们可以在地址main+58处打上断点,准备观察a数组和b数组的值:

b *main+58

5. 然后输入run启动程序,此时gdb就会main入口处:

【零基础学习PWN】GDB调试缓冲区溢出程序

6. 然后输入continue或者c就可以达到我们下的断点:

【零基础学习PWN】GDB调试缓冲区溢出程序

7. 现在,我们可以通过x指令来查看此时a数组和b数组的值:

x/gx $rbp-0x18x/gx $rbp-0x10

8. 此时均为零,为了更方便我们一次输出20字节的值

x/20bx $rbp-0x18

9. 此时0x7fffffffd718这一行就是a数组的值,0x7fffffffd720这一行就是b数组的值,然后我们下断点,经过gets函数再看看:

【零基础学习PWN】GDB调试缓冲区溢出程序

【零基础学习PWN】GDB调试缓冲区溢出程序

10. 可以看到此时a数组的前三位已经变成了0x36,转换为ascii也就是6,对应了我们输入的666,那么,我们只需要输入超过8个长度,而且第9个长度必须是a,就能覆盖b数组的第一个元素,进入shell,重点就是main+98这个地方会不会跳转,如果不会跳转说明会进入shell:

【零基础学习PWN】GDB调试缓冲区溢出程序

11. 我们在main+98处打上断点,然后试一试:

【零基础学习PWN】GDB调试缓冲区溢出程序

12. 现在到了断点处,我们之间一步一步走看会不会跳转:

n

【零基础学习PWN】GDB调试缓冲区溢出程序

13. 可以看到,没有跳转,并且即将进入shell,我们看看内存:

【零基础学习PWN】GDB调试缓冲区溢出程序

14. b数组的第一个元素是a,符合预期,接着我们就可以continue看看是不是最终get shell了:

【零基础学习PWN】GDB调试缓冲区溢出程序

15. 大功告成!

原文始发于微信公众号(赛博安全狗):【零基础学习PWN】GDB调试缓冲区溢出程序

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月18日15:53:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【零基础学习PWN】GDB调试缓冲区溢出程序http://cn-sec.com/archives/2584802.html

发表评论

匿名网友 填写信息