kr 第三阶段(九)64 位逆向

news2024/10/5 21:18:53

X64 汇编程序

64 位与 32 位的区别

更大的内存

  • 64 位 CPU 与 32 位 CPU 的区别
    • 引脚根数:
      • x86 程序:20 根
      • x64 程序:52 根,实际寻址的有 48 根,所以最大内存是 0~256T
    • 寻址区间:
      • x86 程序:0x00000000 ~ 0xFFFFFFFF
      • x64程序:0xXXXX0000`00000000 ~ 0xXXXXFFFF`FFFFFFFF ,intel 高16 位必须是第 48 位符号扩展,所以实际如下:
        • R3 的逻辑地址范围:0x00000000`00000000 ~ 0xFFFFFFFF`FFFFFFFF
        • R0 的逻辑地址范围:0xFFFF8000`00000000 ~ 0xFFFFFFFF`FFFFFFFF
  • 64 位 CPU 的三种运行模式
    • 实模式:在此模式下,可以跑16 位程序,微软的操作系统禁用此模式。
    • 64 位模式:只能运行 64 位的程序。
    • 兼容模式:能运行 x32 位和 x64 位程序。模拟执行 32 位程序。

更快的运行速度

x64 程序最先是 AMD 提出来的,随后 inter 跟着推出了 IA(安腾处理器)但是由于其不兼容 x86 指令集,所以并没有得到太大的适用性。随后 AMD64 的问世,由于指令集并没有变动太多,且还能兼容运行 x86 的程序,所以得到了大量的普适性,Intel 也推出了 AMD64 标准的指令集 IA32e 。

寄存器的扩展:

  • 从原来的 32 位寄存器扩展到了 64 位寄存器。
  • 通用寄存器的数量得到了大量的提升(可以寄存器传参,局部变量寄存器存储)。

x64 汇编基础知识

寄存器

在这里插入图片描述

  • 通用寄存器

    • eaxebxecxedxebpesiediesp 全部又原先的 32 位扩展至 64 位,前缀由 e 改为 r
    • 增加了 r8 ~ r9 8 个 64 位通用寄存器。
  • 多媒体相关寄存器:

    • x87 浮点寄存器(MMX 寄存器)无变化
    • SSE(AVX)寄存器数量由原理的 8 个增加至 16 个(YMM/XMM8 ~ YMM/XMM15
  • 标志寄存器:
    在这里插入图片描述

    • 扩展为 64 位,但高 32 位保留不使用(填充 0)。
  • 段寄存器:
    在这里插入图片描述

    • 64 位模式下忽略了 dsesss 寄存器。
  • eip 寄存器扩展为 rip 寄存器。64 位模式下只能使用 rip 寄存器。

x64 寄存器后缀访问表示访问寄存器大小:b(低8位)、w(低16位)、d(低32位),例: r8br9wr10dr11
在这里插入图片描述
注意: 操作数为低 8 位、16 位,传送不影响高位;操作数为低 32 位,高位被扩展(零扩展,填充 0);

模式

在这里插入图片描述
64 位操作系统在只有 64 位模式下可以使用扩展寄存器,兼容模式可以运行 32 位和 64 位程序,但不能使用扩展寄存器。具体兼容模式和 64 为模式能够使用的寄存器种类如下图所示:
在这里插入图片描述

指令集

32 位原有指令集就能够满足常规使用。

x64 调用约定

64 位汇编与 32 位汇编的最大区别就是函数调用方式的变化。x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall__cdecl__fastcall_thiscall 等)的混乱,只保留了 4 寄存器 fastcall 一种调用约定。

  • 整数传参:
    • 前 4 个参数依序传入 RCXRDXR8R9
    • 前 4 个以外的整数参数将使用栈传参。
  • 浮点参数:
    • 4 个参数依序传入 XMM0XMM1XMM2XMM3,后续的浮点参数将放置到线程堆栈上。
  • 混合传参:
    • 如果整型和浮点型参数混合会放到对应的寄存器中。例如 fun(int, float, int, float) 函数对应参数会依次放到 ecxxmm1r8dxmm3 寄存器中。
  • 返回值:整型放 rax,浮点型放 xmm0

x64 汇编程序设计

开发环境

  • 64 位汇编需要使用 VS 的 x64 编译器 ml64.exe.asm 文件编译为 .obj 文件。该编译器的路径为 Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\ml64.exe
  • 选用 VS 的配套链接器 link.exe ,可以链接x64位的程序,link.exeml64.exe 在同一目录下。
  • 由于编译链接需要很多外部库,为了避免配置环境变量等出错,这里直接在 VS 自带的 x64 Native Tools Command Prompt for VS 中进行编译链接。编译链接命令如下:
    ml64 /c Hello.asm
    link /subsystem:windows /entry:Entry Hello.obj
    

第一个 x64 汇编程序

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc
extern ExitProcess:proc
extern GetCurrentProcessId:proc
extern wsprintfA:proc

.const 
MSG1    db "Hello x64! pid:%d %d %d %d", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code
Entry proc
  push  rbx
  push  rcx
  sub  rsp, 38h     ;保证栈地址模16   必须申请32个字节栈预留空间

  call GetCurrentProcessId
  mov rcx, offset BUFFER
  mov rdx, offset MSG1
  mov r8d, eax
  mov r9d, 5
  mov dword ptr [rsp + 20h], 6
  mov dword ptr [rsp + 28h], 7
  call wsprintfA

  mov  rcx, 0
  mov  rdx, offset BUFFER
  mov  r8, offset TITLE1
  mov  r9d, 0
  call MessageBoxA

  mov   ecx, 0
  call  ExitProcess

  sub  rsp, 38h
  pop  rcx
  pop  rbx
  ret
Entry endp

end 

使用运行时库

如果想使用运行时库函数 sprintf_s 来代替 wsprintfA 需要注意程序入口点应当指定为 WinMainCRTStartup 并且程序从 main 函数开始。因为运行时库函数使用前需要事先初始化相关变量。

编译链接脚本如下:

ml64 /c Hello.asm
link /entry:WinMainCRTStartup /subsystem:windows Hello.obj libcmt.lib msvcrt.lib

示例代码如下:

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc 
extern ExitProcess:proc 
extern GetCurrentProcessId:proc
extern sprintf_s:proc

.const 
MSG1    db "Hello x64! pid:%d %d %d", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code

WinMain proc
  push  rbx
  push  rcx
  sub  rsp, 38h     ;保证栈地址模16   必须申请32个字节栈预留空间

  call GetCurrentProcessId
  mov rcx, offset BUFFER
  mov rdx, sizeof BUFFER
  mov r8, offset MSG1
  mov r9d, eax
  mov dword ptr [rsp + 20h], 6
  mov dword ptr [rsp + 28h], 7
  call sprintf_s

  mov  rcx, 0
  mov  rdx, offset BUFFER
  mov  r8, offset TITLE1
  mov  r9d, 0
  call MessageBoxA

  mov   ecx, 0
  call  ExitProcess

  sub  rsp, 38h
  pop  rcx
  pop  rbx
  ret
WinMain endp

end 

使用资源

以对话框为例,x64 汇编程序使用资源的示例代码如下:

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc 
extern DialogBoxParamA:proc 
extern EndDialog:proc 
extern ExitProcess:proc 

IDD_DIALOG1 equ 101
IDC_BUTTON1 equ 1001
IDC_BUTTON2 equ 1002
WM_CLOSE    equ 10h
WM_COMMAND  equ 111h

.const 
MSG1    db "WM_COMMAND", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code

DialogProc proc
  mov [rsp + 8], rcx
  mov [rsp + 10h], rdx
  mov [rsp + 18h], r8
  mov [rsp + 20h], r9

  sub rsp, 28h
  xor eax, eax
  cmp edx, WM_CLOSE
  jz HANDLE_CLOSE
  cmp edx, WM_COMMAND
  jz HANDLE_COMMAND
  jmp HANDLE_DEFAULT
HANDLE_COMMAND:
  xor ecx, ecx
  mov rdx, offset MSG1
  mov r8, offset TITLE1
  xor r9d, r9d
  call  MessageBoxA
  inc eax
  jmp HANDLE_DEFAULT
HANDLE_CLOSE:
  mov rcx, [rsp + 30h]
  xor edx, edx
  call EndDialog
  inc eax
HANDLE_DEFAULT:
  add rsp, 28h
  ret
DialogProc endp

WinMain proc
  sub  rsp, 38h
  
  mov rdx, IDD_DIALOG1
  xor r8d, r8d
  mov r9, offset DialogProc
  mov qword ptr [rsp + 20h], 0
  call DialogBoxParamA

  xor ecx, ecx
  call  ExitProcess

  sub  rsp, 38h
  ret
WinMain endp

end 

链接时需要将资源也链接进去,资源可以通过 VS 工程创建并编译。

ml64 /c Hello.asm
link /entry:WinMainCRTStartup /subsystem:windows Hello.obj res.res msvcrt.lib

如何寻找 main 函数

从入口函数开始找,第一个 exit 的调用,离该函数最近且有三个参数的函数为 main 函数。

制作 IDA 签名文件

在 VS 的 项目属性 → 配置属性 → VC++目录 → 常规 → 库目录 中包含了 VC 编译时使用的 lib 库。我们可以将这些库制作成签名文件方便逆向分析。

这次制作签名文件我们使用高版本的 Flair ,高本版的 Flair 可以自动将多个 lib 库制作为一个签名文件,不需要手动写脚本实现自动化。

将需要制作签名的 lib 库拷贝到同一目录下,然后运行命令 pcf.exe *.lib vs2022.pat 提取特征。之后运行 sigmake.exe .\vs2022.pat .\vs2022.sig 制作签名文件。

正常情况下制作签名文件时会出现冲突的情况,并且会生成一个 vs2022.exc 文件记录具体冲突的函数。

> sigmake.exe .\vs2022.pat .\vs2022.sig
.\vs2022.sig: modules/leaves: 16108/35926, COLLISIONS: 1996
See the documentation to learn how to resolve collisions.

vs2022.exc 文件中的内容如下:

;--------- (delete these lines to allow sigmake to read this file)
; add '+' at the start of a line to select a module
; add '-' if you are not sure about the selection
; do nothing if you want to exclude all modules

??0CppInlineNamespaceAttribute@?A0x020fbda2@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
??0CppInlineNamespaceAttribute@?A0x0f11cde5@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
??0CppInlineNamespaceAttribute@?A0x9646ae10@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
...

我们有两种方式解决冲突:

  • 删掉签名的 4 行注释,这样 IDA 会使用 unknown_libname 来替代。
  • 删除冲突的函数中选择使用的函数名。

解决冲突后再次运行 sigmake.exe .\vs2022.pat .\vs2022.sig 命令就可以制作出签名文件。

将制作的签名文件放置在 IDA 安装路径下的 sig\pc 目录下就可以 Shift + F5 打开签名窗口然后 Insert 加载签名文件。

最终可以成功识别出 mian 函数的位置。

  Code = invoke_main();
  if ( !(unsigned __int8)j_unknown_libname_2() )
    j_exit(Code);
  if ( !v2 )
    j__cexit();

表达式

加法、减法、乘法

在 x64 位汇编对应的加法、减法、乘法指令的速度相对于 32 位汇编有了很大的提升,因此多数情况都是直接使用对应的指令而不作优化。少部分可以情况可以适用移位和比例因子进行优化。

除法

  • 除数为变量时无法优化,根据除数类型选择 dividiv
  • 除数为常量时可以优化。

被除数无符号,除数为 2 的整数次幂

直接 shr 移位即可。

被除数有符号,除数为 2 的整数次幂

如果是正数直接 sar 移位即可。如果是负数由于算术移位是向下取整,需要改为向上取整,因此 [ − a 2 n ] = ⌊ − a + 2 n − 1 2 n ⌋ = ⌈ − a 2 n ⌉ \left [ \frac{-a}{2^n} \right ] =\left \lfloor \frac{-a+2^n-1}{2^n} \right \rfloor =\left \lceil \frac{-a}{2^n}\right \rceil [2na]=2na+2n1=2na

例如 ⌊ x 8 ⌋ \left \lfloor \frac{x}{8} \right \rfloor 8x 对应的汇编代码如下:

.text:0000000140001000 mov     rax, rcx		; rcx = x
.text:0000000140001003 cqo					; rax 符号扩展至 rdx
.text:0000000140001005 and     edx, 7		; rdx = x < 0 ? 7 : 0
.text:0000000140001008 add     rax, rdx
.text:000000014000100B sar     rax, 3		; rax = (x + (x < 0 ? 7 : 0)) >> 3

注意:如果除数为 2 的整数次幂且无符号则无论被除数正负均向下取整。

被除数无符号,除数为非 2 的整数次幂

对于被除数无符号除数为非 2 的整数次幂的情况,为了避免使用除法指令,可以做如下转换:
⌊ a b ⌋ = ⌊ a × ⌈ 2 n b ⌉ 2 n ⌋   ( n ≥ ⌈ log ⁡ 2 a ⌉ ) \left \lfloor \frac{a}{b} \right \rfloor =\left \lfloor \frac{a\times \left \lceil \frac{2^n}{b} \right \rceil }{2^n} \right \rfloor\ (n\ge \left \lceil \log_2 a \right \rceil ) ba=2na×b2n (nlog2a)
例如 ⌊ a 35 ⌋ \left \lfloor \frac{a}{35} \right \rfloor 35a 对应的汇编代码如下:

.text:0000000140001000 mov     rax, 0EA0EA0EA0EA0EA0Fh
.text:000000014000100A mul     rcx		; a
.text:000000014000100D shr     rdx, 5
.text:0000000140001011 mov     eax, edx

其中 0EA0EA0EA0EA0EA0Fh ⌈ 2 69 35 ⌉ \left \lceil\frac{2^{69}}{35} \right \rceil 35269 ,因此整段汇编计算的是 ⌊ a × ⌈ 2 69 35 ⌉ 2 69 ⌋ \left \lfloor \frac{a\times \left \lceil \frac{2^{69}}{35} \right \rceil }{2^{69}} \right \rfloor 269a×35269

当然如果除数过小可能会出现 MagicNumber 超过 2 64 2^{64} 264 的情况,例如 ⌊ a 7 ⌋ \left \lfloor \frac{a}{7} \right \rfloor 7a 对应的 MagicNumber ⌈ 2 67 7 ⌉ \left \lceil\frac{2^{67}}{7} \right \rceil 726712492492492492493h 超过了 2 64 2^{64} 264 ,需要通过进行多次移位在不超过 2 64 2^{64} 264 的常量参与运算的前提下实现同等的效果。因此实际上 ⌊ a 7 ⌋ \left \lfloor \frac{a}{7} \right \rfloor 7a 对应的汇编代码如下:

.text:0000000140001000 mov     rax, 2492492492492493h
.text:000000014000100A mul     rcx
.text:000000014000100D sub     rcx, rdx
.text:0000000140001010 shr     rcx, 1
.text:0000000140001013 lea     rax, [rdx+rcx]
.text:0000000140001017 shr     rax, 2

这段汇编代码实现了如下计算:
⌊ a × ⌈ 2 67 7 ⌉ 2 67 ⌋ = ⌊ a − ⌊ a × ( ⌈ 2 67 7 ⌉ − 2 64 ) 2 64 ⌋ 2 + ⌊ a × ( ⌈ 2 67 7 ⌉ − 2 32 ) 2 64 ⌋ 2 2 ⌋ \left \lfloor \frac{a\times \left \lceil \frac{2^{67}}{7} \right \rceil }{2^{67}} \right \rfloor=\left \lfloor \frac{\frac{a-\left \lfloor \frac{a\times (\left \lceil \frac{2^{67}}{7} \right \rceil-2^{64} )}{2^{64}} \right \rfloor }{2}+\left \lfloor \frac{a\times (\left \lceil \frac{2^{67}}{7} \right \rceil-2^{32} )}{2^{64}} \right \rfloor }{2^2} \right \rfloor 267a×7267 = 222a264a×(7267264)+264a×(7267232)
其中 ⌈ 2 67 7 ⌉ − 2 64 < 2 64 \left \lceil \frac{2^{67}}{7} \right \rceil-2^{64}<2^{64} 7267264<264 ,满足条件。

被除数有符号,除数为非 2 的整数次幂

对于被除数为有符号数的情况,汇编代码如下:

.text:0000000140001000 mov     rax, 0EA0EA0EA0EA0EA1h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 63						; 取 (MagicNumber * a) >> 63 的符号位
.text:0000000140001017 add     rax, rdx						; 如果结果为负数则需要将结果加上 1

64 位汇编的优化与 32 位汇编相同,在被除数为负数时会在结果上加 1 实现向上取整的效果。编译器会选择合适的 2 n 2^n 2n 来确保 MagicNumber * a 不会被 2 n 2^n 2n 整除。

如果输出为负数则将符号转移至 MagicNumber 。例如 ⌊ a − 7 ⌋ \left \lfloor \frac{a}{-7} \right \rfloor 7a 汇编代码如下,其中 4924924924924925h ⌈ 2 65 7 ⌉ \left \lceil \frac{2^{65}}{7} \right \rceil 7265

.text:0000000140001000 mov     rax, -4924924924924925h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 3Fh
.text:0000000140001017 add     rax, rdx

取模

  • 模(除)数为变量时无法优化,根据模(除)数类型选择 dividiv
  • 模(除)数为常量时可以优化。

被除数无符号,模(除)数为 2 的整数次幂

如果被除数为无符号数,则 a m o d    2 n a\mod 2^n amod2n 相当于 a & ( 2 n − 1 ) a\&(2^n-1) a&(2n1) 。如果模数比较小会使用 32 位寄存器减短指令长度。

被除数有符号,模(除)数为 2 的整数次幂

64 位汇编的取模优化策略与 32 位不同,这里采用了下面的公式进行优化:
a   m o d   2 n = { a & ( 2 n − 1 ) ( a ≥ 0 ) ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) ( a < 0 ) a \bmod 2^n = \left\{\begin{aligned} &a\&(2^n-1) \quad &&(a \geq 0)\\ &(a+2^n-1)\&(2^n-1)-(2^n-1) \quad &&(a < 0) \end{aligned}\right. amod2n={a&(2n1)(a+2n1)&(2n1)(2n1)(a0)(a<0)
证明如下:

  • 对于 a ≥ 0 a\ge0 a0 的情况显然成立。

  • 对于 a < 0 a<0 a<0 的情况,首先 ( a + 2 n − 1 ) & ( 2 n − 1 ) (a+2^n-1)\&(2^n-1) (a+2n1)&(2n1) 可以看做为:
    ( a + 2 n − 1 ) & ( 2 n − 1 ) = { 2 n − 1 ( a   m o d   2 n = 0 ) ( ∣ a ∣   m o d   2 n ) − 1 ( a   m o d   2 n ≠ 0 ) (a+2^n-1)\&(2^n-1)=\left\{\begin{aligned} &2^n-1 &&(a \bmod 2^n= 0)\\ &(\left | a \right | \bmod 2^n)-1 \quad &&(a \bmod 2^n \neq 0) \end{aligned}\right. (a+2n1)&(2n1)={2n1(amod2n)1(amod2n=0)(amod2n=0)

    • 对于 a   m o d   2 n = 0 a \bmod 2^n= 0 amod2n=0 的情况 ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) = 0 (a+2^n-1)\&(2^n-1)-(2^n-1)=0 (a+2n1)&(2n1)(2n1)=0 符合题意。
    • 对于 a   m o d   2 n ≠ 0 a \bmod 2^n \neq 0 amod2n=0 的情况, ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) = ( ∣ a ∣   m o d   2 n ) − 1 − ( 2 n − 1 ) = ( ∣ a ∣   m o d   2 n ) − 2 n = a   m o d   2 n (a+2^n-1)\&(2^n-1)-(2^n-1)=(\left | a \right | \bmod 2^n)-1-(2^n-1)=(\left | a \right | \bmod 2^n)-2^n=a\bmod 2^n (a+2n1)&(2n1)(2n1)=(amod2n)1(2n1)=(amod2n)2n=amod2n ,同样符合题意。

综上原命题得证。

a   m o d   8 a\bmod 8 amod8 为例,对应的汇编代码如下:

.text:0000000140001000 mov     rax, rcx		; rcx = a
.text:0000000140001003 cqo					; rdx = a >= 0 ? 0 : -1
.text:0000000140001005 and     edx, 7		; rdx = a >= 0 ? 0 : 7
.text:0000000140001008 add     rax, rdx
.text:000000014000100B and     eax, 7
.text:000000014000100E sub     rax, rdx

高版本的 clang 编译器采用了另一种优化方式:
a   m o d   2 n = { a − ( a & ∼ ( 2 n − 1 ) ) ( a ≥ 0 ) a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) ( a < 0 ) a \bmod 2^n=\left\{\begin{aligned} &a - (a\& \sim (2^n-1))\quad&&(a \ge 0)\\ &a - ((a+2^n-1)\& \sim (2^n-1)) \quad &&(a < 0) \end{aligned}\right. amod2n={a(a&(2n1))a((a+2n1)&(2n1))(a0)(a<0)
证明如下:

  • 对于 a ≥ 0 a\ge0 a0 的情况显然成立。
  • 对于 a < 0 a<0 a<0 的情况:
    • a   m o d   2 n = 0 a \bmod 2^n= 0 amod2n=0 时, ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) = a & ∼ ( 2 n − 1 ) = a (a+2^n-1)\& \sim (2^n-1)=a\& \sim (2^n-1)=a (a+2n1)&(2n1)=a&(2n1)=a ,所以 a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) = 0 a - ((a+2^n-1)\& \sim (2^n-1)) =0 a((a+2n1)&(2n1))=0 显然成立。
    • a   m o d   2 n ≠ 0 a \bmod 2^n\not = 0 amod2n=0 时, ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) = ( a & ∼ ( 2 n − 1 ) ) + 2 n (a+2^n-1)\& \sim (2^n-1)=(a\& \sim (2^n-1))+2^n (a+2n1)&(2n1)=(a&(2n1))+2n,因此 a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) = ( a − ( a & ∼ ( 2 n − 1 ) ) ) − 2 n = ( ∣ a ∣   m o d   2 n ) − 2 n = a   m o d   2 n a - ((a+2^n-1)\& \sim (2^n-1))=(a-(a\& \sim (2^n-1)))-2^n=(\left | a \right | \bmod 2^n)-2^n=a\bmod 2^n a((a+2n1)&(2n1))=(a(a&(2n1)))2n=(amod2n)2n=amod2n ,同样成立。

综上,原命题得证。

a   m o d   8 a\bmod 8 amod8 为例,对应的汇编代码如下:

.text:0000000000401140 mov     rax, [rsp]					; rax = a
.text:0000000000401144 lea     rcx, [rax+7]					; rcx = a + 7
.text:0000000000401148 test    rax, rax
.text:000000000040114B cmovns  rcx, rax						; rcx = a >= 0 ? a : a + 7
.text:000000000040114F and     rcx, 0FFFFFFFFFFFFFFF8h		; rcx = rcx & ~((1 << 3) - 1)
.text:0000000000401153 sub     rax, rcx						; rax = a - rcx

被除数无符号,模数不为 2 的整数次幂

对于被除数无符号,模数不为 2 的整数次幂的情况, a   m o d   b a \bmod b amodb 会被编译器优化为 a − ⌊ a b ⌋ × b a-\left \lfloor \frac{a}{b} \right \rfloor \times b aba×b,其中 ⌊ a b ⌋ \left \lfloor \frac{a}{b} \right \rfloor ba 会按照被除数无符号,除数为非 2 的整数次幂的除法优化。

x % 7 为例,汇编代码如下:

.text:0000000140001000 mov     rax, 2492492492492493h
.text:000000014000100A mul     rcx						; rcx = x
.text:000000014000100D mov     rax, rcx
.text:0000000140001010 sub     rax, rdx
.text:0000000140001013 shr     rax, 1
.text:0000000140001016 add     rax, rdx
.text:0000000140001019 shr     rax, 2
.text:000000014000101D imul    eax, 7					; eax = x / 7 * 7
.text:0000000140001020 sub     ecx, eax					; ecx = x - x / 7 * 7
.text:0000000140001022 mov     eax, ecx

被除数有符号,模数不为 2 的整数次幂

如果模数为正数,则 a   m o d   b a \bmod b amodb 会同样按照 a − ⌊ a b ⌋ × b a-\left \lfloor \frac{a}{b} \right \rfloor \times b aba×b 来进行优化,只不过这里的除法按照被除数有符号,除数为非 2 的整数次幂的情况进行优化。

.text:0000000140001000 mov     rax, 4924924924924925h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 3Fh
.text:0000000140001017 add     rdx, rax
.text:000000014000101A imul    eax, edx, 7				; eax = x / 7 * 7
.text:000000014000101D sub     ecx, eax					; ecx = x - x / 7 * 7
.text:000000014000101F mov     eax, ecx

如果模数为负数,结果与模数为正数相同,即 a   m o d   b = a   m o d   ∣ b ∣ a \bmod b = a \bmod \left | b \right | amodb=amodb

控制流程

32 位程序和 64 位程序的控制流程基本不变,这里只介绍有变化的部分。

三目运算符

相较于 32 位汇编,64 位汇编普便采用 cmovxx 指令,而不会针对每种类型的三目运算进行无分支优化。

  • 如果三目运算符中的表达式不是特别复杂会采用 cmovxx 指令。
  • 如果三目运算符中的表达式比较复杂会转换为 if-else 形式。
  • 特别的,如果是 x == a ? b : b + 1 形式的三目运算会使用 setnz 进行优化。

switch 语句

分支较多但比较连续

为了节省空间 64 位的跳转表并没有像 32 位那样存放分支的地址,而是存放分支地址相对于某一基址的偏移,而这一基址即 PE 文件的 ImageBase(PE 文件的实际加载基址,PE 文件加载时会通过重定位表进行修正) ,跳转表中元素的大小则为 4 字节。

.text:0000000140001000 sub     rsp, 28h
.text:0000000140001004 dec     ecx                             ; switch 6 cases
.text:0000000140001006 cmp     ecx, 5
.text:0000000140001009 ja      def_140001023                   ; jumptable 0000000140001023 default case

.text:000000014000100F movsxd  rax, ecx
.text:0000000140001012 lea     rdx, cs:140000000h			   ; ImageBase
.text:0000000140001019 mov     ecx, ds:(jpt_140001023 - 140000000h)[rdx+rax*4]	; mov     ecx, [rdx+rax*4+10B4h]
.text:0000000140001020 add     rcx, rdx						   ; 跳转表中的偏移还要加上 ImageBase 才是分支的地址
.text:0000000140001023 jmp     rcx                             ; switch jump

.text:0000000140001025 $LN4:
.text:0000000140001025 lea     rcx, string1                    ; jumptable 0000000140001023 case 1
.text:000000014000102C call    cs:__imp_puts
.text:0000000140001032 xor     eax, eax
.text:0000000140001034 add     rsp, 28h
.text:0000000140001038 retn

.text:0000000140001039 $LN5:
.text:0000000140001039 lea     rcx, string2                    ; jumptable 0000000140001023 case 2
.text:0000000140001040 call    cs:__imp_puts
.text:0000000140001046 xor     eax, eax
.text:0000000140001048 add     rsp, 28h
.text:000000014000104C retn

.text:000000014000104D $LN6:
.text:000000014000104D lea     rcx, string3                    ; jumptable 0000000140001023 case 3
.text:0000000140001054 call    cs:__imp_puts
.text:000000014000105A xor     eax, eax
.text:000000014000105C add     rsp, 28h
.text:0000000140001060 retn

.text:0000000140001061 $LN7:
.text:0000000140001061 lea     rcx, string4                    ; jumptable 0000000140001023 case 4
.text:0000000140001068 call    cs:__imp_puts
.text:000000014000106E xor     eax, eax
.text:0000000140001070 add     rsp, 28h
.text:0000000140001074 retn

.text:0000000140001075 $LN8:
.text:0000000140001075 lea     rcx, string5                    ; jumptable 0000000140001023 case 5
.text:000000014000107C call    cs:__imp_puts
.text:0000000140001082 xor     eax, eax
.text:0000000140001084 add     rsp, 28h
.text:0000000140001088 retn

.text:0000000140001089 $LN9:
.text:0000000140001089 lea     rcx, string6                    ; jumptable 0000000140001023 case 6
.text:0000000140001090 call    cs:__imp_puts
.text:0000000140001096 xor     eax, eax
.text:0000000140001098 add     rsp, 28h
.text:000000014000109C retn

.text:000000014000109D def_140001023:
.text:000000014000109D lea     rcx, string7                    ; jumptable 0000000140001023 default case
.text:00000001400010A4 call    cs:__imp_puts
.text:00000001400010AA xor     eax, eax
.text:00000001400010AC add     rsp, 28h
.text:00000001400010B0 retn

.text:00000001400010B4 jpt_140001023 dd offset $LN4 - 140000000h
.text:00000001400010B4 dd offset $LN5 - 140000000h             ; jump table for switch statement
.text:00000001400010B4 dd offset $LN6 - 140000000h
.text:00000001400010B4 dd offset $LN7 - 140000000h
.text:00000001400010B4 dd offset $LN8 - 140000000h
.text:00000001400010B4 dd offset $LN9 - 140000000h
.text:00000001400010B4 main endp

分支较多且比较不连续

.text:0000000140001000 sub     rsp, 28h
.text:0000000140001004 dec     ecx                             ; switch 123 cases
.text:0000000140001006 cmp     ecx, 7Ah
.text:0000000140001009 ja      def_14000102B                   ; jumptable 000000014000102B default case, cases 6-122

.text:000000014000100F movsxd  rax, ecx
.text:0000000140001012 lea     rdx, cs:140000000h
.text:0000000140001019 movzx   eax, ds:(byte_1400010D8 - 140000000h)[rdx+rax]
.text:0000000140001021 mov     ecx, ds:(jpt_14000102B - 140000000h)[rdx+rax*4]
.text:0000000140001028 add     rcx, rdx
.text:000000014000102B jmp     rcx                             ; switch jump

.text:000000014000102D $LN4:
.text:000000014000102D lea     rcx, string1                    ; jumptable 000000014000102B case 1
.text:0000000140001034 call    cs:__imp_puts
.text:000000014000103A xor     eax, eax
.text:000000014000103C add     rsp, 28h
.text:0000000140001040 retn

.text:0000000140001041 $LN5:
.text:0000000140001041 lea     rcx, string2                    ; jumptable 000000014000102B case 2
.text:0000000140001048 call    cs:__imp_puts
.text:000000014000104E xor     eax, eax
.text:0000000140001050 add     rsp, 28h
.text:0000000140001054 retn

.text:0000000140001055 $LN6:
.text:0000000140001055 lea     rcx, string3                    ; jumptable 000000014000102B case 3
.text:000000014000105C call    cs:__imp_puts
.text:0000000140001062 xor     eax, eax
.text:0000000140001064 add     rsp, 28h
.text:0000000140001068 retn

.text:0000000140001069 $LN7:
.text:0000000140001069 lea     rcx, string4                    ; jumptable 000000014000102B case 4
.text:0000000140001070 call    cs:__imp_puts
.text:0000000140001076 xor     eax, eax
.text:0000000140001078 add     rsp, 28h
.text:000000014000107C retn

.text:000000014000107D $LN8:
.text:000000014000107D lea     rcx, string5                    ; jumptable 000000014000102B case 5
.text:0000000140001084 call    cs:__imp_puts
.text:000000014000108A xor     eax, eax
.text:000000014000108C add     rsp, 28h
.text:0000000140001090 retn

.text:0000000140001091 $LN9:
.text:0000000140001091 lea     rcx, string6                    ; jumptable 000000014000102B case 123
.text:0000000140001098 call    cs:__imp_puts
.text:000000014000109E xor     eax, eax
.text:00000001400010A0 add     rsp, 28h
.text:00000001400010A4 retn

.text:00000001400010A5 def_14000102B:
.text:00000001400010A5 lea     rcx, string7                    ; jumptable 000000014000102B default case, cases 6-122
.text:00000001400010AC call    cs:__imp_puts
.text:00000001400010B2 xor     eax, eax
.text:00000001400010B4 add     rsp, 28h
.text:00000001400010B8 retn

.text:00000001400010BC jpt_14000102B dd offset $LN4 - 140000000h
.text:00000001400010BC dd offset $LN5 - 140000000h             ; jump table for switch statement
.text:00000001400010BC dd offset $LN6 - 140000000h
.text:00000001400010BC dd offset $LN7 - 140000000h
.text:00000001400010BC dd offset $LN8 - 140000000h
.text:00000001400010BC dd offset $LN9 - 140000000h
.text:00000001400010BC dd offset def_14000102B - 140000000h

.text:00000001400010D8 byte_1400010D8 db      0,     1,     2,     3
.text:00000001400010D8 db      4,     6,     6,     6          ; indirect table for switch statement
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     5

函数

x64 调用约定

默认情况下,x64 应用程序二进制接口 (ABI) 使用四寄存器 fast-call 调用约定。 系统在调用堆栈上分配空间作为影子存储,供被调用方保存这些寄存器。

函数调用的参数与用于这些参数的寄存器之间有着严格的一一对应关系。 任何无法放入 8 字节或者不是 1、2、4 或 8 字节的参数都必须按引用传递。 单个参数永远不会分布在多个寄存器中。

整数参数在寄存器 RCXRDXR8R9 中传递。 浮点数参数在 XMM0LXMM1LXMM2LXMM3L 中传递。 16 字节参数按引用传递。

参数传递

前 4 个整数参数从左到右分别在 RCXRDXR8R9 中传递。 如前所述,第 5 个和更高位置的参数在堆栈上传递。 寄存器中的所有整型参数都是向右对齐的,因此被调用方可忽略寄存器的高位,只访问所需的寄存器部分。

.text:0000000140001000 ; __int64 __fastcall func1(int, int, int, int, int e, int f)
.text:0000000140001000 func1 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 lea     eax, [rcx+rdx]				  ; eax = 参数1 + 参数2
.text:0000000140001003 add     eax, r8d						  ; eax = eax + 参数3
.text:0000000140001006 add     eax, r9d						  ; eax = eax + 参数4
.text:0000000140001009 add     eax, [rsp+e]					  ; eax = eax + 参数5
.text:000000014000100D add     eax, [rsp+f]					  ; eax = eax + 参数6
.text:0000000140001011 retn
.text:0000000140001011
.text:0000000140001011 func1 endp

.text:0000000140001020 sub     rsp, 38h
.text:0000000140001024 mov     edx, 2                          ; int 参数2
.text:0000000140001029 mov     dword ptr [rsp+28h], 6          ; int 参数6
.text:0000000140001031 mov     dword ptr [rsp+20h], 5          ; int 参数5
.text:0000000140001039 lea     r9d, [rdx+2]                    ; int 参数4
.text:000000014000103D lea     r8d, [rdx+1]                    ; int 参数3
.text:0000000140001041 lea     ecx, [rdx-1]                    ; int 参数1
.text:0000000140001044 call    func1
.text:0000000140001044
.text:0000000140001049 add     rsp, 38h
.text:000000014000104D retn

前 4 个参数中的所有浮点和双精度参数都在 XMM0 - XMM3(具体视位置而定)中传递。

.text:0000000140001000 ; float __fastcall func2(float, long double, float, long double, float, float)
.text:0000000140001000 func2 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 xorps   xmm4, xmm4
.text:0000000140001003 cvtss2sd xmm4, xmm0			; xmm4 = (double)参数1
.text:0000000140001007 movss   xmm0, [rsp+e]		; xmm0 = 参数5
.text:000000014000100D addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数2 (参数1 + 参数2)
.text:0000000140001011 xorps   xmm1, xmm1
.text:0000000140001014 cvtss2sd xmm1, xmm2			; xmm1 = (double)参数3
.text:0000000140001018 cvtps2pd xmm0, xmm0			; xmm0 = (double)参数5
.text:000000014000101B addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数3 (参数1 + 参数2 + 参数3)
.text:000000014000101F movss   xmm1, [rsp+f]
.text:0000000140001025 cvtps2pd xmm1, xmm1			; xmm1 = (double)参数6
.text:0000000140001028 addsd   xmm4, xmm3			; xmm4 = xmm4 + 参数4 (参数1 + 参数2 + 参数3 + 参数4)
.text:000000014000102C addsd   xmm4, xmm0			; xmm4 = xmm4 + 参数5 (参数1 + 参数2 + 参数3 + 参数4 + 参数5)
.text:0000000140001030 addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数6 (参数1 + 参数2 + 参数3 + 参数4 + 参数5 + 参数6)
.text:0000000140001034 cvtpd2ps xmm0, xmm4			; xmm0 = (float) 参数4
.text:0000000140001038 retn
.text:0000000140001038
.text:0000000140001038 func2 endp

.text:0000000140001040 sub     rsp, 38h
.text:0000000140001044 movss   xmm1, cs:__real@40a00000
.text:000000014000104C movss   xmm0, cs:__real@40c00000
.text:0000000140001054 movsd   xmm3, cs:__real@4010000000000000 ; long double 参数4
.text:000000014000105C movss   xmm2, cs:__real@40400000        ; float 参数3
.text:0000000140001064 movss   dword ptr [rsp+28h], xmm0       ; float 参数6
.text:000000014000106A movss   xmm0, cs:__real@3f800000        ; float 参数1
.text:0000000140001072 movss   dword ptr [rsp+20h], xmm1       ; float 参数5
.text:0000000140001078 movsd   xmm1, cs:__real@4000000000000000 ; long double 参数2
.text:0000000140001080 call    func2
.text:0000000140001080
.text:0000000140001085 cvttss2si eax, xmm0
.text:0000000140001089 add     rsp, 38h
.text:000000014000108D retn

浮点数和整数混合传参,参数会放到对应的整数或浮点寄存器中。例如 fun(int, float, int, float) 函数对应参数会依次放到 ecxxmm1r8dxmm3 寄存器中。

.text:0000000140001000 ; float __fastcall func3(int, long double, int, float, int, float)
.text:0000000140001000 func3 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 xorps   xmm2, xmm2
.text:0000000140001003 xorps   xmm0, xmm0
.text:0000000140001006 cvtsi2sd xmm2, ecx			; xmm2 = (double)参数1
.text:000000014000100A cvtsi2sd xmm0, r8d			; xmm0 = (double)参数3
.text:000000014000100F addsd   xmm2, xmm1			; xmm2 = xmm2 + 参数1 (参数1 + 参数2)
.text:0000000140001013 xorps   xmm1, xmm1
.text:0000000140001016 cvtss2sd xmm1, xmm3			; xmm1 = (double) 参数4
.text:000000014000101A addsd   xmm2, xmm0			; xmm2 = xmm2 + 参数3 (参数1 + 参数2 + 参数3)
.text:000000014000101E xorps   xmm0, xmm0
.text:0000000140001021 cvtsi2sd xmm0, [rsp+e]		; xmm0 = (double)参数5
.text:0000000140001027 addsd   xmm2, xmm1			; xmm2 = xmm2 + 参数4 (参数1 + 参数2 + 参数3 + 参数4)
.text:000000014000102B movss   xmm1, [rsp+f]		; xmm1 = 参数6
.text:0000000140001031 cvtps2pd xmm1, xmm1
.text:0000000140001034 addsd   xmm2, xmm0			; xmm2 = xmm2 + xmm0 (参数1 + 参数2 + 参数3 + 参数4 + 参数5)
.text:0000000140001038 addsd   xmm2, xmm1			; xmm2 = xmm2 + xmm1 (参数1 + 参数2 + 参数3 + 参数4 + 参数5 + 参数6)
.text:000000014000103C cvtpd2ps xmm0, xmm2			; xmm0 = (float)xmm2
.text:0000000140001040 retn
.text:0000000140001040
.text:0000000140001040 func3 endp

.text:0000000140001050 sub     rsp, 38h
.text:0000000140001054 movss   xmm0, cs:__real@40c00000
.text:000000014000105C mov     r8d, 3                          ; int 参数3
.text:0000000140001062 movss   xmm3, cs:__real@40800000        ; float 参数4
.text:000000014000106A movsd   xmm1, cs:__real@4000000000000000 ; long double 参数2
.text:0000000140001072 movss   dword ptr [rsp+28h], xmm0       ; float 参数6
.text:0000000140001078 lea     ecx, [r8-2]                     ; int 参数1
.text:000000014000107C mov     dword ptr [rsp+20h], 5          ; int 参数5
.text:0000000140001084 call    func3
.text:0000000140001084
.text:0000000140001089 cvttss2si eax, xmm0
.text:000000014000108D add     rsp, 38h
.text:0000000140001091 retn

__m128 类型、数组和字符串从不通过即时值传递。 而是将分配的内存的指针传递给被调用方。 大小为 8、16、32 或 64 位的结构和联合以及 __m64 类型作为相同大小的整数传递。 其他大小的结构或联合作为指针传递给调用方分配的内存。 对于这些作为指针传递的聚合类型(包括 __m128),调用方分配的临时内存必须是 16 字节对齐的。

例如下面这段代码:

#include<xmmintrin.h>

struct c {
    float a;
    float b;
};

double func4(__m64 a, __m128 b, struct c c, float d, __m128 e, __m128 f){
    return a.m64_f32[0] + b.m128_f32[0] + c.b + d + e.m128_i16[2] + f.m128_i8[4];
}

int main() {
    return func4((__m64){1}, (__m128){2}, (struct c){3}, 4, (__m128){5}, (__m128){6});
}

根据 IDA 反编译代码可知 __m128 类型传的是地址,其余传的是值。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int128 v4; // [rsp+40h] [rbp-48h] BYREF
  __int128 v5; // [rsp+50h] [rbp-38h] BYREF
  __int128 v6; // [rsp+60h] [rbp-28h] BYREF

  v4 = _xmm;
  v5 = _xmm;
  v6 = _xmm;
  return (int)func4((__m64)1i64, (__m128 *)&v6, (c)0x40400000i64, 4.0, (__m128 *)&v5, (__m128 *)&v4);
}

main 函数汇编如下:

.text:0000000140001050 mov     r11, rsp
.text:0000000140001053 sub     rsp, 88h
.text:000000014000105A mov     rax, cs:__security_cookie
.text:0000000140001061 xor     rax, rsp
.text:0000000140001064 mov     [rsp+70h], rax
.text:0000000140001069 movaps  xmm2, cs:__xmm@00000000000000000000000040c00000
.text:0000000140001070 lea     rax, [r11-48h]
.text:0000000140001074 movaps  xmm1, cs:__xmm@00000000000000000000000040a00000
.text:000000014000107B lea     rdx, [r11-28h]										; 参数2 [rsp+0x60] 地址
.text:000000014000107F movaps  xmm0, cs:__xmm@00000000000000000000000040000000
.text:0000000140001086 mov     ecx, 1												; 参数1
.text:000000014000108B movss   xmm3, cs:__real@40800000								; 参数4
.text:0000000140001093 mov     [r11-60h], rax										; 参数6 将地址 [rsp+0x40] 存入 [rsp+0x28]
.text:0000000140001097 lea     rax, [r11-38h]
.text:000000014000109B mov     qword ptr [r11-58h], 40400000h
.text:00000001400010A3 mov     r8, [r11-58h]										; 参数3
.text:00000001400010A7 mov     [r11-68h], rax										; 参数5 将地址 [rsp+0x50] 存入 [rsp+0x20]
.text:00000001400010AB movaps  xmmword ptr [rsp+40h], xmm2							; 初始化参数6
.text:00000001400010B0 movaps  xmmword ptr [rsp+50h], xmm1							; 初始化参数5
.text:00000001400010B5 movaps  xmmword ptr [rsp+60h], xmm0							; 初始化参数2
.text:00000001400010BA call    func4
.text:00000001400010BA
.text:00000001400010BF cvttsd2si eax, xmm0
.text:00000001400010C3 mov     rcx, [rsp+88h+var_18]
.text:00000001400010C8 xor     rcx, rsp                        ; StackCookie
.text:00000001400010CB call    __security_check_cookie
.text:00000001400010CB
.text:00000001400010D0 add     rsp, 88h
.text:00000001400010D7 retn

返回值

可以适应 64 位的标量返回值(包括 __m64 类型)是通过 RAX 返回的。

; __int64 func()
func proc near
mov     eax, 114514h
retn
func endp

非标量类型(包括浮点类型、双精度类型和向量类型,例如 __m128__m128i__m128d)以 XMM0 的形式返回。

; float func(...)
func proc near
movss   xmm0, cs:__real@47dfa900
retn
func endp

用户定义的结构体如果不超过 64 位则放入 RAX 寄存器返回,调用方传入局部结构体变量的地址作为第一个参数,返回值也是该结构体地址。例如下面这段代码:

struct Struct {
    int j, k, l; // Struct1 exceeds 64 bits.
};

struct Struct func(int a,int b,int c) {
    return (struct Struct){a, b, c};
}

int main() {
    return func(1,2,3).j;
}

反编译代码如下,可以看到 func 函数的第一个参数为结构体地址,而这个结构体是 main 函数的一个局部变量。

Struct *__fastcall func(Struct *s, int a, int b, int c)
{
  Struct *ret; // rax

  s->j = a;
  ret = s;
  s->k = b;
  s->l = c;
  return ret;
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  Struct s; // [rsp+20h] [rbp-18h] BYREF

  return func(&s, 1, 2, 3)->j;
}

具体汇编代码如下:

.text:0000000140001000 ; Struct *__fastcall func(Struct *s, int a, int b, int c)
.text:0000000140001000 func proc near
.text:0000000140001000 mov     [rcx], edx
.text:0000000140001002 mov     rax, rcx
.text:0000000140001005 mov     [rcx+4], r8d
.text:0000000140001009 mov     [rcx+8], r9d
.text:000000014000100D retn
.text:000000014000100D
.text:000000014000100D func endp

.text:0000000140001010 sub     rsp, 38h
.text:0000000140001014 mov     edx, 1                          ; int
.text:0000000140001019 lea     rcx, [rsp+20h]                  ; result
.text:000000014000101E lea     r9d, [rdx+2]                    ; int
.text:0000000140001022 lea     r8d, [rdx+1]                    ; int
.text:0000000140001026 call    func
.text:0000000140001026
.text:000000014000102B mov     eax, [rax]
.text:000000014000102D add     rsp, 38h
.text:0000000140001031 retn

变量

局部变量

  • x64 程度的函数中的局部变量大概率会被优化掉或者使用寄存器存储。
  • 如果局部变量存储在栈上,为了保留函数的影子空间,一般会存放在 [rsp+20h] 以上的位置。

全局变量

示例代码如下(为了便于观察去掉了全局变量的C++名称修饰):

#include<random>

extern "C" {
    int x;
    int y = 1;
    int z = (srand(time(NULL)), rand());
}
int main() {
    printf("%d %d %d\n", x, y, z);
    return 0;
}

我们可以看到全局变量 xyz 全部定义在了 .data 段:

.data:0000000140021000 y dd 1

.data:0000000140021AC0 x dd 0 
.data:0000000140021AC4 z dd 0 

.text:0000000140001090 sub     rsp, 28h
.text:0000000140001094 mov     r9d, cs:z
.text:000000014000109B lea     rcx, _Format                    ; "%d %d %d\n"
.text:00000001400010A2 mov     r8d, cs:y
.text:00000001400010A9 mov     edx, cs:x
.text:00000001400010AF call    printf
.text:00000001400010AF
.text:00000001400010B4 xor     eax, eax
.text:00000001400010B6 add     rsp, 28h
.text:00000001400010BA retn

其中 z 变量被 dynamic_initializer_for__z__ 函数初始化:

int dynamic_initializer_for__z__()
{
  unsigned int v0; // eax
  int result; // eax

  v0 = time(0i64);
  srand(v0);
  result = rand();
  z = result;
  return result;
}

在调用 main 函数之前 _scrt_common_main_seh 函数调用了 C++ 的初始化函数 initterm,该函数会依次调用 _xc_a_xc_z 之间的函数指针指向的函数,其中就包括 dynamic_initializer_for__z__ 函数。

另外 C 的初始化函数是 initterm_e(_xi_a, _xi_z),该函数先于 initterm 函数调用。dynamic_initializer_for__z__ 函数指针没有放到 _xi_a_xi_z 之间也说明了 C 语言不支持全局变量 z 这种初始化方式。

// Calls each function in [first, last).  [first, last) must be a valid range of
// function pointers.  Each function is called, in order.
extern "C" void __cdecl _initterm(_PVFV* const first, _PVFV* const last)
{
    for (_PVFV* it = first; it != last; ++it)
    {
        if (*it == nullptr)
            continue;

        (**it)();
    }
}

// Calls each function in [first, last).  [first, last) must be a valid range of
// function pointers.  Each function must return zero on success, nonzero on
// failure.  If any function returns nonzero, iteration stops immediately and
// the nonzero value is returned.  Otherwise all functions are called and zero
// is returned.
//
// If a nonzero value is returned, it is expected to be one of the runtime error
// values (_RT_{NAME}, defined in the internal header files).
extern "C" int __cdecl _initterm_e(_PIFV* const first, _PIFV* const last)
{
    for (_PIFV* it = first; it != last; ++it)
    {
        if (*it == nullptr)
            continue;

        int const result = (**it)();
        if (result != 0)
            return result;
    }

    return 0;
}


    if ( initterm_e(_xi_a, _xi_z) )
      return 0xFFi64;
    initterm(_xc_a, _xc_z);

静态局部变量

首先如果静态全局变量的初始值为常量则等价为已初始化的全局变量。

如果初值不为常量会设置标志位来确保静态局部变量只被初始化一次。

实例代码:

int main(int argc) {
    static int x = argc;
    static int y = argc;
    return (int) &x + (int) &y;
}

反编译代码:

.data:000000014001A000 _Init_global_epoch dd 80000000h

.data:000000014001AA80 x dd 0
.data:000000014001AA84 $TSS0 dd 0
.data:000000014001AA88 y dd 0
.data:000000014001AA8C $TSS1 dd 0   

void __fastcall Init_thread_header(int *pOnce)
{
  AcquireSRWLockExclusive(&g_tss_srw);
  while ( 1 )
  {
    if ( !*pOnce )
    {
      *pOnce = -1;
      goto LABEL_7;
    }
    if ( *pOnce != -1 )
      break;
    SleepConditionVariableSRW(&g_tss_cv, &g_tss_srw, 0xFFFFFFFF, 0);
  }
  *(_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + tls_index) + 4i64) = Init_global_epoch;
LABEL_7:
  ReleaseSRWLockExclusive(&g_tss_srw);
}

void __fastcall Init_thread_footer(int *pOnce)
{
  __int64 v2; // rdx

  AcquireSRWLockExclusive(&g_tss_srw);
  v2 = tls_index;
  *pOnce = ++Init_global_epoch;
  *(_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + v2) + 4i64) = Init_global_epoch;
  ReleaseSRWLockExclusive(&g_tss_srw);
  WakeAllConditionVariable(&g_tss_cv);
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _DWORD *v4; // rdi

  v4 = (_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + tls_index) + 4i64);
  if ( _TSS0 > *v4 )
  {
    Init_thread_header(&_TSS0);
    if ( _TSS0 == -1 )
    {
      x = argc;
      Init_thread_footer(&_TSS0);
    }
  }
  if ( _TSS1 > *v4 )
  {
    Init_thread_header(&_TSS1);
    if ( _TSS1 == -1 )
    {
      y = argc;
      Init_thread_footer(&_TSS1);
    }
  }
  return (unsigned int)&x + (unsigned int)&y;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1201515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

触摸屏【威纶通】

威纶通&#xff1a; cMT-FHDX-920编程软件&#xff1a; EBproV6.08.02.500_20230828 新建工程&#xff1a; 文件》新建 常用》系统参数 新增设备服务 编程&#xff1a; 目录树》11》新增常用》元件 按钮 标签&#xff1a; 文本信息

MySQL主从环境搭建

MySQL主从环境搭建 主机MySQL配置 修改主机配置文件 vim /etc/my.cnf主要是在my.cnf配置文件中增加以下内容&#xff1a; #主服务器唯一ID server-id1 #启用二进制日志 log-binmysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-dbmysql binlog-ignore-dbinform…

【Python】二维码和条形码的识别

我主要的问题就在于无法识别图片 注意事项&#xff1a; 1、从文件中加载图像的时候注意图片尽量用英文来命名&#xff0c;因为中文无法识别到图片 2、使用绝对地址的时候要用两个双斜杠&#xff0c;因为用一个会被识别为Unicode 转义&#xff0c;但是并没有后续的合法 Unico…

联邦学习研究综述笔记

联邦学习 联邦学习的定义&#xff1a;联邦学习是一种分布式机器学习架构&#xff0c;包含多个客户端&#xff08;参与者&#xff09;和一个聚合服务器。客服端&#xff08;参与方&#xff09;&#xff1a;在本地使用自己的私有数据训练模型&#xff0c;训练完成之后将模型的参…

外中断的应用

前言 软件基础操作参考这篇博客&#xff1a; LED数码管的静态显示与动态显示&#xff08;KeilProteus&#xff09;-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/134101256?spm1001.2014.3001.5501实验一&#xff1a;P1口上接8个LED灯&#xff0c;在外部…

人机交互复习专题

第一章概述 1.1人机交互的概念与理解 人机交互的概念与理解 人机交互是人与机器进行交互的操作方式&#xff0c;即用户与机器互相传递信息的媒介。好的人机交互界面美观且通俗易懂、操作简单有引导功能&#xff0c;使用户感受到愉快、有兴趣&#xff0c;从而提升使用效率。 美…

使用Nodejs搭建简单的Web网页并实现公网访问

目录 前言 1. 安装Node.js环境 2. 创建Node.js应用 3. 安装Cpolar内网穿透实现公网访问Nodejs服务 3.1 注册cpolar账号 3.2 下载cpolar客户端 3.3 创建隧道映射本地端口 4. 固定公网远程地址 前言 Node.js是建立在谷歌Chrome的JavaScript引擎(V8引擎)的Web应用程序框架…

单链表(6)

删除第一个val的值&#xff08;考试重点&#xff09; 思路&#xff1a;例如删除val值为3的数据&#xff0c;直接让数据2的p->next指向数据4就可以了。 所以删除必须依赖前驱。也就是要写删除函数&#xff0c;则先要完成返回key的前驱地址的函数 也就是先知道前驱地址&#…

代码随想录第五十一天 | 动态规划 买卖股票:含冷冻期 的多状态 买卖股票问题(309);包含手续费 的买卖股票问题(贪心,动态规划)(714)

1、含冷冻期 的多状态 买卖股票问题 1.1 leetcode 309&#xff1a;最佳买卖股票时机含冷冻期 第一遍代码 运用之前二维dp数组的方法&#xff0c;第二个维度大小为2&#xff0c;对应持有&#xff0c;不持有 dp[1][0] max(dp[0][0], -prices[1]);注意要考虑只有一天的情况 dp[…

Python---字典的增、删、改、查操作

字典的增操作 基本语法&#xff1a; 字典名称[key] value 注&#xff1a;如果key存在则修改这个key对应的值&#xff1b;如果key不存在则新增此键值对。 案例&#xff1a;定义一个空字典&#xff0c;然后添加name、age以及address这样的3个key # 1、定义一个空字典 person {…

阿里云国际站:密钥管理服务

文章目录 一、密钥管理服务的概念 二、密钥管理服务的功能 三、密钥管理服务的优势 一、密钥管理服务的概念 密钥管理服务KMS&#xff08;Key Management Service&#xff09;是您的一站式密钥管理和数据加密服务平台、一站式凭据安全管理平台&#xff0c;提供简单、可靠、…

creo之混合和扫描混合

案例一&#xff1a;杯子 步骤&#xff1a; 在top平面画一个草图圆角矩形&#xff1a; 然后形状–》混合 然后绘制新增的截面2&#xff1a; 用中心线将圆分割成八分&#xff0c;因为底部的圆角矩形是八份线段组成&#xff0c;所以我们要和他一样分成八份&#xff1a;先画中心线…

深入理解对象存储(OSD)

对象存储 1、对象存储的起源2、什么是对象存储3、对象存储与块存储、文件存储4、对象存储架构4.1、对象&#xff08;Object&#xff09;4.2、对象存储设备&#xff08;OSD&#xff09;4.3、元数据服务器&#xff08;MDS&#xff09;4.4、对象存储系统的客户端&#xff08;Clien…

链表的逆置

方法1&#xff1a; 依次将指针反向&#xff0c;最后令头指针指向尾元素。 逆置过程如下&#xff1a; 当q指针为空时&#xff0c;循环结束。 //试写一算法&#xff0c;对单链表实现就地逆置&#xff0c; void Reverse1(List plist)//太复杂,不用掌握 {assert(plist ! NULL);i…

【计算机网络笔记】IP编址与有类IP地址

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

实验一 Anaconda安装和使用(上机Python程序设计实验指导书)

实验一 Anaconda安装和使用 一、实验目的和要求 &#xff08;一&#xff09;掌握Windows下Anaconda的安装和配置。 &#xff08;二&#xff09;掌握Windows下Anaconda的简单使用&#xff0c;包括IDLE、Jupyter Notebook、Spyder工具的使用。 &#xff08;三&#xff09;掌…

05-Spring中Bean的生命周期

Bean的生命周期 生命周期就是对象从创建开始到最终销毁的整个过程 , Spring其实就是一个管理Bean对象的工厂,它负责对象的创建和销毁等 Bean生命周期的管理可以参考Spring的源码&#xff1a;AbstractAutowireCapableBeanFactory类的doCreateBean()方法 研究生命周期的意义&am…

2023最新版本 从零基础入门C++与QT(学习笔记) -1- C++输入与输出

&#x1f38f;说在前面 &#x1f388;我预计是使用两个月的时间玩转C与QT &#x1f388;所以这是一篇学习笔记 &#x1f388;根据学习的效率可能提前完成学习,加油&#xff01;&#xff01;&#xff01; 输入(代码如下方代码块) &#x1f384;分析一下构成 &#x1f388;…

【可解释AI】Alibi explain: 解释机器学习模型的算法

Alibi explain: 解释机器学习模型的算法 可解释人工智能简介Alibi特点算法Library设计展望参考资料 今天介绍Alibi Explain&#xff0c;一个开源Python库&#xff0c;用于解释机器学习模型的预测(https://github.com/SeldonIO/alibi)。该库具有最先进的分类和回归模型可解释性算…

springboot项目基本配置

接口入口日志 参数校验 业务逻辑执行 异常捕获-统一异常处理 统一数据返回体 接口返回日志 使用的是springboot2.x版本。 Mybatisplus 官网地址&#xff1a;https://baomidou.com/ 导入依赖 <dependency><groupId>com.baomidou</groupId><artifactId&g…