反汇编看 x=5 的原因

今天在群上看见了下面这样一个程序,说 x 能打印出 0这个值来,刚开始以为是简单的溢出,后面仔细一看和我以前的溢出猜想不一样,虽然这个技术hacker早就熟得都烂了,但是我一直都只是知道原理,没有亲手去调试过,今天提前完成了上班的任务,所以调试一下,使用的工具有gcc,objdump,gdb

returnadress.c

#include<stdio.h>

void function(void)
{
    char buf[9];

    int* ret;

    //ret =  buf + 49;
    ret =  buf + 28;
   
    printf(" *ret = %d/n", *ret);
    (*ret) += 10;
}

int main(int argc, char* argv[])
{
    int x;

    x = 5;
    function();

    x = 2;
    printf(" printf x = %d/n", x);

    return 0;
}

 
我使用的gcc版本是gcc 版本 4.1.2 20070925 (Red Hat 4.1.2-33)
gcc -v这个命令可以查看,在不同的编译器上,打印出来的值应该不是5.

首先编译它,然后gdb 调试:
gcc -g -o retaddr returnadress.c

在我的编译器上执行./retaddr,结果是 5
为什么呢,常规的初学者思维应该说是在函数function中没有对x=2修改,即使修改了,在printf前面也将x赋值为2了阿。以前我思维的缓冲溢出程序,应该像我注释掉的那几行互换一样,并把x=2注释掉,向下面的代码一样,应该是5,这个相对好理解一点。

如果把注释的代码换换输出,程序如下:
#include<stdio.h>

void function(void)
{
    char buf[9];

    int* ret;

    ret =  buf + 49;
    //ret =  buf + 17;
   
    *ret = 5;
    //printf(" *ret = %d/n", *ret);
    (*ret) += 7;
}

int main(int argc, char* argv[])
{
    int x;

    x = 5;
    function();

    //x = 2;
    printf(" printf x = %d/n", x);

    return 0;
}

这里很好理解,利用数组缓冲溢出,确定我的编译器上buf的起始地址+49就是main函数中的x的地址。原理很简单关键在于调试的过程。
要是程序是buf + 28 然后再+7的那个,那么在我的编译器上输出的结果
printf x = 5

这是因为在调用函数前,先要保存函数的返回地址(将函数返回地址压栈),然后再去调用函数,buf + 28,就是函数的返回地址(关于为什么 buf + 28 是函数的返回地址,请参考我blog里的另外一篇文章《 毕业设计:linux入侵检测安全增强实现》),取到函数的返回地址以后,用这个语句(*ret) += 7跳过main函数中的 x = 1的赋值,直接去执行printf(" printf x = %d/n", x),所以打印出来的值就是main函数第一次对x赋的值5,第二次赋值被function里面的(*ret) += 7语句跳过了。所以打印出来的是5.

这里是上面的原理,原理比较简单,一看就明白了,关键是function函数里面的buf 应该加多少和 *ret应该加多少才能得到我们想要的结果?如何去确定这些数值呢?其实也不难,只是我以前不会,今天问了问,学会了操作,姬路下来,以后可以复习,也希望能帮组到看这篇文章的其他人:)

首先有几个基础的gdb命令:
第一个是设置断点:break 行号
例如: break 18
第二个是continue,让程序接着断点往下走
例如:continue
第三个打印值:print 变量
例如:print &buf
第四个显示行号左后的程序源码:L 行号
例如:L
     l 13
第五个是开始运行程序
例如:run


我现在编译程序,若想gdb能反汇编,需要加上-g选项给gcc
gcc -g -o retaddr retaddress.c
得出elf文件retaddr

执行程序,结果是:
[hongmy525@lhc laboratory]$ ./ret
 *ret = -204642304
 printf x = 2

这个结果不是我们想要的,因为他们没有给我们带来预想的惊喜。
我把它反汇编看看:

[hongmy525@lhc laboratory]$ gdb ret
GNU gdb Red Hat Linux (6.6-40.fc8rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) L 1
1       #include<stdio.h>
2
3       void function(void)
4       {
5               char buf[9];
6
7               int* ret;
8
9               //ret =  buf + 49;
10              ret =  buf + 28;
(gdb) break 9
Breakpoint 1 at 0x80483ca: file retaddr.c, line 9.
(gdb) run
Starting program: /home/hongmy525/laboratory/ret

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ba/4ea1118691c826426e9410cafb798f25cefad5.debug

Breakpoint 1, function () at retaddr.c:10
10              ret =  buf + 28;
(gdb) print &buf
$1 = (char (*)[9]) 0xbff0b4bb
(gdb) L 19
14      }
15
16      int main(int argc, char* argv[])
17      {
18              int x;
19
20              x = 5;
21              function();
22
23              x = 2;
(gdb) break 22
Breakpoint 2 at 0x804840d: file retaddr.c, line 22.
(gdb) continue
Continuing.
 *ret = -1074744105

Breakpoint 2, main () at retaddr.c:23
23              x = 2;
(gdb) print &x
$2 = (int *) 0xbff0b4f0


现在我们知道了function函数中的buf数组的地址$1 = (char (*)[9]) 0xbff0b4bb 和main函数中的变量x的地址$2 = (int *) 0xbff0b4f0。
    
         0xbff0 b4 f0
    ─    0xbff0 b4 bb
────────────────
                                           35
因为是16进制:3 × 16 + 5 = 53

buf的地址往上偏移 53 就能找到变量 x。于是我们可以在没有函数传参数的情况下在function函数中改变main函数的变量x的值。把程序修改如下:
#include<stdio.h>

void function(void)
{
    char buf[9];

    int* ret;

    //ret =  buf + 49;
    ret =  buf + 53;
   
    printf(" *ret = %d/n", *ret);
    (*ret) += 10;
}

int main(int argc, char* argv[])
{
    int x;

    x = 5;
    function();

    x = 2;
    printf(" printf x = %d/n", x);

    return 0;
}
程序的输出结果就是:
 *ret = 5
 printf x = 2


这里,我们已经得到了一个想要的结果,还差printf x ,要是printf x 也能如意的打印出5,那么前面的原理就能实现了。当然,这里我不是指同时打印出5.

如果说打印出*ret = 5是一道应用题,那么打印printf x = 5应该算一道小综合。现在我们分析一下该如何去求解我们的答案。

首先,我们需要整理一下思路。
一、找到返回地址,因为调用函数以前会将函数的返回地址压栈,我们首先需要找到main函数调用函数function之前的返回地址。

二、以&buf为基点,找到x=2的赋值语句地址(即是找到function函数的返回地址,这个地址压栈在main函数调用function之前) ret =  buf + ??;

三、跳过它·[ (*ret += ??) ]

这样,我们就能让printf x = 5了。

ok,let‘ go on.

[hongmy525@lhc laboratory]$ objdump -d ret

ret:     file format elf32-i386

Disassembly of section .init:

080483f7 <main>:
 80483f7:       8d 4c 24 04             lea    0x4(%esp),%ecx
 80483fb:       83 e4 f0                and    $0xfffffff0,%esp
 80483fe:       ff 71 fc                pushl  -0x4(%ecx)
 8048401:       55                      push   %ebp
 8048402:       89 e5                   mov    %esp,%ebp
 8048404:       51                      push   %ecx
 8048405:       83 ec 24                sub    $0x24,%esp
 8048408:       c7 45 f8 05 00 00 00    movl   $0x5,-0x8(%ebp)
 804840f:       e8 b0 ff ff ff          call   80483c4 <function>
 8048414:       c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%ebp)
 804841b:       8b 45 f8                mov    -0x8(%ebp),%eax
 804841e:       89 44 24 04             mov    %eax,0x4(%esp)
 8048422:       c7 04 24 1c 85 04 08    movl   $0x804851c,(%esp)
 8048429:       e8 ae fe ff ff          call   80482dc <printf@plt>
 804842e:       b8 00 00 00 00          mov    $0x0,%eax
 8048433:       83 c4 24                add    $0x24,%esp
 8048436:       59                      pop    %ecx
 8048437:       5d                      pop    %ebp
 8048438:       8d 61 fc                lea    -0x4(%ecx),%esp
 804843b:       c3                      ret    

8048414:       c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%ebp)
这里,就是我们要找的关键,下面我们继续。

[hongmy525@lhc laboratory]$ gdb ret
(gdb) break 12
Breakpoint 1 at 0x80483d3: file retaddr.c, line 12.
(gdb) break 22
Breakpoint 2 at 0x8048414: file retaddr.c, line 22.
(gdb) run
Starting program: /home/hongmy525/laboratory/ret

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ba/4ea1118691c826426e9410cafb798f25cefad5.debug

Breakpoint 1, function () at retaddr.c:12
12              printf(" *ret = %d/n", *ret);
(gdb) print &buf
$1 = (char (*)[9]) 0xbfd37adb
(gdb) x/50x 0xbfd37adb
0xbfd37adb:     0x04832000      0x00000008      0xd37b1000      0xd37b18bf
0xbfd37aeb:     0x048414bf      0xae3ff408      0xae21f800      0xd37b2800
0xbfd37afb:     0x048469bf      0x9bb6c508      0xd37bbc00      0xd37b28bf
0xbfd37b0b:     0xae3ff4bf      0x00000500      0xd37b3000      0xd37b88bf
0xbfd37b1b:     0x9a5390bf      0x98bca000      0x04845000      0xd37b8808
0xbfd37b2b:     0x9a5390bf      0x00000100      0xd37bb400      0xd37bbcbf
0xbfd37b3b:     0x98c810bf      0x00000000      0x00000100      0x00000100
0xbfd37b4b:     0x00000000      0xae3ff400      0x98bca000      0x00000000
0xbfd37b5b:     0xd37b8800      0xef8985bf      0xbf42fb0c      0x0000009e
0xbfd37b6b:     0x00000000      0x00000000      0x9838c000      0x9a52bd00
0xbfd37b7b:     0x98bfc000      0x00000100      0x0482f000      0x00000008
0xbfd37b8b:     0x04831100      0x0483f708      0x00000108      0xd37bb400
0xbfd37b9b:     0x048450bf      0x04844008

没有结果,因为我们没有4字节对齐吧?呵呵,OK,4字节对齐的再来一次(这个gdb命令是查看这个地址往后50×4=200个字节的内存中的存放内容)

(gdb) x/50x 0xbfd37adc
0xbfd37adc:     0x08048320      0x00000000      0xbfd37b10      0xbfd37b18
0xbfd37aec:     0x08048414      0x00ae3ff4      0x00ae21f8      0xbfd37b28
0xbfd37afc:     0x08048469      0x009bb6c5      0xbfd37bbc      0xbfd37b28
0xbfd37b0c:     0x00ae3ff4      0x00000005      0xbfd37b30      0xbfd37b88
0xbfd37b1c:     0x009a5390      0x0098bca0      0x08048450      0xbfd37b88
0xbfd37b2c:     0x009a5390      0x00000001      0xbfd37bb4      0xbfd37bbc
0xbfd37b3c:     0x0098c810      0x00000000      0x00000001      0x00000001
0xbfd37b4c:     0x00000000      0x00ae3ff4      0x0098bca0      0x00000000
0xbfd37b5c:     0xbfd37b88      0x0cef8985      0x9ebf42fb      0x00000000
0xbfd37b6c:     0x00000000      0x00000000      0x009838c0      0x009a52bd
0xbfd37b7c:     0x0098bfc0      0x00000001      0x080482f0      0x00000000
0xbfd37b8c:     0x08048311      0x080483f7      0x00000001      0xbfd37bb4
0xbfd37b9c:     0x08048450      0x08048440

很明显,我们找到了我们想要的东西
0xbfd37aec:     0x08048414      0x00ae3ff4      0x00ae21f8      0xbfd37b28
看见了吗?0x08048414!!哈哈,兴奋啦~~,有时这样的输出是以字节为单位的,因为x86是little-endian。这时找着就比较费眼睛了。

0x08048414 在0xbfd37aec 这个地址中,又该做减法了

        0xbfd37a ec
    ─    0xbfd37a db
──────────────
                     11

1 × 16 + 1 = 17

也就是说函数的返回地址距离&buf有17个字节,只有17个字节,哇~~~,太开心了,我给他加上就好了。

ret = buf +17;
这样,现在的ret指向的就是function函数的返回地址了。
我们看源码,程序返回以后要做的是赋值,那我们不想让他赋值,
804840f:       e8 b0 ff ff ff          call   80483c4 <function>
 8048414:       c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%ebp)
 804841b:       8b 45 f8                mov    -0x8(%ebp),%eax

于是我们跳过这条指令,
让ret指向804841b,

        0x080484 1b
    ─    0x080484 14
──────────────
                      7
(*ret)+= 7

这样,我们函数的返回地址+7,正好跳过赋值语句,执行printf(" printf x = %d/n", x);
显眼,这样打印出来的就是5,程序如下:

#include<stdio.h>

void function(void)
{
    char buf[9];

    int* ret;

    //ret =  buf + 49;
    ret =  buf + 17;

    printf(" *ret = %d/n", *ret);
    (*ret) += 7;
}

int main(int argc, char* argv[])
{
    int x;

    x = 5;
    function();

    x = 2;
    printf(" printf x = %d/n", x);

    return 0;
}

程序输出如下:
[hongmy525@lhc laboratory]$ ./ret
 *ret = 134513684
 printf x = 5
完工。由群鬼舞者发起讨论,罗琰指导,谢谢他们。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值