新手破解练习Crackme160之006 - aLoNg3x.1

这个程序大佬们评了1星难度,我觉得对于新人来说也得有2星难度
暴力破解:

  1. OK按钮去灰
    打开搜索界面没看到有用线索, 用PEiD检测为未加壳的delphi程序, 用DardDe分析之, 得到有
    1). Nomechange(00442E04),
    2). CodiceChange(00442C78),
    3). OkClick(00442D64),
    4). CancellaClick(00442EA8)
    这步是为了去黑, 跟change事件有关, 用2)来操作, 00442C78下断点(4处都下好断点, 后面也要用)
    在codice框中随便输入, 成功在断点处停下, 发现下面有关键跳, 00442CB2, 与 00442CD6两处nop掉, 破解成功, 保存测试, codice处随便输入, ok按钮正常亮起~ (Nomechange与CodiceChange操作一毛一样)

  2. 隐藏OK按钮
    按下ok键进入断点00442D64, 第一个jnz(00442D84)改为 jmp 00442d98, 第二处je(00442DC8)改为nop, 测试成功隐藏ok, 保存

  3. 隐藏Cancella按钮
    按下Cancella进入00442EA8, 第一处je(00442EEE)改为nop, 直接成功, 保存搞定~

正常破解:
单步跟踪所知, 用户名与注册码的验证算法在CodiceChange事件的00442D0B处, 进入方法分析具体算法:
1). 00442A70处 判断长度>5? ebx = len, edx = len
2). 00442A93处 循环取用户名 ebx = ebx + user[i] * user[i+1] * i
3). 00442AB1处 eax = 注册码算法(进入分析可得是将10进制数转16进制)
4). 00442AB8处 if(ebx - eax == 0x29A){}
所以, 注册码 = 循环(ebx = ebx + user[i] * user[i+1] * i) - 0x29A, 这个不好用计算器一个个算, 我们就写个注册机吧, 代码如下: keygen_code.c

#include <stdio.h>
#include "string.h"

int main() {
	int len = 0;
	char user[21] = {0};
	while(len <= 5){
		printf("请输入用户名: ");
		scanf("%s", user);
		len = strlen(user);
	}
	int ebx = len;
	for(int i=1; i<len; i++){
		ebx += user[i-1] * user[i] * i;
	}
	ebx -= 0x29A;
 	printf("系列号为: %d\n\n", ebx);
	getchar();getchar();
    return 0;
}

示例:
用户名: abcdef
注册码: 149860
输入后OK按钮会亮起, 但点击后, 又灰了, 且注册码变0了, 没有达到隐藏按钮的目的, 所以这步算是失败的, 我猜这步算法是作者故意给我们设置的一个陷阱, 下面我们分别分析ok点击算法与cancel点击算法

分析OkClick中算法, 点击事件单步跟踪发现00442D80 cmp byte ptr ds:[eax+0x47],0x1 这行比较后都会跳转到失败分支(注册码清0, 按钮重新变灰), 右键查找所有常量0x47, 只有两处是有对地址赋值的,
1). 004225CE mov byte ptr ds:[ebx+0x47],0x1
2). 004231CE mov byte ptr ds:[esi+0x47],al
第一个不是我们想要的, 因为只有值是0时才会进入成功分支, al是个变量, 有可能是0也可能是1, 所以我们双击这行, 成功定位到004231CE, 往上来到方法入口004231B0处, 看到信息栏提示 本地调用来自 0042251E, 00424F44, 0042BA8A, 0042CE21, 00438EDA, 0043CB0B, 00442DD2, 00442EF8
我们分别转向调用去查看哪些方法引用了这个方法(信息栏这行右键->转向xxx, 也可以ctrl+G, 输入每一个地址), 经分析, 00442DD2是ok事件中的, 00442EF8是cancel事件中的, 其它的不清楚是哪个事件的我们先放弃研究, ok事件中调用的代码在前面说的跳转代码之后, 所以这个我们也没办法利用了, 只能先研究cancel事件了, 下面我们先来分析cancel事件

分析CancellaClick中算法:
1). eax = user[4] % 0x7 + 0x2 = 'e' % 7 + 2 = 5
2). esi = eax! = 5! = 120 = 0x78
3). 循环ebx += user[i] * esi
4). if(ebx - code == 0x7A69){}
我们先写注册机代码算出注册码来: keygen_cancel.c

#include <stdio.h>
#include "string.h"

int main() {
	int len = 0;
	char user[21] = {0};
	while(len <= 5){
		printf("请输入用户名: ");
		scanf("%s", user);
		len = strlen(user);
	}
	int eax = user[4] % 7 + 2;
	int esi = 1;
	for(; eax>0; eax--){ //eax!
		esi *= eax;
	}
	for(int i=0; i<len; i++){
		eax += user[i] * esi;
	}
	eax -= 0x7A69;
 	printf("系列号为: %d\n\n", eax);
	getchar();getchar();
    return 0;
}

示例1:
用户名: abcdef
注册码: 40303

示例2:
用户名: ABCDEF
注册码: 16298263

输入后, ok按钮没有亮起, 点击cancel按钮, cancel隐藏了, ok也亮起来了, 这步应该是正确的了, 达到了一半作者的要求, cancel已经隐藏了, 我们继续点ok, 发现那行不再跳到失败分支了, 说明cancel事件中已经执行这行代码了(004231CE mov byte ptr ds:[esi+0x47],al), 且其中的al=0, 向下执行到结束ok按钮也不会隐藏, 说明这个答案也不是最终答案, 我们继续详细分析验证算法: (关键位置00442DC1)
1). 注册码长度是否<=5, 上面我们计算出来的示例1是5位, 不符合, 所以我们需要再算一组出来(示例2)
2). 循环计算 code[i] = (((code[i] * code[i]) & 0xFFFF) * (i + 1)) % 0x19 + 0x41;
注册机代码如下: keygen.c

#include <stdio.h>
#include "string.h"

int main() {
	int len = 0;
	char user[21] = {0};
	while(true){
		while(len <= 5){
			printf("请输入用户名: ");
			scanf("%s", user);
			len = strlen(user);
		}
		int eax = user[4] % 7 + 2;
		int esi = 1;
		for(; eax>0; eax--){ //eax!
			esi *= eax;
		}
		for(int i=0; i<len; i++){
			eax += user[i] * esi;
		}
		eax -= 0x7A69;
		if(eax < 100000){
			printf("注册码%d也需要6位以上,请重新输入用户名~\n",eax);
			len = 0;
			continue;
		}
		char code[20] = {0};
		sprintf(code, "%d", eax);
	 	printf("系列号为: %s\n", code);
	 	len = strlen(code);
	 	for(int i=0; i<len; i++){
	 		code[i] = (((code[i] * code[i]) & 0xFFFF) * (i + 1)) % 0x19 + 0x41;
		 }
	 	printf("以上用户名与注册码输入后 点cancel按钮\n再修改用户名为: %s 最后点ok按钮\n\n", code);
	 	len = 0;
	}
    return 0;
}

运行示例:
请输入用户名: ABCDEF
系列号为: 16298263
以上用户名与注册码输入后 点cancel按钮
再修改用户名为: BHAVFAMI 最后点ok按钮

这个答案不知道是不是作者想要的呢? 是否有一组用户名与注册码, 直接就同时满足ok与cancel?, 我们尝试将两种算法结合在一起是否就能算出一组或多组来?
设有那么一字符串x(就定6位吧, 再长就更困难了), 其每一位ascii值分别为x0, x1, ..., x5, 那对应的注册码y(也得是6位的数字串), 转为字符串, 其每一位分别为y0, y1, ..., y5, 那就有:
y = (x4 % 7 + 2)! * (x0+x1+...+x5)
x0 = ((y0 * y0) & 0xFFFF) * (0+1) % 0x19 + 0x41
x1 = ((y1 * y1) & 0xFFFF) * (1+1) % 0x19 + 0x41
x2 = ((y2 * y2) & 0xFFFF) * (2+1) % 0x19 + 0x41
x3 = ((y3 * y3) & 0xFFFF) * (3+1) % 0x19 + 0x41
x4 = ((y4 * y4) & 0xFFFF) * (4+1) % 0x19 + 0x41
x5 = ((y5 * y5) & 0xFFFF) * (5+1) % 0x19 + 0x41
从%0x19,结果是0-24(这里应该%0x1A才对0-25,估计作者当时想当然的以为%多少就是0-多少了吧),与+0x41('A')可知结果一定是0x41-0x5A之间,即A-Z之间用穷举法的话, 代码如下: keygen_all.c

#include <stdio.h>
#include <string.h>

int compute_y(char *x, char *y) {
    int a = (x[4]) % 7 + 2;
    int esi = 1;
    for(; a>0; a--){ esi *= a; }
    int total_sum = 0;
    for (int i = 0; i < 6; i++) {
        total_sum += x[i];
    }
    int y_value = esi * total_sum - 0x7A69;
    sprintf(y, "%06d", y_value); // 转换为6位数,不足部分用0填充
    return y_value;
}

int check_conditions(char *x, char *y) {
    return 
        (x[0] == ((y[0]*y[0]) & 0xFFFF) * (0+1) % 0x19 + 0x41) &&
        (x[1] == ((y[1]*y[1]) & 0xFFFF) * (1+1) % 0x19 + 0x41) &&
        (x[2] == ((y[2]*y[2]) & 0xFFFF) * (2+1) % 0x19 + 0x41) &&
        (x[3] == ((y[3]*y[3]) & 0xFFFF) * (3+1) % 0x19 + 0x41) &&
        (x[4] == ((y[4]*y[4]) & 0xFFFF) * (4+1) % 0x19 + 0x41) &&
        (x[5] == ((y[5]*y[5]) & 0xFFFF) * (5+1) % 0x19 + 0x41);
}

int main() {
    char x[7] = {0}; // 6位数字字符加一个结束符'\0'
    char y[7] = {0};
    char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //由cancel事件可知, 只能是大写字母
    int length = sizeof(letters) - 1; // 字符串长度,减1是因为字符串以'\0'结尾
    for (int i = 0; i < length; i++) {
        for (int j = 0; j < length; j++) {
            for (int k = 0; k < length; k++) {
                for (int l = 0; l < length; l++) {
                    for (int m = 0; m < length; m++) {
                        for (int n = 0; n < length; n++) {
                            x[0] = letters[i];
                            x[1] = letters[j];
                            x[2] = letters[k];
                            x[3] = letters[l];
                            x[4] = letters[m];
                            x[5] = letters[n];
                            compute_y(x, y); // 计算y的值
                            if (check_conditions(x, y)) { // 检查是否满足条件
                                printf("找到解:x = %s, y = %s\n", x, y);
                            }
                        }
                    }
                }
            }
        }
    }
    printf("查找结束~"); 
    getchar();
    return 0;
}

上面代码运行结果符号要求的有三组:
找到解:x = AADQAE, y = -21425
找到解:x = AAIAUG, y = -28793
找到解:x = ACMQFA, y = -30487

这是160个软件part1
这是160个软件part2

1~160每个破解过程,在吾爱破解论坛都有高手破解过了,也有整理好现成的, 我这边主要就是自己动手操作的过程,与他们的不太一样
附上高手们的连接: 点击前往查看
使用的工具连接(工具有点多有点大,可以先下OD,其它的后面慢慢下) 点击前往下载

新人入门教程"玩玩破解,写给新人看" 点击前往查看
我就是从这里开始的,对我这样的小白感觉超级友好~

下面是我的OD的界面布局,我觉得这4个是最常用的界面,其它的我基本上没用到~
OD界面布局

posted @ 2024-04-09 16:46  hankerstudio  阅读(155)  评论(0)    收藏  举报