《逆向工程权威指南》—第3章3.2节x86-64

简介:

本节书摘来自异步社区《逆向工程权威指南》一书中的第3章3.2节x86-64,作者【乌克兰】Dennis Yurichev(丹尼斯),更多章节内容可以访问云栖社区“异步社区”公众号查看。

**3.2 x86-64
3.2.1 MSVC-x86-64**
若用64位MSVC编译上述程序,则会得到下述指令。

指令清单3.7 MSVC 2012 x64

$SG2989  DB      'hello, world', 00H

main     PROC
         sub     rsp, 40
         lea     rcx, OFFSET FLAT:$SG2989
         call    printf
         xor     eax, eax
         add     rsp, 40
         ret     0
main     ENDP

在x86-64框架的CPU里,所有的物理寄存器都被扩展为64位寄存器。程序可通过R-字头的名称直接调用整个64位寄存器。为了尽可能充分地利用寄存器、减少访问内存数据的次数,编译器会充分利用寄存器传递函数参数(请参见64.3节的fastcall约定)。也就是说,编译器会优先使用寄存器传递部分参数,再利用内存(数据栈)传递其余的参数。Win64的程序还会使用RCX、RDX、R8、R9这4个寄存器来存放函数参数。我们稍后就会看到这种情况:printf()使用RCX寄存器传递参数,而没有像32位程序那样使用栈传递数据。

在x86-64硬件平台上,寄存器和指针都是64位的,存储于R-字头的寄存器里。但是出于兼容性的考虑,64位寄存器的低32位,也要能够担当32位寄存器的角色,才能运行32位程序。

在64位x86兼容的CPU中,RAX/EAX/AX/AL的对应关系如下。


38398220c5e6d09aa8a1ff3fd7c35d58c8be616c

main()函数的返回值是整数类型的零,但是出于兼容性和可移植性的考虑,C语言的编译器仍将使用32位的零。换而言之,即使是64位的应用程序,在程序结束时EAX的值是零,而RAX的值不一定会是零。

此时,数据栈的对应空间里仍留有40字节的数据。这部分数据空间有个专用的名词,即阴影空间(shadow space)。本书将在8.2.1节里更详细地介绍它。

3.2.2 GCC-x86-64
我们使用64位Linux的GCC编译器编译上述程序,可得到如下所示的指令。

指令清单3.8 GCC 4.4.6 x64

.string "hello, world\n"
main:
         sub      rsp, 8
         mov      edi, OFFSET FLAT:.LC0 ; "hello, world"
         xor      eax, eax  ; number of vector registers passed
         call     printf
         xor      eax, eax
         add      rsp, 8
         ret

Linux、BSD和Mac OS X系统中的应用程序,会优先使用RDI、RSI、RDX、RCX、R8、R9这6个寄存器传递函数所需的头6个参数,然后使用数据栈传递其余的参数。[6]

因此,64位的GCC编译器使用EDI寄存器(寄存器的32位)存储字符串指针。EDI不过是RDI寄存器中地址位较低的32位地址部分。为何GCC不直接使用整个RDI寄存器?

需要注意的是,64位汇编指令MOV在写入R-寄存器的低32位地址位的时候,即对E-寄存器进行写操作的时候,会同时清除R寄存器中的高32位地址位[7]。所以, “MOV EAX, 011223344h”能够对RAX寄存器进行正确的赋值操作,因为该指令会清除(置零)高地址位的内容。

如果打开GCC生成的obj文件,我们就能看见全部的opcode。[8]

指令清单3.9 GCC 4.4.6 x64

.text:00000000004004D0                   main  proc near
.text:00000000004004D0 48 83 EC 08       sub     rsp, 8
.text:00000000004004D4 BF E8 05 40 00    mov     edi, offset format ; "hello, world\n"
.text:00000000004004D9 31 C0             xor     eax, eax
.text:00000000004004DB E8 D8 FE FF FF    call    _printf
.text:00000000004004E0 31 C0             xor     eax, eax
.text:00000000004004E2 48 83 C4 08       add     rsp, 8
.text:00000000004004E6 C3                retn
.text:00000000004004E6                   main  endp

在地址0x4004D4处,程序对EDI 进行了写操作,这部分代码的opcode占用了5个字节;相比之下,对RDI进行写操作的opcode则会占用7个字节。显然,出于空间方面的考虑,GCC进行了相应的优化处理。此外,因为32位地址(指针)能够描述的地址不超过4GB,我们可据此判断这个程序的数据段地址不会超过4GB。

在调用printf()之前,程序清空了EAX寄存器,这是x86-64框架的系统规范决定的。在系统与应用程序接口的规范中,EAX寄存器用来保存用过的向量寄存器(vector registers)。[9]

相关文章
|
7月前
|
Unix 开发工具 iOS开发
iOS应用逆向工程笔记 -1
iOS应用逆向工程笔记 -1
38 0
|
5月前
|
IDE Java API
Java编程讲义之Eclipse开发工具
Java编程讲义之Eclipse开发工具
41 0
|
10月前
|
设计模式 开发框架 缓存
SSH框架简介篇
SSH框架简介篇
203 0
|
SQL XML 存储
MyBatis框架:第一章:简介
MyBatis框架:第一章:简介
MyBatis框架:第一章:简介
|
Java 数据库连接 数据库