好友
阅读权限35
听众
最后登录1970-1-1
|
solly
发表于 2019-7-17 11:22
本帖最后由 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 下了断点,程序就会断在这个位置,整个事件的代码如下:
[Asm] 纯文本查看 复制代码 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 [ebp-8], ecx ; regCode
00425F72 |. 894D F4 mov dword ptr [ebp-C], ecx ; temp1
00425F75 |. 894D F0 mov dword ptr [ebp-10], ecx ; temp2
00425F78 |. 8945 FC mov dword ptr [ebp-4], 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:[eax]
00425F86 |. 64:8920 mov dword ptr fs:[eax], esp
00425F89 |. C645 EB 00 mov byte ptr [ebp-15], 0 ; 设置为失败标志
00425F8D |. 8D55 F8 lea edx, dword ptr [ebp-8] ; edx == buffer ===> regCode
00425F90 |. 8B45 FC mov eax, dword ptr [ebp-4]
00425F93 |. 8B80 EC010000 mov eax, dword ptr [eax+1EC]
00425F99 |. E8 AAF3FEFF call 00415348 ; Control.GetText()
00425F9E |. 8B45 F8 mov eax, dword ptr [ebp-8] ; 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 [ebp-C] ; [ebp-c] ===> temp1
00425FAB |. E8 94D8FDFF call 00403844 ; SetLength(temp1, (strlen(regCode))
00425FB0 |. 8B45 F8 mov eax, dword ptr [ebp-8] ; 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 [ebp-10] ; [ebp-10] ===> temp2
00425FBD |. E8 82D8FDFF call 00403844 ; SetLength(temp2, strlen(regCode))
00425FC2 |. 8B45 F8 mov eax, dword ptr [ebp-8] ; 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 [ebp-C] ; temp1
00425FD2 |. BA 08000000 mov edx, 8
00425FD7 |. E8 68D8FDFF call 00403844 ; SetLength(temp1, 8)
00425FDC |> C645 EF 01 mov byte ptr [ebp-11], 1 ; int x = 1
00425FE0 |. C645 EE 01 mov byte ptr [ebp-12], 1 ; int k = 1
00425FE4 |. 8B45 F8 mov eax, dword ptr [ebp-8] ; 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 [ebp-16], al ; int n = [ebp-16] == 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 [ebp-8] ; edx ===> regCode
00425FFC |. 8A4402 FF |mov al, byte ptr [edx+eax-1] ; al = regCode[i-1]
00426000 |. 25 FF000000 |and eax, 0FF
00426005 |. 0FA305 407842>|bt dword ptr [427840], eax ; 位测试和设置指令, bt 0x00000000, (eax % 32)
0042600C |. 73 21 |jnb short 0042602F ; 不低于跳转, al为偶数不跳转(即 ASCII 码为偶数),al为奇数则跳转
0042600E |. 8D45 F4 |lea eax, dword ptr [ebp-C] ; temp1
00426011 |. E8 2AD7FDFF |call 00403740 ; eax ===> temp1, 奇
00426016 |. 33D2 |xor edx, edx
00426018 |. 8A55 EF |mov dl, byte ptr [ebp-11] ; dl == x
0042601B |. 33C9 |xor ecx, ecx
0042601D |. 8ACB |mov cl, bl ; cl == i
0042601F |. 8B75 F8 |mov esi, dword ptr [ebp-8] ; esi ===> regCode
00426022 |. 8A4C0E FF |mov cl, byte ptr [esi+ecx-1] ; cl = regCode[i-1], i= 4
00426026 |. 884C10 FF |mov byte ptr [eax+edx-1], cl ; temp1[x-1] == regCode[i-1]
0042602A |. FE45 EF |inc byte ptr [ebp-11] ; x++
0042602D |. EB 1F |jmp short 0042604E
0042602F |> 8D45 F0 |lea eax, dword ptr [ebp-10] ; [eax] ===> temp2
00426032 |. E8 09D7FDFF |call 00403740 ; eax ===> temp2,偶
00426037 |. 33D2 |xor edx, edx
00426039 |. 8A55 EE |mov dl, byte ptr [ebp-12] ; k, [ebp-12] 初始化为 1
0042603C |. 33C9 |xor ecx, ecx ; int j = 0
0042603E |. 8ACB |mov cl, bl ; j=i
00426040 |. 8B75 F8 |mov esi, dword ptr [ebp-8] ; esi ===> regCode
00426043 |. 8A4C0E FF |mov cl, byte ptr [esi+ecx-1] ; cl = regCode[j-1]
00426047 |. 884C10 FF |mov byte ptr [eax+edx-1], cl ; temp1[k-1] == regCode[j-1], temp1== "bb7"
0042604B |. FE45 EE |inc byte ptr [ebp-12] ; k++
0042604E |> 43 |inc ebx ; i++
0042604F |. FE4D EA |dec byte ptr [ebp-16] ; 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 [ebp-12] ; dl == stelen(temp1) + 1
0042605B |. 4A dec edx
0042605C |. 8D45 F0 lea eax, dword ptr [ebp-10] ; eax ===> temp1, 奇
0042605F |. E8 E0D7FDFF call 00403844 ; SetLength(), 写入字符串的长度值,成为 delphi格式字符串
00426064 |. 8A45 EE mov al, byte ptr [ebp-12] ; 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 [ebp-16], 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 [ebp-10] ; edx ===> temp1, 奇
00426078 |. 0FB64402 FF |movzx eax, byte ptr [edx+eax-1]
0042607D |. 66:33F8 |xor di, ax ; xorSum = xorSum ^ temp1[i-1]
00426080 |. 43 |inc ebx ; i++
00426081 |. FE4D EA |dec byte ptr [ebp-16] ; n--
00426084 |.^ 75 EB \jnz short 00426071
00426086 |> 8A45 EF mov al, byte ptr [ebp-11] ; 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 [ebp-11] ; j
00426092 |. 80FB 08 cmp bl, 8
00426095 |. 77 17 ja short 004260AE ; j>8
00426097 |> 8D45 F4 /lea eax, dword ptr [ebp-C] ; [eax] ===> 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 [eax+edx-1], 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 [ebp-C] ; [eax] ===> temp2
004260B1 |. BA 08000000 mov edx, 8
004260B6 |. E8 89D7FDFF call 00403844 ; eax ===> SetLength(temp2, 8)
004260BB |. 8D45 F8 lea eax, dword ptr [ebp-8] ; [eax] ===> 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 [ebp-8] ; 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 [ebp-C] ; edx ===> temp2
004260DD |. 8A5432 FF |mov dl, byte ptr [edx+esi-1] ; dl = temp2[i-1]
004260E1 |. 885430 FF |mov byte ptr [eax+esi-1], 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 [ebp-8] ; eax ===> temp2_0-3
004260EE |. E8 B5FFFDFF call 004060A8 ; eax == StrToInt("6662") == 0x1A06
004260F3 |. 66:8945 EC mov word ptr [ebp-14], ax
004260F7 |. B3 01 mov bl, 1
004260F9 |> 8D45 F8 /lea eax, dword ptr [ebp-8] ; 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 [ebp-C]
0042610C |. 8A5432 03 |mov dl, byte ptr [edx+esi+3]
00426110 |. 885430 FF |mov byte ptr [eax+esi-1], dl
00426114 |. 43 |inc ebx
00426115 |. 80FB 05 |cmp bl, 5
00426118 |.^ 75 DF \jnz short 004260F9
0042611A |. 8B45 F8 mov eax, dword ptr [ebp-8] ; 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 [ebp-14] ; 前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 [ebp-15], 1 ; 设置为成功标志
00426144 |> 807D EB 00 cmp byte ptr [ebp-15], 0
00426148 |. 74 15 je short 0042615F ; 0 - 失败,1 - 成功
0042614A |. 8B45 FC mov eax, dword ptr [ebp-4]
0042614D |. 8B80 F4010000 mov eax, dword ptr [eax+1F4]
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 [ebp-4]
00426162 |. 8B80 F4010000 mov eax, dword ptr [eax+1F4]
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:[eax], edx
0042617A |. 68 94614200 push 00426194
0042617F |> 8D45 F0 lea eax, dword ptr [ebp-10]
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[0] xor str1[1] xor str1[2] xor str1[3] xor str1[4] xor ...... xor str1[n-1];
str2 则将8个字符,分成两组,前4个一组形成32bits整数 n1,后4个一组形成另一个32bits整数 n2,然后两个整数进行 xor 运算,形成结果 r2。如下所示:
n1 = StrToInt(str2[0]str2[1]str2[2]str2[3]),
n2 = StrToInt(str2[4]str2[5]str2[6]str2[7])
r2 = n1 xor n2;
最后,r1 和 r2 要满足以下条件:
r2 mod r1 == 0x0D
从上面分析可以看,r1 通过 xor 运算得到,最简单的情况是 r1 只由一个字符运算得到,即 str1 只有一个字符:
r1 = 0x00 xor str1[0];
我们的注册机就是按这个原则来进行注册码算码的,注册机的源码如下,使用 Dev-C++调试通过:
[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 了。
新的注册机代码如下:
[C++] 纯文本查看 复制代码 /**
* 方式 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[i]; //// 确定 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如下:[C++] 纯文本查看 复制代码 /**
* 方式 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[i]; //// r1+13<100
///
printf("%c%04d\n", o, o+13);
}
return 0;
}
这样得到12个简单的注册码:
[HTML] 纯文本查看 复制代码 !0046
#0048
/0060
10062
30064
50066
70068
C0080
E0082
G0084
I0086
K0088
补充3:
前面是生成“计算简单”的注册码,基本按规则可口算得到。下面考虑一下最短的注册码,因为 CrackMe 会自己补0,我们可以考虑当r2 (或n1)为整百或整千时最简短。修改后的方式4如下:
[C++] 纯文本查看 复制代码 /**
* 方式 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[i];
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[i], r2);
}
}
}
}
return 0;
}
运行后,输出如下:
[HTML] 纯文本查看 复制代码 Q22
M24
542
O42
)44
k44
!46
180
从上面结果还可以看出,180 后面的0可以去掉,只输入 18 即可,这一个应该是最简单的注册码了!!!
|
免费评分
-
查看全部评分
|