160 个 CrackMe 之 091 - Cruehead ,避过陷阱及防护,暴力算出密码
本帖最后由 solly 于 2019-7-1 12:40 编辑160 个 CrackMe 之 091 - Cruehead 在本坛中已有人研究过,不过其陷入 CrackMe 设置的陷阱,没能发现其真正的密码。
这个 CrackMe 也包含了多种反调试,包括 API 代-理,防 bpx 中断,数据加密,防静态分析,代码动态破坏/恢复,设置陷阱(真假两个密码验证,文件内容验证也没有用)。
同时, CrackMe 还要求在其目录中有一个"crueme.dat"的文件,并且其长度、内容无限制,但在假的密码验证内有进行计算等操作。
启动后,如果没有这个“crueme.dat"文件,会报个提示,并会退出,如下图:
我们用文本编辑工具生成一个这样的文件并保存到CrackMe的目录内即可。
再次启动,就可以正常启动了,界面如下:
我们重新用 OD 载入,来到OEP,如下图:
可以看到,其第一条指令就是修改内存的值,其实这个值 0x00401493,一看就是一个地址,实际上,这个地址就是一个 API 代-理函数的入口地址,这个 代-理函数封装了42个API函数,通过 al = ”索引号“ 的方式来调用这些API函数,因此,静态分析就基本没有可能了。
下面就是 CrackMe 开头的代码:
00401000 c> $C705 64234000 93144000 mov dword ptr , 00401493 ;修正 api 代-理 call的函数入口
0040100A .68 D9204000 push 004020D9 ; /pLocaltime = crueme.004020D9
0040100F .E8 F20C0000 call <jmp.&KERNEL32.GetLocalTime> ; \GetLocalTime
00401014 .8D1C9D 00000000 lea ebx, dword ptr
0040101B .D12D 80234000 shr dword ptr , 1 ;== 0x00401224
00401021 .66:A1 E7204000 mov ax, word ptr ;AX == 0x01AC
00401027 .A2 63234000 mov byte ptr , al ;==0xAC,解码密钥
0040102C .E8 4A040000 call 0040147B ;解码 API 代-理函数的索引表
00401031 .83C0 05 add eax, 5 ;eax = 0x0201
00401034 .8D0485 00000000 lea eax, dword ptr
0040103B .83C0 05 add eax, 5 ;eax = 0x0809
0040103E .E8 27040000 call 0040146A ;解码字符串:"Win95 File Monitor"
00401043 .6A 00 push 0
00401045 .68 80000000 push 80
0040104A .6A 03 push 3
0040104C .6A 00 push 0
0040104E .6A 00 push 0
00401050 .68 000000C0 push C0000000
00401055 .68 76214000 push 00402176 ;文件名:CRUEME.DAT
0040105A .B0 1F mov al, 1F ;函数 CreateFileA() 的索引
0040105C .FF15 64234000 call dword ptr ;第1条指令修改了这里的地址,改为 0x00401493,这个调用是一个API入口代-理,AL为索引参数
00401062 .83F8 FF cmp eax, -1 ;eax != -1,表示读取文件成功
00401065 .75 24 jnz short 0040108B
00401067 .6A 30 push 30
00401069 .68 A0214000 push 004021A0 ;ASCII "This is the end - My only friend the end!"
0040106E .68 CA214000 push 004021CA ;ASCII "AAARGH! Where is my CRUEME.DAT file???",CR," I cant go on without my beloved file!"
00401073 .6A 00 push 0
00401075 .B0 1A mov al, 1A ;MessageBoxA()
00401077 .FF15 64234000 call dword ptr ;crueme.00401493
0040107D .FF35 54204000 push dword ptr
00401083 .B0 0A mov al, 0A ;ExitProcess()
00401085 .FF15 64234000 call dword ptr ;crueme.00401493
0040108B >A3 81214000 mov dword ptr , eax ;保存文件句柄
可以看到 call dword ptr 就是这个 API 代-理调用,al 为参数,如 al = 0x1F,表示调用 CreateFileA() ,al=0x1A,表示调用 MessageBoxA(),等等,并且该函数内部通过对 al 参数进行一定的计算,而且参与计算的数据中有一个数据是通过查表取得,结合在一起才能确定 API 的入口跳转位置。这个数据表是加密的,通过上面代码中的 call 0040147B 进行解码,解码后才可以用。同时上面代码中的 call 0040146A 解码了一个字符串: "Win95 File Monitor",后面会用 FindWindow()查找这个标题的窗口,防止文件访问监控。
读取文件内容后,会与一组常量进行运算,不过后面真正计算密码时也没有用到这些数据。
F8 往下走,来到这里,如下图:
中间插入了生成对话框应用的代码,就是由这段代码来显示主对话框界面的。而附近有很多其它代码,其实都是没用的废代码,包括生成标准的 Windows 窗口代码,消息循环代码,WndProc代码,都是没用的。
具体代码如下:
00401138 .6A 00 push 0
0040113A .68 C1114000 push 004011C1 ;DlgProc
0040113F .6A 00 push 0
00401141 .68 E9204000 push 004020E9 ;字符串 (ASCII "DLG_START")
00401146 .FF35 D6234000 push dword ptr ;hInstance
0040114C .B0 12 mov al, 12 ;DialogBoxParamA()
0040114E .FF15 64234000 call dword ptr ;crueme.00401493
00401154 .EB 64 jmp short 004011BA
如下图,上面代码前面的 LoadIconA(), LoadCursorA() 等,就没什么用。
通过前面的代码,我们知道 DlgProc 的地址是 0x004011C1,其主体部分如下图所示:
只处理了 WM_COMMAND 和 WM_CLOSE 消息。具体代码如下:
004011C1 /.C8 000000 enter 0, 0 ;DlgProc
004011C5 |.53 push ebx
004011C6 |.56 push esi
004011C7 |.57 push edi
004011C8 |.817D 0C 11010000 cmp dword ptr , 111 ;WM_COMMAND
004011CF |.0F84 2F010000 je 00401304
004011D5 |.837D 0C 10 cmp dword ptr , 10 ;WM_CLOSE
004011D9 |.0F84 48010000 je 00401327
004011DF |.B8 00000000 mov eax, 0
004011E4 |>5F pop edi
004011E5 |.5E pop esi
004011E6 |.5B pop ebx
004011E7 |.C9 leave
004011E8 |.C2 1000 retn 10
通过跟随 je 00401304 可以找到处理 WM_COMMAND 消息的 Handler。如下图所示:
然后跟随 je 00401213 可以找到按钮”Check It!“的Click事件的 Handler。如下图所示:
具体的代码如下所示:
00401213 |>BA 1D214000 mov edx, 0040211D ;保存密码的缓冲区地址
00401218 |.8D1C85 00000000 lea ebx, dword ptr ;(无用代码)Win9x下eax=ecx=0, WinNT下eax=ecx=DlgProc
0040121F |.83C3 05 add ebx, 5 ;(无用代码)改成 mov ebx, 5
00401222 |.03DA add ebx, edx ;(无用代码)
00401224 |.52 push edx ;lParam, 缓冲区地址
00401225 |.6A 0E push 0E ;wParam, 可取得的最大字符数,14
00401227 |.6A 0D push 0D ;uMsg = WM_GETTEXT
00401229 |.68 EA030000 push 3EA ;ControlID. 密码文本框
0040122E |.FF75 08 push dword ptr ;hInstance, 堆栈 ss:=006F0C10
00401231 |.B0 25 mov al, 25 ;SendDlgItemMessageA(WM_GETTEXT)
00401233 |.FF15 64234000 call dword ptr ;取得注册密码"1234567890", eax为长度
00401239 |.E8 35090000 call 00401B73 ;密码验证,正确返回0,错误返回1
0040123E |.50 push eax ;保存验证结果
0040123F |.BF 01000000 mov edi, 1
00401244 |.E8 FD080000 call 00401B46 ;edi = 1,将0x00401224处的代码替成错误代码
00401249 |.B9 08000000 mov ecx, 8
0040124E |.BF 85214000 mov edi, 00402185 ;文件内容 (ASCII "787878787878")
00401253 |.BE 34214000 mov esi, 00402134 ;esi ==> 常量 FF 77 88 66 99 55 AA 44 BB 33 CC 22 DD 11 EE C0
00401258 |>8A07 /mov al, byte ptr
0040125A |.8A1E |mov bl, byte ptr
0040125C |.32C3 |xor al, bl
0040125E |.D0C0 |rol al, 1
00401260 |.8806 |mov byte ptr , al
00401262 |.47 |inc edi
00401263 |.46 |inc esi
00401264 |.^ E2 F2 \loopd short 00401258
00401266 |.58 pop eax ;密码验证结果
00401267 |.85C0 test eax, eax
00401269 |.0F84 84000000 je 004012F3 ;密码正确则跳转
这一段代码就是真正的密码验证过程,其中算法在 call 00401B73 中,如下图所示:
继续:
其算法代码如下:
00401B73 /$33FF xor edi, edi
00401B75 |.33F6 xor esi, esi
00401B77 |.85C0 test eax, eax ;长度
00401B79 |.0F84 0F010000 je 00401C8E
00401B7F |.A3 72214000 mov dword ptr , eax ;注册密码的长度 len
00401B84 |.BB 1D214000 mov ebx, 0040211D ;ebx ===> (ASCII "1234567890")
00401B89 |>B9 FF000000 /mov ecx, 0FF ;循环次数, count=255
00401B8E |>8B03 |/mov eax, dword ptr ;第1~4字符
00401B90 |.05 87D61200 ||add eax, 12D687
00401B95 |.0343 08 ||add eax, dword ptr ;第9~12字符
00401B98 |.2D 672B0000 ||sub eax, 2B67
00401B9D |.33D2 ||xor edx, edx ;清空积高32位
00401B9F |.F725 72214000 ||mul dword ptr ;乘以长度值,取积低32位
00401BA5 |.8B53 04 ||mov edx, dword ptr ;第5~8字符
00401BA8 |.81C2 EAF48F04 ||add edx, 48FF4EA
00401BAE |.33C2 ||xor eax, edx
00401BB0 |.8B53 08 ||mov edx, dword ptr ;第9~12字符
00401BB3 |.81EA 015CBC00 ||sub edx, 0BC5C01
00401BB9 |.2B13 ||sub edx, dword ptr ;第1~4字符
00401BBB |.81C2 672B0000 ||add edx, 2B67
00401BC1 |.33C2 ||xor eax, edx
00401BC3 |.0D E2599302 ||or eax, 29359E2
00401BC8 |.03F8 ||add edi, eax
00401BCA |.233D 62214000 ||and edi, dword ptr ;== 0x15263748,静态常量
00401BD0 |.8B03 ||mov eax, dword ptr ;第1~4字符
00401BD2 |.2D 87D61200 ||sub eax, 12D687
00401BD7 |.2B43 08 ||sub eax, dword ptr ;第9~12字符
00401BDA |.05 CE560000 ||add eax, 56CE
00401BDF |.33D2 ||xor edx, edx ;清除被除数高32位
00401BE1 |.F735 72214000 ||div dword ptr ;除以长度值
00401BE7 |.8B53 04 ||mov edx, dword ptr ;第5~8字符
00401BEA |.81EA EAF48F04 ||sub edx, 48FF4EA
00401BF0 |.33C2 ||xor eax, edx
00401BF2 |.8B53 08 ||mov edx, dword ptr ;第9~12字符
00401BF5 |.81C2 015CBC00 ||add edx, 0BC5C01
00401BFB |.0313 ||add edx, dword ptr ;第1~4字符
00401BFD |.81EA CE560000 ||sub edx, 56CE
00401C03 |.33C2 ||xor eax, edx
00401C05 |.25 E2599302 ||and eax, 29359E2
00401C0A |.03F0 ||add esi, eax
00401C0C |.0B35 66214000 ||or esi, dword ptr ; = 0x596A7B8C,静态常量
00401C12 |.E2 02 ||loopd short 00401C16
00401C14 |.EB 05 ||jmp short 00401C1B
00401C16 |>^ E9 73FFFFFF |\jmp 00401B8E
00401C1B |>81C7 11090000 |add edi, 911
00401C21 |.81EE 11090000 |sub esi, 911
00401C27 |.FE0D 33214000 |dec byte ptr ;0xFF,静态初始化的变量
00401C2D |.803D 33214000 00 |cmp byte ptr , 0
00401C34 |.^ 0F85 4FFFFFFF \jnz 00401B89
00401C3A |.C605 33214000 FF mov byte ptr , 0FF ;恢复
00401C41 |.BB 1D214000 mov ebx, 0040211D ;ebx ===> (ASCII "1234567890")
00401C46 |.B9 0E000000 mov ecx, 0E ;长度,14
00401C4B |>C603 00 /mov byte ptr , 0 ;循环清除密码数据
00401C4E |.43 |inc ebx
00401C4F |.^ E2 FA \loopd short 00401C4B
00401C51 |.81C7 AFE3FDEF add edi, EFFDE3AF
00401C57 |.75 35 jnz short 00401C8E ;等于0
00401C59 |.81C6 238D94A4 add esi, A4948D23
00401C5F |.75 2D jnz short 00401C8E ;等于0
00401C61 |.BE 2C224000 mov esi, 0040222C ;ASCII "for input"
00401C66 |.83C6 0A add esi, 0A ;esi ===> "Correct password - Good work!"
00401C69 |.BF 6F224000 mov edi, 0040226F ;ASCII "Correct password - Good work!"
00401C6E |.B9 1D000000 mov ecx, 1D
00401C73 |.F3:A4 rep movs byte ptr es:, byte pt>;复制 "Correct password - Good work!"
00401C75 |.68 6F224000 push 0040226F ;addr ===> "Correct password - Good work!"
00401C7A |.68 F2030000 push 3F2
00401C7F |.FF75 08 push dword ptr ;hInstance
00401C82 |.B0 15 mov al, 15 ;SetDlgItemTextA()
00401C84 |.FF15 64234000 call dword ptr ;crueme.00401493
00401C8A |.33C0 xor eax, eax
00401C8C |.EB 05 jmp short 00401C93
00401C8E |>B8 01000000 mov eax, 1
00401C93 \>C3 retn
可见,在上面函数包括了验证算法,并且验证通过,则会修改界面静态文本标签(控件ID为 0x03F2)的内容为"Correct password - Good work!",不会象另一个假的验证过程,会在一个无效的控件ID(0x03F3)上显示,正确标签控件上还是会显示错误提示。
这个算法并不复杂,但对数据计算是破坏性的,并且循环计算256x256次,好象无法逆推,只好暴破。暴破条件就是最后 edi 和 esi 都等于0时,则输入的密码有效。如下图所示位置,就是所需的条件:
先回到界面,输入假码:
进行验证处理,可了解其算法,并写出暴力计算密码代码。通过35分钟计算,得到一组4字符的密码:
”*A*
其中引号是半角的。这里说明一下,虽然算法中用到了 12 个字符密码,但并没有要求一定要 12 个字符,只要长度大于 0 即可,其它位置可以置 '\0'代替,所以暴力计算密码时可以从1 ~ 12 逐个增加密码长度,控制一定的计算量,不然,一开始就测试 12 位的密码,那只有请 "神威·太湖之光" 出马了。
输入这个密码,如下图:
再次按“Check it!”,可得到正确的提示“Correct password - Good work!”。
下面看看其陷阱,call 00401B73 中如果验证错误,则没有显示提示直接返回,来到下面代码处,如下图:
先对API 函数 GetDlgItemTextA 进行检查,看看有没有 BPX GetDlgItemTextA 进行中断跟踪。有则提示并退出。没有则来到下面的代码:
可以看到 call 004013B1 又对密码进行了一番操作,但是返回后,并没有正确的进行提示。如下代码:
004012BE |>FFD7 call edi ;call GetDlgItemTextA(),读取密码
004012C0 |.E8 EC000000 call 004013B1 ;对密码进行操作
004012C5 |.85C0 test eax, eax
004012C7 |.74 15 je short 004012DE
004012C9 |.68 36224000 push 00402236 ;addr ===> (ASCII "Correct password - Good work!")
004012CE |.68 F3030000 push 3F3 ;ControlID,这个控件ID是错误的
004012D3 |.FF75 08 push dword ptr
004012D6 |.B0 15 mov al, 15 ;SetDlgItemTextA()
004012D8 |.FF15 64234000 call dword ptr ;crueme.00401493,提示完成但并没有跳转,而是又直接显示失败的提示了
004012DE |>68 54224000 push 00402254 ;addr ===>(ASCII "False password - Try again")
004012E3 |.68 F2030000 push 3F2 ;提示框 ControlID
004012E8 |.FF75 08 push dword ptr ;hInstance
004012EB |.B0 15 mov al, 15 ;SetDlgItemTextA()
004012ED |.FF15 64234000 call dword ptr ;crueme.00401493
004012F3 |>33FF xor edi, edi ;edi = 0 恢复代码
004012F5 |.E8 4C080000 call 00401B46 ;恢复代码
004012FA |.B8 01000000 mov eax, 1
004012FF |.^ E9 E0FEFFFF jmp 004011E4
不管call 004013B1 返回什么值,最终我们能看到的提示都是“False password - Try again”。所以这里就是个陷阱,并不是真正的密码验证的地方。
另一个需要说明一下的是,在真正的算法函数的前面,CrackMe 是通过 SendDlgItemMessageA(WM_GETTEXT)来取得的密码,并不是常用的 GetDlgItemTextA()函数。并且通过API代-理封装,不注意就很难断下其真正取密码和验证的位置。因为后面假验函数是用的 GetDlgItemTextA()取的密码,并且没有进行API代-理封装,是明文调用的,更具迷惑性,很容易上当掉入其陷阱。
下面交出暴破代码,使用 Dev-C++调试通过(没有逆推,如果谁有能力,看看是否可以逆推或优化,我这个在 i5 4670K 上计算了2047秒):
#include <iostream>
#include <string.h>
int checkPassword(char * password);
int getPassword(int len);
int genPassword(int index, int len, char * testPassword);
int main(int argc, char** argv) {
//char password[] = "1234567890";
//char password[] = "\"*A*";
//checkPassword(password);
/// 暴破密码
int len = 4;
int b = getPassword(len);
return 0;
}
int checkPassword(char * password) {
union {
char pwd;
unsigned long key;
} passwd;
/// 初始化缓冲区
passwd.key = 0;
passwd.key = 0;
passwd.key = 0;
passwd.key = 0;
long n = strlen(password);
if(n<=0) {
return -1;
}
/// 长度处理,最长12个字符
if (n>12) {
n = 12;
password = '\0'; /// 截断
}
//// 存入计算缓冲区
strcpy(passwd.pwd, password);
long a, d;
long check1 = 0;/// edi
long check2 = 0;/// esi
for(long i=255; i>0; i--) {
for(long j=255; j>0; j--) {
a = passwd.key;
a += 0x0012D687;
a += passwd.key;
a -= 0x00002B67;
a *= n;
d = passwd.key;
d += 0x048FF4EA;
a ^= d;// xor
d = passwd.key;
d -= 0x00BC5C01;
d -= passwd.key;
d += 0x00002B67;
a ^= d;
a |= 0x029359E2;
check1 += a;
check1 &= 0x15263748;
a = passwd.key;
a -= 0x0012D687;
a -= passwd.key;
a += 0x000056CE;
//a &= 0xFFFFFFFF;
a /= n;
d = passwd.key;
d -= 0x048FF4EA;
a ^= d;
d = passwd.key;
d += 0x00BC5C01;
d += passwd.key;
d -= 0x000056CE;
a ^= d;
a &= 0x029359E2;
check2 += a;
check2 |= 0x596A7B8C;
}
check1 += 0x00000911;
check2 -= 0x00000911;
}
//printf("check: 0x%08X - 0x%08X\n", check1, check2);
check1 += 0xEFFDE3AF;
check2 += 0xA4948D23;
//// check1 = 0x05201300,check2 = 0x000EFFEE
//printf("check: 0x%08X - 0x%08X\n", check1, check2);
if(check1 | check2) {
return 1; //// ERROR
}
return 0; ///OK
}
int getPassword(int len) {
char password;
for(int i=0; i<16;i++) {
password = '\0';
}
int b = genPassword(0, len, password);
if(b) {
printf("Password not found.\n");
} else {
printf("Found Password: %s\n", password);
}
return b;
}
int genPassword(int index, int len, char * testPassword) {
if(index == len) {
return checkPassword(testPassword);
}
for(int i=0x20; i<0x7F; i++) { // printable characters,有
//for(int i=0x30; i<=0x39; i++) { // '0'~'9',无
//for(int i=0x41; i<=0x5A; i++) { // 'A'~'Z',无
//for(int i=0x61; i<=0x7A; i++) {// 'a'~'z',无
testPassword = (char)i;
int b = genPassword(index+1, len, testPassword);
if(b==0) {
return 0;
}
}
return 1;
}
计算结果:
Found Password: "*A*
--------------------------------
Process exited after 2047 seconds with return value 0
分析完毕!!!
更新一下,算码程序用 VC6 编译,只要120秒左右即可算出4位密码。
2019-07-01 整理调整了一下算法,Dev-C++下 465 秒,VC6下70秒,都是 Release 编译,VC 还是牛。
#include <iostream>
#include <string.h>
int checkPassword(char * password);
int getPassword(int len);
int genPassword(int index, int len, char * testPassword);
int main(int argc, char** argv) {
//char password[] = "1234567890";
//char password[] = "\"*A*";
//int a = checkPassword(password);
//if(a) {
// printf("test failure!\n");
//} else {
// printf("test seccuss!\n");
//}
/// 暴破密码
int len = 4;
int b = getPassword(len);
return 0;
}
int checkPassword(char * password) {
union {
char pwd;
unsigned long key;
} passwd;
/// 初始化缓冲区
passwd.key = 0;
passwd.key = 0;
passwd.key = 0;
passwd.key = 0;
long n = strlen(password);
if(n<=0) {
return -1;
}
/// 长度处理,最长12个字符
if (n>12) {
n = 12;
password = '\0'; /// 截断
}
//// 存入计算缓冲区
strcpy(passwd.pwd, password);
//long a, d;
long check1 = 0;/// edi
long check2 = 0;/// esi
long A1, D1;
long A2, D2;
////=================================================================
A1 = (passwd.key + passwd.key + 0x0012AB20) * n;
D1 = passwd.key + 0x048FF4EA;
A1 ^= D1;// xor
////-----------------------------------------------------------------
D1 = passwd.key - passwd.key - 0x00BC309A;
A1 ^= D1;
A1 |= 0x029359E2;
////=================================================================
A2 = (passwd.key - passwd.key - 0x00127FB9) / n;
D2 = passwd.key - 0x048FF4EA;
A2 ^= D2;// Xor
///------------------------------------------------------------------
D2 = passwd.key + passwd.key + 0x00BC0533;
A2 ^= D2;
A2 &= 0x029359E2;
///==================================================================
for(long i=255; i>0; i--) {
for(long j=255; j>0; j--) {
check1 += A1; //a;
check1 &= 0x15263748;
check2 += A2; //a;
check2 |= 0x596A7B8C;
}
check1 += 0x00000911;
check2 -= 0x00000911;
}
//printf("check: 0x%08X - 0x%08X\n", check1, check2);
check1 += 0xEFFDE3AF;
check2 += 0xA4948D23;
//// check1 = 0x05201300,check2 = 0x000EFFEE
//printf("check: 0x%08X - 0x%08X\n", check1, check2);
if(check1 | check2) {
return 1; //// ERROR
}
return 0; //// OK
}
int getPassword(int len) {
char password;
for(int i=0; i<16;i++) {
password = '\0';
}
int b = genPassword(0, len, password);
if(b) {
printf("Password not found.\n");
} else {
printf("Found Password: %s\n", password);
}
return b;
}
int genPassword(int index, int len, char * testPassword) {
if(index == len) {
return checkPassword(testPassword);
}
for(int i=0x20; i<0x7F; i++) { // printable characters,有
//for(int i=0x30; i<=0x39; i++) { // '0'~'9',无
//for(int i=0x41; i<=0x5A; i++) { // 'A'~'Z',无
//for(int i=0x61; i<=0x7A; i++) {// 'a'~'z',无
testPassword = (char)i;
int b = genPassword(index+1, len, testPassword);
if(b==0) {
return 0;
}
}
return 1;
}
膜拜大神呀 本帖最后由 solly 于 2019-7-30 16:32 编辑
darksmile 发表于 2019-7-30 12:19
楼主好厉害!如果是8位密码,暴力破解需要算几天?!
8位密码,用可见字符尝试,次数最多为94的9次方减1,等于 572994802228616704 - 1 次。每一次尝试,光循环的计算有65000多次,加上前后的计算则更多。几天只怕解决不了滴。。。 给力!膜拜大牛 好复杂,看的都晕。 技术贴,完全看不懂,有空再对照看看 这个教程确实很溜 坚持学习,谢谢!!! 给大佬,磕头,牛
一个软件被植入了,注册码
需要付费,给作者钱,所以求大神看看
https://www.lanzouj.com/i4sgw1g 吾爱破解论坛最帅的人是谁? 大佬们 小弟在此膜拜你们了;。、