【汇编逆向系列】九、函数传参之结构体 - SHL、SHR指令,小型结构体参数和返回值
目录
1. 汇编代码
1.1 debug编译
1.2 release编译
2. 汇编分析
2.1 结构体的头文件
2.2 结构体传参
2.3 计算
2.4返回结构体
2.5 汇编优化 - SHR指令
3. 汇编转化
3.1 debug编译
3.2 Release编译
3.3 C语言转化
1. 汇编代码
前面章节描述了参数传参的汇编写法,但是一般情况下如果参数非常多,也不会使用很多参数而是使用结构体,那么参数是一个结构体在汇编上是如何体现的呢?先来看一组汇编代码。
1.1 debug编译
struct_by_value_param:0000000000000A90: 48 89 4C 24 08 mov qword ptr [rsp+8],rcx0000000000000A95: 57 push rdi0000000000000A96: 48 83 EC 40 sub rsp,40h0000000000000A9A: 48 8B FC mov rdi,rsp0000000000000A9D: B9 10 00 00 00 mov ecx,10h0000000000000AA2: B8 CC CC CC CC mov eax,0CCCCCCCCh0000000000000AA7: F3 AB rep stos dword ptr [rdi]0000000000000AA9: 48 8B 4C 24 50 mov rcx,qword ptr [rsp+50h]0000000000000AAE: 8B 44 24 50 mov eax,dword ptr [rsp+50h]0000000000000AB2: D1 E0 shl eax,10000000000000AB4: 89 44 24 28 mov dword ptr [rsp+28h],eax0000000000000AB8: 8B 44 24 54 mov eax,dword ptr [rsp+54h]0000000000000ABC: D1 E0 shl eax,10000000000000ABE: 89 44 24 2C mov dword ptr [rsp+2Ch],eax0000000000000AC2: 48 8B 44 24 28 mov rax,qword ptr [rsp+28h]0000000000000AC7: 48 8B F8 mov rdi,rax0000000000000ACA: 48 8B CC mov rcx,rsp0000000000000ACD: 48 8D 15 00 00 00 lea rdx,[struct_by_value_param$rtcFrameData]000000000000000AD4: E8 00 00 00 00 call _RTC_CheckStackVars0000000000000AD9: 48 8B C7 mov rax,rdi0000000000000ADC: 48 83 C4 40 add rsp,40h0000000000000AE0: 5F pop rdi0000000000000AE1: C3 ret
1.2 release编译
struct_by_value_param:0000000000000000: 8D 04 09 lea eax,[rcx+rcx]0000000000000003: 48 C1 E9 20 shr rcx,20h0000000000000007: 03 C9 add ecx,ecx0000000000000009: 89 44 24 08 mov dword ptr [rsp+8],eax000000000000000D: 89 4C 24 0C mov dword ptr [rsp+0Ch],ecx0000000000000011: 48 8B 44 24 08 mov rax,qword ptr [rsp+8]0000000000000016: C3 ret
2. 汇编分析
2.1 结构体的头文件
在此类汇编中,汇编语言是操作地址,编译器会将结构体的每个参数都转化为地址,所以一定需要头文件开看结构体的声明才能了解汇编做了什么,所以先找到该结构体的声明
typedef struct {int x;int y;
} Point;
可以看到该结构体只是非常简单两个4字节的有符号整型参数
2.2 结构体传参
在 x64 调用约定中,小型结构体(如 8 字节的 Point
)通过寄存器 RCX
传递(Windows)或拆分到多个寄存器(Linux)。
此处通过 mov qword ptr [rsp+8], rcx
保存到栈的 [rsp+8]位置
push rdi和sub rsp,40h两个指令将rsp的地址减了0x48h,所以[rsp+8]地址变为[rsp+50h]
后续通过 [rsp+50h]
访问:
[rsp+50h]
→p.x
(低 4 字节)[rsp+54h]
→p.y
(高 4 字节)
2.3 计算
mov eax, dword ptr [rsp+50h] ; 加载 p.x
shl eax, 1 ; eax = p.x * 2
mov dword ptr [rsp+28h], eax ; 存储到局部变量 result.x
mov eax,dword ptr [rsp+54h]; 加载p.y
shl eax,1 ; eax= p.yu *2
mov dword ptr [rsp+2Ch],eax ; 存储局部变量 result.y
可以看到这一步操作将p.x和p.y 都乘以2
SHL为左移指令,左移一位代表乘以2
将两个临时变量存储到了[rsp+0x28h]和[rsp+0x2ch]
2.4返回结构体
小型结构体通过 RAX
寄存器返回:
mov rax, qword ptr [rsp+28h] ; 将 result.x 和 result.y 作为 8 字节值加载到 RAX
mov rdi, rax ; 临时保存返回值
call _RTC_CheckStackVars ; 栈检查(调试用)
mov rax, rdi ; 恢复返回值到 RAX
ret ; 返回 RAX 中的 Point 结构体
2.5 汇编优化 - SHR指令
lea eax, [rcx+rcx] // LEA指令计算 x*2:取RCX低32位(x)与自身相加,结果存入EAX(相当于x*2)
shr rcx, 20h // 将RCX右移32位(0x20),使原高32位(y)移动到低32位
add ecx, ecx // 将ECX(原y值)乘以2:ecx = ecx + ecx
汇编优化操作,几个指令后,EAX存放X*2, ECX存放Y*2
3. 汇编转化
可以看到这个函数做了一个小型结构体的转化示例,但是大型结构体的操作是不一样的下一节,我们单独介绍大型结构体如何传参
3.1 debug编译
struct_by_value_param:; 函数入口:保存参数寄存器RCX(结构体指针)到栈上[rsp+8]0000000000000A90: 48 89 4C 24 08 mov qword ptr [rsp+8], rcx ; 保存RCX(结构体参数)到调用者栈帧; 函数序言:保存非易失寄存器并分配栈空间0000000000000A95: 57 push rdi ; 保存RDI(非易失寄存器)0000000000000A96: 48 83 EC 40 sub rsp, 40h ; 分配64字节栈空间(含影子空间+局部变量); 调试模式栈初始化:用0xCC填充栈空间(调试器未初始化标记)0000000000000A9A: 48 8B FC mov rdi, rsp ; RDI = 栈顶地址0000000000000A9D: B9 10 00 00 00 mov ecx, 10h ; 循环次数=16(64字节/4)0000000000000AA2: B8 CC CC CC CC mov eax, 0CCCCCCCCh ; 填充值=0xCCCCCCCC0000000000000AA7: F3 AB rep stos dword ptr [rdi] ; 重复填充栈空间; 加载结构体参数并处理第一个字段(x)0000000000000AA9: 48 8B 4C 24 50 mov rcx, qword ptr [rsp+50h] ; RCX = 原始结构体指针(参数区)0000000000000AAE: 8B 44 24 50 mov eax, dword ptr [rsp+50h] ; EAX = 结构体第一个成员(x)0000000000000AB2: D1 E0 shl eax, 1 ; EAX = x * 20000000000000AB4: 89 44 24 28 mov dword ptr [rsp+28h], eax ; 结果存局部变量1(x*2); 处理第二个字段(y)0000000000000AB8: 8B 44 24 54 mov eax, dword ptr [rsp+54h] ; EAX = 结构体第二个成员(y)0000000000000ABC: D1 E0 shl eax, 1 ; EAX = y * 20000000000000ABE: 89 44 24 2C mov dword ptr [rsp+2Ch], eax ; 结果存局部变量2(y*2); 准备返回值(新结构体)0000000000000AC2: 48 8B 44 24 28 mov rax, qword ptr [rsp+28h] ; RAX = 合并的x*2和y*2(8字节)0000000000000AC7: 48 8B F8 mov rdi, rax ; RDI临时保存返回值(RAX是返回寄存器); 调试检查:验证栈变量是否被破坏0000000000000ACA: 48 8B CC mov rcx, rsp ; RCX = 栈帧基址0000000000000ACD: 48 8D 15 00 00 00 lea rdx, [struct_by_value_param$rtcFrameData] ; 调试信息表000000000000000AD4: E8 00 00 00 00 call _RTC_CheckStackVars ; 调用运行时栈检查(调试模式); 函数收尾:设置返回值并恢复栈0000000000000AD9: 48 8B C7 mov rax, rdi ; RAX = 返回值(新结构体)0000000000000ADC: 48 83 C4 40 add rsp, 40h ; 释放栈空间(64字节)0000000000000AE0: 5F pop rdi ; 恢复RDI寄存器0000000000000AE1: C3 ret ; 函数返回
3.2 Release编译
// 函数:struct_by_value_param
// 输入:RCX = 64位参数(低32位为x,高32位为y)
// 输出:RAX = 64位返回值(低32位为x*2,高32位为y*2)0000000000000000: 8D 04 09 lea eax, [rcx+rcx] // LEA指令计算 x*2:取RCX低32位(x)与自身相加,结果存入EAX(相当于x*2)0000000000000003: 48 C1 E9 20 shr rcx, 20h // 将RCX右移32位(0x20),使原高32位(y)移动到低32位0000000000000007: 03 C9 add ecx, ecx // 将ECX(原y值)乘以2:ecx = ecx + ecx0000000000000009: 89 44 24 08 mov dword ptr [rsp+8], eax // 将x*2的结果存入栈[rsp+8](低32位)000000000000000D: 89 4C 24 0C mov dword ptr [rsp+0Ch], ecx // 将y*2的结果存入栈[rsp+0Ch](高32位)0000000000000011: 48 8B 44 24 08 mov rax, qword ptr [rsp+8] // 从栈中加载8字节数据(x*2和y*2)到RAX作为返回值0000000000000016: C3 ret // 函数返回,RAX包含新结构体
3.3 C语言转化
Point struct_by_value_param(Point p) {Point result = {p.x * 2, p.y * 2};return result;
}