本文为看雪论坛优秀文章
看雪论坛作者ID:10ngvv
简单使用PEiD分析目标文件,发现文件是32位文件,没有加壳,文件采用Borland Delphi。
3.1 去NAG
观察NAG窗口,该窗口很简单,可能使用MessageBox或者CreateDialog创建的。
1)手动跟踪法
使用OD载入,使用断点和F9配合,跟踪到位置0042F784,这里将标题和内容传到ecx和edx以及eax。跟进入唯一的call,发现内部调用确实是调用系统的MessageBox,可以判断0042A170是Delphi对MessageBox的一个封装。
2)加速分析
通过字符串查找目标代码
可以通过弹窗内容进行查找,可以快速找到目标代码位置。
通过关键API查找目标代码
通过MessageBox进行断点,然后弹出NAG时,朔源代码位置。
3)去除处理
将CALL进行NOP处理即可,但需要注意堆栈平衡。
3.2 破解右边的Serial的验证
通过关键字符串快速定位到弹窗代码位置,然后朔源代码,这里明显是一个if else判断。通过观察堆栈发现正确的序列号 Hello Dude! 。
实际实验发现序列号正确。
3.3 破解左边的Name和Serial验证
随便输入测试数据:
Name:kkkkk
Serial:11111111111
用相同的方法跟踪到目标代码位置,同样一个ifelse判断,同样此时在堆栈中就能得到计算的正确序列号CW-8774-CRACKED。
测试发现,对应输入测试数据Name验证通过。
3.4 注册机算法分析
判断Name长度大于4。
取第一个字符,乘以0x29,再乘以2。
然后以10方式打印成字符串,然后使用-,头尾格拼接CW和CRACKED。
#include <stdio.h>
#include <string.h>
#define LIMIT_NAME_SIZE 4
int main()
{
char buff[100] = { 0 };
printf("please intput Name:");
gets_s(buff, sizeof(buff));
if (strlen(buff) <= LIMIT_NAME_SIZE) {
puts("length of name must large than 4");
return -1;
}
int num = buff[0] * 0x29 * 2;
sprintf_s(buff, "CW-%d-CRACKED", num);
puts(buff);
return 0;
}
2022-11-13
使用PEiD分析目标文件,发现文件是32位文件,没有加壳,文件采用Borland Delphi。
查看界面,没有点击按钮,提示注册成功会出现图片。
猜测是通过键盘按下释放事件触发验证代码。
3.1 暴力破解
通过查看关键字符串,跳转目标代码位置,发现一个关键判断00458031。
将跳转部分nop掉,保证输入任意代码都可以注册成功,将修改后的代码另存到新文件。
测试输入,输入任意字符并不会触发成功提示,,移动鼠标发现可点击,点击发现注册成功。
3.2 注册算法
通过前面发现,设置鼠标断点能在00458031暂停,查看代码发现没有计算序列号的实现,判断结论由内存获取,猜测计算序列号实现在别处,这里仅获取结果将图片进行显示。再次设置键盘事件断点,得到序列号计算代码实现位置,此时堆栈数据。
序列号是直接通过拼接字符串合成的,这里测试注册码是:黑头Sun Bird10dseloffc-012-OKkkkkk,验证通过。
该程序将注册码计算和用户结果判断两部分逻辑拆分,前面通过键盘事件触发注册码计算这块猜对了,但没有想到还通过一个隐藏的面板点击逻辑进行触发显示。
使用PEiD分析目标文件,发现文件是32位文件,没有加壳,文件采用Borland Delphi。
这道题难点在于和上次一样将按钮状态更改验证和序列号验证两个算法拆开,在破解过程中需要多次输入不同的账号密码进行验证,且存在一定的误导。
3.1 隐藏 cancella按钮,ok按钮变可用
如果不没注意到cancella按钮,会发现ok按钮的响应函数有一个关键比较是无法跳过的,因为该变量修改需要通过cancella的响应事件进行触发,且响应正确的序列号和验证码后,cancella按钮会隐藏,ok按钮会可用。
这里计算得到:
注册码(name)- 输入的序列号 = 0x7A69
=》注入的序列号 = 注册码(name)- 0x7A69
注册码(name)的算法:
int invisableCancelButton(const char* name) {
if (strlen(name) <= 4) {
return 0;
}
char ch = name[4];
char rem = ch % 0x7; // 余数
char tmp = Factorial(rem + 2);
int sum = 0;
for (int i = 0; i < strlen(name); i++) {
sum += name[i] * tmp;
}
return sum - 0x7A69;
}
输入测试序列号和计算出来的注册码:
3.2 成功注册并隐藏ok按钮
查看ok按钮的响应函数,发现这里是通过codice反推Nome(这里应该是name,猜作者写错了)的,算法不难直接给代码。
// code是十进制的数字字符串
void codeToName(const char* code, char* name) {
int sum = 0;
int aaa = 0;
for (int i = strlen(code); i > 0; i--) {
sum = code[i - 1] * code[i - 1];
sum &= 0x0000ffff;
sum *= i;
aaa = sum % 0x19;
sum /= 0x19;
aaa += 'A';
name[i - 1] = char(aaa);
}
}
将前面的code值 -15929带入函数,计算出Nome的值,填入验证通过。
如果没有注意到cancella按钮的实现函数,只通过输入另外一个算法得到的序列号和验证码可将ok按钮变成可用,但此路是死胡同,第二部点击ok按钮验证name和codice时,有一个cmp一定过不了。
注册码算法(name)- 输入注册码 = 0x29A
int calcName(const char* name) {
int sum = strlen(name);
for (int i = 1; i <= strlen(name); i++) {
sum += name[i] * name[i - 1] * i;
}
return sum;
}
2022-11-15
这个程序和昨天那个是同一个作者,不过难度加强。从昨天来看作者会在各种问题上来恶心破解者,实际逆向过程也确实如何,暴力比较简单就不展示,首先定位到点击注册按钮的回调函数00442F28。
算法流程如下:
3.1 注册算法
注册验证算法在004429A8中,首先通过name计算值。
对code的值的计算如下。
当两者计算出来的值相等时,注册通过。通过上面两个逻辑,可以使用遍历的方式,尝试得到符合条件的值。
// v3是某内存变量的值 ds:445830, v3必须大于0
int calcName(const char* name, int v3)
{
int nNameLen = strlen(name);
int index = 1;
int sum = 0;
if (nNameLen <= 4) {
printf("too short");
return -1;
}
for (int i = nNameLen; i > 0; i--) {
for (int j = nNameLen; j > 0; j--) {
sum += v3 * name[j - 1] * name[index - 1];
}
index++;
}
int retVal = abs(sum) % 666666;
return retVal;
}
char name[MAX_SIZE] = { 0 };
gets_s(name);
int nCode = calcName(name, v3);
for (int i = 0; i < 0x10000000; i++) {
if (nCode == (i % 'P' + i / 'Y' + 1)) {
printf("useful code: %d\n", i);
}
}
这里有一个问题,V3的值来源于全局变量ds:445830,但发现该值在测试过程中都是0,因为V3的值只有不为0时,上面的逻辑才能通过,因此需要寻找写入V3的地址。
通过查找命令的方式来查看:
除了第一条外,其他都是写入0到V3。跳转到第一条地址,发现回到开头地方,即当code输入不为数字时,除了弹窗提示外,还隐含了计算然后写入到ds:445830的操作。
逆向 00442A8C 得到代码如下:
int getV3(const char* code){
if (strlen(code) <= 5) {
return 0;
}
int sum = 981;
int index = 1;
for (int i = strlen(code) - 1; i > 0; i--, index++) {
sum += code[index - 1] * (code[index] % 0x11 + 1);
}
return abs(sum) % 29000;
}
这样条件后凑齐了,假设通过在code中输入AAAAAA,让程序写入V3.下面是测试程序的执行情况:
然后通过代码输入name位abcdef,可得到一批code,取其中一个使用。
3.1 再次验证
跟踪再次验证的回调函数,发现使用的算法函数和注册是一样的,但直接点击会发现注册按钮又再次出现。因为前面查找命令中发现,在一次函数验证结束后,会重新将V3赋值位0,因此again按钮本质上就是上面注册按钮的重现,需要先输入非数字字符到code产生V3的值,然后再输入正确的用户名和注册码。
作者的思路就是将常见一个按钮触发的验证过程,拆成几个,并且脱离常规,将一些逻辑放到错误执行中,从而误导破解者。
这两个都是同一个作者,第一个是单纯找序列号,第二个则通过用户名计算序列号,因为都比较简单,把他们放在一起。
3.1 获取程序1的序列号
程序1非常简单,只要找到关键比较位置,就可以得到正确的序列号。
3.1 获取程序2的序列号计算算法
程序2也比较简单,通过常规方法找到判断代码位置,然后往上查看代码,可以得到序列号计算的地方。
将其转为C实现,测试通过。代码如下:
int main()
{
unsigned int sum = 0x81276345;
char name[MAX_SIZE] = { 0 };
gets_s(name);
int nLen = strlen(name);
if (nLen >= 5) {
for (int i = 0; i < nLen; i++) {
sum += name[i];
sum ^= i << 8;
sum *= ~(i * nLen) * (i + 1);
}
}
printf("%lu", sum);
}
这两个程序比前面几个都简单。
看雪ID:10ngvv
https://bbs.pediy.com/user-home-920080.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!