C/C++通过改变函数堆栈改变函数执行流程

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

returnadress.c
#include
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

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

       #include

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

               int* ret;

               //ret =  buf + 49;
              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

              ret =  buf + 28;

(gdb) print &buf
$1 = (char (*)[9]) 0xbff0b4bb
(gdb) L 19

      }

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

              x = 5;
             function();

              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 *) 0xbff0b4,

现在我们知道了 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
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 :
 80483f7:       8d 4c 24 04             lea    0x4(%esp),�x
 80483fb:       83 e4 f0                and    $0xfffffff0,%esp
 80483fe:       ff 71 fc                pushl  -0x4(�x)
 8048401:       55                      push   �p
 8048402:       89 e5                   mov    %esp,�p
 8048404:       51                      push   �x
 8048405:       83 ec 24                sub    $0x24,%esp
 8048408:       c7 45 f8 05 00 00 00    movl   $0x5,-0x8(�p)
 804840f:       e8 b0 ff ff ff          call   80483c4
 8048414: c7 45 f8 02 00 00 00    movl   $0x2,-0x8(�p)
 804841b:       8b 45 f8                mov    -0x8(�p),�x
 804841e:       89 44 24 04             mov    �x,0x4(%esp)
 8048422:       c7 04 24 1c 85 04 08    movl   $0x804851c,(%esp)
 8048429:       e8 ae fe ff ff          call   80482dc
 804842e:       b8 00 00 00 00          mov    $0x0,�x
 8048433:       83 c4 24                add    $0x24,%esp
 8048436:       59                      pop    �x
 8048437:       5d                      pop    �p
 8048438:       8d 61 fc                lea    -0x4(�x),%esp
 804843b:       c3                      ret   

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

[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

16 + 1 = 17

也就是说函数的返回地址距离 &buf有17个字节,只有17个字节,哇~~~,太开心了,我给他加上就好了。
ret = buf +17;
这样,现在的 ret指向的就是function函数的返回地址了。

我们看源码,程序返回以后要做的是赋值,那我们不想让他赋值,

804840f:       e8 b0 ff ff ff          call   80483c4
 8048414:       c7 45 f8 02 00 00 00    movl   $0x2,-0x8(�p)
 804841b:       8b 45 f8                mov    -0x8(�p),�x

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

 0x080484  1b
─ 0x080484 14
 ——————————————
            7

( *ret)+= 7

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

#include

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
发布了3 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37902078/article/details/80835951