加载器
| 加载器选项 | 适用文件类型 | 主要特点 | 场景 |
Portable executable for 80386 (PE) |
32位 Windows 可执行文件 (EXE, DLL等) |
IDA能正确解析PE文件头、节区、导入/导出表等结构,自动识别处理器类型,提供最准确的反汇编结果 |
分析绝大多数Windows平台上的32位应用程序、动态链接库 |
MS-DOS executable (EXE) |
16位 MS-DOS 可执行文件 |
用于处理古老的DOS程序。如果你分析的确实是一个DOS时代的文件,才需要选择此项 |
分析16位的DOS程序。对于现代Windows程序,选择此项会导致分析错误 |
Binary file |
无标准格式的原始二进制数据 | 最底层的加载方式。IDA不会做任何结构解析,需要手动指定处理器类型、加载地址等参数 |
分析固件、引导扇区、Shellcode或任何没有标准文件头的原始二进制数据 |
处理器
Metapc
x86: Intel 80x86 processors- 绝大多数
32位Windows可执行程序(PE文件)、DOS程序。这是最常见的选择
- 绝大多数
示例函数
汇编
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
.text:100116A0 push ebp .text:100116A1 mov ebp, esp .text:100116A3 sub esp, 0CCh .text:100116A9 push ebx .text:100116AA push esi .text:100116AB push edi .text:100116AC .text:100116AC __$EncStackInitStart: .text:100116AC lea edi, [ebp+var_C] .text:100116AF mov ecx, 3 .text:100116B4 mov eax, 0CCCCCCCCh .text:100116B9 rep stosd .text:100116BB .text:100116BB __$EncStackInitEnd: ; JMC_flag .text:100116BB mov ecx, offset _DE27BDBC_dllmain@cpp .text:100116C0 call j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x) .text:100116C5 nop .text:100116C6 mov esi, esp .text:100116C8 push offset aA ; "a" .text:100116CD push offset aDDownloadRepoF ; "d:\\download_repo\\fadfss\\test\\log\\d"... .text:100116D2 call ds:__imp__fopen .text:100116D8 add esp, 8 .text:100116DB cmp esi, esp .text:100116DD call j___RTC_CheckEsp .text:100116E2 mov [ebp+f], eax .text:100116E5 cmp [ebp+f], 0 .text:100116E9 jz short loc_10011717 .text:100116EB mov eax, [ebp+message] .text:100116EE push eax .text:100116EF push offset aTargetdllS ; "[TargetDll] %s\n" .text:100116F4 mov ecx, [ebp+f] .text:100116F7 push ecx ; _Stream .text:100116F8 call j__fprintf .text:100116FD add esp, 0Ch .text:10011700 mov esi, esp .text:10011702 mov eax, [ebp+f] .text:10011705 push eax ; Stream .text:10011706 call ds:__imp__fclose .text:1001170C add esp, 4 .text:1001170F cmp esi, esp .text:10011711 call j___RTC_CheckEsp .text:10011716 nop .text:10011717 .text:10011717 loc_10011717: ; CODE XREF: LogToFile(char const *)+49↑j .text:10011717 pop edi .text:10011718 pop esi .text:10011719 pop ebx .text:1001171A add esp, 0CCh .text:10011720 cmp ebp, esp .text:10011722 call j___RTC_CheckEsp .text:10011727 mov esp, ebp .text:10011729 pop ebp .text:1001172A retn .text:1001172A ?LogToFile@@YAXPBD@Z endp |
函数开场白
push/pop:esp的变化是指令的副作用(push顺带-4,pop顺带+4)sub esp / add esp:是直接、显式地修改esp的值
|
1 2 3 |
push ebp ; ① 把旧 ebp 压栈保存。esp 自动 -4(push 的副作用) mov ebp, esp ; ② 把当前 esp 复制给 ebp,建立新栈帧基址。esp 不变! sub esp, 0CCh ; ③ esp 直接 -0xCC,开辟局部变量空间。esp 变小(显式修改) |
- 执行完这三条后,栈的样子(地址从高到低往下画):
|
1 2 3 4 5 6 7 8 9 |
高地址 ... ← 调用者的栈帧 返回地址 ← call 进来时压的 旧的 ebp ← push ebp 存在这,ebp 正指向这里 ──────────── ← ebp 和 esp 在 mov 之后曾重合于此 局部变量空间 (0xCC 字节) ← sub esp 撑开的区域 ... ← esp 现在指向这片区域的底部 低地址 |
- 之后函数里访问局部变量就是
[ebp - 偏移](在ebp下方那片0xCC空间里),访问参数就是[ebp + 偏移](在ebp上方,返回地址再往上)ebp在整个函数执行期间固定不动,当一个稳定的"标尺"用- 这就是为什么要先把
esp存进ebp——esp后面还会因为push/pop不停变动,不适合当标尺
Debug 版特有的栈初始化
- 它把刚分配的局部变量内存全填成
0xCCCCCCCC0xCC是Debug版的标志性魔数- 只要在内存里看到一片
CC CC CC CC,就知道这是未初始化的栈空间(Release版没有这个)
rep stosd的意思是"重复存储eax到edi指向的内存,重复ecx(=3)次"
|
1 2 3 4 5 |
__$EncStackInitStart: lea edi, [ebp+var_C] mov ecx, 3 mov eax, 0CCCCCCCCh rep stosd |
调试特性检查
MSVC "Just My Code"调试特性插入的检查
|
1 2 3 |
mov ecx, offset _DE27BDBC_dllmain@cpp call j_@__CheckForDebuggerJustMyCode@4 nop |
打开文件
- 这是在调用
fopen(filename, mode)- 注意这是
x86,参数靠栈传,从右往左压,所以 - 先压的
"a"是第 2 个参数 → 打开模式"a"(append追加) - 后压的路径
"d:\download_repo\..."是第 1 个参数 → 文件名
- 注意这是
- 调用后
add esp, 8把两个参数从栈上清掉(cdecl约定,调用方清栈)
|
1 2 3 4 5 |
mov esi, esp ; RTC 用,记下当前 esp push offset aA ; "a" ; 第2个参数 push offset aDDownloadRepoF ; "d:\\download_repo\\..." ; 第1个参数 call ds:__imp__fopen add esp, 8 ; 清理栈(2个参数×4字节) |
fopen的返回值(FILE指针)在eax里,这里把它存进局部变量(IDA已命名为f)
判断文件是否打开成功
- 比较
f和0,如果等于0(jz= 为零则跳)就跳到loc_10011717(也就是函数结尾,直接返回)
|
1 2 |
cmp [ebp+f], 0 jz short loc_10011717 |
写日志(成功分支)
- 调用
fprintf(f, "[TargetDll] %s\n", message)- 同样从右往左压参,还原出来三个参数清清楚楚:文件流
f、格式字符串、要写入的message
- 同样从右往左压参,还原出来三个参数清清楚楚:文件流
- 注意
[ebp+message]这个正偏移- 正偏移取的是传进来的参数(就是
LogToFile的那个char const *入参),负偏移如[ebp+f]、[ebp+var_C]是局部变量
- 正偏移取的是传进来的参数(就是
fclose(f)——写完关闭文件
|
1 2 3 4 5 6 7 |
mov eax, [ebp+message] ; 取参数 message push eax ; fprintf 第3个参数 push offset aTargetdllS ; "[TargetDll] %s\n" ; 第2个参数(格式串) mov ecx, [ebp+f] push ecx ; 第1个参数(文件流) call j__fprintf add esp, 0Ch ; 清理3个参数(3×4=0xC) |
函数收尾
- 和开头对称:逆序弹回保存的寄存器、回收栈空间、还原
ebp、返回
|
1 2 3 4 5 6 7 8 9 |
loc_10011717: pop edi pop esi pop ebx ; 恢复之前保存的3个寄存器(逆序pop) add esp, 0CCh ; 回收局部变量空间 ... mov esp, ebp pop ebp ; 恢复栈帧 retn ; 返回 |
函数签名相关
解析
?—— 起始标记- 所有
MSVC修饰过的名字都以?开头 - 看到
?就知道这是个C++修饰名(C 函数通常是_funcname这种,不走这套)
- 所有
LogToFile—— 函数名@@—— 名称限定结束符- 这里的
@用来分隔"名称作用域" - 如果函数在类或命名空间里,中间会夹着类名/命名空间名,比如
?foo@MyClass@@...表示MyClass::foo - 这里是
@@(两个连着,中间什么都没有),说明它不属于任何类或命名空间,是个全局函数 - 第一个
@结束函数名,第二个@表示作用域列表为空
- 这里的
YA—— 函数属性 + 调用约定- 第一个字母(这里是
Y):表示这是个普通的非成员函数(全局函数) - 如果是类成员函数,这里会是别的字母(如
Q/A等,还带访问权限信息) - 第二个字母
A:表示调用约定是__cdecl(C默认约定) - 其他常见的:
G=__stdcall,E=__thiscall,I=__fastcall - 所以
YA= 全局函数 +__cdecl
- 第一个字母(这里是
- 返回类型和参数:编码后紧接着先写返回类型,再写参数
| 编码 | 类型 |
X |
void |
D |
char |
C |
signed char |
E |
unsigned char |
F |
short |
H |
int |
I |
unsigned int |
J |
long |
N |
double |
M |
float |
_N |
bool |
P |
指针 |
A |
引用(&) |
PA |
指向非 const 的指针 |
PB |
指向 const 的指针 |
Q |
const 指针 |
@Z—— 参数列表结束@结束参数列表,Z是函数的终止标记- 如果函数无参数,参数位置会是
X,比如void f(void)是...YAXXZ
示例
|
1 |
?LogToFile@@YAXPBD@Z endp |
|
1 2 3 4 5 6 7 8 9 |
? LogToFile @@ YA X PBD @Z │ │ │ │ │ │ │ │ │ │ │ │ │ └─ 参数列表结束标记 │ │ │ │ │ └───── 参数:char const * │ │ │ │ └───────── 返回类型:void │ │ │ └───────────── 调用约定 + 函数属性 │ │ └───────────────── 名称限定结束 │ └─────────────────────────── 函数名 └─────────────────────────────── 修饰名起始标记 |
undname
- 开发者命令行提示
|
1 2 3 4 5 6 7 8 9 10 11 |
********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.13.5 ** Copyright (c) 2022 Microsoft Corporation ********************************************************************** D:\code_test\windows\dll_injector_demo>undname ?LogToFile@@YAXPBD@Z Microsoft (R) C++ Name Undecorator Copyright (C) Microsoft Corporation. All rights reserved. Undecoration of :- "?LogToFile@@YAXPBD@Z" is :- "void __cdecl LogToFile(char const *)" |
IDA的正负偏移
IDA显示的[ebp+message]、[ebp+f]里那些名字(message、f、var_C)本身代表的就是一个数值偏移,而且这个值可正可负- 加号是
IDA统一的显示格式,真正的正负藏在那个名字背后
- 加号是
IDA为什么都显示成+?- 为了可读性,把栈上的位置都起了名字(
symbol),然后统一用[ebp + 符号名]的格式显示 - 但符号名背后绑定的偏移量是带符号的
- 为了可读性,把栈上的位置都起了名字(
- 怎么一眼区分正负?
var_XXX开头的 → 一定是负偏移(局部变量)arg_XXX开头的 → 一定是正偏移(参数)
- 自己改过名 /
IDA根据符号识别的名字(如message、f)- 像
message、f这种有意义的名字,要么是有符号信息(pdb)IDA自动还原的,要么是你或别人手动N改的 - 这种名字本身看不出正负
- 方法一:靠语义推断
- 方法二:直接看
IDA给的实际数值
- 像
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-000000000000000C _BYTE var_C; -000000000000000B // padding byte -000000000000000A // padding byte -0000000000000009 // padding byte -0000000000000008 _iobuf *f; -0000000000000004 // padding byte -0000000000000003 // padding byte -0000000000000002 // padding byte -0000000000000001 // padding byte +0000000000000000 _DWORD __saved_registers; +0000000000000004 _UNKNOWN *__return_address; +0000000000000008 const char *message; +000000000000000C +000000000000000C // end of stack variables |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Visual Studio:内存泄露AddressSanitizer(跨平台)03/14
- ♥ Fiddler03/27
- ♥ wireshark06/30
- ♥ Python编程基础10/31
- ♥ Dump分析:重复释放堆内存,死锁03/17
- ♥ 标志寄存器05/13