160 个 CrackMe 之 153 - The_q.1 的注册算法分析和注册机实现
本帖最后由 solly 于 2019-7-19 14:54 编辑160 个 CrackMe 之 153 - The_q.1 是一个 Delphi 编译的 CrackMe 程序,文件信息如下,没有加壳:
既然是 Delphi 程序,直接先用 DeDeDark 打开看看,寻找事件入口:
可以看到到,有一个 Edit1Change 事件就是我们需要的事件,其入口地址为 0x00425F64,我们直接在 OD 中跳转到这个地址下断即可:
用 OD 载入并下好断点,F9 直接运行 CrackMe,显示界面如下图:
因为是 change 事件,我们先不要手动录入注册码。可以在记事本(notepad.exe)中录好,并复制,然后再到 CrackMe 中粘贴,这样可以一次性输入多个字符而只中断一次。如下图,我们输入的注册码:
我们前面已经在 0x00425F64 下了断点,程序就会断在这个位置,整个事件的代码如下:
00425F64/.55 push ebp
00425F65|.8BEC mov ebp, esp
00425F67|.83C4 E8 add esp, -18
00425F6A|.53 push ebx
00425F6B|.56 push esi
00425F6C|.57 push edi
00425F6D|.33C9 xor ecx, ecx
00425F6F|.894D F8 mov dword ptr , ecx ;regCode
00425F72|.894D F4 mov dword ptr , ecx ;temp1
00425F75|.894D F0 mov dword ptr , ecx ;temp2
00425F78|.8945 FC mov dword ptr , eax ;Sender,文本控件对象(0x02331C9C)
00425F7B|.33C0 xor eax, eax
00425F7D|.55 push ebp
00425F7E|.68 8D614200 push 0042618D ;try
00425F83|.64:FF30 push dword ptr fs:
00425F86|.64:8920 mov dword ptr fs:, esp
00425F89|.C645 EB 00 mov byte ptr , 0 ;设置为失败标志
00425F8D|.8D55 F8 lea edx, dword ptr ;edx == buffer ===> regCode
00425F90|.8B45 FC mov eax, dword ptr
00425F93|.8B80 EC010000 mov eax, dword ptr
00425F99|.E8 AAF3FEFF call 00415348 ;Control.GetText()
00425F9E|.8B45 F8 mov eax, dword ptr ;eax ===> regCode
00425FA1|.E8 CAD5FDFF call 00403570 ;eax == strlen(regCode)
00425FA6|.8BD0 mov edx, eax ;edx == eax == strlen(regCode)
00425FA8|.8D45 F4 lea eax, dword ptr ; ===> temp1
00425FAB|.E8 94D8FDFF call 00403844 ;SetLength(temp1, (strlen(regCode))
00425FB0|.8B45 F8 mov eax, dword ptr ;eax ===> regCode
00425FB3|.E8 B8D5FDFF call 00403570 ;eax == strlen(regCode)
00425FB8|.8BD0 mov edx, eax ;edx == eax == strlen(regCode)
00425FBA|.8D45 F0 lea eax, dword ptr ; ===> temp2
00425FBD|.E8 82D8FDFF call 00403844 ;SetLength(temp2, strlen(regCode))
00425FC2|.8B45 F8 mov eax, dword ptr ;eax ===> regCode
00425FC5|.E8 A6D5FDFF call 00403570 ;strlen()
00425FCA|.83F8 08 cmp eax, 8 ;strlen(regCode)>=8 ???
00425FCD|.7D 0D jge short 00425FDC
00425FCF|.8D45 F4 lea eax, dword ptr ;temp1
00425FD2|.BA 08000000 mov edx, 8
00425FD7|.E8 68D8FDFF call 00403844 ;SetLength(temp1, 8)
00425FDC|>C645 EF 01 mov byte ptr , 1 ;int x = 1
00425FE0|.C645 EE 01 mov byte ptr , 1 ;int k = 1
00425FE4|.8B45 F8 mov eax, dword ptr ;eax ===> regCode
00425FE7|.E8 84D5FDFF call 00403570 ;strlen()
00425FEC|.84C0 test al, al
00425FEE|.76 64 jbe short 00426054 ;strlen(regCode) <= 0 ???
00425FF0|.8845 EA mov byte ptr , al ;int n = == strlen(regCode)
00425FF3|.B3 01 mov bl, 1 ;int i = 1;
00425FF5|>33C0 /xor eax, eax
00425FF7|.8AC3 |mov al, bl ;i
00425FF9|.8B55 F8 |mov edx, dword ptr ;edx ===> regCode
00425FFC|.8A4402 FF |mov al, byte ptr ;al = regCode
00426000|.25 FF000000 |and eax, 0FF
00426005|.0FA305 407842>|bt dword ptr , eax ;位测试和设置指令, bt 0x00000000, (eax % 32)
0042600C|.73 21 |jnb short 0042602F ;不低于跳转, al为偶数不跳转(即 ASCII 码为偶数),al为奇数则跳转
0042600E|.8D45 F4 |lea eax, dword ptr ;temp1
00426011|.E8 2AD7FDFF |call 00403740 ;eax ===> temp1, 奇
00426016|.33D2 |xor edx, edx
00426018|.8A55 EF |mov dl, byte ptr ;dl == x
0042601B|.33C9 |xor ecx, ecx
0042601D|.8ACB |mov cl, bl ;cl == i
0042601F|.8B75 F8 |mov esi, dword ptr ;esi ===> regCode
00426022|.8A4C0E FF |mov cl, byte ptr ;cl = regCode, i= 4
00426026|.884C10 FF |mov byte ptr , cl ;temp1 == regCode
0042602A|.FE45 EF |inc byte ptr ;x++
0042602D|.EB 1F |jmp short 0042604E
0042602F|>8D45 F0 |lea eax, dword ptr ; ===> temp2
00426032|.E8 09D7FDFF |call 00403740 ;eax ===> temp2,偶
00426037|.33D2 |xor edx, edx
00426039|.8A55 EE |mov dl, byte ptr ;k, 初始化为 1
0042603C|.33C9 |xor ecx, ecx ;int j = 0
0042603E|.8ACB |mov cl, bl ;j=i
00426040|.8B75 F8 |mov esi, dword ptr ;esi ===> regCode
00426043|.8A4C0E FF |mov cl, byte ptr ;cl = regCode
00426047|.884C10 FF |mov byte ptr , cl ;temp1 == regCode, temp1== "bb7"
0042604B|.FE45 EE |inc byte ptr ;k++
0042604E|>43 |inc ebx ;i++
0042604F|.FE4D EA |dec byte ptr ;n--, n == strlen(regCode)
00426052|.^ 75 A1 \jnz short 00425FF5
00426054|>33FF xor edi, edi ;int xorSum = 0
00426056|.33D2 xor edx, edx
00426058|.8A55 EE mov dl, byte ptr ;dl == stelen(temp1) + 1
0042605B|.4A dec edx
0042605C|.8D45 F0 lea eax, dword ptr ;eax ===> temp1, 奇
0042605F|.E8 E0D7FDFF call 00403844 ;SetLength(), 写入字符串的长度值,成为 delphi格式字符串
00426064|.8A45 EE mov al, byte ptr ;k = strlen(temp1) + 1
00426067|.48 dec eax
00426068|.84C0 test al, al
0042606A|.76 1A jbe short 00426086
0042606C|.8845 EA mov byte ptr , al ;n = strlen(temp1) - 1, 奇
0042606F|.B3 01 mov bl, 1 ;int i = 1
00426071|>33C0 /xor eax, eax
00426073|.8AC3 |mov al, bl ;i
00426075|.8B55 F0 |mov edx, dword ptr ;edx ===> temp1, 奇
00426078|.0FB64402 FF |movzx eax, byte ptr
0042607D|.66:33F8 |xor di, ax ;xorSum = xorSum ^ temp1
00426080|.43 |inc ebx ;i++
00426081|.FE4D EA |dec byte ptr ;n--
00426084|.^ 75 EB \jnz short 00426071
00426086|>8A45 EF mov al, byte ptr ;j = strlen(temp2)+1, 偶
00426089|.F6D0 not al ;al == -6
0042608B|.3C 08 cmp al, 8
0042608D|.76 1F jbe short 004260AE
0042608F|.8A5D EF mov bl, byte ptr ;j
00426092|.80FB 08 cmp bl, 8
00426095|.77 17 ja short 004260AE ;j>8
00426097|>8D45 F4 /lea eax, dword ptr ; ===> temp2, 偶
0042609A|.E8 A1D6FDFF |call 00403740 ;eax ===> temp2
0042609F|.33D2 |xor edx, edx
004260A1|.8AD3 |mov dl, bl ;x
004260A3|.C64410 FF 30|mov byte ptr , 30 ;temp2 padding "0"
004260A8|.43 |inc ebx
004260A9|.80FB 09 |cmp bl, 9
004260AC|.^ 75 E9 \jnz short 00426097
004260AE|>8D45 F4 lea eax, dword ptr ; ===> temp2
004260B1|.BA 08000000 mov edx, 8
004260B6|.E8 89D7FDFF call 00403844 ;eax ===> SetLength(temp2, 8)
004260BB|.8D45 F8 lea eax, dword ptr ; ===> regCode
004260BE|.BA 04000000 mov edx, 4
004260C3|.E8 7CD7FDFF call 00403844 ;eax ===> SetLength(regCode, 4)
004260C8|.B3 01 mov bl, 1 ;int i=1
004260CA|>8D45 F8 /lea eax, dword ptr ;regCode, 开始循环取temp2前4位
004260CD|.E8 6ED6FDFF |call 00403740 ;eax ===> temp0
004260D2|.8BF3 |mov esi, ebx ;i
004260D4|.81E6 FF000000 |and esi, 0FF
004260DA|.8B55 F4 |mov edx, dword ptr ;edx ===> temp2
004260DD|.8A5432 FF |mov dl, byte ptr ;dl = temp2
004260E1|.885430 FF |mov byte ptr , dl ;regCode == "0068"
004260E5|.43 |inc ebx
004260E6|.80FB 05 |cmp bl, 5
004260E9|.^ 75 DF \jnz short 004260CA
004260EB|.8B45 F8 mov eax, dword ptr ;eax ===> temp2_0-3
004260EE|.E8 B5FFFDFF call 004060A8 ;eax == StrToInt("6662") == 0x1A06
004260F3|.66:8945 EC mov word ptr , ax
004260F7|.B3 01 mov bl, 1
004260F9|>8D45 F8 /lea eax, dword ptr ;regCode, 开始循环取 temp2 后4位
004260FC|.E8 3FD6FDFF |call 00403740 ;eax ===> temp0
00426101|.8BF3 |mov esi, ebx
00426103|.81E6 FF000000 |and esi, 0FF
00426109|.8B55 F4 |mov edx, dword ptr
0042610C|.8A5432 03 |mov dl, byte ptr
00426110|.885430 FF |mov byte ptr , dl
00426114|.43 |inc ebx
00426115|.80FB 05 |cmp bl, 5
00426118|.^ 75 DF \jnz short 004260F9
0042611A|.8B45 F8 mov eax, dword ptr ;eax ===> temp2_4-7
0042611D|.E8 86FFFDFF call 004060A8 ;eax == StrToInt("4680") == 0x1248
00426122|.8BC8 mov ecx, eax
00426124|.66:85FF test di, di ;xorSum 不能为 0
00426127|.74 1B je short 00426144
00426129|.66:8B45 EC mov ax, word ptr ;前4位 0x44
0042612D|.66:33C1 xor ax, cx ;前4位 xor 后4位
00426130|.0FB7C0 movzx eax, ax
00426133|.0FB7D7 movzx edx, di
00426136|.8BCA mov ecx, edx
00426138|.99 cdq
00426139|.F7F9 idiv ecx ;(前4位 xor 后4位) mod (xorSum(regCode前3位))
0042613B|.83FA 0D cmp edx, 0D ;余数为 0x0D, 13
0042613E|.75 04 jnz short 00426144
00426140|.C645 EB 01 mov byte ptr , 1 ;设置为成功标志
00426144|>807D EB 00 cmp byte ptr , 0
00426148|.74 15 je short 0042615F ; 0 - 失败,1 - 成功
0042614A|.8B45 FC mov eax, dword ptr
0042614D|.8B80 F4010000 mov eax, dword ptr
00426153|.BA A4614200 mov edx, 004261A4 ;ASCII "R-E-G-I-S-T-E-R-E-D !!"
00426158|.E8 1BF2FEFF call 00415378 ;Control.SetText()
0042615D|.EB 13 jmp short 00426172
0042615F|>8B45 FC mov eax, dword ptr
00426162|.8B80 F4010000 mov eax, dword ptr
00426168|.BA C4614200 mov edx, 004261C4 ;ASCII "NOT Registered !"
0042616D|.E8 06F2FEFF call 00415378 ;Control.SetText()
00426172|>33C0 xor eax, eax
00426174|.5A pop edx
00426175|.59 pop ecx
00426176|.59 pop ecx
00426177|.64:8910 mov dword ptr fs:, edx
0042617A|.68 94614200 push 00426194
0042617F|>8D45 F0 lea eax, dword ptr
00426182|.BA 03000000 mov edx, 3
00426187|.E8 8CD1FDFF call 00403318
0042618C\.C3 retn
0042618D .^ E9 4ECCFDFF jmp 00402DE0 ;catch
00426192 .^ EB EB jmp short 0042617F
00426194 .5F pop edi
00426195 .5E pop esi
00426196 .5B pop ebx
00426197 .8BE5 mov esp, ebp
00426199 .5D pop ebp
0042619A .C3 retn
上面的代码中,我在每行代码后基本都有说明,不过可能流程不清晰,下面的图,更能説明问题:
CrackMe 首先将我们输入的序列号进行字符分组,分组原则就是序列号中每个字符的 ASCII 码值,按码值的奇偶性,分成两组字符串:str1 和 str2。
str1 所含字符的ascii 码值都是奇数的数字或其它非数字字符,str2 所含字符的 ascii 码值均为偶数的数字字符。
str1 的长度无限制,最少为1个字符。str2 最少要8个数字字符,如果不够,在后面补“0”字符,如果有多,则8个以后的字符用不到。
接下来就是运算,str1 中的每个字符依次进行 xor 运算,结果为 r1。如下所示,str1 的长度为 n:
r1 = str1 xor str1 xor str1 xor str1 xor str1 xor ...... xor str1;
str2 则将8个字符,分成两组,前4个一组形成32bits整数 n1,后4个一组形成另一个32bits整数 n2,然后两个整数进行 xor 运算,形成结果 r2。如下所示:
n1 = StrToInt(str2str2str2str2),
n2 = StrToInt(str2str2str2str2)
r2 = n1 xor n2;
最后,r1 和 r2 要满足以下条件:
r2 mod r1 == 0x0D
从上面分析可以看,r1 通过 xor 运算得到,最简单的情况是 r1 只由一个字符运算得到,即 str1 只有一个字符:
r1 = 0x00 xor str1;
我们的注册机就是按这个原则来进行注册码算码的,注册机的源码如下,使用 Dev-C++调试通过:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char** argv) {
///RAND_MAX = 47; // 0~46
srand((unsigned)time(NULL)); //// 初始化随机数
srand((unsigned)rand() * rand()); //// 初始化随机数
printf("Keygen for The.q.1, please wait...\n");
long o = 0x20;
for(long k=0; k<100; k++) {//// 最多测试100次
/// 生成两个由偶数数字组成的4位数字
long n1 = 0;
long n2 = 0;
for(int i=0; i<3; i++) {
n1 = n1 * 10 + ((long)(4 * rand() / (RAND_MAX + 1))) * 2;
n2 = n2 * 10 + ((long)(3 * rand() / (RAND_MAX + 1))) * 2;
}
/// 增加成功机率
n1 += 8000;
n2 += 6000;
long n = n2 xor n1;
//// 生成 1 位除数,1位奇数即可,跳过xor操作
for (long i=33; i<126; i+=2) { /// 可见字符 (0x21~0x7D)
if((n % i) == 13) {
o = i; /// 成功
//printf("o = %d\n", i);
break;
}
}
/// U08220640, k82446222, S82626242, G80406
if(o != 0x20) {
printf("Serial: %c%04u%04u\n", o, n1, n2);
break;
}
//sleep(5);
}
if(o == 0x20) {
printf("Serial Not Found, please run again!\n");
}
return 0;
}
上面注释中 U08220640, k82446222, S82626242, G80406,都是计算出来的可用注册码。
下面对注册码规则进一步説明:
1、如果计算出来的注册码后面是‘0’,可以不用输入这些‘0’,如 G80406000,只要输入 G80406 即可,其余的 CrackMe 自己会补上。
2、由于两个相同的字符 xor 后结果为0,不会影响下一次 xor 运算,所以在 str1 中,可以成对(或成双,2,4,6,8....)地插入非数字字符或者奇数数字(1,3,5,7,9),并且位置不限,如上面 G80406,可以输入 ggG80406,G80g4g06, gGg80g4g06gkkg 等等,都是有效的。
分析完毕!!!
补充:
前面的注册机,首先确定n1,n2,形成 r2,然后测试 r1(即 str1),如果反过来考虑。我们先确定 r1(即 str1),然后强制设置 n2 = 0000,就是不考虑 n2 了,这样就可以根据条件算出 r2,就得到 n1 了,因为 n1 = r2 了。
新的注册机代码如下:
/**
* 方式 2
***/
long getSN2() {
// for(int i=33; i<127; i++) {
// printf("%c", i); /// 生成 base 字符串,手动删除其中的0,2,4,6,8,放到后的base数组变量中。
// }
// printf("\n");
///
srand((unsigned)time(NULL)); //// 初始化随机数
srand((unsigned)rand() * rand()); //// 初始化随机数
printf("Keygen for The.q.1, please wait...\n");
char base[] = "!\"#$%&'()*+,-./13579:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
long n = strlen(base);
//// 测试次数
long n1 = 0;
for (int k=0; k<100; k++) {
int i = ((int)(n * rand() / (RAND_MAX + 1)));
long o = (long)base; //// 确定 str1
//计算n1
n1 = 1;
long j = 0;
while(n1<8888) {
j++;
n1 = o * j + 13; //// 计算 n1
//// 下面测试n1是否全由偶数数字组成
long m = n1 % 2;
if(m == 1) {
continue;
}
m = (n1/10) % 2;
if(m == 1) {
continue;
}
m = (n1/100) % 2;
if(m == 1) {
continue;
}
m == (n1/1000) % 2;
if(m == 0) {
break; /// 成功找到 n1
}
}
if(n1 <= 8888) {
//long n2 = 0;
printf("Serial: %c%04d\n", o, n1);
break;
}
}
if(n1>8888) {
printf("Serial Not Found, please run again!\n");
}
return 0;
}
补充2:
如果对方式2进一步简化,设置 j==1,这样,r2 = o + 13 了。当计算的 r2 (或 n1)只有两位数时,就能满足条件了。
方式3如下:/**
* 方式 3
***/
long getSN3() {
// for(int i=33; i<87; i++) {///控制 i+13<100
// int x = i + 13;/// 计算r2, (j==1)
// long m = x % 2;
// if(m == 1) {
// continue;
// }
// m = (x/10) % 2;
// if(m == 0) {
// printf("%c", i);//// 生成下面的 base 字符串
// }
// }
// printf("\n");
char base[] = "!#/1357CEGIK";
//long i = (unsigned)time(NULL) % 12; ////随机在12个序列号中选一个
for(long i=0; i<12; i++) //// 生成全部 12 个简单注册码
{
long o = (long)base; //// r1+13<100
///
printf("%c%04d\n", o, o+13);
}
return 0;
}
这样得到12个简单的注册码:
!0046
#0048
/0060
10062
30064
50066
70068
C0080
E0082
G0084
I0086
K0088
补充3:
前面是生成“计算简单”的注册码,基本按规则可口算得到。下面考虑一下最短的注册码,因为 CrackMe 会自己补0,我们可以考虑当r2 (或n1)为整百或整千时最简短。修改后的方式4如下:
/**
* 方式 4,控制输入的位数最少
***/
long getSN4() {
char base[] = "!\"#$%&'()*+,-./13579:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
long n = strlen(base);
for(int r=2000; r<8800; r+=200) {///控制 r2 去除尾0后只有1~2位
for(int i=0; i<n; i++) {
long x = (r - 13) % base;
if(x==0) {
long r2 = r/100;
long m = r2 % 2;
if(m == 1) {
continue;
}
m = (r2/10) % 2;
if(m == 0) {
printf("%c%d\n", base, r2);
}
}
}
}
return 0;
}
运行后,输出如下:
Q22
M24
542
O42
)44
k44
!46
180
从上面结果还可以看出,180 后面的0可以去掉,只输入 18 即可,这一个应该是最简单的注册码了!!!:lol
{:1_893:}和高人还有很大的差距!!!! 数学不好怎么办不会算啊 有没有VB的教程呀,以前的方法不管用 好的 谢谢楼主的无私分享 谢谢楼主的无私分享!{:1_893:} 社会峰哥 发表于 2019-7-17 12:24
数学不好怎么办不会算啊
这只是代码逻辑算法而已,不会算可以用计算器(软体工程型)。有错请指正。 学习了,感谢分享~ 天空藍 发表于 2019-7-17 14:48
这只是代码逻辑算法而已,不会算可以用计算器(软体工程型)。有错请指正。
不是计算器,还是要先理解怎么算的,怎么算还是要有点数学逻辑滴,计算器只是验算你的理解。 solly 发表于 2019-7-19 14:41
不是计算器,还是要先理解怎么算的,怎么算还是要有点数学逻辑滴,计算器只是验算你的理解。
我的意思是说这个数学比例占得少,主要是代码逻辑问题,这些主要是靠调试来弄懂代码逻辑,而不是靠数学,靠数学的会是一堆数学符号。 xor像我就不晓得如何算,指令逻辑不懂,只要将指令和数字放入计算机运算,答案就出来了,接下来就是靠调试弄懂代码原理。有错在帮忙指正。thank
页:
[1]
2