160CM-011
1.破解过程
先看看题,打开运行,界面如下图所示,要求输入正确的序列号,然后状态会成未注册变成已注册。序列号不正确时没有提示。
先拖到PE看看,无壳,VB,那VB Decompiler先来一波。
从左侧的函数列表可以看出,有一堆是事件响应函数,分别对应界面上的按键,另外还有4个定时处理函数。简单的浏览一下可以发现,注册算法应该在定时处理函数里面,按键处理函数只负责对按键进行响应和显示。
这道题爆破太简单了,就不说了,主要分析一下算法。4个定时处理函数里面的内容基本相同,每个里面都是好几个重复的验证算法,取其中一个进行分析。
For var_24 = 1 To Len(var_44) Step 1
loc_0040492F:
If var_F8 = 0 Then GoTo loc_00404A60
var_50 = CStr(Left(var_44, 1)) '取字符串的第一个字符
var_304 = Asc(Mid$(CStr(var_44), CLng(var_24), 1)) '依次取字符串中的第n个字符,并转换成ASCII码值
var_8C = Hex$((var_30C + var_CC)) '上面的ASCII码值+var_CC后转成16进制字符串
var_34 = var_34 & Hex$((var_30C + var_CC)) '16进制字符串加在字符串的后面
Next var_24
GoTo loc_0040492F
上面代码理解起来不难,需要注意的是var_304和var_30C其实是同一个变量(一个是变量体的指针地址,一个是变量体中存储数据的指针地址),然后有个var_CC不知道是什么东西,VB Decompiler去掉“程序分析器和优化器”重新编译也查不出这个变量的来源,只好到OD里面跟跟看了。
OD加载程序,然后运行,输入“12345#”后,再找到函数入口地址404650,然后往下找到第一个段算法中for循环的起始位置和结束位置,在起始位置前一行和结束位置后一行设下断点。第一个断点断下后,F8单步跟踪一下,可以发现算法在取完第一个字符后,进行了一些浮点运算,然后就得到一个数字和ASCII码值相加了。
浮点运算的汇编指令看不董,只好百度一下,备注好,基本可以看出是将取出的字符串转换成浮点数后与ASCII码值相加,也就是说var_CC就是取出字符串的浮点数值。
跟完一遍for循环后再点运行,然后断在第二个断点(404A60)位置,我们查看一下ebp-0x34对应的地址(12FB44)的数据,根据变量体数据类型的结构,08表示字符串,“0015F2D4”为字符串的地址。
查看一下“0015F2D4”地址的字符串内容,字符串为“032333435362B24”,除第一个0外,其余和字符串“12345#”的ASCII码加“1”均吻合。
再回去F8跟踪一下前面的代码,发现在4048CF位置对整型变量体var_34赋值为0,然后在404A01字符串连接时,var_34变成了字符串变量体,数值0自动转成成字符串“0”,放在了最前面。VB Decompiler对这部份的识别还是有些问题的。
至此,核心的算法过程已经弄清楚了。可是程序里面为什么要整4个Timer?每个Timer里面还反复的进行计算,然后和一串字符进行比较。仔细看了看算法,发现不同的算法中中,唯一的区别就是“CStr(Left(var_44, 1))”这一行了,有的是取1个字符,有的是取2个、3个……
但是除了这个区别,还是反反复复在重复啊,嗯,好奇怪。
我们在看看算完以后要比较的字符串,“0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C”。呃……不是16进制字符串吗?怎么字母“R”、“K”也有,那怎么可能相等呢?
其实这些都是程序作者(机智的作者)的障眼法,看起来一大堆的字符串长的都一样,其实仔细比较一下可以发现有些串串还是不一样的。由于前面算法过程分析已经很明确了,计算后的字符串是16进制字符串,因此我们需要找一串不包含16进制字符以外的串串。最终只有Timer1中有一个串串符合要求。
For var_24 = 1 To Len(var_44) Step 1
loc_004064A8:
If var_24 = 0 Then GoTo loc_004065D9
var_50 = CStr(Left(var_44, 2))
var_3A0 = Asc(Mid$(CStr(var_44), CLng(var_24), 1))
var_8C = Hex$((var_3A8 + var_CC))
var_34 = 0 & Hex$((var_3A8 + var_CC))
Next var_24
GoTo loc_004064A8
loc_004065D9:
If (var_34 = "0817E747D7A7D7C7F82836D74747A7F7E7B7C7D826D817E7B7C") = 0 Then GoTo loc_0040664F
Set var_54 = Me
Label3.Caption = "REGISTRIERT"
var_eax = %fobj
loc_0040664F:
从这个代码中可以发现,字符的ASCII码需要加的是字符串前两位。由于ASCII码是个两位16进制数,两位10进制数换成16进制数也最多只有两位,而两个两位16进制数相加,其和即时是三位16进制数,其最高位也不可能超过1。因此这一长串字符串,去掉第一个“0”后,应该是每两个字符表示一个序列号数字。假设序列号第一个数字为a1,第二个数字为a2,则可以列出两个等式:
129(81的10进制表述)=a1的ASSII码+a110+a2;
126(7E的10进制表述)=a2的ASSII码+a110+a2;
从上面的计算式可以看出,数字a1的ASCII码值比数字a2的ASCII码值大3,也就是说数字a1比数字a2大3,因此数字a1最小也应该是3,此时数字a2应该为0。3个ASCCII码为51(10进制),0的ASCII码为48(10进制),代入第一行,a1的ASSII码+a110+a2=51+30+0=81,与129相比还小48。假设正确的a1需要比3大n,则n+10n+n=48,求解得到n=4。因此a1=3+4=7,a2=0+4=4。
这样我们就可以将长串反向解析出来,公式为
字符串中取出的两个字符转换成10进制数-78,即为输入数字的ASCII码值。
编写注册码计算程序如下:
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
char key[100] = "817E747D7A7D7C7F82836D74747A7F7E7B7C7D826D817E7B7C";
char code[100] = "";
char a[10]="",b[10]="";
int i,j,m,n;
for (i=0;i<(strlen(key)/2);i++)
{
sprintf_s(a,"%c",key[i*2]);
sprintf_s(b,"%c",key[i*2+1]);
//printf("a=%s \n",a);
//printf("b=%s \n",b);
m=strtol(a,NULL,16);
n=strtol(b,NULL,16);
j=m*16+n-74;
//printf("j=:%d \n",j);
code[i]=j;
}
printf("注册码为:%s \n",code);
system("pause");
return 0;
}
输入计算得到的注册码,成功结果如下:
2.总结
- 再一次证明,VB Decompiler虽然好用,但也不可全信,有问题的地方还是得看汇编代码。
- 碰到一些浮点运算的指令,都不认识,这里整理一下。
指令 |
说明 |
fld |
将浮点数据压入协处理器的堆栈中 |
fild |
将整型数据压入协处理器的堆栈中 |
fst |
将协处理器堆栈栈顶的数据传送到目标操作数中 |
fstp |
与FST相类似,所不同的是:指令FST执行完后,不进行堆栈的弹出操作,即:堆栈不发生变化,而指令FSTP执行完后,则需要进行堆栈的弹出操作,堆栈将发生变化 |
fstsw |
取协处理器的状态字 |
fadd |
浮点数相加 |
fclex |
浮点检查错误清除 |
- 野生菜鸟选手的悲哀,C语言的字符串函数都要现搜现用,怎么依次取字符串中的两个字符都尝试了各种方法之后才解决,怎么将16进制数字转换成16进制字符串又是各种折腾,动不动就是:==不能将参数 1 从“char”转换为“const char *”==
- VB逆向碰到的一些新函数:
函数 |
功能 |
rtcR8ValFromBstr |
将var1(字符串)转换成双精度浮点数存入ST0 |
vbaI4Var |
将一个var1(变量体)转换为I4(长整数),结果存入eax |
rtcHexBstrFromVar |
将var1(变量体)转换为16进制字符串,结果存入eax |
vbaVarCat |
将var2(变量体)连接到var1(变量体)的后面,结果存入var3(变量体)中 |