• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-11-11 14:23 Aet 隐藏边栏 |   抢沙发  16 
文章评分 2 次,平均分 5.0

加载器

加载器选项 适用文件类型 主要特点 场景
Portable executable for 80386 (PE) 32Windows 可执行文件 (EXE, DLL等) IDA能正确解析PE文件头、节区、导入/导出表等结构,自动识别处理器类型,提供最准确的反汇编结果 分析绝大多数Windows平台上的32位应用程序、动态链接库
MS-DOS executable (EXE) 16MS-DOS 可执行文件 用于处理古老的DOS程序。如果你分析的确实是一个DOS时代的文件,才需要选择此项 分析16位的DOS程序。对于现代Windows程序,选择此项会导致分析错误
Binary file 无标准格式的原始二进制数据 最底层的加载方式。IDA不会做任何结构解析,需要手动指定处理器类型、加载地址等参数 分析固件、引导扇区、Shellcode或任何没有标准文件头的原始二进制数据

处理器

Metapc

  1. x86: Intel 80x86 processors
    1. 绝大多数32Windows可执行程序(PE文件)、DOS程序。这是最常见的选择

示例函数

汇编

函数开场白

  1. push/popesp 的变化是指令的副作用(push 顺带 -4pop 顺带 +4
  2. sub esp / add esp:是直接、显式地修改 esp 的值

  1. 执行完这三条后,栈的样子(地址从高到低往下画):

  1. 之后函数里访问局部变量就是 [ebp - 偏移](在 ebp 下方那片 0xCC 空间里),访问参数就是 [ebp + 偏移](在 ebp 上方,返回地址再往上)
    1. ebp 在整个函数执行期间固定不动,当一个稳定的"标尺"用
    2. 这就是为什么要先把 esp 存进 ebp——esp 后面还会因为 push/pop 不停变动,不适合当标尺

Debug 版特有的栈初始化

  1. 它把刚分配的局部变量内存全填成 0xCCCCCCCC
    1. 0xCCDebug 版的标志性魔数
    2. 只要在内存里看到一片 CC CC CC CC,就知道这是未初始化的栈空间(Release 版没有这个)
  2. rep stosd 的意思是"重复存储 eaxedi 指向的内存,重复 ecx(=3) 次"

调试特性检查

  1. MSVC "Just My Code" 调试特性插入的检查

打开文件

  1. 这是在调用 fopen(filename, mode)
    1. 注意这是 x86,参数靠栈传,从右往左压,所以
    2. 先压的 "a" 是第 2 个参数 → 打开模式 "a"append 追加)
    3. 后压的路径 "d:\download_repo\..." 是第 1 个参数 → 文件名
  2. 调用后 add esp, 8 把两个参数从栈上清掉(cdecl 约定,调用方清栈)

  1. fopen 的返回值(FILE 指针)在 eax 里,这里把它存进局部变量(IDA 已命名为 f

判断文件是否打开成功

  1. 比较 f0,如果等于 0jz = 为零则跳)就跳到 loc_10011717(也就是函数结尾,直接返回)

写日志(成功分支)

  1. 调用 fprintf(f, "[TargetDll] %s\n", message)
    1. 同样从右往左压参,还原出来三个参数清清楚楚:文件流 f、格式字符串、要写入的 message
  2. 注意 [ebp+message] 这个正偏移
    1. 正偏移取的是传进来的参数(就是 LogToFile 的那个 char const * 入参),负偏移如 [ebp+f][ebp+var_C] 是局部变量
  3. fclose(f)——写完关闭文件

函数收尾

  1. 和开头对称:逆序弹回保存的寄存器、回收栈空间、还原 ebp、返回

函数签名相关

解析

  1. ? —— 起始标记
    1. 所有 MSVC 修饰过的名字都以 ? 开头
    2. 看到 ? 就知道这是个 C++ 修饰名(C 函数通常是 _funcname 这种,不走这套)
  2. LogToFile —— 函数名
  3. @@ —— 名称限定结束符
    1. 这里的 @ 用来分隔"名称作用域"
    2. 如果函数在类或命名空间里,中间会夹着类名/命名空间名,比如 ?foo@MyClass@@... 表示 MyClass::foo
    3. 这里是 @@(两个连着,中间什么都没有),说明它不属于任何类或命名空间,是个全局函数
    4. 第一个 @ 结束函数名,第二个 @ 表示作用域列表为空
  4. YA —— 函数属性 + 调用约定
    1. 第一个字母(这里是 Y):表示这是个普通的非成员函数(全局函数)
    2. 如果是类成员函数,这里会是别的字母(如 Q/A 等,还带访问权限信息)
    3. 第二个字母 A:表示调用约定是 __cdeclC 默认约定)
    4. 其他常见的:G=__stdcallE=__thiscallI=__fastcall
    5. 所以 YA = 全局函数 + __cdecl
  5. 返回类型和参数:编码后紧接着先写返回类型,再写参数
编码 类型
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 指针
  1. @Z —— 参数列表结束
    1. @ 结束参数列表,Z 是函数的终止标记
    2. 如果函数无参数,参数位置会是 X,比如 void f(void)...YAXXZ

示例

undname

  1. 开发者命令行提示

IDA的正负偏移

  1. IDA 显示的 [ebp+message][ebp+f] 里那些名字(message、f、var_C)本身代表的就是一个数值偏移,而且这个值可正可负
    1. 加号是 IDA 统一的显示格式,真正的正负藏在那个名字背后
  2. IDA 为什么都显示成 +
    1. 为了可读性,把栈上的位置都起了名字(symbol),然后统一用 [ebp + 符号名] 的格式显示
    2. 但符号名背后绑定的偏移量是带符号的
  3. 怎么一眼区分正负?
    1. var_XXX 开头的 → 一定是负偏移(局部变量)
    2. arg_XXX 开头的 → 一定是正偏移(参数)
  4. 自己改过名 / IDA 根据符号识别的名字(如 message、f
    1. messagef 这种有意义的名字,要么是有符号信息(pdbIDA 自动还原的,要么是你或别人手动 N 改的
    2. 这种名字本身看不出正负
    3. 方法一:靠语义推断
    4. 方法二:直接看 IDA 给的实际数值

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2026-06-12
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享