160个CrackMe系列-002-Afkayas.1

selph
selph
发布于 2020-10-07 / 631 阅读
0
0

160个CrackMe系列-002-Afkayas.1

前言

这是160个CreakMe系列的第002个CM,难度:☆;

国庆给自己放了个小假期,现在开始恢复学习!

本次分析使用到的环境和工具如下:

  • 操作系统:Windows 10 2004(物理机)

  • 工具:x32dbg

  • IDE:Visual Studio 2019

程序分析

image-20201007084736777

这个CM程序界面很直白,输入用户名和序列号进行验证,以及程序是用VB语言编写的

那么这次的分析目标是:实现验证的破解,以及分析出来序列号的算法

分析目标1:暴力破解

随便输入个用户名和序列号,点击OK:

image-20201006203106726

根据错误提示,搜索字符串:

image-20201006203218781

根据字符串所在代码的位置,可以很明显看出来,这里的je是用来判断完序列号进行跳转的,如果把je改成jne,当我们再次点击OK时,则:

image-20201006203338787

序列号验证成功,暴力破解成功(其实设置je为nop可能更好一些,万一瞎填的序列号填对了呢哈哈哈)

分析目标2:序列号分析(新)

看了别人的分析之后,发现我傻了,我分析了半天的东西就是个16-10的进制转换。。。原文我保留了,以后再来回顾当下愚蠢的我吧哈哈哈

这里重新分析一下

经过测试,由于序列号的生成与字符串长度有关,所以查一下导入的函数有没有字符串长度有关的,嗯
image-20201006213655574

真是非常的巧,刚好有一个,下断点,看看这一块代码:

image-20201006214426952

这里算完长度之后,拿长度*17CFB+首字母ascii码值,这就是真正的序列号了,只不过要进制转换一下,之前我还傻傻的步进进去分析这个进制转换是怎么转换的了。。

例子还用原理的例子:用户名abc

3*17CBF + 61 = 47752

47752h转换成10进制就是:292690,就是真正的序列号了

编写注册机程序(新)

#include<stdio.h>
#include<string.h>
#include<Windows.h>
int main() {
	//获取用户名
	char username[10];
	printf("请输入用户名:");
	scanf_s("%s",username,10);
	//计算序列号
	char szbuf[MAXCHAR] = { 0 };
	int series_num = strlen(username) * 0x17CFB + username[0];
	sprintf_s(szbuf, "AKA-%d", series_num);
	printf("序列号是:%s\r\n", szbuf);

	system("pause");
	return 0;
}

分割线,下面是我之前愚蠢的解法,有兴趣可以看个乐,算是分析进制转换流程了哈哈哈


分析目标2:序列号分析(旧)

在判断验证成功与否的je跳转那里下断点,往上找找看有没有验证函数调用啥的

image-20201006203622067

上面有一个被判断为strcmp的函数很可疑,下断点看看:

image-20201006203733891

经过测试,这就是是正确的序列号,接着往上面找,这个序列号是哪里算出来的,在上面所有可疑函数都下个断点看看:

image-20201006204045660

这里是字符串AKA-和序列号的数字部分,在这之前ecx里就已经存有了正确的序列号,说明序列号的计算还在前面,接着去看看ecx的值是哪来的:ecx的值来自[ebp-1c],往上翻翻接着看看这个值是哪里来的:

image-20201006205259460

这里把那个栈的地址取走了,通过这个函数之后,栈中地址里存储着序列号的地址,想到上一关的折腾,我打算这次换个方式找下去

经过反复测试,序列号的值与用户名首字母和长度有关,相同首字母和长度的用户名序列号是一样的

那么这可能调用了一个函数来判断字符串长度,去搜一搜导入函数里有没有一个跟len相关的函数:

image-20201006213655574

真是非常的巧,刚好有一个,下断点,看看这一块代码:

image-20201006214426952

再跟进这个函数层层深入探寻真相:

image-20201006214732441

继续深入:

image-20201006220452412

然后就找到了生成序列号的算法了:

image-20201006220518317

之前有一段是,用字符串长度乘以17CFB,这三数相乘,然后加上首字符的ascii码,这个数字就是序列号生成的关键,以下简称为数字a

序列号的生成是从后往前生成的,用数字a/Ah,得到的数字放在ecx,余数放在edx,最后一个字符为edx+30对应的ascii码,然后不断循环,直到数字被除到0

举个例子吧,比如用户名是abc

那么计算流程如下:

3*17CBF + 61 = 47752
47752/A = 7255  余数是0
7255 /A = B6E   余数是9
B6E  /A = 124   余数是6
124  /A = 1D    余数是2
1D   /A = 2     余数是9
2    /A = 0	    余数是2

把余数加上30h(30h 是 0),然后倒序排列是:292690

我们来验证一下:

image-20201006223206394

编写注册机程序(旧)

效果演示

image-20201007084013138

源代码

用C语言写个注册机:

#include<stdio.h>
#include<string.h>
#include<Windows.h>
int main() {
	//获取用户名
	char username[10];
	printf("请输入用户名:");
	scanf_s("%s",username,10);
	//获取用户名长度
	int u_length = strlen(username);
	//获取首字母ascii
	int first = username[0];
	//计算乘积
	int num = u_length * 0x17CFB + first;

	//计算序列号
	char series[10] = {0};
	int i = 0;
	int remainder = 0;
	do {
		remainder = num % 0xA;
		num = num / 0xA;
		series[i++] = remainder + 0x30;
	} while (num!=0);
	
	//打印完整的序列号
	int s_length = strlen(series);
	printf("\r\n序列号为:AKA-");
	for (i = s_length - 1; i >= 0; i--) {
		printf("%c", series[i]);
	}
	printf("\r\n");
	system("pause");
	return 0;
}

总结

这次的教训是,下次分析的时候,先用PE工具查看一下导入函数,这样也许可以从函数下手,更精准的定位到目标位置,从而减少了一个一个看CALL的过程,提高了准确率和效率


评论