Nisy's Second CrackMe 简单分析
本帖最后由 F8LEFT 于 2014-11-24 11:15 编辑2014PYG10周年庆典,Nisy校长发了个异常强大的cm,我是菜鸟一个,不过在机缘巧合之下还是弄出来了,发一下分析,来加深印象。这个cm的想法还是挺不错的。
简单的东西就不多提了。我直接用一组key来进行讲解:
Name:F8LEFT
Pass : NS-oZQe7Eru-8EE8
切入算法流程可以直接使用GetWindowTextW断点,直接在命令行进行bpx GetWindowTextW就可以同时在两个验证段断下了。然后单步跟踪。
进入第一步验证,除去基础长度判断后,会来到下面一个小buff
0040E43E|.50 PUSH EAX ;Push Pass
0040E43F|.8D85 D0FBFFFF LEA EAX,
0040E445|.50 PUSH EAX ;Push Name
0040E446|.8D45 FC LEA EAX,
0040E449|.8038 00 CMP BYTE PTR DS:, 0x0 ; = 0
0040E44C|.75 05 JNZ SHORT NsCrackM.0040E453
0040E44E|.8B00 MOV EAX, DWORD PTR DS: ;Err1
0040E450|.C600 CC MOV BYTE PTR DS:, 0xCC ;Err2
0040E453|>FF10 CALL NEAR DWORD PTR DS: ;--> Func (In SEH)
这里入栈了 Pass 与 Name ,没啥的,关键是下面:
CMP BYTE PTR DS:, 0x0 ; = 0
JNZ SHORT NsCrackM.0040E453
MOV EAX, DWORD PTR DS: ;Err1
MOV BYTE PTR DS:, 0xCC ;Err2
---------------------------->--------------------------->--------------------------->
cmp ,0 => jnz XXXXXXXX,这里保证了里面的值是0,也即 BYTE: = 0
MOV EAX, DWORD :, 这里取了,于是,EAX = 0
那么问题就来了,下一句
MOV BYTE PTR DS:, 0xCC , 此时EAX = 0,这就相当于 MOV DS:, 0xCC,显然是无法进行的。因此,程序到了这里,便会产生异常。
既然有异常,那么就自然可以想到异常处理函数,直接查看OD的SEH链窗体,可以看到以下几个异常处理函数:
只要一个函数是属于程序段的,这个便是处理函数了,可以在这个函数上面下个断点,接着F9,便会停在004127C0处,此时便可以进行下一步的跟踪了(不过,暂时用不上)。这时候可以再一次F9,因为前面下了GetWindowTextW断点,此时会停留在第二段验证处。0040DAB0
跟踪下去就是第一段比较了:
0040DB7A .50 PUSH EAX ;Push Name
0040DB7B .8D4D E4 LEA ECX, DWORD PTR SS:
0040DB7E .E8 BD6FFFFF CALL <NsCrackM.N_Strcpy>
0040DB83 .8D8D D0FDFFFF LEA ECX, DWORD PTR SS:
0040DB89 .51 PUSH ECX ;Push Pass
0040DB8A .8D8D C8FDFFFF LEA ECX, DWORD PTR SS:
0040DB90 .E8 AB6FFFFF CALL <NsCrackM.N_Strcpy>
0040DB95 .C745 FC 01000>MOV DWORD PTR SS:, 0x1
0040DB9C .BA 4E000000 MOV EDX, 0x4E ;N
0040DBA1 .66:8995 B0FBF>MOV WORD PTR SS:, DX
0040DBA8 .B8 53000000 MOV EAX, 0x53
0040DBAD .66:8985 B2FBF>MOV WORD PTR SS:, AX ;S
0040DBB4 .33C9 XOR ECX, ECX
0040DBB6 .66:898D B4FBF>MOV WORD PTR SS:, CX ;创建字符串 NS
0040DBBD .8D8D C8FDFFFF LEA ECX, DWORD PTR SS: ;Pass
0040DBC3 .E8 F893FFFF CALL <NsCrackM.N_Strlen> ;求密码长度
0040DBC8 .83F8 03 CMP EAX, 0x3 ;长度>=3
0040DBCB .7C 39 JL SHORT NsCrackM.0040DC06
0040DBCD .8D95 B0FBFFFF LEA EDX, DWORD PTR SS:
0040DBD3 .52 PUSH EDX
0040DBD4 .6A 02 PUSH 0x2 ;长度为2
0040DBD6 .8D85 94FBFFFF LEA EAX, DWORD PTR SS:
0040DBDC .50 PUSH EAX
0040DBDD .8D8D C8FDFFFF LEA ECX, DWORD PTR SS:
0040DBE3 .E8 380A0000 CALL <NsCrackM.N_StrSub> ;取Pass的子窜,这里是取头两个字符
0040DBE8 .838D 80FBFFFF>OR DWORD PTR SS:, 0x1
0040DBEF .8BC8 MOV ECX, EAX
0040DBF1 .E8 6A090000 CALL <NsCrackM.N_Strcmp?> ;与前面创建的字符串NS比较
这里可以知道密码的前2个字符为NS。再继续跟踪。
0040DC44 .8B0D 90664200 MOV ECX, DWORD PTR DS: ; = 0
0040DC4A .C701 01000000 MOV DWORD PTR DS:, 0x1 ;SEH => 2
喜闻乐见+丧心病狂,这里又一次触发了SEH异常,函数函数哪一个,下好断点,然后F9吧。
断在异常处理函数后F8跟踪,注意大致的浏览一下每一个字call,此时可以定位到一个非常可疑的Call中:
00412927|.E8 A5030000 CALL NsCrackM.00412CD1 ;In (函数分派) ECX = 跳转目标地址
这个Call的里面是这样的。:
00412CD1 [ DISCUZ_CODE_2321 ]nbsp; 8BEA MOV EBP, EDX
00412CD3 .8BF1 MOV ESI, ECX ;复制跳转地址
00412CD5 .8BC1 MOV EAX, ECX ;取跳转地址
00412CD7 .6A 01 PUSH 0x1
00412CD9 .E8 B74A0000 CALL NsCrackM.00417795
00412CDE .33C0 XOR EAX, EAX ;NsCrackM.0040DC62
00412CE0 .33DB XOR EBX, EBX
00412CE2 .33C9 XOR ECX, ECX
00412CE4 .33D2 XOR EDX, EDX
00412CE6 .33FF XOR EDI, EDI
00412CE8 .FFE6 JMP NEAR ESI ;jmp Func
清空了一下寄存器的内容,紧接着就直接跳走了,这个显然是人为用汇编代码写上去的,非常的可疑。实际上这里是非常重要的函数分配段,后面还有一次验证会用到这里,下好断点了,然后jmp ESI,跳到相应的处理段中。来到第3段验证:0040DC62
0040DC62 .8B65 E8 MOV ESP, DWORD PTR SS: ;3
0040DC65 .8D95 D0FDFFFF LEA EDX, DWORD PTR SS:
0040DC6B .52 PUSH EDX
0040DC6C .8D8D ACFBFFFF LEA ECX, DWORD PTR SS:
0040DC72 .E8 C96CFFFF CALL <NsCrackM.N_Strcpy> ;复制Pass
0040DC77 .8D8D A8FBFFFF LEA ECX, DWORD PTR SS:
0040DC7D .E8 CE92FFFF CALL NsCrackM.00406F50
0040DC82 .8D8D ACFBFFFF LEA ECX, DWORD PTR SS: ;Pass
0040DC88 .E8 3393FFFF CALL <NsCrackM.N_Strlen> ;PassLen
0040DC8D .83F8 03 CMP EAX, 0x3
0040DC90 .0F8E 2D010000 JLE NsCrackM.0040DDC3
0040DC96 .6A 2D PUSH 0x2D ;0x2D = "-"
0040DC98 .8D8D ACFBFFFF LEA ECX, DWORD PTR SS: ;Pass
0040DC9E .E8 6D0A0000 CALL <NsCrackM.N_StrIndex> ;取-在Pass中的第一个位置
0040DCA3 .8985 A0FBFFFF MOV DWORD PTR SS:, EAX
0040DCA9 .8B85 A0FBFFFF MOV EAX, DWORD PTR SS:
0040DCAF .83C0 01 ADD EAX, 0x1
0040DCB2 .50 PUSH EAX
0040DCB3 .6A 2D PUSH 0x2D
0040DCB5 .8D8D ACFBFFFF LEA ECX, DWORD PTR SS:
0040DCBB .E8 C00A0000 CALL <NsCrackM.IndexOfStr> ;取第二个-在Pass的位置
0040DCC0 .8985 A4FBFFFF MOV DWORD PTR SS:, EAX
0040DCC6 .8B8D A4FBFFFF MOV ECX, DWORD PTR SS: ;Len
0040DCCC .2B8D A0FBFFFF SUB ECX, DWORD PTR SS: ;-2
0040DCD2 .83E9 01 SUB ECX, 0x1 ;-1(-3)
0040DCD5 .51 PUSH ECX ;第二个-位置
0040DCD6 .8B95 A0FBFFFF MOV EDX, DWORD PTR SS:
0040DCDC .83C2 01 ADD EDX, 0x1
0040DCDF .52 PUSH EDX ;第一个-位置
0040DCE0 .8D85 90FBFFFF LEA EAX, DWORD PTR SS:
0040DCE6 .50 PUSH EAX
0040DCE7 .8D8D ACFBFFFF LEA ECX, DWORD PTR SS:
0040DCED .E8 8E080000 CALL <NsCrackM.N_SubStr> ;Pass取两个-中间的字符串
0040DCF2 .8B08 MOV ECX, DWORD PTR DS:
0040DCF4 .51 PUSH ECX
0040DCF5 .8B95 A4FBFFFF MOV EDX, DWORD PTR SS:
0040DCFB .52 PUSH EDX
0040DCFC .8B85 A0FBFFFF MOV EAX, DWORD PTR SS:
0040DD02 .50 PUSH EAX
0040DD03 .68 9C0C4200 PUSH NsCrackM.00420C9C ;%d %d %s
0040DD08 .8D8D A8FBFFFF LEA ECX, DWORD PTR SS:
0040DD0E .51 PUSH ECX
0040DD0F .E8 DC0A0000 CALL NsCrackM.0040E7F0 ;(格式化字符串)第一个-位置 第二个-位置 两个-中间的字符串
0040DD14 .83C4 14 ADD ESP, 0x14
0040DD17 .8D8D 90FBFFFF LEA ECX, DWORD PTR SS:
0040DD1D .E8 7E70FFFF CALL <NsCrackM.ToString>
0040DD22 .8B95 A4FBFFFF MOV EDX, DWORD PTR SS:
0040DD28 .3B95 A0FBFFFF CMP EDX, DWORD PTR SS:
0040DD2E 0F8E 8F000000 JLE NsCrackM.0040DDC3
0040DD34 .8B85 A4FBFFFF MOV EAX, DWORD PTR SS: ;同上
0040DD3A .2B85 A0FBFFFF SUB EAX, DWORD PTR SS:
0040DD40 .83E8 01 SUB EAX, 0x1
0040DD43 .50 PUSH EAX
0040DD44 .8B8D A0FBFFFF MOV ECX, DWORD PTR SS:
0040DD4A .83C1 01 ADD ECX, 0x1
0040DD4D .51 PUSH ECX
0040DD4E .8D95 8CFBFFFF LEA EDX, DWORD PTR SS:
0040DD54 .52 PUSH EDX
0040DD55 .8D8D ACFBFFFF LEA ECX, DWORD PTR SS:
0040DD5B .E8 20080000 CALL <NsCrackM.N_SubStr> ;继续取两个-中间的字符串
0040DD60 .50 PUSH EAX
0040DD61 .8D8D A8FBFFFF LEA ECX, DWORD PTR SS:
0040DD67 .E8 44B4FFFF CALL NsCrackM.004091B0
0040DD6C .8D8D 8CFBFFFF LEA ECX, DWORD PTR SS: ;取中间的字符串
0040DD72 .E8 2970FFFF CALL <NsCrackM.ToString>
0040DD77 .8D85 A8FBFFFF LEA EAX, DWORD PTR SS:
0040DD7D .50 PUSH EAX ;str2
0040DD7E .8D8D 88FBFFFF LEA ECX, DWORD PTR SS:
0040DD84 .51 PUSH ECX ;str2
0040DD85 .E8 A689FFFF CALL NsCrackM.00406730 ;进行某种变换
0040DD8A .83C4 08 ADD ESP, 0x8
0040DD8D .50 PUSH EAX
0040DD8E .8D8D A8FBFFFF LEA ECX, DWORD PTR SS:
0040DD94 .E8 17B4FFFF CALL NsCrackM.004091B0
0040DD99 .8D8D 88FBFFFF LEA ECX, DWORD PTR SS:
0040DD9F .E8 FC6FFFFF CALL <NsCrackM.ToString>
0040DDA4 .8D95 B8FBFFFF LEA EDX, DWORD PTR SS:
0040DDAA .52 PUSH EDX ;Push Name
0040DDAB .8D8D A8FBFFFF LEA ECX, DWORD PTR SS:
0040DDB1 .E8 AA070000 CALL <NsCrackM.N_Strcmp?> ;与用户名比较
代码虽然比较长,但实际上就做了几件事。首先,用-分割Pass为3段,然后取中间的那一段,进行某种变换,并把变换结果与用户名比较。所以,我们得知Pass的大致结构如下:
NS-Code(Name)-Str3
这里先不管加密方式,继续跟踪下去:
下面几步F8后,达到一个call
0040DDE7 .E8 05000000 CALL NsCrackM.0040DDF1 ;OneCheck F7进去
F7跟踪进去:
0040DDF1/[ DISCUZ_CODE_2324 ]nbsp; 6A 02 PUSH 0x2
0040DDF3|.8D8D C8FDFFFF LEA ECX,
0040DDF9|.E8 E2060000 CALL NsCrackM.0040E4E0 ;取Pass
0040DDFE|.0FB7C0 MOVZX EAX, AX
0040DE01|.83F8 2D CMP EAX, 0x2D ;Pass = 0x2D('-')
0040DE04 75 77 JNZ SHORT NsCrackM.0040DE7D
0040DE06|.83BD CCFDFFFF>CMP , 0x1 ;bool
0040DE0D|.75 6E JNZ SHORT NsCrackM.0040DE7D
0040DE0F|.8D4D E4 LEA ECX,
0040DE12|.51 PUSH ECX
0040DE13|.8D8D 9CFBFFFF LEA ECX,
0040DE19|.E8 5291FFFF CALL NsCrackM.00406F70 ;Name
0040DE1E|.68 B00C4200 PUSH NsCrackM.00420CB0 ;-
0040DE23|.8D8D 9CFBFFFF LEA ECX,
0040DE29|.E8 02070000 CALL NsCrackM.0040E530
0040DE2E|.8D95 C8FDFFFF LEA EDX,
0040DE34|.52 PUSH EDX
0040DE35|.8D8D 9CFBFFFF LEA ECX, ;Pass
0040DE3B|.E8 C0060000 CALL NsCrackM.0040E500
0040DE40|.8D8D 9CFBFFFF LEA ECX,
0040DE46|.E8 E573FFFF CALL <NsCrackM.*p> ;用 - 链接Name与Pass
0040DE4B|.50 PUSH EAX
0040DE4C|.68 04010000 PUSH 0x104
0040DE51|.68 A0664200 PUSH NsCrackM.004266A0
0040DE56|.E8 35060000 CALL NsCrackM.0040E490
0040DE5B|.6A 00 PUSH 0x0 ; /lParam = 0x0
0040DE5D|.6A 00 PUSH 0x0 ; |wParam = 0x0
0040DE5F|.A1 AC684200 MOV EAX, DWORD PTR DS: ; |
0040DE64|.50 PUSH EAX ; |Message => MSG(0xC1A3)
0040DE65|.8B0D B0684200 MOV ECX, DWORD PTR DS: ; |
0040DE6B|.51 PUSH ECX ; |hWnd => 0xB02A6
0040DE6C|.FF15 B0014200 CALL NEAR DWORD PTR DS:[<&USER32.PostMessage>; \PostMessageW
0040DE72|.8D8D 9CFBFFFF LEA ECX,
0040DE78|.E8 236FFFFF CALL <NsCrackM.ToString>
0040DE7D\>C3 RETN
这里发送了一个我们不知道的消息,显然这个消息是Nisy校长自己定义的。跟踪之下,会发现是发给主窗体的,于是,需要在主窗体相应的Msg处理的地方下一个断点,才能继续跟踪。
在0040DE5F处,右键,查找参考,地址常量,找到一处CMP EDX, DWORD PTR DS:, (00407441) 可以快速的到底处理的地方,在 CMP...JNZ...的下面下个int3断点,F9运行,便会断下来。到底第4处验证(真多。。。。)
这里用F7跟踪,进入一个CALL后,便会看到希望了。
紧接着马上对这个字符串进行某种变换得到: 恭喜,注册成功!@>--- (Nice!!!!)
然后继续初始化另外一个字符串: EOo&xA^W
变换后得到:恭喜!
Good Job!!于是,这里就成功了吗?不是的,必须得再留心一下,前面的Pass还有第三段没有进行验证呢!肯定还有下一个验证点!慢慢跟踪下去,会发现一个非常典型的跳转:
004079DD|.E8 CE660000 CALL NsCrackM.0040E0B0 ;4
004079E2|.85C0 TEST EAX, EAX
004079E4 74 27 JE SHORT NsCrackM.00407A0D
这个CALL便是最后一段验证,下面的JE Nop掉就直接成功了,当然,我们继续跟进这个call,看一下它做了什么。
这个call便是处理Pass第三段的验证段。0040E0B0 $55 PUSH EBP
0040E0B1 .8BEC MOV EBP, ESP
0040E0B3 .6A FE PUSH -0x2
0040E0B5 .68 48354200 PUSH NsCrackM.00423548
0040E0BA .68 C0274100 PUSH NsCrackM.004127C0
0040E0BF .64:A1 0000000>MOV EAX, DWORD PTR FS:
0040E0C5 .50 PUSH EAX
0040E0C6 .83C4 C0 ADD ESP, -0x40
0040E0C9 .53 PUSH EBX
0040E0CA .56 PUSH ESI
0040E0CB .57 PUSH EDI
0040E0CC .A1 74534200 MOV EAX, DWORD PTR DS:
0040E0D1 .3145 F8 XOR DWORD PTR SS:, EAX
0040E0D4 .33C5 XOR EAX, EBP
0040E0D6 .50 PUSH EAX
0040E0D7 .8D45 F0 LEA EAX, DWORD PTR SS:
0040E0DA .64:A3 0000000>MOV DWORD PTR FS:, EAX
0040E0E0 .8965 E8 MOV DWORD PTR SS:, ESP
0040E0E3 .C745 DC 00000>MOV DWORD PTR SS:, 0x0
0040E0EA .C745 D4 00000>MOV DWORD PTR SS:, 0x0
0040E0F1 .C745 E0 00000>MOV DWORD PTR SS:, 0x0 ;取前面链接的 Name-Pass
0040E0F8 .68 A0664200 PUSH NsCrackM.004266A0 ;F8LEFT-NS-oZQe7Eru-8EE8
0040E0FD .8D4D E4 LEA ECX, DWORD PTR SS:
0040E100 .E8 3B68FFFF CALL <NsCrackM.N_Strcpy>
0040E105 .8D4D D8 LEA ECX, DWORD PTR SS:
0040E108 .E8 438EFFFF CALL NsCrackM.00406F50
0040E10D .C745 FC 00000>MOV DWORD PTR SS:, 0x0
0040E114 .C745 FC 01000>MOV DWORD PTR SS:, 0x1
0040E11B .8D4D E4 LEA ECX, DWORD PTR SS:
0040E11E .E8 9D8EFFFF CALL <NsCrackM.N_Strlen> ;buffLen
0040E123 .85C0 TEST EAX, EAX ;判断长度是否为0
0040E125 .75 05 JNZ SHORT NsCrackM.0040E12C
0040E127 .E9 AB000000 JMP NsCrackM.0040E1D7
0040E12C >68 08020000 PUSH 0x208
0040E131 .6A 00 PUSH 0x0
0040E133 .68 A0664200 PUSH NsCrackM.004266A0
0040E138 .E8 23400000 CALL <NsCrackM.memset?> ;清除空间
0040E13D .83C4 0C ADD ESP, 0xC
0040E140 .6A 2D PUSH 0x2D ;'-'
0040E142 .8D4D E4 LEA ECX, DWORD PTR SS: ;Name-Pass链接
0040E145 .E8 E6050000 CALL <NsCrackM.StrLastIndex> ;取最后一个-的位置
0040E14A .8945 D0 MOV DWORD PTR SS:, EAX
0040E14D .8B45 D0 MOV EAX, DWORD PTR SS:
0040E150 .50 PUSH EAX
0040E151 .8D4D CC LEA ECX, DWORD PTR SS:
0040E154 .51 PUSH ECX
0040E155 .8D4D E4 LEA ECX, DWORD PTR SS:
0040E158 .E8 C3040000 CALL <NsCrackM.N_StrSub> ;取最后一个-前面的所有数据
0040E15D .51 PUSH ECX ;F8LEFT-NS-oZQe7Eru
0040E15E .8BCC MOV ECX, ESP
0040E160 .8D55 CC LEA EDX, DWORD PTR SS:
0040E163 .52 PUSH EDX
0040E164 .E8 078EFFFF CALL NsCrackM.00406F70 ;利用上面取出的字符串
0040E169 .E8 8287FFFF CALL NsCrackM.004068F0 ;计算出加密key (DWORD)
0040E16E .83C4 04 ADD ESP, 0x4
0040E171 .8945 E0 MOV DWORD PTR SS:, EAX ;保存DWORD
0040E174 .8D4D E4 LEA ECX, DWORD PTR SS:
0040E177 .E8 448EFFFF CALL <NsCrackM.N_Strlen>
0040E17C .2B45 D0 SUB EAX, DWORD PTR SS:
0040E17F .83E8 01 SUB EAX, 0x1
0040E182 .50 PUSH EAX
0040E183 .8D45 C0 LEA EAX, DWORD PTR SS:
0040E186 .50 PUSH EAX
0040E187 .8D4D E4 LEA ECX, DWORD PTR SS:
0040E18A .E8 01050000 CALL NsCrackM.0040E690 ;取Pass的最后的一个buff
0040E18F .50 PUSH EAX
0040E190 .8D4D D8 LEA ECX, DWORD PTR SS:
0040E193 .E8 18B0FFFF CALL NsCrackM.004091B0
0040E198 .8D4D C0 LEA ECX, DWORD PTR SS:
0040E19B .E8 006CFFFF CALL <NsCrackM.ToString>
0040E1A0 .8D4D D8 LEA ECX, DWORD PTR SS:
0040E1A3 .E8 188EFFFF CALL <NsCrackM.N_Strlen> ;取长度,与4比较
0040E1A8 .83F8 04 CMP EAX, 0x4
0040E1AB .74 02 JE SHORT NsCrackM.0040E1AF
0040E1AD .EB 28 JMP SHORT NsCrackM.0040E1D7
0040E1AF >8D4D D8 LEA ECX, DWORD PTR SS:
0040E1B2 .E8 098EFFFF CALL <NsCrackM.N_Strlen>
0040E1B7 .83F8 04 CMP EAX, 0x4
0040E1BA .75 13 JNZ SHORT NsCrackM.0040E1CF
0040E1BC .C745 D4 01000>MOV DWORD PTR SS:, 0x1 ;bool Continue = 1
0040E1C3 .8B0D 90664200 MOV ECX, DWORD PTR DS: ;ECX = 0
0040E1C9 .C701 01000000 MOV DWORD PTR DS:, 0x1 ;Err3 (To SEH)
处理了一下用户名与密码,计算出一个加密key,判断Pass的最后一段长度是否为4,是的话就触发新的异常。异常处理函数还是哪一个,就连分配的地点也是一样的。这下知道了我前面下分配函数的那个断点的作用了吧。慢慢跟踪下去,来到最后一段验证: 0040E1E9
0040E1E9 .8B65 E8 MOV ESP, DWORD PTR SS:
0040E1EC .837D D4 00 CMP DWORD PTR SS:, 0x0 ;if bool = 0?
0040E1F0 .74 74 JE SHORT NsCrackM.0040E266
0040E1F2 .51 PUSH ECX
0040E1F3 .8BCC MOV ECX, ESP
0040E1F5 .8D55 D8 LEA EDX, DWORD PTR SS:
0040E1F8 .52 PUSH EDX
0040E1F9 .E8 728DFFFF CALL NsCrackM.00406F70 ;取Pass最后一段
0040E1FE .E8 1D88FFFF CALL <NsCrackM.atoi> ;Str 转换 为 DWORD = Temp
0040E203 .83C4 04 ADD ESP, 0x4
0040E206 .8945 C8 MOV DWORD PTR SS:, EAX
0040E209 .8B45 E0 MOV EAX, DWORD PTR SS: ;取出刚才算出的key
0040E20C .25 0000FFFF AND EAX, 0xFFFF0000
0040E211 C1E8 10 SHR EAX, 0x10 ;a = (key & 0xFFFF0000) >> 10
0040E214 .8B4D E0 MOV ECX, DWORD PTR SS:
0040E217 .81E1 FFFF0000 AND ECX, 0xFFFF ;b = key & 0x0000FFFF
0040E21D .33C1 XOR EAX, ECX ;c = a ^ b, 也就是key高地位异或
0040E21F .8945 E0 MOV DWORD PTR SS:, EAX
0040E222 .8B55 C8 MOV EDX, DWORD PTR SS:
0040E225 .0355 E0 ADD EDX, DWORD PTR SS: ;d = Temp + c
0040E228 .33C0 XOR EAX, EAX
0040E22A .81FA 00000100 CMP EDX, 0x10000 ;cmp d, 0x10000
0040E230 .0F94C0 SETE AL ;Set Flag
这下终于弄完大致的流程了,终结一下,程序现在有两段加密的算法没有弄出来,记为Encode1, Encode2.那么加密的数据为
Pass1 = NS
Name = Encode1(Pass2)
Pass3 = Encode2(Name-Pass1-Pass2)
最后就剩下Encode1与Encode2要判断了。先来看Encode2,只是一个简单的查表变换,很简单的。
00406890/$55 PUSH EBP
00406891|.8BEC MOV EBP, ESP
00406893|.83EC 08 SUB ESP, 0x8
00406896|.8B45 08 MOV EAX,
00406899|.83F0 FF XOR EAX, 0xFFFFFFFF
0040689C|.8945 FC MOV , EAX ;sum = -1
0040689F|.C745 F8 00000>MOV , 0x0
004068A6|.EB 09 JMP SHORT NsCrackM.004068B1
004068A8|>8B4D F8 /MOV ECX,
004068AB|.83C1 01 |ADD ECX, 0x1
004068AE|.894D F8 |MOV , ECX
004068B1|>8B55 F8 MOV EDX,
004068B4|.3B55 10 |CMP EDX, ;while(i < iPasslen)
004068B7|.73 24 |JNB SHORT NsCrackM.004068DD
004068B9|.8B45 0C |MOV EAX,
004068BC|.0345 F8 |ADD EAX,
004068BF|.0FBE08 |MOVSX ECX, BYTE PTR DS: ;UCHAR a = Str
004068C2|.334D FC |XOR ECX, ;a ^= sum
004068C5|.81E1 FF000000 |AND ECX, 0xFF
004068CB|.8B55 FC |MOV EDX,
004068CE|.C1EA 08 |SHR EDX, 0x8 ;sum >>= 8
004068D1|.33148D C80342>|XOR EDX, DWORD PTR DS: (查表变换)
004068D8|.8955 FC |MOV , EDX
004068DB|.^ EB CB \JMP SHORT NsCrackM.004068A8
004068DD|>8B45 FC MOV EAX,
004068E0|.83F0 FF XOR EAX, 0xFFFFFFFF ;sum ^= -1
004068E3|.8BE5 MOV ESP, EBP
004068E5|.5D POP EBP
004068E6\.C3 RETN
接着来看Encode1,直接说结论,这是一个变形Base64解密的代码,变换表变了,数据组合方式也有一点改变。
假设原始数据为 A,B,C 分组后的数据为 a,b,c,d。 其中A,B,C都是8bit的数据,a,b,c,d 都是6bit的数据。那么他们的组合为
d | c | b | a
111111111111 111111111111
C | B | A
与标准的组合算法恰好相反。注意一下就可以了。另外,解密的DecodeTable为:
base64: |Start Y为结束标记,解密表为 0012E4B5 ~ 0012E535 长0x80 * 2
0012E4B3 59 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00Y...............
0012E4C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00................
0012E4D3 00 00 00 00 02 37 00 3A 00 00 00 00 0F 00 00 00....7.:.......
0012E4E3 3E 30 11 10 28 17 24 00 05 25 0A 00 00 36 3D 00>0($.%...6=.
0012E4F3 00 1A 00 00 1F 07 19 1E 09 1C 2F 0C 31 29 38 00. 32.../.1)8.
0012E503 00 2A 03 27 0B 00 33 12 32 00 00 21 00 2B 1B 16.*'.32..!.+
0012E513 08 00 22 2D 01 34 13 18 35 00 26 14 1D 00 2C 39."-45.&.,9
0012E523 06 00 2E 04 0D 20 15 23 3C 0E 3F 3B 00 00 00 00... #<?;....
0012E533 00 00 变换一下:
unsigned char CodeTable = {0};
int i;
for(i = 0; i < 0x80; i++) {
CodeTable] = i;
} 得到原始的加密表为:
char table[] = {
0x36, 0x63, 0x23, 0x51, 0x72, 0x37, 0x6F, 0x44, 0x5F, 0x47, 0x39, 0x53, 0x4A, 0x73, 0x78, 0x2B,
0x32, 0x31, 0x56, 0x65, 0x6A, 0x75, 0x5E, 0x34, 0x66, 0x45, 0x40, 0x5D, 0x48, 0x6B, 0x46, 0x43,
0x74, 0x5A, 0x61, 0x76, 0x35, 0x38, 0x69, 0x52, 0x33, 0x4C, 0x50, 0x5C, 0x6D, 0x62, 0x71, 0x49,
0x30, 0x4B, 0x57, 0x55, 0x64, 0x67, 0x3C, 0x24, 0x4D, 0x6E, 0x26, 0x7A, 0x77, 0x3D, 0x2F, 0x79
}; 到此,这个算法也就得到了,CM中相应的分析就不发了,因为我本身也没有做多少,知道是Base64后就直接弄出结果来了。。。
keygen源码见附件,相信也是不难的。要爆破的话就把上面相应的点给修改一下足以。这里在说一下程序的自校验(暗桩)。
程序在点击注册的时候会验证代码是否被修改(KMP),如果改了的话就直接退出程序。首先异常的地方在上面说过PostMessage处,哪里变成这样了。
0040DE5B|.6A 00 PUSH 0x0 ; /lParam = 0x0
0040DE5D|.6A 00 PUSH 0x0 ; |wParam = 0x0
0040DE5F|.A1 AC684200 MOV EAX, DWORD PTR DS: ; |
0040DE64|.50 PUSH EAX ; |Message => WM_CLOSE
0040DE65|.8B0D B0684200 MOV ECX, DWORD PTR DS: ; |
0040DE6B|.51 PUSH ECX ; |hWnd => 0x130204
0040DE6C|.FF15 B0014200 CALL NEAR DWORD PTR DS:[<&USER32.Post>; \PostMessageW
这次变成了发送WM_CLOSE消息了。。。继续的查找地址常量参考,来到赋值处
00401853|.E8 E8F9FFFF CALL Crack.00401240 ;自校验
00401858|.85C0 TEST EAX, EAX
0040185A 75 0A JNZ SHORT Crack.00401866
0040185C|.C705 AC684200>MOV DWORD PTR DS:, 0x10 ;WM_CLOSE
该怎么搞就怎么搞,call里面是使用KMP算法来验证程序段某些部分是否被修改掉了。
keygen附上,大家来指点一下吧。
本帖最后由 wgz001 于 2014-11-24 11:33 编辑
沙发
明显自己功力还不够,看了文章还是不懂 {:soso_e149:} 大赞!太厉害了!! 膜拜中,纯粹是来膜拜的 膜拜把校长的CM玩的如此透彻。。。 膜拜 + 羡慕 写的太精彩了,谢谢分享 分析清晰 表述详细 。。LZ异常的叼 分析的很不错,厉害。功底很深呀。 分析很好,各种膜拜