本帖最后由 MistHill 于 2013-12-26 17:59 编辑
还是说一下吧!
这个CM暴露出的问题,是每个写程序的人都可能碰到、而且经常犯的错误,吸取经验和教训总是有益的。从某种程度上讲,我觉得比分析和解答CM自身更有意义!
前面提到Name里的中文字符会造成目标崩溃,仔细看下来后发现,原因不是中文引起的,西文同样可能使目标崩溃。
在程序对话框的编辑框里随便输入点儿字符串会“搞死”程序,这无论如何是说不过去的,看来程序有很严重的Bug。
这次问题出在Key和Name的第二次变换那两个循环:
[Plain Text] 纯文本查看 复制代码 0040161F |. 8B7424 20 MOV ESI, [ESP+0x20] pKey(After Variation #1)
00401623 |. 83C9 FF OR ECX, 0xFFFFFFFF
00401626 |. 8BFE MOV EDI, ESI pKey
00401628 |. 83C4 08 ADD ESP, 0x8
0040162B |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
0040162D |. 8B5424 1C MOV EDX, [ESP+0x1C] pName(After Variation #1)
00401631 |. F7D1 NOT ECX
00401633 |. 49 DEC ECX ECX=strlen(pKey)
00401634 |. 8BFA MOV EDI, EDX pName
00401636 |. 8BD9 MOV EBX, ECX EBX=strlen(pKey)
00401638 |. 83C9 FF OR ECX, 0xFFFFFFFF
0040163B |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
0040163D |. F7D1 NOT ECX
0040163F |. 49 DEC ECX ECX=strlen(pName)
00401640 |. 894C24 18 MOV [ESP+0x18], ECX ECX->[ESP+0x18], save to local variable
00401644 |> 8A0C30 / MOV CL, [EAX+ESI] ESI=pKey, EAX=0 Init.
00401647 |. 300C28 | XOR [EAX+EBP], CL EBP=pPreset, Decoding #1 with Key
0040164A |. 83F8 0A | CMP EAX, 0xA
0040164D |. 75 13 | JNZ SHORT 00401662
0040164F |. 8D7B F6 | LEA EDI, [EBX-0xA] EDI=strlen(pKey)-0A, EDI: unsigned int
00401652 |. 3BF8 | CMP EDI, EAX
00401654 |. 72 0C | JB SHORT 00401662 <- *** BUG!
00401656 |> 8A0C06 |/MOV CL, [ESI+EAX] <- Memory access violation if strlen(pKey) < 0A
00401659 |. 304C06 01 ||XOR [ESI+EAX+0x1], CL <- write
0040165D |. 40 ||INC EAX
0040165E |. 3BC7 ||CMP EAX, EDI <- *** Never stop!
00401660 |. 76 F4 |\JBE SHORT 00401656
00401662 |> 40 | INC EAX
00401663 |. 83F8 0A | CMP EAX, 0xA
00401666 |. 76 DC \ JBE SHORT 00401644
00401668 |. 8B4C24 18 MOV ECX, [ESP+0x18] ECX<-strlen(pName)
0040166C |. B8 0A000000 MOV EAX, 0xA EAX=0A Init.
00401671 |> 8A1C10 / MOV BL, [EAX+EDX] EDX=pName
00401674 |. 301C28 | XOR [EAX+EBP], BL EBP=pPreset, Decoding #2 with Name
00401677 |. 83F8 14 | CMP EAX, 0x14
0040167A |. 75 13 | JNZ SHORT 0040168F
0040167C |. 8D79 EC | LEA EDI, [ECX-0x14] EDI=strlen(pName)-14, EDI: unsigned int
0040167F |. 3BF8 | CMP EDI, EAX
00401681 |. 72 0C | JB SHORT 0040168F <- *** BUG!
00401683 |> 8A1C02 |/MOV BL, [EDX+EAX] <- Memory access violation if strlen(pName) < 14
00401686 |. 205C02 01 ||AND [EDX+EAX+0x1], BL <- write
0040168A |. 40 ||INC EAX
0040168B |. 3BC7 ||CMP EAX, EDI <- *** Never stop!
0040168D |. 76 F4 |\JBE SHORT 00401683
0040168F |> 40 | INC EAX
00401690 |. 83F8 14 | CMP EAX, 0x14
00401693 |. 76 DC \ JBE SHORT 00401671
00401695 |. 8BCE MOV ECX, ESI 在00401644和00401671处的外层循环是分别用Key和Name解码预设值得到答案;现在我们重点关注00401656和00401683两处内层循环。
搞这么个Key和Name的第二次变换无非是想增加一点解Key的难度。实际上也没啥作用,我在Keygen的脚本中写有一个针对Key第二次变换的逆过程,后来发现调用这个子过程与否对获得正确结果没太大影响,为节省运行时间就将那个调用注释掉了。
进入这两个循环的条件是:strlen(pKey)-0A >= 0和strlen(pName)-14 >= 0;换言之,即strlen(pKey) >= 0A和strlen(pName) >= 14。
“换言之”是我的第一反应,看起来strlen(pKey)=01~09,strlen(pName)=01~13都不会进入循环。可事实却完全不是我们想象的那样。
比如strlen(pKey)=09或strlen(pName)=13时,09-0A或13-14都等于-1,在32位系统里表示为0xFFFFFFFF。进入循环的条件就变成:FFFFFFFF与0A或14比较,紧接着的那条JB指令(00401654和00401681)就出问题了,这是一定要进循环的!
而结束循环的条件是:EAX++ > FFFFFFFF(0040165E和0040168B)。将永远不可能退出循环滴!于是它会读写4G内存空间的每一个地址,直到出现“内存访问冲突”,操作系统忍无可忍、不得不终止这个失控的进程。
由于Key和Name的第一次变换算法为两相邻字符进行异或,只要两个相邻字符相同,就得到一个字符串结尾NULL。在中文字符串的情况下不易察觉:比如“吾爱破解”中的“破”字,其两字节内码为0xC60 xC6,同样得到一个NULL字符。
下面这张表可用于对比测试:
[Plain Text] 纯文本查看 复制代码 0123456789ABCDEF0123456789
<--------> Key 紧邻字符相同崩溃区
<-----------------> Name 紧邻字符相同崩溃区
Name: White_CrackMe_JUST_For_Fun 标准输入
Key: Do_You_Like_It_Or_NOT__:)_
会造成崩溃的Name:
White_CrackMe_JUST__or_Fun strlen(pName)=13
White_CrackMe_JUSTFFor_Fun strlen(pName)=13
WWite_CrackMe_JUST_For_Fun strlen(pName)=01
会造成崩溃的Key:
Do_You_Liie_It_Or_NOT__:)_ strlen(pKey)=09
Do_You_Lkke_It_Or_NOT__:)_ strlen(pKey)=09
DD_You_Like_It_Or_NOT__:)_ strlen(pKey)=01
oo_You_Like_It_Or_NOT__:)_ strlen(pKey)=01 显然,写程序的人用错了数据类型声明,没有达到预期的设想。编译器只是忠实地将源代码变成机器指令。
先将目标这两处:
[Plain Text] 纯文本查看 复制代码 00401654 72 0C JB SHORT 00401662
00401681 72 0C JB SHORT 0040168F 改为:
[Plain Text] 纯文本查看 复制代码 00401654 7C 0C JL SHORT 00401662
00401681 7C 0C JL SHORT 0040168F 再试试,就不会出问题了。不要忽视这一个字节的差异:JB用于无符号数,而JL用于有符号数!
模仿题目写了一个命令行的东西,数据声明部分如下:
[C] 纯文本查看 复制代码 void __cdecl sub_4015E0(char *pKey, char *pName, int DisplayMode)
{
char *pPreset;
unsigned int i;
/* Correct data type declaration: */
int nKeyLength;
int nNameLength;
/* Wrong data type declaration: */
// unsigned int nKeyLength;
// unsigned int nNameLength;
... 数据声明分别为int和unsigned int时,生成的代码对比如下:
[Plain Text] 纯文本查看 复制代码 正确(int): # 错误(unsigned int):
00401523 8A0C30 / MOV CL, [EAX+ESI] # 00401523 8A0C30 / MOV CL, [EAX+ESI]
00401526 300C28 | XOR [EAX+EBP], CL # 00401526 300C28 | XOR [EAX+EBP], CL
00401529 83F8 0A | CMP EAX, 0xA # 00401529 83F8 0A | CMP EAX, 0xA
0040152C 75 16 | JNZ SHORT 00401544 # 0040152C 75 13 | JNZ SHORT 00401541
0040152E 8D4B F6 | LEA ECX, [EBX-0xA] # 0040152E 8D7B F6 | LEA EDI, [EBX-0xA]
00401531 3BC8 | CMP ECX, EAX # 00401531 3BF8 | CMP EDI, EAX
00401533 7C 0F | JL SHORT 00401544 # 00401533 72 0C | JB SHORT 00401541
00401535 8D7B F6 | LEA EDI, [EBX-0xA] # 00401535 8A0C06 |/MOV CL, [ESI+EAX]
00401538 8A0C06 |/MOV CL, [ESI+EAX] # 00401538 304C06 01 ||XOR [ESI+EAX+0x1], CL
0040153B 304C06 01 ||XOR [ESI+EAX+0x1], CL # 0040153C 40 ||INC EAX
0040153F 40 ||INC EAX # 0040153D 3BC7 ||CMP EAX, EDI
00401540 3BC7 ||CMP EAX, EDI # 0040153F 76 F4 |\JBE SHORT 00401535
00401542 76 F4 |\JBE SHORT 00401538 # 00401541 40 | INC EAX
00401544 40 | INC EAX # 00401542 83F8 0A | CMP EAX, 0xA
00401545 83F8 0A | CMP EAX, 0xA # 00401545 76 DC \ JBE SHORT 00401523
00401548 76 D9 \ JBE SHORT 00401523 # 00401547 8B4C24 14 MOV ECX, [ESP+0x14]
0040154A 8B4C24 14 MOV ECX, [ESP+0x14] # 0040154B B8 0A000000 MOV EAX, 0xA
0040154E B8 0A000000 MOV EAX, 0xA # 00401550 8A1C10 / MOV BL, [EAX+EDX]
00401553 8A1C10 / MOV BL, [EAX+EDX] # 00401553 301C28 | XOR [EAX+EBP], BL
00401556 301C28 | XOR [EAX+EBP], BL # 00401556 83F8 14 | CMP EAX, 0x14
00401559 83F8 14 | CMP EAX, 0x14 # 00401559 75 13 | JNZ SHORT 0040156E
0040155C 75 16 | JNZ SHORT 00401574 # 0040155B 8D79 EC | LEA EDI, [ECX-0x14]
0040155E 8D79 EC | LEA EDI, [ECX-0x14] # 0040155E 3BF8 | CMP EDI, EAX
00401561 3BF8 | CMP EDI, EAX # 00401560 72 0C | JB SHORT 0040156E
00401563 7C 0F | JL SHORT 00401574 # 00401562 8A1C02 |/MOV BL, [EDX+EAX]
00401565 8D79 EC | LEA EDI, [ECX-0x14] # 00401565 205C02 01 ||AND [EDX+EAX+0x1], BL
00401568 8A1C02 |/MOV BL, [EDX+EAX] # 00401569 40 ||INC EAX
0040156B 205C02 01 ||AND [EDX+EAX+0x1], BL # 0040156A 3BC7 ||CMP EAX, EDI
0040156F 40 ||INC EAX # 0040156C 76 F4 |\JBE SHORT 00401562
00401570 3BC7 ||CMP EAX, EDI # 0040156E 40 | INC EAX
00401572 76 F4 |\JBE SHORT 00401568 # 0040156F 83F8 14 | CMP EAX, 0x14
00401574 40 | INC EAX # 00401572 76 DC \ JBE SHORT 00401550
00401575 83F8 14 | CMP EAX, 0x14
00401578 76 D9 \ JBE SHORT 00401553 从对比可以看到:1) JL和JB的差异;2) 右边的错误代码与目标代码几乎完全一致!
下面说说附件里的东西。
[Plain Text] 纯文本查看 复制代码 Date Time Size Name
------------------- ------------ ----------------
2013-12-25 18:23:00 5968 WDC Verifier.c
2013-12-25 22:41:47 16384 WDC Verifier.exe 在命令提示符下不带参数运行会显示用法:
[Plain Text] 纯文本查看 复制代码 A Verification Program for "White Decrypt & CrackMe"
By MistHill, 25/12/2013
SYNTAX:
"WDC Verifier.exe" "Name" "Key"
or
"WDC Verifier.exe" "Log file created by my ODbgScript T228419.Keygen.osc"
You can get the script at:
[url=http://www.52pojie.cn/thread-230338-1-1.html]http://www.52pojie.cn/thread-230338-1-1.html[/url] 第一种用法类似题目的命令行版本,即输入Name和Key参数会显示结果,自己检查与标准答案是否匹配。
运行实例:
[Plain Text] 纯文本查看 复制代码 "WDC Verifier.exe" "White_CrackMe_JUST_For_Fun" "Do_You_Like吾爱破解_V__^As"
INFO:
Something Is always Changing !
"WDC Verifier.exe" "White_CrackMe_JUST_For_Fun" "Do_You_Like吾爱破解_V__\O]"
INFO:
Something Is always Changing !
"WDC Verifier.exe" "Weare_ChildBjPEZ\[PII@@AR@" "Do_You_LikeMMwb}{|wn%,,->,"
INFO:
Something Is always changing !
"WDC Verifier.exe" "Weare吾爱__yQk~ag`krR[[ZI[" "Do_You_LikmWEa{|@addenol{h"
INFO:
Something Is always Changing ! 因为已经修正了已知Bug,所以不会崩溃,也能正确处理字符'%'。
但受命令提示符传递给程序main函数的命令行参数的影响,通常Name和Key字符串必须用西文字符'"'封闭。在Name或Key存在'"'字符的情况下,见以下示例:
[Plain Text] 纯文本查看 复制代码 Name: Weare_ChildBjPEZ\[PII@@AR@
Key1: Do_You_LikeMCQD[]ZQH*##"1#
Key2: Do_You_LikeMCQD[]ZQH+""#0"
"WDC Verifier.exe" "Weare_ChildBjPEZ\[PII@@AR@" "Do_You_LikeMCQD[]ZQH*##""1#"
INFO:
Something Is always changing !
"WDC Verifier.exe" "Weare_ChildBjPEZ\[PII@@AR@" Do_You_LikeMCQD[]ZQH+\"\"#0\"
INFO:
Something Is always changing ! 即在只有一个'"'的情况下,多加一个'"'字符;在有多个'"'的情况下,不使用'"'进行封闭,其中的'"'字符前加脱字符'\'。
上面例子中,有几个答案是不正确的,注意'c'和'C'的区别。这是最早调试脚本时的记录,现在一时找不到适合的样本。
总之,这时使用命令行是很麻烦的。对这个感兴趣的可参阅:Everyone quotes command line arguments the wrong way[http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx]。
还是把Name和Key放到文件比较方便,第二种用法:
只需一个参数,为Key的记录文件,其格式用Keygen脚本生成一个就清楚了。
文件里包含一个Name和多个Key,进行批量自动验证。合格的Key后面会显示'V'字符,而无效的Key会以'X'字符表示。
|