这个程序是 VB6 的,没有编译到机器码,全程都在 VB 虚拟机里打转。要做内存注册机的话,需要写代码注入到进程,判断什么时候可以得到注册码(例如 HOOK rtcMsgBox
函数,调用时爬堆栈得到真码)。
建议使用 VB-Decompiler 分析算法,然后编写算法注册机。
VB 部分的代码:
Private Sub Proc_10_0_416FE0
'Data Table: 4014A4
Dim var_90 As Long
loc_416F1F: var_98 = CStr(Space(&HFF))
loc_416F36: var_94 = CStr(Space(&HFF))
loc_416F99: var_90 = GetVolumeInformation("c:\", var_94, Len(var_94), var_8C, 0, 0, var_98, Len(var_98))
loc_416FD2: var_88 = CStr(Right("00000000" & Hex(var_8C), 8))
loc_416FDE: Exit Sub
End Sub
Private Sub Proc_10_1_419820(arg_C) '419820
'Data Table: 4014A4
loc_4196A8: var_A8(0) = CByte("&h" & Mid(arg_C, 1, 2))
loc_4196E3: var_A8(1) = CByte("&h" & Mid(arg_C, 3, 2))
loc_41971E: var_A8(2) = CByte("&h" & Mid(arg_C, 5, 2))
loc_419759: var_A8(3) = CByte("&h" & Mid(arg_C, 7, 2))
loc_41980E: var_88 = CStr(CVar(CStr(Right("0000" & Hex(CRCB(var_A8(0))), 4))) & Right("0000" & Hex(CRCB(var_A8(2))), 4))
loc_41981C: Exit Sub
End Sub
Private Sub Command2_Click() '4191F0
'Data Table: 402FDC
Dim var_B0 As TextBox
loc_419084: var_88 = Proc_10_1_419820(Proc_10_0_416FE0())
loc_4190B5: var_D4 = Trim(CVar(Me.Text2.Text))
loc_4190C0: var_E4 = Ucase(var_D4)
loc_4190D8: If (Ucase(var_88) = var_E4) Then
loc_4190E7: var_B0 = Me.Global.App
loc_419100: var_100 = Me.Global.App
loc_419119: var_114 = Me.Global.App
loc_419164: SaveSetting("DITAI", "XLB" & CStr(App.Major) & "." & CStr(App.Minor) & "." & CStr(App.Revision), "FLAG1", var_88)
loc_4191A6: MsgBox("恭喜你注册成功!", &H40, "注册成功", var_D4, var_E4)
loc_4191B6: Call Command1_Click()
loc_4191BE: Else
loc_4191DF: MsgBox("序列号不正确,请重新输入!", &H10, "错误", var_D4, var_E4)
loc_4191EF: End If
loc_4191EF: Exit Sub
End Sub
Proc_10_0_416FE0
是获取机器码,Proc_10_1_419820
是计算对应的序列号。
序列号计算这里的反编译代码漏了个参数。观察反汇编的 P-Code:
loc_419764: LitI4 2
loc_419769: LitI4 0
loc_41976E: FLdRfVar var_A8
loc_419771: Ary1LdRf
loc_419772: ImpAdCallI2 CRCB()
' ...
loc_419781: LitI4 2
loc_419786: LitI4 2
loc_41978B: FLdRfVar var_A8
loc_41978E: Ary1LdRf
loc_41978F: ImpAdCallI2 CRCB()
应当为 CRCB(&var_A8(0), 2)
和 CRCB(&var_A8(2), 2)
(其实我是直接在调试器看传参的,手动看也没差多少)。
这个 CRCB 是 CRC.DLL 的一个导出函数,用查表来加速运算。实际上就是标准的 CRC16 算法(初始值为 0
)。
这些搞清楚后,就可以写算法注册机了。
将序列号分为两半,每一半转换为对应的字节(两字节),求对应的 CRC16 值,然后拼接字符串。
下面是 CRC16 的 Python 代码,你可以试试将其改写为完整的算法注册机:
def crc16(data: bytes, crc: int = 0):
"""
不查表的 CRC16 实现,等价于 CRC.DLL 提供的 CRCB 函数。
:param data: 计算 CRC16 的内容
:param crc: 初始 CRC16 值,默认为 0
:return: 对应的 CRC16 值
"""
for byte in data:
crc ^= byte << 8
for _ in range(8):
if crc & 0x8000:
crc = (crc << 1) ^ 0x1021
else:
crc <<= 1
return crc & 0xFFFF
print(hex(crc16(b"\x44\x0D"))) # 0x10a5
print(hex(crc16(b"\x23\x9C"))) # 0x1180
如果想完全跳过注册验证,可以对照反编译的代码和 P-Code 来魔改。
首先对计算序列号的函数 Proc_10_1_419820
交叉查询引用,只有两处。一处是注册窗口初始化,还有一处是点击注册时调用。
直接看前者即可,可以看到如下序列号检测代码:
loc_41EC18: If (Len(var_98) > 1) Then // 必须成立
loc_41EC2E: If (var_98 = Call Proc_10_1_419820(var_A4)) Then // 必须成立
loc_41EC31: Call Command1_Click()
loc_41EC36: Exit Sub
loc_41EC37: End If
loc_41EC37: End If
第一个条件,查看对应的 P-Code 反汇编:
loc_41EC12: F501000000 LitI4 1
loc_41EC17: DB GtI4
loc_41EC18: 1CAF02 BranchF loc_41EC37
将 loc_41EC12 入栈的值修改为负数即可永远成立,如 F501000080
。
第二个条件是检查字符串是否匹配:
loc_41EC26: 6C68FF ILdRf var_98
loc_41EC29: 6C58FF ILdRf var_A8
loc_41EC2C: FB30 EqStr
loc_41EC2E: 1CAF02 BranchF loc_41EC37
将 loc_41EC26 的对应字节码改成下面那行一样的 ILdRf var_A8 即可。
最后,将补丁好后的程序重新用反编译工具打开,查看更改:
loc_41EC18: If (Len(var_98) > -2147483647) Then
loc_41EC2E: If (Call Proc_10_1_419820(var_A4) = Call Proc_10_1_419820(var_A4)) Then
loc_41EC31: Call Command1_Click()
loc_41EC36: Exit Sub
loc_41EC37: End If
loc_41EC37: End If
当然,因为不确定是否有完整性校验,因此还是建议手动算号注册。