随机输入序列号,弹出对话框
查找引用到的字符串
跳转到引用字符串的汇编指令处
往上拖动找到函数起始处,402440函数就是判断序列号是否正确的函数,开始单步调试
查看PE头或者用工具得知软件是32位的
使用IDApro 32位打开,发现函数窗口没找到402440函数,进入汇编代码区域,在402440位置右键创建函数,再F5进行反汇编
得到如下反汇编代码
设置断点,动态调试
分析:由弹出窗口信息可知序列号形式位xxxx-xxxx-xxxx-xxxx,可猜测知代码使用strtol函数将四部分字符串转换成整数(视字符串为16进制数)后赋给了变量v7 v4 v5 和?。
接下来是判断语句,要使得判断条件均为假
执行过程中可见 v7=v6=0x831,因此序列号第一部分是0831
V4=0x496>>8=4 ,因此序列会第二部分是0004
V5=(0x831+0x6897)>>8=112=0x70 因此序列会第三部分是0070
由于水平有限,无法分析出第四块字符赋给了谁,依据判断条件猜测第四块的后三个字符的ascii值为70 70 48,即为字符FF0
第一个字符使用暴力攻击的方法得知为字符a
因此序列号为0831-0004-0070-aFF0
经更多测试可知,在不同环境下序列号会发生改变,第一、三部分会发生改变,第二、四部分不会发生改变(实际上,第二部分会根据电脑名称而改变,只是跟时间没关系)
修改部分变量名,接着对动态序列号的生成原理进行分析和分析第四个字符串的处理
分析v9的值由来
发现在main函数外一个地方对dword_406270进行了写操作,跳转到那个函数,命名为write_global
发现全局变量dword_406270、 dword_406274、 dword_406278都在这进行了初始化
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
使用vs来调试理解该函数
#include<stdio.h>
#include "windows.h"
int main()
{
SYSTEMTIME st;
GetLocalTime(&st);
printf("%d年%d月%d日%d时%d分%d秒%d毫秒\n",st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond,st.wMilliseconds);
Sleep(10000);
return 0;
}
发现调用GetLocalTime函数前把eax的值(0x008FF89C)(即结构体的地址)压入栈,因此查看内存窗口对应位置,执行GetLocalTime函数后如下,
回看Demo2020的汇编代码
注记:点击该地址(寄存器),自动算出了地址并显示了内存中的部分数据,这点挺方便
另外,显示数据时遵循了汇编代码中首字母不是0-9的数字时需添加0前缀的规则
还可以跳转到新的IDA view窗口来看内存中的数据
或者跳转到hex窗口
CDialog *v1; // esi@1
变量v1是esi的值,而 lea edi, [esi+64h] 令edi=esi+64h,调用GetLocalTime函数前把edi的值压入栈作为函数参数
因此反汇编出的伪代码是GetLocalTime((LPSYSTEMTIME)((char *)v1 + 0x64));
注记:读IDA反汇编出来的代码需要对C语言的语法很熟悉!
如上面,对指针进行加法,每加1对应 指针的地址值+指针指向的类型单位大小
因此(_WORD *)v1 + 0x36=*v1+6Ch !
还有前面的
使用逗号表达式时,式子整体的值为最右边的那个式子的值
因此
v2 = *((_WORD *)v1 + 0x36) + *((_WORD *)v1 + 0x35)=000F+000F=001E
为了更直观方便来看,创建结构体SYSTEMTIME
选中结构对应的内存区域,创建结构体
对照位置在
因此v2=wDay+wHour
同理可知
v3=wDayOfWeek
v4=wMonth
v4 = *((_WORD *)v1 + 0x33);
则地址(_WORD *)v1 + 0x32对应的数据是wYear
因此
dword_406270 =
*((_WORD *)v1 + 0x32) + v4 + v3 + 2 * v2=wYear+wMonth+wDayOfWeek+2*(wDay+wHour)
因此第一个序列
可以破解成功,exp如下
SYSTEMTIME st;
GetLocalTime(&st);
printf("%x",st.wYear+st.wMonth+st.wDayOfWeek+2*(st.wDay+st.wHour));
接着看GetComputerNameA(&Buffer, &nSize);
Buffer是一个全局变量(字符char类型),字符串缓存区
GetComputerNameA将电脑名称写入参数&Buffer指向的缓冲区,将输出电脑名称的大小写入变量nSize(因此传入时才把变量地址作为参数)
为了便于查看Buffer对应内容,把Buffer改成字符数组
执行完GetComputerNameA(&Buffer, &nSize)后如下
15个字符,因此nSize值为0xF
v5 = 0;
if ( strlen(Buffer) != 0 )
{
v6 = dword_406274;
do
{
v7 = Buffer[v5++];
v6 += wMonth + v7;
dword_406274 = v6;
}
while ( v5 < strlen(Buffer) );
}
dword_406274是unsigned int,v6 、v7都是int,wMonth是unsigned __int16(short) int
dword_406274的初始值为0
而main函数中
(_WORD)num2 != (unsigned __int16)((unsigned int)dword_406274 >> 8)
因此第二部分的序列号破解如下
int v7,v5,v6;
DWORD len,second;
char computername[100];
GetComputerNameA(computername,&len);
v6=0;
for(v5=0;v5<len;v5++)
{
v7=computername[v5];
v6+=st.wMonth+v7;
second=v6;
}
printf("%x",second>>8);
GetModuleFileNameA(0, &Filename, 0x100u);
Filename修改数据类型为char Filename[256],则上面语句变成
GetModuleFileNameA(0, Filename, 0x100u);
执行上面的语句,结果如下
(_WORD *)v1 + 0x36)=wHour
而且注意,上面的循环不是对dword_406278进行递增,而是不断重复赋值,实际可以简化为dword_406278=st.wHour*(dword_406274+filename最后一个字符ascii值)
filename以Demo2020.exe结尾,即取e的ascii值101
因此Demo2020的序列号跟执行路径并没有关系,简化了一些
再看main函数中
(_WORD)num3 != (unsigned __int16)((unsigned int)(dword_406270 + dword_406278) >> 8)
因此第三部分的exp为
third=st.wHour*(second+101); //'e'=101
printf("%x-",(third+first)>>8);
至此write_global函数分析完毕(剩下内容不相关),只剩第四个序列号的首字符了
修改一下变量名
找到关键语句 (CWnd::MessageBoxA(v4, aZUgb3Gb, 0, 0), v12 != 1769416289)
key((int)&str4_0, strlen(&str4_0), (int)&v11);
和关键变量v11、 v12、 str4_0、 str4_0_end
执行key((int)&str4_0, strlen(&str4_0), (int)&v11)前后,v12的值发生了改变,因此需要深入key函数的代码
signed int __cdecl key(int a1, signed int a2, int a3)
a1是str4_0的地址(12F4B8),a2是str4_0字符串的长度,因为str4_0是第四个序列号的首字符,因此为1,a3是变量v11的地址
因为最终检查的是v12,而v11与v12相邻且它的地址(a3)传了进来
循环56次,刚开始v3=1,而a1是整形指针,因此str4_0的开始四个字节不置为0,后面56个字节置为零
。。。待续