【吾爱2013CM大赛解答】-- White Decrypt & CrackMe -- White 算法还原(复杂)
本帖最后由 playboysen 于 2013-12-25 07:40 编辑本文是针对下面这个CM的解答:
http://www.52pojie.cn/thread-228419-1-1.html解压出WhiteDecrypt & CrackMe主程序,看到了MFC特有图标
再结合程序入口代码特有的kernel32.GetVersion标示一般可以认为编程语言是C系无疑,使用系统API下断点就相对方便了(C系底层和系统API结合最紧密),不错的开端,继续……
运行程序随便输入测试出现不同错误提示,OD加载主程序查看字符串后轻松定位关键代码
注册码初步验证函数功能如下(具体解释见注释)00401486|.E8 B38A0000 call White_De.00409F3E
0040148B|.8B8E D4000000 mov ecx,dword ptr ds: ;userName "White_CrackMe_JUST_For_Fun"
00401491|.8BF8 mov edi,eax
00401493|.8B86 D8000000 mov eax,dword ptr ds: ;regCode "Do_You_Like_It_Or_NOT__:)_"
00401499|.83C4 08 add esp,8
0040149C|.8B49 F8 mov ecx,dword ptr ds: ;ecx = len(userName)
0040149F|.33D2 xor edx,edx
004014A1|.8B40 F8 mov eax,dword ptr ds: ;eax = len(regCode)
004014A4|.894D FC mov dword ptr ss:,ecx
004014A7|.85C9 test ecx,ecx
004014A9|.0F94C2 sete dl
004014AC|.33C9 xor ecx,ecx
004014AE|.897D F0 mov dword ptr ss:,edi
004014B1|.85C0 test eax,eax
004014B3|.0F94C1 sete cl
004014B6|.0BD1 or edx,ecx
004014B8|.74 1A je short White_De.004014D4 ;二者不能为空
004014BA|.6A 10 push 10
004014BC|.68 28614100 push White_De.00416128 ;错误
004014C1|.68 14614100 push White_De.00416114 ;请输入用户名和密码
004014C6|.8BCE mov ecx,esi
004014C8|.E8 6BB00000 call White_De.0040C538
004014CD|.5F pop edi
004014CE|.5E pop esi
004014CF|.5B pop ebx
004014D0|.8BE5 mov esp,ebp
004014D2|.5D pop ebp
004014D3|.C3 retn
004014D4|>8B4D FC mov ecx,dword ptr ss:
004014D7|.33D2 xor edx,edx
004014D9|.83F9 17 cmp ecx,17 ;比较userName和regCode长度是不是23
004014DC|.0F9CC2 setl dl
004014DF|.33C9 xor ecx,ecx
004014E1|.83F8 17 cmp eax,17
004014E4|.0F9CC1 setl cl
004014E7|.0BD1 or edx,ecx ;往下分析就知道23位不够解码完整key
004014E9|.74 1A je short White_De.00401505 ;下面的提示做了误导
004014EB|.6A 10 push 10
004014ED|.68 28614100 push White_De.00416128 ;错误
004014F2|.68 00614100 push White_De.00416100 ;字符串长度等于23
004014F7|.8BCE mov ecx,esi
004014F9|.E8 3AB00000 call White_De.0040C538
004014FE|.5F pop edi
004014FF|.5E pop esi
00401500|.5B pop ebx
00401501|.8BE5 mov esp,ebp
00401503|.5D pop ebp
00401504|.C3 retn
00401505|>8B86 D4000000 mov eax,dword ptr ds:
0040150B|.50 push eax
0040150C|.57 push edi
0040150D|.E8 EC0D0000 call White_De.004022FE
00401512|.8B96 D8000000 mov edx,dword ptr ds:
00401518|.52 push edx
00401519|.53 push ebx
0040151A|.E8 DF0D0000 call White_De.004022FE
0040151F|.8B86 D4000000 mov eax,dword ptr ds:
00401525|.83C4 10 add esp,10
00401528|.33C9 xor ecx,ecx
0040152A|.8078 04 65 cmp byte ptr ds:,65 ;userName="e"
0040152E|.0F95C1 setne cl
00401531|.33D2 xor edx,edx
00401533|.8038 57 cmp byte ptr ds:,57 ;userName="W"
00401536|.0F95C2 setne dl
00401539|.0BCA or ecx,edx
0040153B|.74 1A je short White_De.00401557
0040153D|.6A 10 push 10
0040153F|.68 28614100 push White_De.00416128 ;错误
00401544|.68 F4604100 push White_De.004160F4 ;错误字符串
00401549|.8BCE mov ecx,esi
0040154B|.E8 E8AF0000 call White_De.0040C538
从以上代码可以看出,userName和regCode长度起码不能为空或小于23位,其中userName第一位是“W”,第五位是“e”00401577|.8B4D F4 mov ecx,dword ptr ss: ;ecx = Len(regCode)-1
0040157A|.8B45 F8 mov eax,dword ptr ss: ;eax = regCode
0040157D|.03C1 add eax,ecx
0040157F|.8D51 01 lea edx,dword ptr ds: ;edx = Len(regCode)
00401582|>8A48 FF /mov cl,byte ptr ds:
00401585|.8A18 |mov bl,byte ptr ds:
00401587|.32D9 |xor bl,cl
00401589|.8ACB |mov cl,bl
0040158B|.8818 |mov byte ptr ds:,bl ;regCode从最后一位xor前一位并依次替换原值
0040158D|.0FBE58 FF |movsx ebx,byte ptr ds:
00401591|.0FBEC9 |movsx ecx,cl
00401594|.03D9 |add ebx,ecx
00401596|.48 |dec eax
00401597|.4A |dec edx ;注意regCode第一位xor固定值0x08
00401598|.^ 75 E8 \jnz short White_De.00401582 ;变形后的注册码设为regCode1
0040159A|.8B7D F0 mov edi,dword ptr ss: ;edi = userName
0040159D|.8B55 FC mov edx,dword ptr ss: ;edx = len(userName)-1
004015A0|.33F6 xor esi,esi
004015A2|.8D043A lea eax,dword ptr ds: ;eax指向userName最后一字节的地址
004015A5|>8A48 FF /mov cl,byte ptr ds:
004015A8|.8A10 |mov dl,byte ptr ds:
004015AA|.32D1 |xor dl,cl
004015AC|.8810 |mov byte ptr ds:,dl ;userName从最后一位xor前一位并以次替换原值
004015AE|.0FBE0C3E |movsx ecx,byte ptr ds:
004015B2|.0FBED2 |movsx edx,dl
004015B5|.03CA |add ecx,edx
004015B7|.8B55 FC |mov edx,dword ptr ss:
004015BA|.46 |inc esi
004015BB|.48 |dec eax
004015BC|.3BF2 |cmp esi,edx ;注意userName第一位xor固定值0x08
004015BE|.^ 76 E5 \jbe short White_De.004015A5 ;变形后的注册码设为userName1
004015C0|.8B45 F8 mov eax,dword ptr ss:
004015C3|.23CB and ecx,ebx
004015C5|.51 push ecx
004015C6|.57 push edi ;edi = userName1
004015C7|.50 push eax ;eax = regCode1
004015C8|.E8 13000000 call White_De.004015E0 ;(╰_╯)#
004015CD|.83C4 0C add esp,0C
004015D0|.5F pop edi
004015D1|.5E pop esi
004015D2|.5B pop ebx
004015D3|.8BE5 mov esp,ebp
004015D5|.5D pop ebp
004015D6\.C3 retn主程序读入userName和regCode后分别从最后一位xor前一位并替换原值(第一位xor 0x08),以此来变形userName和regCode,字节变形前后对比见图:regCode
regCode从最后一位依次xor前一位并替换原值(第一位xor 0x08),变形后的注册码设为regCode1
userName
userName从最后一位依次xor前一位并替换原值(第一位xor 0x08),变形后的用户名设为userName1
主程序内置了一组Bytes(见下图黄色选区),设为Key用合适的用户名和注册码参与解码后就是字符串“Something Is always Changing !”(作为注册成功提示MessageBoxA的内容)细心人可能发现了该字符串共30位,其中前26位是变形字节,最后4位是明文——也就是说主程序要做的就是解码前26字节
0040163F|.49 dec ecx
00401640|.894C24 18 mov dword ptr ss:,ecx
00401644|>8A0C30 /mov cl,byte ptr ds: ;Key从首字节开始xor regCode1对应字节
00401647|.300C28 |xor byte ptr ds:,cl
0040164A|.83F8 0A |cmp eax,0A ;共解码Key的前11字节
0040164D|.75 13 |jnz short White_De.00401662
0040164F|.8D7B F6 |lea edi,dword ptr ds:
00401652|.3BF8 |cmp edi,eax
00401654|.72 0C |jb short White_De.00401662
00401656|>8A0C06 |/mov cl,byte ptr ds: ;regCode1从第十二字节开始依次xor前一字节
00401659|.304C06 01 ||xor byte ptr ds:,cl
0040165D|.40 ||inc eax
0040165E|.3BC7 ||cmp eax,edi
00401660|.^ 76 F4 |\jbe short White_De.00401656 ;共处理regCode1第12-14字节
00401662|>40 |inc eax
00401663|.83F8 0A |cmp eax,0A
00401666|.^ 76 DC \jbe short White_De.00401644
00401668|.8B4C24 18 mov ecx,dword ptr ss: ;ecx = len(userName1)
0040166C|.B8 0A000000 mov eax,0A
00401671|>8A1C10 /mov bl,byte ptr ds: ;Key从第11字节开始xor userName1对应字节
00401674|.301C28 |xor byte ptr ds:,bl
00401677|.83F8 14 |cmp eax,14 ;共解码Key的第11-21字节
0040167A|.75 13 |jnz short White_De.0040168F
0040167C|.8D79 EC |lea edi,dword ptr ds:
0040167F|.3BF8 |cmp edi,eax
00401681|.72 0C |jb short White_De.0040168F
00401683|>8A1C02 |/mov bl,byte ptr ds:
00401686|.205C02 01 ||and byte ptr ds:,bl
0040168A|.40 ||inc eax
0040168B|.3BC7 ||cmp eax,edi
0040168D|.^ 76 F4 |\jbe short White_De.00401683
0040168F|>40 |inc eax
00401690|.83F8 14 |cmp eax,14
00401693|.^ 76 DC \jbe short White_De.00401671
00401695|.8BCE mov ecx,esi第一段解码:Key从首字节开始xor regCode1对应字节,共处理Key的前11字节(截图是前十一字节解码后的明文)for j in range(1,12):
key = chr(ord(key) ^ ord(regCode1))
由于异或运算是可逆的,所以可以根据已知Key和最终对应的“Something Is always Changing !”前十一位逆推出regCode前十位(因为第十一位后续变幻多次,暂无法确定)
key = {1:"\x1F",2:"\x44",3:"\x5D",4:"\x63",5:"\x42",6:"\x72",7:"\x43",8:"\x7D",9:"\x42",10:"\x22",11:"\x4F",12:"\x55",13:"\x08",14:"\x5B",15:"\x79",16:"\x68",17:"\x67",18:"\x7E",19:"\x78",20:"\x39",21:"\x63",22:"\x61",23:"\x61",24:"\x6F",25:"\x74",26:"\x7B",27:"\x6E",28:"\x67",29:"\x20",30:"\x21"}
OK = {1:"S",2:"o",3:"m",4:"e",5:"t",6:"h",7:"i",8:"n",9:"g",10:" ",11:"I",12:"s",13:" ",14:"a",15:"l",16:"w",17:"a",18:"y",19:"s",20:" ",21:"C",22:"h",23:"a",24:"n",25:"g",26:"i",27:"n",28:"g",29:" ",30:"!"}
regCode = dict()
for j in range(1,12):
regCode= chr(ord(key)^ord(OK))
if i ==1:
regCode = chr(ord(regCode)^ord('\x08'))
else:
regCode=chr(ord(regCode)^ord(regCode))
>>> print regCode
{1: 'D', 2: 'o', 3: '_', 4: 'Y', 5: 'o', 6: 'u', 7: '_', 8: 'L', 9: 'i', 10: 'k', 11: 'm'}
regCode1从第十二字节开始依次xor前一字节,共处理regCode1第12-14字节for j in range(11,14):
regCode1 = chr(ord(regCode1) ^ ord(regCode1))
第二段解码:Key从第11字节开始xor userName1对应字节,共处理Key的第11-21字节(截图是Key解码后的明文)for j in range(11,22):
key = chr(ord(key) ^ ord(userName1))
004016A0|>8A1C01 /mov bl,byte ptr ds: ;userName1从首字节开始依次xor regCode1对应字节
004016A3|.3018 |xor byte ptr ds:,bl
004016A5|.40 |inc eax
004016A6|.4F |dec edi ;共处理userName1的前11字节
004016A7|.^ 75 F7 \jnz short White_De.004016A0
004016A9|.8BFA mov edi,edx ;edi指向变形后的userName1
004016AB|.8D46 0A lea eax,dword ptr ds:
004016AE|.2BFE sub edi,esi
004016B0|.BE 0B000000 mov esi,0B
004016B5|>8A1C07 /mov bl,byte ptr ds: ;regCode1从第11字节开始and userName1对应字节
004016B8|.2018 |and byte ptr ds:,bl
004016BA|.40 |inc eax
004016BB|.4E |dec esi ;共处理regCode1的11-21字节
004016BC|.^ 75 F7 \jnz short White_De.004016B5
004016BE|.8D42 14 lea eax,dword ptr ds: ;eax指向变形后的userName1
004016C1|.BE 06000000 mov esi,6
004016C6|>8A1C08 /mov bl,byte ptr ds: ;userName1从第21字节开始and regCode1对应字节
004016C9|.2018 |and byte ptr ds:,bl
004016CB|.40 |inc eax
004016CC|.4E |dec esi ;共处理userName1的21-26字节
004016CD|.^ 75 F7 \jnz short White_De.004016C6
004016CF|.8D45 14 lea eax,dword ptr ss: ;eax指向key的第21字节
004016D2|.2BD5 sub edx,ebp
004016D4|.BE 06000000 mov esi,6
004016D9|>8A0C02 /mov cl,byte ptr ds: ;Key从第21字节开始xor userName1对应字节
004016DC|.8A18 |mov bl,byte ptr ds:
004016DE|.32D9 |xor bl,cl
004016E0|.8818 |mov byte ptr ds:,bl
004016E2|.40 |inc eax
004016E3|.4E |dec esi ;共解码Key的第21-26字节
004016E4|.^ 75 F3 \jnz short White_De.004016D9
004016E6|.8B7424 10 mov esi,dword ptr ss:
004016EA|.55 push ebp
004016EB|.68 38614100 push White_De.00416138 ;%s
004016F0|.56 push esi
004016F1|.E8 080C0000 call White_De.004022FE
004016F6|.83C4 0C add esp,0C
004016F9|.6A 40 push 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004016FB|.68 30614100 push White_De.00416130 ; |info
00401700|.56 push esi ; |Text
00401701|.6A 00 push 0 ; |hOwner = NULL
00401703|.FF15 58134100 call dword ptr ds:[<&USER32.Message>; \MessageBoxA
userName1从首字节开始xor regCode1对应字节,共处理userName1的前11字节for j in range(1,12):
userName1 = chr(ord(userName1) ^ ord(regCode1))
regCode1从第11字节开始and userName1对应字节,共处理regCode1的11-21字节for j in range(11,22):
regCode1 = chr(ord(regCode1) & ord(userName1))
userName1从第21字节开始and regCode1对应字节,共处理userName1的21-26字节for j in range(21,27):
userName1 = chr(ord(userName1) & ord(regCode1))
第三段解码:Key从第21字节开始xor userName1对应字节,共处理Key的第21-26字节(截图是Key解码后的明文)for j in range(21,27):
key = chr(ord(key) ^ ord(userName1))
整段解码算法并不复杂,简单来说就是程序预置了30位加密Bytes,需要填入合适的用户名和注册码来参与运算解码,最终生成需要的文字“Something Is always Changing !”,运算只是简单的xor和and,不过由于全部都是字节操作编码解码多次,故需全部理解、重现、调试和验证等等需耗大量时间,心力交瘁!我估计到大赛结束,这个“White Decrypt & CrackMe”解答的人也不会太多了{:1_923:}
放一段已还原的算法Python示例以供参考#!/usr/bin/env python
##Python 2.7 & Windows 7
import string,random
"""
随机生成用户名和注册码,考虑到后面计算方便,首字节忠于原程序设置为0x08
key是主程序内置的一组加密Bytes(用合适的用户名和注册码参与解密后就是最终的字符串“Something Is always Changing !”)
"""
txt = "ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_"
key = {1:"\x1F",2:"\x44",3:"\x5D",4:"\x63",5:"\x42",6:"\x72",7:"\x43",8:"\x7D",9:"\x42",10:"\x22",11:"\x4F",12:"\x55",13:"\x08",14:"\x5B",15:"\x79",16:"\x68",17:"\x67",18:"\x7E",19:"\x78",20:"\x39",21:"\x63",22:"\x61",23:"\x61",24:"\x6F",25:"\x74",26:"\x7B",27:"\x6E",28:"\x67",29:"\x20",30:"\x21"}
OK = {1:"S",2:"o",3:"m",4:"e",5:"t",6:"h",7:"i",8:"n",9:"g",10:" ",11:"I",12:"s",13:" ",14:"a",15:"l",16:"w",17:"a",18:"y",19:"s",20:" ",21:"C",22:"h",23:"a",24:"n",25:"g",26:"i",27:"n",28:"g",29:" ",30:"!"}
def bruteAttack():
userName = "\x08" + "Wallet" + string.join(random.sample(txt,20)).replace(' ','')
regCode = "\x08" + "Do_You_Lik" + string.join(random.sample(txt,16)).replace(' ','')
"""
regCode最后一位xor前一位并替换原值(其它字节以此类推,第一位xor 0x08),变形后的注册码设为regCode1
userName计算方法相同
"""
regCode1 =dict()
for j in range(26,0,-1):
regCode1 = chr(ord(regCode) ^ ord(regCode))
userName1 = dict()
for j in range(26,0,-1):
userName1 = chr(ord(userName) ^ ord(userName))
"""Key从首字节开始xor regCode1对应字节,共处理Key的前11字节"""
for j in range(1,12):
key = chr(ord(key) ^ ord(regCode1))
"""Key从第11字节开始xor userName1对应字节,共处理Key的第11-21字节"""
for j in range(11,22):
key = chr(ord(key) ^ ord(userName1))
"""regCode1从第十二字节开始xor前一字节,共处理regCode1第12-14字节"""
for j in range(11,14):
regCode1 = chr(ord(regCode1) ^ ord(regCode1))
"""userName1从首字节开始xor regCode1对应字节,共处理userName1的前11字节"""
for j in range(1,12):
userName1 = chr(ord(userName1) ^ ord(regCode1))
"""regCode1从第11字节开始and userName1对应字节,共处理regCode1的11-21字节"""
for j in range(11,22):
regCode1 = chr(ord(regCode1) & ord(userName1))
"""userName1从第21字节开始and regCode1对应字节,共处理UserName1的21-26字节"""
for j in range(21,27):
userName1 = chr(ord(userName1) & ord(regCode1))
"""Key从第21字节开始xor userName1对应字节,共处理Key的第21-26字节"""
for j in range(21,27):
key = chr(ord(key) ^ ord(userName1))
while True:
bruteAttack()
if key == OK:
print "Success!"
break
不能免俗,只为加分O(∩_∩)O~——做个爆破Loader
给力啊稳坐沙发 牛掰啊 见识了
牛掰啊 见识了 膜拜...上午刚开始分析,中午吃完饭回来LZ就发帖了。。。
算法 是在休息时间做的,主要的就是三个函数。
在这个CrackMe 修改很大,因为原版本Kegen比较难做,这个版本的相较简单点。
分析 很精彩。 很精彩,感谢大牛分享优秀文章。 膜拜 厉害...学习了 xiaobai 发表于 2013-12-17 14:50 static/image/common/back.gif
膜拜...上午刚开始分析,中午吃完饭回来LZ就发帖了。。。
坐等分析结论,求指导……{:301_997:}
页:
[1]
2