华科软件安全实验--逆向Demo2020.exe--序列号生成算法破解--分析过程

随机输入序列号,弹出对话框

查找引用到的字符串

跳转到引用字符串的汇编指令处

往上拖动找到函数起始处,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个字节置为零

。。。待续

猜你喜欢

转载自blog.csdn.net/m0_43406494/article/details/109105737