看雪《加密与解密》第三版2-1TraceMe的学习体会
本帖最后由 lizhirui 于 2018-1-15 11:25 编辑该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:的地方正好是用户名的第一个字符,从该行开始往下三行检测用户名的第一个字节是否为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
Type: HWND
A handle to the dialog box that contains the control.
nIDDlgItem
Type: int
The identifier of the control whose title or text is to be retrieved.
lpString
Type: LPTSTR
The buffer to receive the title or text.
nMaxCount
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处就是该循环体内部,
我们可以看出这一部分代码计算出了注册码,而下面的一段代码:
将注册码直接转换为字符串,然后与用户输入的字符串比较,从而判断注册码是否正确。
这里我们还原一下注册码计算算法:
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 * arr;
}
return r;
}
该函数返回的数值就是注册码
我们输入试验一下:
可见注册成功。
由于Windows是事件驱动的操作系统,所以除了以上方法以外,我们还要实验另一种定位注册代码的方法:消息定位法。
我们知道,按下按钮操作系统必定会传递WM_LBUTTONDOWN和WM_LBUTTONUP事件到窗口消息循环队列中。
那么按钮使用的是哪个事件呢?我们可以试验一下,我们按下按钮,发现什么都没有发生,然后放开按钮,出现了提示框,因此我们断定,按钮使用的必然是WM_LBUTTONUP事件。
我们用OD加载程序并运行,按Alt+W打开窗口句柄窗口,右键点击刷新,可以看到所有的窗口,我们在按钮“Check”上设置消息断点:
我们可以点击下面的“根据名称排列消息”,这样从上面的列表中找消息更容易一些。
现在我们点击“Check”按钮,程序中断了下来,我们看堆栈窗口:
很明显已经中断了下来,由于事件处理需要经过操作系统传递,用Ctrl+F9难以跟踪,因此我们按Alt+M,打开内存映射窗口,在TraceMe的.text上按F2下断,按F9运行程序,中断在了这里:
我们按F8跟踪,
慢慢的又回到了user32内存空间,我们再一次在.text上下断,运行程序,再一次返回到了这里,
我们按F8跟踪,发现我们到了这里:
这是从上面的一个较远的跳转跳过来的,因此我们在这个地方调试的时候如果发现远距离跳转,一般就是进入了用户真正的事件处理程序。
至此,程序跟踪完成,以上是我的一些体会,这是我第一次使用事件跟踪和人工逆向非爆破的方式解决这个crackme,从中获得了大量的经验和体会。
以下是TraceMe:
学习了,谢谢。 :eee能反,能正。。我们就是需要你这样的,真正懂汇编的大神。{:1_893:}{:1_893:} 算法分析的头头是道。 学习了,很感谢分享 接着学习下先 图文并茂,条理明晰,学习了 学习了,谢谢。 楼主这个很详细!!! mark
学习一下。。。