编辑ELF二进制文件中的调用指令

6

我正在尝试操作二进制调用函数。以下是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void myfunc2(char *str2, char *str1) {

    // enter code here
}

void myfunc(char *str2, char *str1)
{
    memcpy(str2 + strlen(str2), str1, strlen(str1));
}

int main(int argc, char **argv)
{
    char str1[4] = "tim";
    char str2[10] = "hello ";

    myfunc((char *)&str2, (char *)&str1);

    printf("%s\n", str2);
    
    myfunc2((char *)&str2, (char *)&str1);

    printf("%s\n", str2);

    return 0;
}

void myfunc2(char *str2, char *str1)
{
    memcpy(str2, str1, strlen(str1));
}

我已经编译好了程序,并使用readelf或objdump查看到我的两个函数在以下地址:

46: 000000000040072c 52 FUNC GLOBAL DEFAULT 13 myfunc2**

54: 000000000040064d 77 FUNC GLOBAL DEFAULT 13 myfunc**

通过命令objdump -D test(二进制文件的名称),可以看到main函数有两个callq函数调用。我试图编辑第一个函数,将其指向上述地址72c的myfunc2函数,但是这种方法行不通,会导致二进制文件失败。

000000000040069a <main>:
  40069a:   55                      push   %rbp
  40069b:   48 89 e5                mov    %rsp,%rbp
  40069e:   48 83 ec 40             sub    $0x40,%rsp
  4006a2:   89 7d cc                mov    %edi,-0x34(%rbp)
  4006a5:   48 89 75 c0             mov    %rsi,-0x40(%rbp)
  4006a9:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  4006b0:   00 00 
  4006b2:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4006b6:   31 c0                   xor    %eax,%eax
  4006b8:   c7 45 d0 74 69 6d 00    movl   $0x6d6974,-0x30(%rbp)
  4006bf:   48 b8 68 65 6c 6c 6f    movabs $0x206f6c6c6568,%rax
  4006c6:   20 00 00 
  4006c9:   48 89 45 e0             mov    %rax,-0x20(%rbp)
  4006cd:   66 c7 45 e8 00 00       movw   $0x0,-0x18(%rbp)
  4006d3:   48 8d 55 d0             lea    -0x30(%rbp),%rdx
  4006d7:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  4006db:   48 89 d6                mov    %rdx,%rsi
  4006de:   48 89 c7                mov    %rax,%rdi
  4006e1:   e8 67 ff ff ff          callq  40064d <myfunc>
  4006e6:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  4006ea:   48 89 c7                mov    %rax,%rdi
  4006ed:   e8 0e fe ff ff          callq  400500 <puts@plt>
  4006f2:   48 8d 55 d0             lea    -0x30(%rbp),%rdx
  4006f6:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  4006fa:   48 89 d6                mov    %rdx,%rsi
  4006fd:   48 89 c7                mov    %rax,%rdi
  400700:   e8 27 00 00 00          callq  40072c <myfunc2>
  400705:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  400709:   48 89 c7                mov    %rax,%rdi
  40070c:   e8 ef fd ff ff          callq  400500 <puts@plt>
  400711:   b8 00 00 00 00          mov    $0x0,%eax
  400716:   48 8b 4d f8             mov    -0x8(%rbp),%rcx
  40071a:   64 48 33 0c 25 28 00    xor    %fs:0x28,%rcx
  400721:   00 00 
  400723:   74 05                   je     40072a <main+0x90>
  400725:   e8 f6 fd ff ff          callq  400520 <__stack_chk_fail@plt>
  40072a:   c9                      leaveq 
  40072b:   c3                      retq 

我猜我需要通过相对位置计算地址信息或使用lea/mov指令来做些什么。
如果能帮忙学习如何修改调用函数,将不胜感激 - 请勿提供有关编辑字符串的指针,因为这些内容已经在互联网上的大部分教程中出现过了...

1
你还应该使用例如gcc -fverbose-asm -S编译你的C代码,然后查看生成的汇编代码。顺便问一下,你的确切问题是什么?你是否仔细阅读了callq x86机器指令的文档? - Basile Starynkevitch
3
为什么不直接写myfunc(str2, str1);,而要写成myfunc((char *)&str2, (char *)&str1);?这样不是更易读吗?我永远无法理解为什么有那么多人会在任何地方都进行类型转换的冲动。 - unwind
1
你还需要阅读x86-64 ABI规范。 - Basile Starynkevitch
1
请编辑您的问题以改进它。您确切要求的内容不清楚。“任何帮助”对于SO来说太广泛了! - Basile Starynkevitch
你不能只是“重写”地址。你需要知道callq指令的确切编码方式,并将新指令编码到其位置。如果新编码的指令恰好大小不同,那么你就没有那么幸运了 :-) - Blagovest Buyukliev
1个回答

10
为了重写地址,您需要知道callq指令的确切编码方式。
让我们来看看第一个调用的反汇编输出:
4006e1: e8 67 ff ff ff          callq  40064d <myfunc>
4006e6: ...

你可以清楚地看到该指令由5个字节编码。 e8 字节是指令操作码,67 ff ff ff 是要跳转到的地址。此时,一个人会问,67 ff ff ff0x40064d 有什么关系?

嗯,答案是 e8 编码了所谓的 "相对调用",跳转是相对于下一条指令的位置执行的。 您必须计算调用的函数与 4006e6 之间的距离,以便重写地址。 如果该调用是绝对的(ff),您只需将函数地址放入这4个字节中。

为了证明这一点,请考虑以下算术:

0x004006e6 + 0xffffff67 == 0x10040064d

好的,那么您之前说过,如果编码指令的大小不同,我可能就没有那么幸运了。这是相同的指令,那么可以吗?还是不行? - mcdoomington
在特定情况下,这不应该是一个问题 - 它可以很容易地再次使用5个字节进行编码。然而,在一般情况下,如果您需要用更大的指令替换指令,它将会破坏该指令以下的所有代码。它下面的指令可能使用PC相对寻址或call寻址,就像这个一样,并且它们所有的地址都将受到您引入的差异的影响。如果新指令较小,则可以用NOP填充结尾以解决此问题。 - Blagovest Buyukliev
好的,计算“相对位置”的意思是从主函数中myfunc调用到myfunc2的位置吗?目前这个对我来说有点含糊不清。 - mcdoomington
首先,找出“myfunc”和“myfunc2”的确切绝对地址(提示:40064d和40072c)。然后,找到要打补丁的指令位置。计算函数地址与调用后紧随其后的指令地址之间的差异。使用该差异来打补丁指令中的4个字节(这些字节必须按小端序排列)。 - Blagovest Buyukliev
非常感谢,我已经让它工作了。最后一个问题,假设我想调用一个位于比我当前位置更低的地址的函数。相对位置是有符号整数还是无符号整数(以便可以处理返回到堆栈顶部)? - mcdoomington
这是一个有符号整数,因此您应该能够向下跳转。 - Blagovest Buyukliev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接