算法难度:⭐⭐⭐⭐
爆破难度:⭐
信息收集
运行情况:
一开始就是Nag:
然后进入界面:
查壳与脱壳:
调试分析
首先是一个创建互斥量,然后接收一个错误,如果是特定错误就不执行程序,这是一种简单有效的防多开手段,然后就是创建窗口了,这里跟进窗口函数
去除Nag
窗口过程的开头:
第一个0x110号消息的分支里,存在一个MessageBoxA的调用,查阅资料[1]可知,创建对话框的时候,会有一个初始化消息会发送到消息循环,这个时候窗口还未显示出来,这个消息WM_INITDIALOG就是0x110号消息
Nag存在于这个消息里,而这里MessageBox调用前面有一个跳过的条件,这里如果要消除Nag,就直接改这个跳转语句jz为jmp即可
分析校验算法
进入之后就是switch-case里的多分支语句了,使用xray查看check按钮id=22b,按钮按下的消息码是0x111,直接找111消息码控件id为22b的分支:
首先是先获取Name到全局变量里(长度需要大于等于5)
接下来是Serial校验过程
这里调用了三个生成真码的函数,这一段反汇编的主要功能就是这三个函数,中间的这些对比可以忽视,是提升效率用的
先看第一个生成函数:
获取了Name长度,保存到全局变量里
然后拼接字符串:tsrh-一个数字-
第二个生成函数:
获取生成Serial的长度,遍历用户名,对于每个用户名字节,都进行一系列计算得到一个新的数值,以十六进制的形式拼接到生成Serial的后面
第三个生成函数:
这里分别从Name和生成Serial里取一个字节,异或(Name从头开始取,Serial从12偏移处开始取)
把结果变成大写字母,然后把该大写字母以int型赋值的形式,赋值到Serial第10字节处
最后调用对比函数
对比函数:逐字节对比,如果全都一样,则返回1,返回1则会跳转到成功提示弹窗处
注册机
注册码生成算法:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#pragma region check
int midValue = 0;
char serial[100];
char name[100];
void GenSerial1() {
midValue = strlen(name);
sprintf(serial,"tsrh-%d-",midValue+0x7d3);
}
void GenSerial2() {
int len = strlen(serial);
int tmp = midValue;
for (int i = 0; i < midValue; i++)
{
int tmp1 = name[i] + 0xc;
int tmp2 = tmp1 - 0x11 + tmp1 - len;
tmp1 ^= tmp2;
sprintf(&serial[len], "%X", tmp1);
len = strlen(serial);
}
}
void GenSerial3() {
for (int i = 0; i < 0x10; i++)
{
if (name[i] == 0)break;
char tmpn = name[i]+1;
char tmps = serial[i + 12];
char c = tmpn ^= tmps;
while (c < 'A')c += 8;
while (c > 'Z')c -= 3;
*(int*)&serial[i + 10] = c;
}
std::cout << serial;
}
#pragma endregion 校验函数
int main()
{
std::cin >> name;
GenSerial1();
GenSerial2();
GenSerial3();
}
效果:
总结
分析这类的窗口程序,如果知道常见的窗口消息是什么,那就很好定位到控件操作的事件