• 修改函数的返回地址


    这篇随笔源自今天看的这篇文章http://www.cnblogs.com/bluesea147/archive/2012/05/19/2508208.html

    1. 如何修改函数返回地址

    今天主要写测试程序思考和验证了一下这个问题,先看一下这个C程序

     1 #include <stdio.h>
     2 void foo(){
     3         int a, *p;
     4         p = (void*)((long)&a + 12);
     5         *p += 20;
     6 }
     7 int main(){
     8         foo();
     9         printf("First printf call\n");
    10         printf("Second printf call\n");
    11         printf("Third printf call\n");
    12         return 0;
    13 }

    在我的机子上运行这个程序,结果是:

    third printf call

    在foo返回后直接跳到了11去执行,这个程序和我看的那篇文章的程序稍有不同,主要是我的机子Intel64架构的,指针是用8个字节来表示的,主要不同在4行,long也是8个字节的,所以gcc没有做任何的warning,之前在一篇随笔中就提到过,c应该是在很早的时候就支持各种类型的指针的转换,因为这里是对指针指向的内容操作,所以用什么类型的指针都是可以的,这里是存储的是指令,所以就用(void *)了。 第4行代码是让p指向调用main调用foo()时压入栈的那个返回地址,改变这个地址的值让它指向一个语句的开头,就做到了更改函数的返回地址。

    怎么确定如何修改p中的值让它是另一个指令的合法地址呢,即指向另一个指令的开始,这就要用到objdump了,编译链接上面的程序生成可执行文件,然后objdump -d。得到下面的片段。

     1 00000000004004f4 <foo>:
     2   4004f4:    55                       push   %rbp
     3   4004f5:    48 89 e5                 mov    %rsp,%rbp
     4   4004f8:    48 8d 45 fc              lea    -0x4(%rbp),%rax
     5   4004fc:    48 83 c0 0c              add    $0xc,%rax
     6   400500:    48 89 45 f0              mov    %rax,-0x10(%rbp)
     7   400504:    48 8b 45 f0              mov    -0x10(%rbp),%rax
     8   400508:    8b 00                    mov    (%rax),%eax
     9   40050a:    8d 50 14                 lea    0x14(%rax),%edx
    10   40050d:    48 8b 45 f0              mov    -0x10(%rbp),%rax
    11   400511:    89 10                    mov    %edx,(%rax)
    12   400513:    5d                       pop    %rbp
    13   400514:    c3                       retq   
    14 
    15 0000000000400515 <main>:
    16   400515:    55                       push   %rbp
    17   400516:    48 89 e5                 mov    %rsp,%rbp
    18   400519:    b8 00 00 00 00           mov    $0x0,%eax
    19   40051e:    e8 d1 ff ff ff           callq  4004f4 <foo>                    # call foo
    20   400523:    bf 3c 06 40 00           mov    $0x40063c,%edi                  # printf("first..")
    21   400528:    e8 c3 fe ff ff           callq  4003f0 <puts@plt>
    22   40052d:    bf 4e 06 40 00           mov    $0x40064e,%edi                  # printf("second..")
    23   400532:    e8 b9 fe ff ff           callq  4003f0 <puts@plt>
    24   400537:    bf 61 06 40 00           mov    $0x400661,%edi                  # printf("third..")
    25   40053c:    e8 af fe ff ff           callq  4003f0 <puts@plt>
    26   400541:    b8 00 00 00 00           mov    $0x0,%eax
    27   400546:    5d                       pop    %rbp
    28   400547:    c3                       retq   
    29   400548:    90                       nop
    30   400549:    90                       nop
    31   40054a:    90                       nop
    32   40054b:    90                       nop
    33   40054c:    90                       nop
    34   40054d:    90                       nop
    35   40054e:    90                       nop
    36   40054f:    90                       nop

    call foo时压入的返回地址应该是20行的地址,0x400523,现在把这个值加20改到0x400537,就把返回值定位到了24行的指令,从上面也可以看出各条指令的大小,push是一个字节,而上面的mov带了参数也才5个字节。

    那如何确定调foo时压入栈的那个返回地址在存储器中的位置而好去修改它呢,现在看一下上面c程序中foo()函数对应的gas代码

     1 foo:
     2     pushq    %rbp
     3     movq    %rsp, %rbp
     4     leaq    -4(%rbp), %rax      # 取&a
     5     addq    $12, %rax           # &a+12
     6     movq    %rax, -16(%rbp)     # p存在-16(%rbp)中
     7     movq    -16(%rbp), %rax
     8     movl    (%rax), %eax
     9     leal    20(%rax), %edx      #  *p+20=>%edx
    10     movq    -16(%rbp), %rax     #   p=>%rax
    11     movl    %edx, (%rax)        #   %edx=>*p
    12     popq    %rbp
    13     ret

    从第4行可以看出a就存在栈最开始的4个字节中,a之上的肯字是入栈的 %rbp,这占8个字节, 而这之上的就是由main压入的返回地址,因此内存中返回地址的地址就是  &a+12。

    2。gdb的简单使用

    用gdb查看一下当汇编指令刚进入foo时栈顶的值,这个值应该要是调用foo后main中下条要执行的指令的地址。

    如图可以看到,在进入foo,执行 pushq %rbp前时,栈顶的值确实是main中调用foo之后地那个指令的地址,而我们所修改的也就是这个值。

    简单的说一说这里gdb的使用,在用gcc编译的时候带上-g才会把源代码的信息放在可执行文件中,如上面我是从汇编直接编译的,带上-g就会把汇编的源代码信息编进可执行码中,这样在gdb中才可以单步执行以及在该列出源码的时候列出源码。b是break的简写,打断点,可以指令某一行代码,某个函数,或某个地址(地址前加上*), 若指定一个函数,则在这个函数开始的代码执行前停住,gdb会列出下面一行要执行的代码,n是nexti的缩写,可以接一个参数表示执行的代码行数,这里我说是代码的行数,gdb确实是这么做的,我把一行放两个语句(用;分割),一个n也就执行过了,看来在debug的信息中,行是很重要的单位,n遇到subroutine call会直接当作一行代码跳过,而s(stepi)会进入到函数调用内部。上面有s进入到foo中,然后用x查看栈顶的内容, x是用来查看内存中内容的(examin memory),实际上x必须跟上一些信息表示你要查看多少个字节,因为地址只会指向一个字节,只用x的话,默认是上次用过的count和letter size, 图中的x实际上是 x/1xw, 而实际上因为地址用了8个字节来存,所以我应该用 x/1xg 的, b, h(half word), w(word), g(giant)分别表示1,2,4,8个字节,前面的数是count表示看几个,而中间那个x表示hex,以16进制显示,除了x还有a(address), t(binary), o(octal), d(decimal), i(instruction), c(char), s(string).  其中a(address)这个我在看虚表中内容的时候直接就把函数名给我显示出来了,很有用

    关于gdb,以后会深入的写一些

  • 相关阅读:
    linux命令
    牛顿法|阻尼牛顿法|拟牛顿法|DFP算法|BFGS算法|L-BFGS算法
    借One-Class-SVM回顾SMO在SVM中的数学推导--记录毕业论文5
    【转】白话经典算法系列之七 堆与堆排序
    volatie关键字
    【转】从输入网址到显示网页的全过程分析
    CSS和HTML的一些事
    JavaScript笔记梳理
    形状文法--建筑风格分类总结
    Baidu ECharts知识梳理
  • 原文地址:https://www.cnblogs.com/livingintruth/p/2594081.html
Copyright © 2020-2023  润新知