该TraceMe.exe来源于看雪加密与解密第三版的2-1 ASCII版程序,我经过独立分析后,在这里向大家分享一些经验和体会:
首先我们打开程序:
经过尝试,输入的字符数至少为5个才行。
输入数目够了之后,这样提示:
我们使用OD将程序加载,然后搜索ASCII字符串,发现了大量特征字符串,不过这一次我不打算从这里入手。
由于该程序是VC++写成(可用PEID分析得到,或者分析该文件的导入表,可以看到msvcrt字样),该程序一般也就是用MFC实现的。
那么由于验证注册码是否正确必定要获取文本框内容,因此我们在GetDlgItemTextA下断(该程序是ASCII版程序,Unicode版则在GetDlgItemTextW下断,常用断点还有GetWindowTextA,GetWindowTextW等),我们在底部命令栏输入“bp GetDlgItemTextA”,运行程序,输入上面的用户名和序列号,点击Check按钮,程序中断在了user32.GetDlgItemTextA中:
从这个API的实现中我们可以看出,GetDlgItemTextA本身也调用了GetWindowTextA函数,因此在GetWindowTextA下断也是可以的。
我们按Ctrl+F9执行到返回,然后按F8返回程序领空:
我们看到了这样的代码,两个GetDlgItemTextA经过跟踪可以看出,第一个GetDlgItemTextA读入了用户名,而第二个则读入了序列号。
我们按Alt+B,在断点窗口中删除GetDlgItemTextA的断点,
紧接着不断F8到004011CA的地方,我们可以看到ss:[esp+0x4C]的地方正好是用户名的第一个字符,从该行开始往下三行检测用户名的第一个字节是否为0,也就是用户名是否为空,若不为空,继续往下走,
cmp ebx,0x5
jl short TraceMe.00401248
这两行的作用是判断用户名长度是否小于5,若不成立,继续往下走,
在004011DB处有一行push ebx,我们在004011C6处看到了一句mov ebx,eax,在API的调用约定中,eax是API函数返回值,我们这里看一下GetDlgItemTextA的MSDN解释:
-------------------------------------------------以下来自MSDN------------------------------------------------------------
GetDlgItemText function
Retrieves the title or text associated with a control in a dialog box.
Syntax
C++
UINT WINAPI GetDlgItemText(
_In_ HWND hDlg,
_In_ int nIDDlgItem,
_Out_ LPTSTR lpString,
_In_ int nMaxCount
);
Parameters
hDlg [in]
Type: HWND
A handle to the dialog box that contains the control.
nIDDlgItem [in]
Type: int
The identifier of the control whose title or text is to be retrieved.
lpString [out]
Type: LPTSTR
The buffer to receive the title or text.
nMaxCount [in]
Type: int
The maximum length, in characters, of the string to be copied to the buffer pointed to by lpString. If the length of the string, including the null character, exceeds the limit, the string is truncated.
Return value
Type: UINT
If the function succeeds, the return value specifies the number of characters copied to the buffer, not including the terminating null character.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
-------------------------------------------------以上来自MSDN------------------------------------------------------------
我们着重说一下返回值部分的含义,中文翻译过来大致意思是“如果函数执行成功,返回值是被写入到缓冲区中的字符串的长度(不包含最后的'\0'),若函数执行失败,返回值是0,为了获取更多的错误信息,请调用GetLastError”
因此该函数的返回值也就是文本框中输入的字符串长度
我们继续往下看:
很容易看出,那个004011E5处的call调用的就是注册验证程序,因为下方显而易见已经是根据判断结果输出不同信息的代码了。
这些代码出现了三次push,经过分析,该函数的第一个参数为序列号缓冲区地址,第二个参数为用户名缓冲区地址,第三个参数为用户名长度,
写成c格式大概是 :
int CheckCode(char *code,char *username,char *usernamelen)
我们跟入该函数,看到以下代码(每一行大致的含义我都已经注释出了):
那个00401358处的jle,00401374处的cmp和00401376处的jl,以及ecx,eax想要表达的含义如下:
for(ecx=3,eax=0;ecx < usernamelen;ecx++,eax++)
从00401359-00401376处就是该循环体内部,
我们可以看出这一部分代码计算出了注册码,而下面的一段代码:
将注册码直接转换为字符串,然后与用户输入的字符串比较,从而判断注册码是否正确。
这里我们还原一下注册码计算算法:
[C++] 纯文本查看复制代码
int GenRegCode(TCHAR *username,int len)
{
int i,j;
int r = 0;
char arr[]={0x0C,0x0A,0x13,0x09,0x0C,0x0B,0x0A,0x08};
for(i = 3,j = 0;i < len;i++,j++)
{
if(j > 7)
{
j = 0;
}
r += username[i] * arr[j];
}
return r;
}