006-注册算法分析
一、工具和调试环境
PE
信息查看工具:Die
- 动态调试工具:
x64dbg
- 反编译工具:
IDR(Interactive Delphi Reconstructor 01.04.2017)
,IDA 7.6
- 系统环境:
win10 1909
二、分析用户名/注册码的算法
2.1运行程序
作者意图是让我们将OK
和Cancella
两个按钮隐藏。其中OK
按钮是不可点击的。需要填写Nome
和Codice
。
2.2查壳
用Die
查壳,没有加壳,是Delphi
程序,貌似是Delphi 4
,记住版本后面会用到
2.3详细分析
通过005
的分析,我们了解到了Delphi
的改变控件有效状态的api
为:SetVisible
,更具体点为:TControl.SetVisible
。那么下面就借助IDR
,找到该API
的地址
如上图,选择ClassViewer
视图,然后在该视图空白处右键Search
,然后如下图,在弹出的查找框中寻找SetVisible
找到如下地址
双击,会跳转到CodeViewer
视图,显示该函数代码,可以看到TControl.SetVisible
双击进去,就可以得到TControl.SetVisible
的地址了,如下图为:0x004231B0
但是如果x64dbg
在该地址下断点,并不能找到关键地方。只有使用正确的方法使Cancella
隐藏才是我们想要的触发,问题的关键是我们就是在寻找正确的方法。
那么怎么办呢?接下来就要请出我们的新伙伴IDA
了。
使用IDA
打开程序,然后打开签名视图,如下图或者直接使用快捷键Shift + F5
在签名视图空白处右键选择添加一个新的签名,或者使用快捷键Ins
,然后选择Delphi 4
(之前Die
查到的版本)
在汇编视图中查找之前的函数(TControl.SetVisible
)地址:0x004231B0
由于OK
按钮目前是不可点击的,那么我们先看Cancella
按钮的点击事件,双击过去。然后和IDR
对照看,如有不懂,可以先看上一篇005
可以看出关键的地方在于0x442EE7
处的call algorithm1(名字是我改的,算法1)
函数调用,当该函数返回非0时(其实返回值不是0就是1),Cancella
按钮会被隐藏且ok
按钮变为可点击。如果返回为0时,Codice
编辑框内容重新设置为0。
接下来就进入算法1函数中,看如何使其返回1。关键算法如下
Nome
必须大于五位,且Codice
和Nome
满足以下关系就返回1,否则返回0。
Codice
与Nome
的关系:Nome
的第5个字符的ASCII
码值模7,再加上2。其结果的阶乘与Nome
的每一个字符的ASCII
码值相乘,所有乘积累加。新的结果减去0x7A69
,就是Codice
的内容。算法代码如下
char* GetSerial6_1(char* szNome)
{
static char szCodice[60] = {};
int nNomeLen = strlen(szNome);
if (nNomeLen <= 5)
{
printf("Nome 需要大于五位\r\n");
return nullptr;
}
int nTmp = szNome[4] % 7 + 2;
int nFactorial = 1;
for (int i = 1; i <= nTmp; ++i)
{
nFactorial *= i;
}
int sum = 0;
for (int i = 1; i <= nNomeLen; i++)
{
sum += szNome[i - 1] * nFactorial;
}
sum = sum - 0x7A69;
sprintf(szCodice, "%d", sum);
return szCodice;
}
现在成功的隐藏了Cancella
按钮,且ok
按钮可点击了。接下来就是隐藏ok
按钮了。还记得前面查找的OK
按钮的点击处理函数中也调用了控件隐藏的函数。那么就看看ok
按钮的点击处理函数
可以看出关键的地方在于0x442DC1
处的call algorithm2(名字是我改的,算法2)
函数调用,当该函数返回非0时(其实返回值不是0就是1),OK
按钮会被隐藏。如果返回为0时,不做处理。和前面Cancella
按钮点击处理函数差不多。不过该处理函数,会首先检测Cancella
按钮是否被隐藏,如果没隐藏就会将Codice
编辑框内容设置为0,然后返回。只有Cancella
被隐藏了。才会执行前面关键代码。
接下来就进入算法2函数中,看如何使其返回1。关键算法如下
Codice
必须大于五位,且Codice
和Nome
满足以下关系就返回1,否则返回0。
Codice
与Nome
的关系:Codice
的数字转换为字符串,然后将该字符串的每个字符的ascii
码值进行平方计算,计算结果再乘以该字符的字符串中的位数(从1开始)得到新的结果,再用新的结果模0x19
,最后再加上0x41
。得到的新的字符串和Nome
字符串相同就返回1,否则返回0。
算法代码如下:
char* GetSerial6_2(char* szCodice)
{
static char szNome[60] = {};
int nCodiceLen = strlen(szCodice);
if (nCodiceLen <= 5)
{
printf("Codice 需要大于五位\r\n");
return nullptr;
}
char chTmp = '0';
for (int i = nCodiceLen; i != 0; i--)
{
szNome[i - 1] = (szCodice[i - 1] * szCodice[i - 1] * i) % 0x19 + 0x41;
}
return szNome;
}