【问题标题】:Getting the exact position of a specific OpCode in a binary file获取二进制文件中特定操作码的确切位置
【发布时间】:2015-07-04 03:19:53
【问题描述】:

我们的计算机体系结构教授给了我们一个要求输入密码的示例程序。任务是在比较输入的密码并确定是否可以之后更改跳转操作码。 我编写了一个程序,它可以更改给定二进制文件中特定位置的任何字节。

这是密码程序的代码:

int main(int argc, char* argv[]){
    char *pw = "12441233";
    char pass[32];

    printf("Enter password:\n");

    scanf("%s", pass);

    if(strncmp(pass,pw,8))
    {
        printf("Password wrong\n");
        exit(-1);
    }
    printf("Welcome\n");
}

所以我在控制台输入$ objdump -d task 得到了这个:

...

400703:|| 48 8b 5d e8 || mov -0x18(%rbp),%rbx

400707:|| 64 48 33 1c 25 28 00 || xor %fs:0x28,%rbx

40070e:|| 00 00

400710:|| 74 05 || je 400717

400712:|| e8 29 fe ff ff || callq 400540 <__stack_chk_fail>

400717: || 48 83 c4 58 ||添加 $0x58,%rsp

40071b:|| 5b ||弹出 %rbx

...

74je 的字节,我想将 jne 更改为 75。 如何获取我的74 字节在相应二进制文件中的确切位置,以便将其更改为新值?

【问题讨论】:

  • 内存中的位置与文件中的位置关系不大。您知道 74 05 之前和之后的字节值。在文件中搜索这些值。
  • 我知道内存地址与它无关,这就是为什么我问我还应该做什么。我的更改程序需要它,它看起来像这样:changebyte -d POSITION BYTE OLDFILE NEWFILE。我想我通过二进制编辑器搜索功能找到了相应的字节,但是对于所需的解决方案,我需要一个可重现的 wy 来找到二进制文件中的位置。
  • 这是linux还是windows?我可以给你一个 windows 可执行文件的答案。
  • 这个特殊的问题是在 linux 系统上。不过,我很高兴听到 Windows 解决方案。

标签: c binary opcode objdump disassembly


【解决方案1】:

正如 Hans 所说,内存位置与它无关。您需要做的是找到字节(或字节)在文件中的位置。为此,一个非常好的反汇编程序(这将是 IDA-Pro)让您生活得非常轻松。遗憾的是,IDA 的价格曲线陡峭,因此我们将坚持使用您可以轻松获得的工具。

以下是在 Linux 机器上完成的,使用 gcc 版本 4.8.2 和 gdb 7.7 在 Ubuntu 14.04 上运行

  1. 使用gcc -g -ansi -pedantic -Wall 编译代码。不是真正的问题,但main 应该有一个返回值。

  2. 将程序加载到 gdb 中,在 main 处放置一个断点,然后运行程序。当我遇到断点时,我使用 gdb 的disass 命令得到了如下所示的反汇编列表:

       (gdb) disass
       Dump of assembler code for function main:
       0x000000000040067d <+0>:     push   %rbp
       0x000000000040067e <+1>:     mov    %rsp,%rbp
       0x0000000000400681 <+4>:     push   %rbx
       0x0000000000400682 <+5>:     sub    $0x58,%rsp
       0x0000000000400686 <+9>:     mov    %edi,-0x54(%rbp)
       0x0000000000400689 <+12>:    mov    %rsi,-0x60(%rbp)
    => 0x000000000040068d <+16>:    mov    %fs:0x28,%rax
       0x0000000000400696 <+25>:    mov    %rax,-0x18(%rbp)
       0x000000000040069a <+29>:    xor    %eax,%eax
            [ ... ]
       0x00000000004006cc <+79>:    mov    $0x8,%edx
       0x00000000004006d1 <+84>:    mov    %rcx,%rsi
       0x00000000004006d4 <+87>:    mov    %rax,%rdi
       0x00000000004006d7 <+90>:    callq  0x400520 <strncmp@plt>
       0x00000000004006dc <+95>:    test   %eax,%eax
       0x00000000004006de <+97>:    je     0x4006f4 <main+119>
       0x00000000004006e0 <+99>:    mov    $0x4007c0,%edi
       0x00000000004006e5 <+104>:   callq  0x400530 <puts@plt>
            [ ... ]
       0x0000000000400718 <+155>:   retq   
       End of assembler dump.
    
  3. 看起来很不祥,我知道,但请稍等片刻,环顾四周,注意一些事情; 一种。尖括号中的数字 (i.e. ) 给出了从函数开始处指令所在的字节数。在您的情况下,您需要的操作码是函数开头的 97 个字节。
    湾。前 9 个字节(前四个指令)通常看起来像我们这里的内容,它们是函数的序言,它们正在为函数设置激活(或堆栈)帧。

  4. 现在,我们需要找到 main 在您的可执行文件中的位置。这往往是操作系统和编译器特定的。在我的情况下,在 Linux 主机上工作,我知道代码存储在文件的 .text 部分中,我可以使用工具readelf 来获取该位置,如下所示;

    readelf --wide -S task
    There are 35 section headers, starting at offset 0x1478:
    
    Section Headers:
      [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
      [ 1] .interp           PROGBITS        0000000000400238 000238 00001c 00   A  0   0  1
      [ 2] .note.ABI-tag     NOTE            0000000000400254 000254 000020 00   A  0   0  4
      [ 3] .note.gnu.build-id NOTE            0000000000400274 000274 000024 00   A  0   0  4
      [ 4] .gnu.hash         GNU_HASH        0000000000400298 000298 00001c 00   A  5   0  8
      [ 5] .dynsym           DYNSYM          00000000004002b8 0002b8 0000c0 18   
                [ .... ]
      [13] .text             PROGBITS        0000000000400590 000590 000202 00  AX  0   0 16
      [14] .fini             PROGBITS        0000000000400794 000794 000009 00  AX  0   0  4
      [15] .rodata           PROGBITS        00000000004007a0 0007a0 000037 00   
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)
    
  5. 从上面我们可以看出,.text 部分将从内存位置 0x400590 开始,结束位置为 0x400792(基于 0x202 的大小)。此外,我们可以看到 .text 部分在文件中的偏移量为 0x590。回顾我们的反汇编,我们可以看到main 从 0x0040067d 开始,在 .text 的范围内(只是一个健全的检查)。

  6. 我们现在有了应用程序的入口点(.text部分的起始内存地址,如果我们从入口点减去main的起点,我们应该获取main相对于代码映射位置的偏移量(在内存中)。最后将此值添加到可执行文件中代码的偏移量应该给我们文件位置main:

    000067d: 5548 89e5 5348 83ec 5889 7dac 4889 75a0  UH..SH..X.}.H.u.
    000068d: 6448 8b04 2528 0000 0048 8945 e831 c048  dH..%(...H.E.1.H
    000069d: c745 b8a4 0740 00bf ad07 4000 e882 feff  .E...@....@.....
    00006ad: ff48 8d45 c048 89c6 bfbd 0740 00b8 0000  .H.E.H.....@....
    00006bd: 0000 e8ac feff ff48 8b4d b848 8d45 c0ba  .......H.M.H.E..
    00006cd: 0800 0000 4889 ce48 89c7 e844 feff ff85  ....H..H...D....
    00006dd: c074 14bf c007 4000 e846 feff ffbf ffff  .t....@..F......
    00006ed: ffff e88c feff ffbf cf07 4000 e832 feff  ..........@..2..
    00006fd: ff48 8b5d e864 4833 1c25 2800 0000 7405  .H.].dH3.%(...t.
    000070d: e82e feff ff48 83c4 585b 5d              .....H..X[]
    
  7. 作为一个快速的完整性检查,请记住我提到过 main 反汇编的前几个字节是相当样板的,如果我们查看它们并将它们组装到我们的机器中,我们会得到:

      push   %rbp             0x55
      mov    %rsp,%rbp        0x48 0x89 0xe5
      push   %rbx             0x53
      sub    $0x58,%rsp       0x48 0x83 0xec 0x58
      mov    %edi,-0x54(%rbp) 0x89 0x7d 0xac
    
  8. 鉴于上述字节序列与步骤 5 中的请求字节序列相匹配,可以相当安全地假设我们在可执行文件中找到了 main 的位置。现在剩下要做的就是将偏移量 0x6de 处的字节从 0x74 修改为 0x73。

注意IDA 确实有一个免费版本,但有许多限制,因此值得您花时间去尝试一下。

希望对您有所帮助, T

【讨论】:

  • 非常感谢,这让我明白了很多。再次感谢您非常好的解释。
【解决方案2】:

使用readelf 实用程序并查找LOAD 程序头。它们给出了地址objdump 显示给你的映射(VirtAddr,又名p_vaddr)和文件中的偏移量(PhysAddr,又名p_paddr)。

【讨论】:

    猜你喜欢
    • 2011-12-04
    • 1970-01-01
    • 2013-11-04
    • 2018-11-07
    • 2018-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-13
    相关资源
    最近更新 更多