这个程序如何知道存储这个字符串的确切位置?

How does this program know the exact location where this string is stored?

我用Radare2反汇编了一个C程序。在这个程序中有许多对 scanf 的调用,如下所示:

0x000011fe      488d4594       lea rax, [var_6ch]
0x00001202      4889c6         mov rsi, rax
0x00001205      488d3df35603.  lea rdi, [0x000368ff]       ; "%d" ; const char *format
0x0000120c      b800000000     mov eax, 0
0x00001211      e86afeffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
0x00001216      8b4594         mov eax, dword [var_6ch]
0x00001219      83f801         cmp eax, 1                  ; rsi ; "ELF\x02\x01\x01"
0x0000121c      740a           je 0x1228

此处 scanf 具有从行 lea rdi, [0x000368ff] 传递给它的字符串 "%d" 的地址。我假设 0x000368ff 是可执行文件中 "%d" 的位置,因为如果我在调试模式 (r2 -d ./exec) 中重新启动 Radare2,那么 lea rdi, [0x000368ff] 将被 lea rdi, [someMemoryAddress].

如果 lea rdi, [0x000368ff] 是文件中硬编码的内容,那么当 运行 时指令如何更改为实际内存地址?

完整的说明是

48 8d 3d f3 56 03 00

这条指令是字面意思

lea rdi, [rip + 0x000356f3]

使用 rip 相对寻址模式。执行指令时,指令指针rip的值为0x0000120c,因此rdi接收到想要的值0x000368ff

如果这不是真实地址,则您的程序可能是位置无关的可执行文件 (PIE),需要重新定位。由于地址是使用 rip-relative 寻址模式编码的,因此不需要重定位并且地址是正确的,无论二进制文件加载到哪里。

雷达在骗你,你看到的不是真正的指令,它已经为你简化了。

真正的指令是:

0x00001205    488d3df3560300    lea rdi, qword [rip + 0x356f3]
0x0000120c    b800000000        mov eax, 0

这是一个典型的位置无关lea。要使用的字符串存储在二进制文件中的偏移量 0x000368ff 处,但由于可执行文件与位置无关,因此需要在运行时计算实际地址。由于下一条指令在偏移量 0x0000120c 处,您知道,无论二进制文件加载到内存中的哪个位置,您想要的地址都将是 rip + (0x000368ff - 0x0000120c) = rip + 0x356f3,这就是您所看到的以上。

做静态分析时,由于Radare不知道二进制文件在内存中的基地址,所以简单计算0x0000120c + 0x356f3 = 0x000368ff。这使得逆向工程更容易,但可能会造成混淆,因为真正的指令是不同的。


例如,以下程序:

int main(void) {
    puts("Hello world!");
}

编译时产生:

  6b4:   48 8d 3d 99 00 00 00    lea    rdi,[rip+0x99] 
  6bb:   e8 a0 fe ff ff          call   560 <puts@plt>

所以 rip + 0x99 = 0x6bb + 0x99 = 0x754,如果我们看一下二进制文件中的偏移量 0x754 hd:

$ hd -s 0x754 -n 16 a.out
00000754  48 65 6c 6c 6f 20 77 6f  72 6c 64 21 00 00 00 00  |Hello world!....|
00000764