免费制作app生成器网站包装设计费用大概多少
本文会使用IDA分析局部变量,全局变量在内存的存储
目录
使用IDA分析局部变量
使用IDA分析全局变量
总结
使用IDA分析局部变量
#include <stdio.h>int main()
{int nNum = 1;float fNum = 2.5;char ch = 'A';printf("int %d, float %f, char %c", nNum, fNum, ch);return 0;
}
 
使用IDA跟进到main函数,F5调出反汇编

摁下tab键,进入汇编界面,Rename函数名为main

Rename函数名为main

CODE XREF: 当前函数被一个或多个地方引用,摁下CTRL+X可以查看调用main()的地方

局部变量汇编分析
.text:00411760 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00411760 main            proc near               ; CODE XREF: j_main↑j
.text:00411760
.text:00411760 var_FC          = qword ptr -0FCh
.text:00411760 var_24          = byte ptr -24h
.text:00411760 var_1D          = byte ptr -1Dh
.text:00411760 var_14          = dword ptr -14h
.text:00411760 var_8           = dword ptr -8
.text:00411760 argc            = dword ptr  8
.text:00411760 argv            = dword ptr  0Ch
.text:00411760 envp            = dword ptr  10h
.text:00411760
.text:00411760                 push    ebp             ; 把调用者函数的EBP压入堆栈
.text:00411761                 mov     ebp, esp        ; 开辟栈帧
.text:00411763                 sub     esp, 0E4h       ; 开辟函数局部变量空间
.text:00411769                 push    ebx             ; 保存易失性寄存器
.text:0041176A                 push    esi
.text:0041176B                 push    edi
.text:0041176C                 lea     edi, [ebp+var_24] ; ebp+var_24 <==> ebp-24h      lea取局部变量地址到edi        EDI = EBP-36 36个字节要初始化作为局部变量的空间
.text:0041176F                 mov     ecx, 9
.text:00411774                 mov     eax, 0CCCCCCCCh
.text:00411779                 rep stosd
.text:0041177B                 mov     ecx, offset unk_41C003
.text:00411780                 call    sub_41130C      ; CheckForDebuggerJustMyCode  VS系统函数
.text:00411785                 mov     [ebp+var_8], 1  ; int nNum = 1
.text:0041178C                 movss   xmm0, ds:dword_417BE8 ; xmm0寄存器用于处理浮点数,2.5存入寄存器
.text:00411794                 movss   [ebp+var_14], xmm0 ; float fNum = 2.5
.text:00411799                 mov     [ebp+var_1D], 41h ; char ch ='A'
.text:0041179D                 movsx   eax, [ebp+var_1D] ; 把ch扩展为4个字节,为压栈做准备
.text:004117A1                 push    eax             ; ch压栈
.text:004117A2                 cvtss2sd xmm0, [ebp+var_14] ; float拓展为double
.text:004117A7                 sub     esp, 8          ; 开辟堆栈空间
.text:004117AA                 movsd   [esp+0FCh+var_FC], xmm0 ; double入栈
.text:004117AF                 mov     ecx, [ebp+var_8]
.text:004117B2                 push    ecx             ; 把nNum的值保存到ecx后入栈
.text:004117B3                 push    offset aIntDFloatFChar ; 格式化字符串后入栈 offset取地址4个字节大小
.text:004117B8                 call    sub_4113A2      ; 调用printf函数
.text:004117BD                 add     esp, 14h        ; 平衡堆栈
.text:004117C0                 xor     eax, eax        ; 把eax清0,等价于 return 0
.text:004117C2                 pop     edi             ; 恢复易失性寄存器
.text:004117C3                 pop     esi
.text:004117C4                 pop     ebx
.text:004117C5                 add     esp, 0E4h       ; 清理main函数开辟的局部变量空间
.text:004117CB                 cmp     ebp, esp
.text:004117CD                 call    sub_411230      ; CheckEsp()   用于在程序的运行时检查堆栈指针是否被篡改
.text:004117D2                 mov     esp, ebp        ; 恢复栈帧
.text:004117D4                 pop     ebp
.text:004117D5                 retn                    ; 将程序的控制流转移到调用它的指令的下一条指令
.text:004117D5 main            endp 
main函数主体堆栈,堆栈存储的是函数的局部变量,函数参数
- push EBP EBP存储当前函数的栈底地址,调用者函数栈底地址压入堆栈
 - mov EBP,ESP 开辟栈帧
 - sub esp, 0xXXX 为函数局部变量开辟空间
 - push ebx,esi,edi 保存易失性寄存器
 - 为局部变量空间堆栈初始化为0xCC
 - 如果main函数有新的局部变量或者调用函数,都会引发堆栈变化
 - main函数执行完毕开始恢复堆栈
 - pop 寄存器 恢复易失性寄存器
 - add esp,0xXX 恢复局部变量的空间
 - mov esp,ebp ESP=EBP=函数调用者的栈底地址
 - pop ebp 恢复原先的栈底
 - retn 将执行main后下一条汇编指令地址给EIP寄存器,恢复程序控制流转,也恢复了原先的栈顶
 
一些细节:
rep stosd:完成对局部变量的初始化
.text:00411769                 push    ebx             ; 保存易失性寄存器
.text:0041176A                 push    esi
.text:0041176B                 push    edi
.text:0041176C                 lea     edi, [ebp+var_24] ; ebp+var_24 <==> ebp-24h      lea取局部变量地址到edi
.text:0041176F                 mov     ecx, 9
.text:00411774                 mov     eax, 0CCCCCCCCh
.text:00411779                 rep stosd               ; 把9个栈空间初始化为0xCC 
汇编:
- rep 重复操作,重复操作的次数是ecx的值
 - stosd:把EAX值复制到EDI寄存器指向的地址,一次复制4个字节
 - stosw:把EAX值复制到EDI寄存器指向的地址,一个复制2个字节
 - stosb:把EAX值复制到EDI寄存器指向的地址,一次复制1个字节
 
之前的操作中,保存易失性寄存器,每个寄存器8个字节,共计24字节
lea edi, [ebp+var_24] ; ebp+var_24 <==> ebp-24h edi寄存器指向main函数局部变量空间的尾地址
下面代码,给局部变量空间初始化
000F176F B9 09 00 00 00       mov         ecx,9  
000F1774 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
000F1779 F3 AB                rep stos    dword ptr es:[edi] 
"rep stos dword ptr es:[edi]" 的执行步骤如下:
- 使用ES:EDI指定目标内存区域的起始地址。
 - 使用ECX指定要重复执行的次数。
 - 将EAX中的值作为DWORD数据写入ES:[EDI]指向的内存位置,然后根据DF(方向标志位)决定EDI是递增还是递减,以定位下一个内存位置。
 - 重复步骤3,直到ECX达到零,或根据DF决定的方向不再满足条件。
 因此,"rep stos dword ptr es:[edi]" 指令将会将EAX寄存器中的DWORD数据连续地写入以ES:[EDI]为起点的目标内存区域,并且重复执行次数由ECX指定。这通常用于快速地内存初始化或者进行内存拷贝操作。
但是在64位下,不再使用ES:EDI模式寻址,64位模式下采用了平坦模型(Flat Model),所有线性地址都直接映射到相同的物理地址空间,即,你可以直接使用RDI寄存器作为指针来操作内存地址,而无需使用ES寄存器进行段寻址。
在x64dbg下验证

DF(Direction Flag)是x86处理器的一个标志位,用于指示字符串操作的方向。
当DF标志位被设置为0时,字符串操作是从低地址向高地址进行的,也就是正向(forward)操作。例如,使用REP MOVS指令将数据从源内存区域复制到目标内存区域时,数据会按照从源地址递增到目标地址的顺序进行复制。
当DF标志位被设置为1时,字符串操作是从高地址向低地址进行的,也就是反向(backward)操作。例如,使用REP MOVS指令将数据从目标内存区域复制到源内存区域时,数据会按照从源地址递减到目标地址的顺序进行复制。

得以验证在64位下,直接可以通过EDI寻址操作,不再使用ES:EDI寻址
为什么要每个字节初始化为0xCC

0xCC表示int33异常 0xCC指令是软中断指令,它会导致CPU进入一个中断处理例程。如果程序在调用函数时忘记初始化局部变量,那么在访问这些未初始化的变量时,CPU会立即进入一个中断处理例程,程序会停止运行,并且IDE或调试器会提示有异常产生。
这个地址存储的是浮点数

可以在Edit-->Operand type,选择合适的数据类型,正确的话,就会把16进制数据转换为对应的值。选择floatinteresting point

常见的操作数大小指定符号:
byte ptr:指定一个数据操作数为8位字节(byte)。word ptr:指定一个数据操作数为16位字(word)。- dword ptr:指定一个数据操作数为32位字 (dword)
 mmword ptr是一个指示符,指示使用 64 位内存操作。
下面这两句代码在不同的工具中解析虽然不同,但意思一样。估计IDA 7.0 解析时是按照x86,而VS2019是按照x64,x64中已经不使用DS:EDI寻址了
VS2019: movss xmm0,dword ptr [__real@40200000 (0AF7BE8h)]
IDA: movss xmm0, ds:dword_417BE8
dword ptr [__real@40200000 (0AF7BE8h)] = ds:dword_417BE8 都表示以后面地址为首,取四个字节的数据
xmm0是 XMM 寄存器中的第一个寄存器- 在 x86 指令集中,有 8 个 xmm 寄存器(xmm0~xmm7),每个寄存器的大小为 128 位(16 字节)
 
- movss:将一个单精度浮点数(32 位)从源操作数移动到目标操作数。
 - movsx:将源操作数进行符号扩展后移动到目标操作数,源:8位,目标:16位,从低位存,高 8 位设置为源操作数的符号位,不改变大小,拓展位数 默认情况下,内存操作数被假定为 32 位的双字(DWORD)数据类型。
 - cvtss2sd:将单精度浮点数(4)转换为双精度浮点数(8)
 - xor:用于执行两个二进制数按位异或操作。当两个对应位的值不同时,结果位为 1,否则为 0。
 
cmp指令:用于比较两个操作数的大小关系,并根据比较结果设置标志位。cmp 指令会将 operand1 减去 operand2 的值,并根据结果设置标志位寄存器(如零标志位、符号标志位等)。
- 零标志位(ZF):如果两个操作数相等,则 ZF 被设置为 1;否则被设置为 0。
 - 符号标志位(SF):如果结果为负数,则 SF 被设置为 1;否则被设置为 0。
 - 位标志位(CF):用于无符号数比较,如果 
operand1小于operand2,则 CF 被设置为 1;否则被设置为 0。 
使用IDA分析全局变量
#include <stdio.h>int nNum = 1;
float fNum = 2.5;
char ch = 'A';int main()
{printf("int %d, float %f, char %c", nNum, fNum, ch);return 0;
}
 
局部变量汇编分析
.text:00411760 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00411760 main            proc near               ; CODE XREF: j_main↑j
.text:00411760
.text:00411760 var_D8          = qword ptr -0D8h
.text:00411760 argc            = dword ptr  8
.text:00411760 argv            = dword ptr  0Ch
.text:00411760 envp            = dword ptr  10h
.text:00411760
.text:00411760                 push    ebp
.text:00411761                 mov     ebp, esp        ; 开辟栈帧
.text:00411763                 sub     esp, 0C0h       ; 给局部变量开辟空间
.text:00411769                 push    ebx             ; 保存易失性寄存器
.text:0041176A                 push    esi
.text:0041176B                 push    edi
.text:0041176C                 mov     edi, ebp        ; 从EDI从低地址向高地址初始化局部变量空间
.text:0041176E                 xor     ecx, ecx
.text:00411770                 mov     eax, 0CCCCCCCCh
.text:00411775                 rep stosd
.text:00411777                 mov     ecx, offset unk_41C003
.text:0041177C                 call    sub_41130C
.text:00411781                 movsx   eax, byte_41A03C ; 把字符拓展为4字节,压入堆栈
.text:00411788                 push    eax
.text:00411789                 cvtss2sd xmm0, dword_41A038 ; 把float拓展为double
.text:00411791                 sub     esp, 8
.text:00411794                 movsd   [esp+0D8h+var_D8], xmm0 ; 把xmm0压入堆栈 这种压入堆栈方式相当于先抬升,esp变小,在压入xmm,从低地址填充到高地址
.text:00411799                 mov     ecx, dword_41A034
.text:0041179F                 push    ecx
.text:004117A0                 push    offset aIntDFloatFChar ; 压入的是字符串的地址,共计四个字节
.text:004117A5                 call    sub_4113A2
.text:004117AA                 add     esp, 14h        ; ch(4) + xmm0(8) + ecx(4) + 字符串地址(4) = 20字节 = 0x14 平衡堆栈
.text:004117AD                 xor     eax, eax        ; 清0 eax,相当于 return 0
.text:004117AF                 pop     edi             ; 恢复易失性寄存器
.text:004117B0                 pop     esi
.text:004117B1                 pop     ebx
.text:004117B2                 add     esp, 0C0h       ; 恢复函数局部变量空间
.text:004117B8                 cmp     ebp, esp
.text:004117BA                 call    sub_411230      ; CheckEsp检查当前函数栈帧是否被破坏
.text:004117BF                 mov     esp, ebp        ; 恢复栈帧
.text:004117C1                 pop     ebp
.text:004117C2                 retn
.text:004117C2 main            endp 
 
总结
- 全局变量保存在data段,在程序编译链接就确定了;局部变量保存在stack段
 - 访问局部变量往往是通过[ebp-0xXXX],全局变量往往是一个地址[0xXXX]
 - 把局部变量或者全局变量压入堆栈需要使用寄存器
 - 给局部变量赋值,mov [esp-0xXXX],值
 - 函数有返回值往往借助寄存器,值或者地址
 
