160CM-014
今天才发现之前的帖子好像发错了版块,以后都发这里了。
先运行看一下,这是一道序列号验证题,输入错误有弹窗提示。PE看一下,无壳,VB。
1.爆破
这道题爆破比较简单,无论是字符串搜索,还是弹窗下断,都能找到关键函数和关键跳转位置,当然用VB Decompiler的话就更简单了,具体过程就不详述了。爆破代码如下:
00403704 /0F85 1A030000 jnz bjanes_1.00403A24
jnz修改为nop
00403787 /0F8F 17030000 jg bjanes_1.00403AA4
jg修改为jl
2.算法
先用VB Decompiler反编译一下,找到按钮事件响应函数,代码如下:
Private Sub Command1_Click() '403620
loc_004036BB: var_1C = CrackmeV20a.Text1.Text
loc_00403704: If (Len(var_1C) <> 9) <> 0 Then GoTo loc_00403A24
loc_00403727: var_1C = CrackmeV20a.Text1.Text
loc_00403787: If var_18 > Len(var_1C) Then GoTo loc_00403AA4
loc_004037AA: var_1C = CrackmeV20a.Text1.Text
loc_004037E5: var_24 = CrackmeV20a.Text1.Text
loc_004038AD: If ((Asc(Mid$(var_24, vbNull, 1)) > 57) And (Asc(Mid$(var_1C, vbNull, 1)) < 48)) <> 0 Then GoTo loc_00403A22
loc_004038D3: var_1C = CrackmeV20a.Text1.Text
loc_00403909: var_58 = var_18 xor 0002h
loc_0040391B: var_28 = Str$(2)
loc_00403942: var_8028 = Asc(Mid$(var_1C, vbNull, 1))
loc_0040394B: var_48 = var_8028
loc_004039A5: var_80 = Right(0, 1) 'Right函数的第一个参数0识别错误
loc_00403A04: If ((Str$(var_8028) - 48) <> var_80) <> 0 Then GoTo loc_00403A22
loc_00403A18: var_18 = 1+var_18
loc_00403A1D: GoTo loc_0040377C
loc_00403A22: ' Referenced from: 004038AD
loc_00403A24: ' Referenced from: 00403704
loc_00403A8C: MsgBox("Sorry, try again!", 0, "Wrong serial!", 10, 10)
loc_00403AA2: GoTo loc_00403B22
loc_00403AA4: ' Referenced from: 00403787
loc_00403B0C: MsgBox("Good job, tell me how you do that!", 0, "Correct serial!", 10, 10)
loc_00403B22: ' Referenced from: 00403AA2
loc_00403B36: GoTo loc_00403B87
loc_00403B86: Exit Sub
loc_00403B87: ' Referenced from: 00403B36
End Sub
初步分析上述代码,会发现var_80 = Right(0, 1)
这行代码的第一个参数有问题,这个是软件反编译时参数识别不准确导致的。去掉“程序分析器和优化器”选项后重新编译,可以得到更详细一些的代码。根据前面了解的VB Variant(变量体数据类型)的特点,var_28 = 0
这行代码实际上是更改变量表示的数据类型,而不是将其值赋零。而且还应该注意的是,如果ebp-0x60
是变量体的指针地址,则ebp-0x58
才是其实际数值,因此VB Decompiler反编译后的代码中var_60和var_58表示的实际上是同一个变量。因此从这些代码中,我们可以看出right函数的第一个参数实际上应该是Str$(var_18 xor 02h)
。
除了Right()
函数的参数外,Mid$()
函数的第二个参数实际上也识别错了,vbNull的执行效果相当于从第一个字符开始,按这个参数计算出来的注册码是通不过的。这个只能在OD中跟踪一下来确认了,通过跟踪我们发现在403803位置将var_18赋值给了edi,而edi是后续调用的Mid$()
函数的第二个参数。
将代码进行适当整理后,我们得到验证算法的过程如下:
Private Sub Command1_Click() '403620
code = CrackmeV20a.Text1.Text
If (Len(code) <> 9) Then GoTo loc_00403A24 '长度不等于9跳转
loc_0040377C: 'While循环
If i > Len(code) Then GoTo loc_00403AA4 '循环退出条件
If ((Asc(Mid$(code, i, 1)) > 57) And (Asc(Mid$(code, i, 1)) < 48)) Then GoTo loc_00403A22 '字符的ASCII码在48~57之间,即只能是数字0~9
var_58 = i Xor 2 '异或运算
code_i = Asc(Mid$(code, i, 1)) '取字符的ASCII码
var_80 = Right(Str$(var_58), 1) '取数字的个位数
If ((Str$(code_i) - 48) <> var_80) Then GoTo loc_00403A22 '数字的ASCII码值-48(0的ASCII码值)和var_80比较,等于数字var_80
i = 1 + i
GoTo loc_0040377C
loc_00403A22:
loc_00403A24:
MsgBox("Sorry, try again!", 0, "Wrong serial!")
GoTo loc_00403B22
loc_00403AA4:
MsgBox("Good job, tell me how you do that!", 0, "Correct serial!")
loc_00403B22:
GoTo loc_00403B87
Exit Sub
loc_00403B87:
End Sub
根据代码我们可以看出,注册码需要满足的条件如下:
- 注册码长度必须为9;
- 注册码必须全不是数字;
- 第一个注册码比零要大3(1 xor 2)……
编写注册码计算程序如下:
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int i,j,m;
printf("注册码为:");
for (i=1;i<10;i++)
{
m=(i^2)%10;
printf("%d",m);
}
printf("\n");
system("pause");
return 0;
}
最终验证结果如下:
2.总结
- 前面已经多次提到过VB Decompiler反汇编的代码不准确的问题,这道题里面出现的错误更严重,只有查看汇编代码才能修正过来。
- 这道题在403783位置处的判断条件
var_18 > Len(var_1C)
分析汇编代码的时候比较容易被误导,因为后面有个往回跳的大跳转,没注意到的话很容易认为这个条件永远不会满足。而查看VB Decompiler的反汇编代码就很容易看出来。
- 注意Variant变量的特点,反汇编代码中var_58和var_60有时表示同一个意思,有时又不同,需要注意理解。