这个程序没有注册或者确定按钮,所以我们只有找到窗口过程才能知道
按下F5便可知道窗口过程的处理流程
LRESULT __stdcall sub_401239(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
LRESULT result; // eax@2
switch ( Msg )
{
case 0x10u:
KillTimer(hWnd, 0xCAu);
PostQuitMessage(0);
result = 0;
break;
case 0xFu:
sub_401325();
result = 0;
break;
case 1u:
SetTimer(hWnd, 0xCAu, 1u, 0);
result = 0;
break;
case 0x113u:
sub_401453();
if ( byte_403166 == byte_403167 )
{
result = 0;
}
else
{
byte_403167 = byte_403166;
if ( byte_403166 == 16 )
{
SetWindowTextA(::hWnd, "YES! You found your serial!!");
result = 0;
}
else
{
SetWindowTextA(::hWnd, "Your serial is not valid.");
result = 0;
}
}
break;
default:
if ( Msg != 273 || lParam != dword_403180 )
{
result = DefWindowProcA(hWnd, Msg, wParam, lParam);
}
else
{
MessageBoxA(
hWnd,
"Small crack-me program written by Chafe/TEX99\n\rRULES: Find a serial that matches your name or write a keygenerator. NO PATCHES IS ALLOWED!\n\r\n\rReleased 25/3-99",
"About TEXme v1.0",
0);
result = 0;
}
break;
}
return result;
}
通过简单的分析上面的函数,我们知道程序的验证应该是通过Timer消息来完成的,并且核心在sub_401453
这个函数按F5反编译过来的函数是不准确的,所以这边我们需要自己分析函数
这边可以看到红色部分先保存了当前的esp的值到一个全局变量,然后再将一处地址赋给esp(esp可以看做一个指针),然后根据byte_403166的值对esp进行调整,相当于移动指针,使其指向下一个数据,最后指向retn后返回的地址是当前esp指向的一块dd大小的数据值,上面的步骤相当于模拟一个函数的调用,即call FuncArray[index],其中FuncArray就是off_403152表示指向函数地址数组的指针,而index就是byte_403166,这边在IDA将其重命名下
这边也可以看到我们的猜测是正确的,也就是验证函数就是依次执行这个函数数组中的每个函数
首先来看第一个函数,按下F5自动分析
可以看到,这个函数就是取序列号,序列号如果转换成整形失败,则不继续执行下一个函数(index+4前面已经分析过了,esp+4表示指向下一个函数,每个函数地址的大小为4字节),我们看到转换后的整形序列号值保存到dword_403188中了,所以这个地方可以将其重命名为iSerial。函数①的关键:序列号只能使用数字字符0~9,而且不能使用”-”号
接下来分析第二个函数,这边由于函数是嵌套在另一个函数中的,所以使用F5没有效果,这边直接根据视图来分析函数
简单的分析可以得知,这就是一个获取用户名的过程,将名字保存到byte_40318C字符数组里面(这边将byte_40318C重命名为szBuf),红色部分以及第二幅图的作用就是将szBuf未使用部分填充0,第三幅图发现eax是关键,而通过第一幅图可以知道,eax保存的是用户名的字符长度,这边还要注意如果用户名长度不为0,还会多一个byte_403168赋0的操作,这个操作会影响下一个函数的分析。函数②的关键就是:用户名长度>0。
现在来看第三个函数
编辑下原先的函数范围,将中间的loc_401361设置为函数,然后再在loc_401325处创建新函数,这样原先的loc_401361就可以使用F5了,当然直接分析也是很简单的,分析结果如下
在函数②的时候知道byte_403168会被赋值0,所以它也相当于一个索引计数i,我们看到只要i没变成16,index就不会+4,可以知道这边就是利用timer来模拟一个while循环,它的等价代码如下:(IDA的反编译结果有些问题,需要结合实际代码进行分析)
int __cdecl sub_401361()
{
char *p;
do
{
p = &szBuf[i++];
iSerial++;
iSerial = *(int *)p ^ iSerial;
}while (i < 16);
}
现在分析下第四个函数
这个函数非常简单,就是iSerial的值在经过函数③后加上152118392要为0,而前面①已经知道iSerial不能为负数,函数③也没法将iSerial变成负数,所以这边要利用溢出,即两个大数相加要超过32位的寄存器标示范围,那么结果也是0,只是多一个进位,用计算器可以得出这个数为4142848904
通过上面的分析可以得出等价伪代码以及对应注册算法如下
#include <stdio.h>
/*int __cdecl sub_401361()
{
char *p;
char szBuf[?] = "?";
int iSerial = ?;
int i=0;
do
{
p = &szBuf[i++];
iSerial++;
iSerial = *(int *)p ^ iSerial;
}while (i < 16);
}*/
void main()
{
char szBuf[20] = "@@@@@@@@@@@@@@@@@@@";
int iSerial = 4142848904;
char *p;
for (int i=15; i>=0; i--)
{
p = &szBuf[i];
iSerial = *(int *)p ^ iSerial;
iSerial--;
}
printf("The Serial:%u\t\n", iSerial);
}