43.逆向分析实战10:160crackme之003逆向破解分析IDA+dbg(上)

neg

NEG 是 x86 汇编语言中的一个指令,全称是 Negate(取反),作用是对一个操作数进行 二进制求反后加 1 的操作(也就是取补码),等价于 0 - 操作数,即:

NEG dest  ≡  dest = 0 - dest

功能描述

它把一个数变成它的 负值,无论这个数是正的还是负的。

例如:

mov al, 5      ; AL = 00000101b = 0x05
neg al         ; AL = 11111011b = 0xFB = -5 (以补码形式)

rtcAnsiValueBstr

将某个变量转换为 BSTR(Unicode 字符串)格式的 ANSI 编码形式字符串

如果传入的是单个字符,rtcAnsiValueBstr 不是返回它的 ASCII 值,而是返回一个只包含这个字符的字符串,这个字符串内部的字节当然就是这个字符的 ASCII 编码。

🔍 名称拆解理解

  • rtc:runtime call(VB/VBA 的运行时支持函数)
  • Ansi:表示 ANSI 字符编码格式(而非 Unicode)
  • Value:表示是将值(变量)转换
  • Bstr:VB 的字符串类型,全称是 Basic String(BSTR),是一种以双字节 Unicode 编码为基础的 COM 字符串类型

__vbaR8Str

__vbaR8Str 是 VB6(Visual Basic 6.0)运行时库中的内部函数

函数功能

将 BSTR 字符串转换为 Double(R8)类型的浮点数。

🔍 分解命名含义

  • R8 → 表示 IEEE-754 的 8 字节浮点数(double
  • Str → 表示字符串(BSTR)
  • 所以 __vbaR8Str 就是:

BSTR → double(字符串转浮点数)

🔄 其他相关函数对照

函数名

功能

__vbaStrR8

doubleBSTR

__vbaR8Str

BSTRdouble

__vbaStrI4

intBSTR

__vbaI4Str

BSTRint

fld 指令

fld ds:flt_401008 是一条 x86 浮点指令,意思是:

从内存地址 ds:flt_401008 加载一个浮点数(通常是单精度或双精度)到 x87 浮点栈的顶部(即 st(0) 寄存器中)。


🔍 逐项解释:

组件

含义

fld

浮点加载指令(Floating-point LoaD),将内存中的浮点数压入 x87 浮点栈(最多8个元素)

ds:

数据段寄存器,一般是默认段前缀,可省略

flt_401008

是一个标签,通常代表某个内存地址处的浮点常量(例如 doublefloat


🧠 举个具体例子:

.data
flt_401008  dq 10.0

那么:

fld     ds:flt_401008

就相当于把这个 10.0double 常量加载进 x87 浮点栈顶部。


🧪 加载后效果(浮点栈):

st(0) = 10.0

🧩 附加说明:

  • fld 可以加载不同精度的浮点数:
    • fld dword ptr [...] → 加载 float (4字节)
    • fld qword ptr [...] → 加载 double (8字节)
    • fld tbyte ptr [...] → 加载 long double (10字节)

adj_fdiv_m32(x)

adj_fdiv_m32(0x40A00000); 是汇编里的宏或伪代码,表示调用 浮点除法辅助函数,用于支持一些老的、精度或兼容性特殊的浮点除法操作。


🧠 0x40A00000 是什么?

这个是一个 32 位浮点数的原始内存表示(IEEE-754格式),我们来看它是什么实际数值:

0x40A00000 → float → 5.0

你可以在 C 中验证一下:

float* f = (float*)0x40A00000;
// f 的值是 5.0

🔍 代码解释

__asm { fld     ds:flt_401008 }  // 将 10.0 压入浮点栈 st(0)
adj_fdiv_m32(0x40A00000);        // 让 st(0) = st(0) / 5.0 → 即 10.0 / 5.0 = 2.0

也就是

fld     10.0         ; st(0) = 10.0
fdiv    5.0          ; st(0) = 10.0 / 5.0 = 2.0

faddp st(1), st

把浮点栈 st(0)st(1) 相加,结果存到 st(1) 中,然后弹出st(0)

faddpfadd + pop 的组合指令,常用于边加边清栈的场景

📌 举个例子

假设

fld 2.0        ; st(0) = 2.0
fld 3.0        ; st(0) = 3.0, st(1) = 2.0

然后执行:

faddp st(1), st

那么执行过程为:

  • st(1) = 2.0 + 3.0 = 5.0
  • 弹出 st(0)(原来的 3.0)

最后栈变为:

st(0) = 5.0

faddfaddp 是经典的 FPU 指令对,非常容易混淆。我们来一张对比表+实例彻底说明清楚。


fadd 和 faddp 核心区别一览表:

指令

含义

是否弹栈?

结果保存位置

fadd

浮点相加

不弹栈

默认在 st(0) 或指定位置

faddp

浮点相加并弹出(p=pop)

弹出 st(0)

保存到指定的 st(i)


用法对比(默认参数)

; === fadd 示例 ===
fld 2.0         ; st(0) = 2.0
fld 3.0         ; st(0) = 3.0, st(1) = 2.0
fadd st(1), st  ; st(1) = 2.0 + 3.0 = 5.0,st(0) = 3.0(不变)

结果:

st(0) = 3.0
st(1) = 5.0

; === faddp 示例 ===
fld 2.0         ; st(0) = 2.0
fld 3.0         ; st(0) = 3.0, st(1) = 2.0
faddp st(1), st ; st(1) = 2.0 + 3.0 = 5.0,弹出 st(0)

结果:

st(0) = 5.0       ← 原来的 st(1)

类比帮助记忆

  • fadd 是加法,不清栈,适合临时计算
  • faddp 是加法 + 清栈,适合循环或连续处理(类似累加器)

🧠 补充:常见等价形式

faddp st(1), st
; 等价于:
fadd st(1), st(0)
fstp st(0)

总结一张图

操作

栈前状态

操作

栈后状态

fadd

st(0)=A, st(1)=B

B = B + A

st(0)=A, st(1)=B+A

faddp

st(0)=A, st(1)=B

B = B + A
pop st(0)

st(0)=B+A


fdiv vs fdivr

指令

表达式

意义

fdiv

st(0) = st(0) / mem

正常除法

fdivr

st(0) = mem / st(0)

逆序除法(r = reverse)


fdivr st(0), qword ptr ss:[ebp-0E4]

是:

st(0) = [ebp-0E4] / st(0)

即:

st(0) = 123456.0 / 1600363.0


🧮 实际数值计算:

123456 ÷ 1600363 ≈ 0.0771...

所以:

st(0) ≈ 0.0771

总结解释:

组成

含义

fdivr

逆序浮点除法(memory ÷ register)

st(0)

是目标寄存器,保存最终结果

[ebp-0E4]

是内存地址中的 double 值(123456)

原始值

st(0) = 1600363.0,内存 = 123456.0

执行结果

st(0) = 123456.0 / 1600363.0 ≈ 0.0771


改成 fdiv,就是反过来:

fdiv st(0), qword ptr [ebp-0E4] ; st(0) = st(0) / [mem]
→ 1600363.0 ÷ 123456.0 ≈ 12.97

 

你给的这条汇编指令:

fcomp   ds:dbl_401028

其中 dbl_401028 是一个内存中的 double 类型值(8 字节浮点数),其内容是 1.0


fcomp

fcomp 是浮点比较指令,含义是:

将浮点栈顶 st(0) 与指定数值(这里是内存中的 1.0)进行比较,并弹出 st(0)

这一句相当于:

compare st(0), 1.0
pop st(0)

FPU 比较行为

FPU 比较不是通过设置 EFLAGS 来判断,而是设置 FPU 状态字(SW) 的标志位。

常用组合是:

fcomp ds:dbl_401028     ; 比较 st(0) 与 1.0,并弹出 st(0)
fnstsw ax               ; 把 FPU 状态字存入 AX
sahf                    ; 把 AH 的标志位放入 EFLAGS

这样就可以通过 jz, jb, ja 等指令判断结果。


🔍 比较结果会设置哪些位?

  • C0, C2, C3 是 FPU 状态字中的关键位,用来反映比较结果:

比较结果

C3

C2

C0

st(0) > mem

0

0

0

st(0) < mem

0

0

1

st(0) == mem

1

0

0

unordered(NaN)

1

1

1


📌 举个例子

fld     qword ptr [ebp-10]      ; 假设 = 1.5 → st(0) = 1.5
fcomp   ds:dbl_401028           ; 与 1.0 比较,弹出 st(0)
fnstsw  ax
sahf
ja      isGreater               ; 如果 > 1.0,跳转

总结

fcomp ds:dbl_401028

的含义是:

将 FPU 栈顶的值 st(0) 与内存地址 dbl_401028 中的值(即 1.0)进行浮点比较,比较后弹出 st(0),并设置 FPU 状态字中的标志位(C0/C2/C3)用于判断大小关系。


如后续有看到 fnstsw axsahfjpja 等,基本就是在用这个比较结果做分支判断。

请登录后发表评论

    没有回复内容