(一)分析
运行一下这个程序发现,这个程序没有确认按钮,推测是实时获取用户输入,然后在文本框中显示输入是否正确。
OD中打开,自动定位到了主函数。
GetModuleHandle获取窗口句柄,ExitProcess函数是退出程序的进程,中间那个401023函数就是程序运行的主体部分,F7步入,看到一些加载图标,加载光标,注册窗口的api函数等等,
直到ShowWindow函数,才显示出窗口,前面一直是在进行一些窗口的初始化操作
后面一个大循环,在循环里面看到GetMessage函数,这个函数可以实现获取源源不断的消息,因为所有在窗口上的输入消息,都会放到应用程序的消息队列里,然后再发送给窗口回调函数处理。
目的是了解程序如何对用户输入的文本进行操作,势必要跳出循环。
Ctrl+F2重新加载,这次直接按F9让程序跑飞,这样才能输入测试数据。
输入完测试数据之后,可以注意到在循环的下一步,程序开始获取文本框的输入,有个GetDlgItemInt函数,给它下个断点,点一下文本框,程序便停在了这个函数的位置,这样就可以看程序对数据做了什么处理。
首先要弄清楚输入文本都被存入了哪里。
MSDN文档查找一下GetDlgItemInt函数,这个函数意在实现int型读取用户输入文本框的内容并返回。pSuccess指向用户输入的序列号所在的内存单元的地址,即0x0018F920。
GetWindowTextA函数就是获取文本框的文本,把文本存入了Buffer指向的内存(从OD看出Buffer指向的地址是0x0040316C)
下拉发现在对用户名跟序列号处理的代码下面一些奇怪的汇编指令,且没有跳转指向输入正确的提示。推测这里是SMC。
先分析一下程序对数据的处理。
004012A3 . A1 0B304000 mov eax, dword ptr [40300B];常量0x58455443存入eax
004012A8 . BB6C314000 mov ebx,0040316C ;ASCII "123456"
004012AD > 0303 add eax, dword ptr [ebx]
004012AF . 43 inc ebx
004012B0 . 81FB 7C314000 cmp ebx, 0040317C
004012B6 .^ 75 F5 jnz short 004012AD;输入的用户名“123456”循环16次每次读取4个字节累加到常量0x58455443
004012B8 . 5B pop ebx;输入的序列号存入ebx
004012B9 . 03C3 add eax, ebx
004012BB . 3105 D9124000 xor dword ptr [4012D9], eax;常量0x584554(0x4012D9处的字节码)跟累加后的值异或【SMC】
004012C1 . C1E8 10 shr eax, 10;异或后右移16位
004012C4 . 66:2905 D9124 >sub word ptr [4012D9], ax;异或后的值减去累加后的值,可以注意到0x4012D9指向的不是数据,而是那一段奇怪的代码的地址,明显在对0x4012D9的代码进行修改。【SMC】
现在分析一下这部分SMC,之所以没有跳转指令跳转到提示正确的字符的位置,推测是因为SMC自修改出来的代码就是缺失的跳转语句。
但跳转到自修改出的跳转语句之前,还有一部分bx跟ax异或0x3E次的,
这边有个lods 取串指令(把源串中的元素(字或字节)逐一装入AL或AX中),从si寄存器存放的地址处,也就是0x4011EC处开始取串,然后必须要异或出固定的值0xAFFCCFFB才会有后来的跳转。
这里的循环后期写注册机的时候其实是可以忽略的,因为这个循环主要的作用是进一步验证SMC出的代码是不是jmp到yes字符串,而破解的话只要前面的数据处理就能爆破出来了。
理清程序的大体过程之后可以写注册机。
(二)注册机
源代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string>
using namespace std;
int main()
{
char usrname[20] = { 0 };
int serial = 0x58455443, temp = 0, temp1 = 0,n;
printf("username:");
scanf("%s", usrname);
for (int i = 0; i < 16; i++)
serial += *(int *)(usrname + i);
for (int j = 0; j < 0xFFFFFFFF; j++)
{
temp = (0x584554 ^ (serial + j))-((serial + j) >> 16);
if (temp == 0x585426EB)
{
printf("serial:%u", j);
break;
}
}
return 0;
}
后来发现temp = (0x584554 ^ (serial + j))-((serial + j) >> 16);这一步可以优化,用分步运算,
先算高四位的值
(0x58 ^ ((serial + j)>>16))==0x5854,
后算第四位的值,
(0x4554 ^ ((serial + j)&0x0000FFFF))-((serial + j) >> 16)==0x26EB
可以看出最后序列号与累加后的用户名的和其实等于一个常数,优化之后的程序源码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string>
using namespace std;
int main()
{
char usrname[20] = { 0 };
int serial = 0x58455443, temp = 0, temp1 = 0,n;
printf("username:");
scanf("%s", usrname);
for (int i = 0; i < 16; i++)
serial += *(int *)(usrname + i);
printf("Serial:%u\n", 0x580C3BA3 - serial);
return 0;
}