-
-
[原创] CTF2017 第11题 ToBeBetter CrackMe 详细破解过程
-
2017-6-23 10:35 4459
-
序言:
本题使用了父子进程守护、壳、SMC、反调试、DES算法、多个坑等设计,难度不低,可惜多解,现仅提交140组答案,
破解此题需要攻击者具备解壳或动态调试能力、跟踪父子进程、熟谙DES算法细节和直觉(否则栽在坑里了)。
一、侦壳、PE格式,试运行了解作品(套路),未知壳,作品有窗口还有错误提示(容易找到合适的断点位置)
二、解壳:
// 破解本题无需真的脱掉壳,只需要找到OEP,利用OD脚本进行自动断在OEP并Patch代码即可。
// OD载入,停在这里:
0044742E > E8 ECFBFFFF CALL 11-ToBeB.0044701F 00447433 C3 RETN
// 跟入CALL 44701F,省略一系列解码过程,破解此题不需要关心这些,只需要找到OEP
0044739E 8B85 E0FEFFFF MOV EAX,DWORD PTR SS:[EBP-0x120] 004473A4 8BE5 MOV ESP,EBP 004473A6 5D POP EBP 004473A7 59 POP ECX 004473A8 FFE0 JMP EAX ; 飞向光明顶,此处设断 004473AA 83BD ECFEFFFF 0>CMP DWORD PTR SS:[EBP-0x114],0x0
// 来到OEP
00418356 E8 574C0000 CALL 11-ToBeB.0041CFB2 ; OEP 0041835B ^ E9 7FFEFFFF JMP 11-ToBeB.004181DF 00418360 8B5424 0C MOV EDX,DWORD PTR SS:[ESP+0xC] 00418364 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+0x4]
// 可以写个OD脚本,自动断OEP
BPHWS 418356,"x" RUN BPHWC 418356
三、分析子进程的坑
// OD加载运行后,只出来一个带窗口进程,与正常运行的双进程不同,说明这是个坑
// 是坑也得分析不是?bp GetDlgItemTextW
0012F4F8 004017A9 /CALL 到 GetDlgItemTextW 来自 11-ToBeB.004017A3 0012F4FC 001701AC |hWnd = 001701AC ('CrackMe',class='#32770') 0012F500 000003E9 |ControlID = 3E9 (1001.) 0012F504 0012F510 |Buffer = 0012F510 0012F508 000001FE \Count = 1FE (510.)
// 返回程序
004017A3 FF15 90A14200 CALL DWORD PTR DS:[0x42A190] ; 获得输入 004017A9 C745 08 0000000>MOV DWORD PTR SS:[EBP+0x8],0x0 004017B0 33D2 XOR EDX,EDX 004017B2 B8 108B1E45 MOV EAX,0x451E8B10 004017B7 8B0D 087A4300 MOV ECX,DWORD PTR DS:[0x437A08] 004017BD F7F1 DIV ECX ; 父进程对次处NOP 004017BF 8945 08 MOV DWORD PTR SS:[EBP+0x8],EAX 004017C2 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] 004017C5 8D85 00FAFFFF LEA EAX,DWORD PTR SS:[EBP-0x600] 004017CB 50 PUSH EAX 004017CC E8 6F020000 CALL 11-ToBeB.00401A40 ; 算法流程(带坑) 004017D1 83C4 08 ADD ESP,0x8 004017D4 C785 00FCFFFF D>MOV DWORD PTR SS:[EBP-0x400],0x793A63D0 004017DE 85C0 TEST EAX,EAX 004017E0 8D85 04FCFFFF LEA EAX,DWORD PTR SS:[EBP-0x3FC] 004017E6 68 FA010000 PUSH 0x1FA 004017EB 6A 00 PUSH 0x0 004017ED 50 PUSH EAX 004017EE 74 58 JE X11-ToBeB.00401848 ; 不能跳 004017F0 E8 6B6B0100 CALL 11-ToBeB.00418360 004017F5 68 F6010000 PUSH 0x1F6 004017FA 8D85 08FEFFFF LEA EAX,DWORD PTR SS:[EBP-0x1F8] 00401800 C785 00FEFFFF E>MOV DWORD PTR SS:[EBP-0x200],0x518C6CE8 0040180A 6A 00 PUSH 0x0 0040180C 50 PUSH EAX 0040180D C785 04FEFFFF 1>MOV DWORD PTR SS:[EBP-0x1FC],0x529F6210 00401817 E8 446B0100 CALL 11-ToBeB.00418360 0040181C 83C4 18 ADD ESP,0x18 0040181F 8D85 00FCFFFF LEA EAX,DWORD PTR SS:[EBP-0x400] 00401825 6A 00 PUSH 0x0 00401827 50 PUSH EAX 00401828 8D85 00FEFFFF LEA EAX,DWORD PTR SS:[EBP-0x200] 0040182E 50 PUSH EAX 0040182F 56 PUSH ESI 00401830 FF15 8CA14200 CALL DWORD PTR DS:[0x42A18C] ; 注册成功 00401836 6A 02 PUSH 0x2 00401838 56 PUSH ESI 00401839 FF15 88A14200 CALL DWORD PTR DS:[0x42A188] ; user32.EndDialog 0040183F 33C0 XOR EAX,EAX 00401841 5E POP ESI 00401842 8BE5 MOV ESP,EBP 00401844 5D POP EBP 00401845 C2 1000 RETN 0x10 00401848 E8 136B0100 CALL 11-ToBeB.00418360 0040184D 68 F6010000 PUSH 0x1F6 00401852 8D85 08FEFFFF LEA EAX,DWORD PTR SS:[EBP-0x1F8] 00401858 C785 00FEFFFF E>MOV DWORD PTR SS:[EBP-0x200],0x518C6CE8 00401862 6A 00 PUSH 0x0 00401864 50 PUSH EAX 00401865 C785 04FEFFFF 3>MOV DWORD PTR SS:[EBP-0x1FC],0x8D255931 0040186F E8 EC6A0100 CALL 11-ToBeB.00418360 00401874 83C4 18 ADD ESP,0x18 00401877 8D85 00FCFFFF LEA EAX,DWORD PTR SS:[EBP-0x400] 0040187D 6A 00 PUSH 0x0 0040187F 50 PUSH EAX 00401880 8D85 00FEFFFF LEA EAX,DWORD PTR SS:[EBP-0x200] 00401886 50 PUSH EAX 00401887 56 PUSH ESI 00401888 FF15 8CA14200 CALL DWORD PTR DS:[0x42A18C] ; 注册失败 0040188E 33C0 XOR EAX,EAX 00401890 5E POP ESI 00401891 8BE5 MOV ESP,EBP 00401893 5D POP EBP 00401894 C2 1000 RETN 0x10 00401897 66:83F8 02 CMP AX,0x2 0040189B 75 0B JNZ X11-ToBeB.004018A8 0040189D 6A 02 PUSH 0x2 0040189F FF75 08 PUSH DWORD PTR SS:[EBP+0x8] 004018A2 FF15 88A14200 CALL DWORD PTR DS:[0x42A188] ; user32.EndDialog 004018A8 33C0 XOR EAX,EAX 004018AA 8BE5 MOV ESP,EBP 004018AC 5D POP EBP 004018AD C2 1000 RETN 0x10
// CALL 401A40 为子进程算法,跟进
00401A40 55 PUSH EBP 00401A41 8BEC MOV EBP,ESP 00401A43 83EC 0C SUB ESP,0xC ; 父进程对此处进行流程修改 00401A46 53 PUSH EBX 00401A47 56 PUSH ESI 00401A48 57 PUSH EDI 00401A49 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] 00401A4C 33D2 XOR EDX,EDX 00401A4E 33F6 XOR ESI,ESI 00401A50 8955 F8 MOV DWORD PTR SS:[EBP-0x8],EDX 00401A53 8975 FC MOV DWORD PTR SS:[EBP-0x4],ESI 00401A56 33C9 XOR ECX,ECX 00401A58 8D5F 02 LEA EBX,DWORD PTR DS:[EDI+0x2] 00401A5B EB 03 JMP X11-ToBeB.00401A60 00401A5D 8D49 00 LEA ECX,DWORD PTR DS:[ECX] 00401A60 66:8B07 MOV AX,WORD PTR DS:[EDI] 00401A63 83C7 02 ADD EDI,0x2 00401A66 66:85C0 TEST AX,AX 00401A69 ^ 75 F5 JNZ X11-ToBeB.00401A60 00401A6B 2BFB SUB EDI,EBX 00401A6D D1FF SAR EDI,1 00401A6F 83FF 14 CMP EDI,0x14 ; 虚假的 < 20位 00401A72 7C 09 JL X11-ToBeB.00401A7D 00401A74 5F POP EDI 00401A75 5E POP ESI 00401A76 33C0 XOR EAX,EAX 00401A78 5B POP EBX 00401A79 8BE5 MOV ESP,EBP 00401A7B 5D POP EBP 00401A7C C3 RETN
// 异或累加 == 0x127514D 就是个很直白的坑
00401AE2 8D42 61 LEA EAX,DWORD PTR DS:[EDX+0x61] 00401AE5 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-0x8] 00401AE8 98 CWDE 00401AE9 33F0 XOR ESI,EAX ; 异或 00401AEB 03D6 ADD EDX,ESI ; 累加 00401AED 8B75 FC MOV ESI,DWORD PTR SS:[EBP-0x4] 00401AF0 8955 F8 MOV DWORD PTR SS:[EBP-0x8],EDX 00401AF3 3BDF CMP EBX,EDI 00401AF5 ^ 7C 99 JL X11-ToBeB.00401A90 00401AF7 33C0 XOR EAX,EAX 00401AF9 81FA 4D512701 CMP EDX,0x127514D ; 异或累加值 == 0x127514D 00401AFF 5F POP EDI ; 测试了几组长度离答案差太多,肯定是坑
// 此时想到父进程必然有小动作
// 找到加载窗口的位置(父进程不显示窗口,所以在相关位置必有选择)
00401691 |. 8BEC MOV EBP,ESP 00401693 |. 83EC 70 SUB ESP,0x70 00401696 |. 803D 509B4300>CMP BYTE PTR DS:[0x439B50],0x0 0040169D |. 0F84 88000000 JE dumped_.0040172B 004016A3 |. E8 08FFFFFF CALL dumped_.004015B0 004016A8 |. 803D 519B4300>CMP BYTE PTR DS:[0x439B51],0x0 004016AF |. 74 1C JE Xdumped_.004016CD ; 父进程子进程选择分支 004016B1 |. 6A 00 PUSH 0x0 ; /lParam = NULL 004016B3 |. 68 40174000 PUSH dumped_.00401740 ; |DlgProc = dumped_.00401740 004016B8 |. 6A 00 PUSH 0x0 ; |hOwner = NULL 004016BA |. 6A 67 PUSH 0x67 ; |pTemplate = 0x67 004016BC |. FF75 08 PUSH DWORD PTR SS:[EBP+0x8] ; |hInst 004016BF |. FF15 94A14200 CALL DWORD PTR DS:[<&user32.DialogBoxParamW>] ; \DialogBoxParamW 004016C5 |. 33C0 XOR EAX,EAX 004016C7 |. 8BE5 MOV ESP,EBP 004016C9 |. 5D POP EBP 004016CA |. C2 1000 RETN 0x10 004016CD |> 8D4D 90 LEA ECX,DWORD PTR SS:[EBP-0x70] 004016D0 |. E8 0BF10000 CALL dumped_.004107E0 004016D5 |. E8 C6FEFFFF CALL dumped_.004015A0 004016DA |. 8D4D EC LEA ECX,DWORD PTR SS:[EBP-0x14]
//置父子进程标志位的地方
0040145D A2 519B4300 MOV BYTE PTR DS:[0x439B51],AL
四、分析父进程小动作
// 正常运行之后,OD附加父进程,对CODE段下访问断点,断到
04115B0 56 PUSH ESI 004115B1 8BF1 MOV ESI,ECX 004115B3 8B46 20 MOV EAX,DWORD PTR DS:[ESI+0x20] 004115B6 85C0 TEST EAX,EAX 004115B8 74 07 JE Xdumped_.004115C1 004115BA 50 PUSH EAX 004115BB FF15 7CA04200 CALL DWORD PTR DS:[<&kernel32.SetEvent>] ; kernel32.SetEvent 004115C1 8B46 24 MOV EAX,DWORD PTR DS:[ESI+0x24] 004115C4 85C0 TEST EAX,EAX 004115C6 74 07 JE Xdumped_.004115CF 004115C8 50 PUSH EAX 004115C9 FF15 78A04200 CALL DWORD PTR DS:[<&kernel32.DebugActiveProcessStop>] ; kernel32.DebugActiveProcessStop 004115CF 8B46 1C MOV EAX,DWORD PTR DS:[ESI+0x1C] 004115D2 83F8 FF CMP EAX,-0x1 004115D5 74 3E JE Xdumped_.00411615 004115D7 57 PUSH EDI 004115D8 8B3D 80A04200 MOV EDI,DWORD PTR DS:[<&kernel32.WaitForSingleObject>] ; kernel32.WaitForSingleObject 004115DE 6A 00 PUSH 0x0 004115E0 50 PUSH EAX 004115E1 FFD7 CALL EDI 004115E3 3D 02010000 CMP EAX,0x102 004115E8 75 1A JNZ Xdumped_.00411604 004115EA 53 PUSH EBX 004115EB 8B1D 88A04200 MOV EBX,DWORD PTR DS:[<&kernel32.Sleep>] ; kernel32.Sleep 004115F1 6A 64 /PUSH 0x64 004115F3 FFD3 |CALL EBX 004115F5 6A 00 |PUSH 0x0 004115F7 FF76 1C |PUSH DWORD PTR DS:[ESI+0x1C] 004115FA FFD7 |CALL EDI 004115FC 3D 02010000 |CMP EAX,0x102 00411601 ^ 74 EE \JE Xdumped_.004115F1 00411603 5B POP EBX 00411604 FF76 1C PUSH DWORD PTR DS:[ESI+0x1C] 00411607 FF15 2CA04200 CALL DWORD PTR DS:[<&kernel32.CloseHandle>] ; kernel32.CloseHandle 0041160D C746 1C FFFFFFF>MOV DWORD PTR DS:[ESI+0x1C],-0x1 00411614 5F POP EDI 00411615 8B46 20 MOV EAX,DWORD PTR DS:[ESI+0x20] 00411618 85C0 TEST EAX,EAX 0041161A 74 0E JE Xdumped_.0041162A 0041161C 50 PUSH EAX 0041161D FF15 2CA04200 CALL DWORD PTR DS:[<&kernel32.CloseHandle>] ; kernel32.CloseHandle 00411623 C746 20 0000000>MOV DWORD PTR DS:[ESI+0x20],0x0 0041162A C746 54 0000000>MOV DWORD PTR DS:[ESI+0x54],0x0 00411631 5E POP ESI 00411632 C3 RETN
// 父进程再设断点
00410AA2 8B1D 74A04200 MOV EBX,DWORD PTR DS:[0x42A074] ; kernel32.WaitForDebugEvent 00410AA8 6A FF PUSH -0x1 00410AAA 8D45 8C LEA EAX,DWORD PTR SS:[EBP-0x74] 00410AAD 50 PUSH EAX 00410AAE FFD3 CALL EBX 00410AB0 85C0 TEST EAX,EAX ; 断点这里 00410AB2 74 3C JE X11-ToBeB.00410AF0 00410AB4 8D45 8C LEA EAX,DWORD PTR SS:[EBP-0x74] 00410AB7 50 PUSH EAX 00410AB8 8B47 18 MOV EAX,DWORD PTR DS:[EDI+0x18] 00410ABB 57 PUSH EDI 00410ABC 6A 00 PUSH 0x0 00410ABE FFD0 CALL EAX 00410AC0 8D45 8C LEA EAX,DWORD PTR SS:[EBP-0x74] 00410AC3 50 PUSH EAX 00410AC4 57 PUSH EDI 00410AC5 FF75 8C PUSH DWORD PTR SS:[EBP-0x74] 00410AC8 E8 93010000 CALL 11-ToBeB.00410C60 00410ACD 8945 EC MOV DWORD PTR SS:[EBP-0x14],EAX 00410AD0 8D45 8C LEA EAX,DWORD PTR SS:[EBP-0x74] 00410AD3 50 PUSH EAX 00410AD4 8B47 18 MOV EAX,DWORD PTR DS:[EDI+0x18] 00410AD7 57 PUSH EDI 00410AD8 6A 01 PUSH 0x1 00410ADA FFD0 CALL EAX ; 修改子进程2处 00410ADC 83C4 24 ADD ESP,0x24 00410ADF FF75 EC PUSH DWORD PTR SS:[EBP-0x14] 00410AE2 FF75 94 PUSH DWORD PTR SS:[EBP-0x6C] 00410AE5 FF75 90 PUSH DWORD PTR SS:[EBP-0x70] 00410AE8 FF15 70A04200 CALL DWORD PTR DS:[0x42A070] ; kernel32.ContinueDebugEvent
// 410ADA CALL EAX 有小动作,跟进
00402000 50 PUSH EAX 00402001 6A 02 PUSH 0x2 00402003 8D45 08 LEA EAX,DWORD PTR SS:[EBP+0x8] 00402006 66:C745 08 9090 MOV WORD PTR SS:[EBP+0x8],0x9090 0040200C 50 PUSH EAX 0040200D FF76 18 PUSH DWORD PTR DS:[ESI+0x18] 00402010 52 PUSH EDX 00402011 FFD3 CALL EBX ; WriteProcessMemory
// 堆栈
015BFCD4 00000154 015BFCD8 004017BD 11-ToBeB.004017BD 015BFCDC 015BFE5C 015BFCE0 00000002 015BFCE4 015BFE44 015BFCE8 76EFD000 kernel32.GetLastError
// 将子进程 4017BD 位置用 9090 NOP掉
// 改前 004017BD F7F1 DIV ECX // 改后 004017BD 90 NOP ; 父进程对次处NOP 004017BE 90 NOP
// 还有小动作
00402080 50 PUSH EAX 00402081 6A 05 PUSH 0x5 00402083 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-0x10] 00402086 50 PUSH EAX 00402087 8B02 MOV EAX,DWORD PTR DS:[EDX] 00402089 8B00 MOV EAX,DWORD PTR DS:[EAX] 0040208B 05 37373737 ADD EAX,0x37373737 00402090 35 4EE5B3C8 XOR EAX,0xC8B3E54E 00402095 83C0 03 ADD EAX,0x3 00402098 50 PUSH EAX 00402099 57 PUSH EDI 0040209A FFD3 CALL EBX ; WriteProcessMemory
// 堆栈
015BFCD8 00401A43 11-ToBeB.00401A43 015BFCDC 015BFE44 015BFCE0 00000005 015BFCE4 015BFE5C 015BFCE8 76EFD000 kernel32.GetLastError 015BFCEC 0012FECC
// [15BFE44] = E9 BB 08 00 00
// 将子进程 401A43 位置用 E9 BB 08 00 00 修改
// 改前: 00401A43 83EC 0C SUB ESP,0xC 00401A46 53 PUSH EBX 00401A47 56 PUSH ESI // 改后: 00401A43 /E9 BB080000 JMP 11-ToBeB.00402303
// 至此,已经分析清楚父进程对子进程的两处代码修改,后续调试我们就脱离父进程
// OD脚本模拟父进程自动patch代码
BPHWS 418356,"x" RUN BPHWC 418356 MOV [4017BD],#9090# MOV [401A43],#E9BB080000#
五、分析第一段算法
// 重新再来(代码已经patch)
004017A3 FF15 90A14200 CALL DWORD PTR DS:[0x42A190] ; 获得输入 004017A9 C745 08 0000000>MOV DWORD PTR SS:[EBP+0x8],0x0 004017B0 33D2 XOR EDX,EDX 004017B2 B8 108B1E45 MOV EAX,0x451E8B10 004017B7 8B0D 087A4300 MOV ECX,DWORD PTR DS:[0x437A08] 004017BD 90 NOP ; 父进程对次处NOP 004017BE 90 NOP 004017BF 8945 08 MOV DWORD PTR SS:[EBP+0x8],EAX 004017C2 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] 004017C5 8D85 00FAFFFF LEA EAX,DWORD PTR SS:[EBP-0x600] 004017CB 50 PUSH EAX 004017CC E8 6F020000 CALL 11-ToBeB.00401A40 ; 算法流程(带坑) 00401A40 55 PUSH EBP 00401A41 8BEC MOV EBP,ESP 00401A43 E9 BB080000 JMP 11-ToBeB.00402303 ; 父进程对此处进行流程修改
// 跟踪改变后的流程,动态申请内存0x5000字节,用于存放待解密数据
00402657 6A 40 PUSH 0x40 00402659 68 00100000 PUSH 0x1000 0040265E 68 00500000 PUSH 0x5000 00402663 6A 00 PUSH 0x0 00402665 FFD7 CALL EDI ; ZwAllocateVirtualMemory 00402667 8945 F4 MOV DWORD PTR SS:[EBP-0xC],EAX
// 用于读取自身0x5000字节待解密数据
004029B7 6A 00 PUSH 0x0 004029B9 8D45 80 LEA EAX,DWORD PTR SS:[EBP-0x80] 004029BC 50 PUSH EAX 004029BD 68 00500000 PUSH 0x5000 004029C2 FF75 F4 PUSH DWORD PTR SS:[EBP-0xC] 004029C5 FF75 D0 PUSH DWORD PTR SS:[EBP-0x30] 004029C8 FFD7 CALL EDI ; ZwReadFile 004029CA 8B75 80 MOV ESI,DWORD PTR SS:[EBP-0x80]
// 获取输入并转换格式十六进制
004029D5 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+0x8] ; input 004029D8 8D55 F8 LEA EDX,DWORD PTR SS:[EBP-0x8] 004029DB 8BCB MOV ECX,EBX 004029DD C745 F8 0000000>MOV DWORD PTR SS:[EBP-0x8],0x0 004029E4 E8 D7F1FFFF CALL 11-ToBeB.00401BC0 ; 检查格式,转16进制 004029E9 85C0 TEST EAX,EAX 004029EB 0F84 49010000 JE 11-ToBeB.00402B3A ; 不能跳 004029F1 8D4B 10 LEA ECX,DWORD PTR DS:[EBX+0x10] 004029F4 C645 F3 00 MOV BYTE PTR SS:[EBP-0xD],0x0 004029F8 8D55 F3 LEA EDX,DWORD PTR SS:[EBP-0xD] 004029FB E8 10F1FFFF CALL 11-ToBeB.00401B10 ; 检查格式,转16进制 00401BC0 56 PUSH ESI ; 转十六进制 00401BC1 57 PUSH EDI 00401BC2 8BF9 MOV EDI,ECX 00401BC4 C702 00000000 MOV DWORD PTR DS:[EDX],0x0 00401BCA 33F6 XOR ESI,ESI 00401BCC 8D6424 00 LEA ESP,DWORD PTR SS:[ESP] 00401BD0 0FB70477 MOVZX EAX,WORD PTR DS:[EDI+ESI*2] ; input 00401BD4 83F8 30 CMP EAX,0x30 00401BD7 72 05 JB X11-ToBeB.00401BDE 00401BD9 83F8 39 CMP EAX,0x39 00401BDC 76 0A JBE X11-ToBeB.00401BE8 00401BDE 83F8 41 CMP EAX,0x41 00401BE1 72 3E JB X11-ToBeB.00401C21 00401BE3 83F8 5A CMP EAX,0x5A 00401BE6 77 39 JA X11-ToBeB.00401C21 ; 要求输入 0 - 9 或 A - Z 00401BE8 8B0A MOV ECX,DWORD PTR DS:[EDX] ; 取变量,初值0 00401BEA C1E1 04 SHL ECX,0x4 ; *16 -- > 进位,转十六进制 00401BED 890A MOV DWORD PTR DS:[EDX],ECX ; 结果存回变量 00401BEF 83F8 30 CMP EAX,0x30 00401BF2 72 0A JB X11-ToBeB.00401BFE 00401BF4 83F8 39 CMP EAX,0x39 00401BF7 77 05 JA X11-ToBeB.00401BFE 00401BF9 83E8 30 SUB EAX,0x30 ; -0x30 数字 00401BFC EB 11 JMP X11-ToBeB.00401C0F 00401BFE 83F8 41 CMP EAX,0x41 00401C01 72 0A JB X11-ToBeB.00401C0D 00401C03 83F8 5A CMP EAX,0x5A 00401C06 77 05 JA X11-ToBeB.00401C0D 00401C08 83E8 37 SUB EAX,0x37 ; -0x37 大写字母 00401C0B EB 02 JMP X11-ToBeB.00401C0F 00401C0D 33C0 XOR EAX,EAX 00401C0F 03C1 ADD EAX,ECX ; 加到累加变量 00401C11 46 INC ESI 00401C12 8902 MOV DWORD PTR DS:[EDX],EAX 00401C14 83FE 08 CMP ESI,0x8 00401C17 ^ 7C B7 JL X11-ToBeB.00401BD0 00401C19 5F POP EDI ; 01389DBE 00401C1A B8 01000000 MOV EAX,0x1 00401C1F 5E POP ESI 00401C20 C3 RETN
// 第一段算法暴露(SMC)
00402A12 0FB645 F3 MOVZX EAX,BYTE PTR SS:[EBP-0xD] ; 第9、10位 00402A16 8B7D F8 MOV EDI,DWORD PTR SS:[EBP-0x8] ; 第1-8位 00402A19 8B5D F4 MOV EBX,DWORD PTR SS:[EBP-0xC] 00402A1C 69F0 01010101 IMUL ESI,EAX,0x1010101 ; ESI = EAX 第9、10位 * 0x1010101 00402A22 8B0493 MOV EAX,DWORD PTR DS:[EBX+EDX*4] ; 表数组,准备解密 00402A25 8D0C3A LEA ECX,DWORD PTR DS:[EDX+EDI] ; 初值第1-8位,后面就是对其累加1 00402A28 03C6 ADD EAX,ESI ; 表数组 + (第9、10位 * 0x1010101) 00402A2A 33C8 XOR ECX,EAX ; 第1-8位 xor (表 + (第9、10位 * 0x1010101)) 00402A2C 890C93 MOV DWORD PTR DS:[EBX+EDX*4],ECX ; 异或结果存回地址 00402A2F 42 INC EDX ; 计数器+1 00402A30 8B45 80 MOV EAX,DWORD PTR SS:[EBP-0x80] ; 0x5000 00402A33 C1E8 02 SHR EAX,0x2 ; /4 (>>2) 00402A36 3BD0 CMP EDX,EAX ; 5120次 00402A38 ^ 72 E8 JB X11-ToBeB.00402A22 if ( v68 & 0xFFFFFFFC ) { v71 = v168; v72 = v167; v73 = 0x1010101 * v166; do { *(_DWORD *)(v72 + 4 * v70) = (v73 + *(_DWORD *)(v72 + 4 * v70)) ^ (v70 + v71); ++v70; } while ( v70 < v135 >> 2 ); v69 = a1; }
// 异或解密后比较
00402A50 8A81 B0404300 MOV AL,BYTE PTR DS:[ECX+0x4340B0] ; 解密后数据必须与 [4340B0] 相同 00402A56 3A8411 B0404300 CMP AL,BYTE PTR DS:[ECX+EDX+0x4340B0] ; 比较异或解密后的数据是否正确 00402A5D 0F85 D7000000 JNZ 11-ToBeB.00402B3A 00402A63 41 INC ECX 00402A64 83F9 60 CMP ECX,0x60 ; 0x60字节 00402A67 ^ 72 E7 JB X11-ToBeB.00402A50
// 待解密代码:(后面省略一堆) 004F0000 83F08EA7 004F0004 3F0FBA29 004F0008 E747E97C 004F000C 93D03647 004F0010 EC72CD2C 004F0014 93C0BA2E 004F0018 90A578A3 004F001C 2A40BA2F 004F0020 DB3FF233 004F0024 9031FB09 004F0028 D1477258 004F002C 905E3DAC 004F0030 AB817C35 004F0034 6BD43434 004F0038 C49E84E4 004F003C 83B426AF 004F0040 51C0BA3A 004F0044 280080B8 004F0048 93BE3FF3 004F004C 8E36BA3B 004F0050 E9C0BA3C 004F0054 93C0BA29 004F0058 93C0B2C5 004F005C 1680CD3F 004F0060 92D0D6BC // 解密后必须为:(后面省略一堆) 004340B0 1070EC81 004340B4 55530000 004340B8 BC8B5756 004340BC 00108424 004340C0 BBF63300 004340C4 00000001 004340C8 0725C68B 004340CC 79800000 004340D0 C8834805 004340D4 07B140F8 004340D8 C68BC82A 004340DC 07E28399 004340E0 F8C1C203 004340E4 38148A03 004340E8 D322FAD2 004340EC 10349488 004340F0 46000002 004340F4 7C40FE83 004340F8 0002BDCF 004340FC 05BA0000 00434100 BE000000 00434104 00000014 00434108 000008B9 0043410C 8DC03300
// 据此写Python穷举第一段注册码(穷举第9、10位,组合很少,秒出)
code_x = [0x83F08EA7,0x3F0FBA29] code_m = [0x1070EC81,0x55530000] for sn_9_10 in range(0x00,0x100): #注册码第9、10位只有这几百种 y = code_m[0] ^ ((code_x[0] + sn_9_10 * 0x1010101) & 0xffffffff) y = y + 1 if code_m[1] ^ y == ((code_x[1] + sn_9_10 * 0x1010101) & 0xffffffff): print ('找到 sn_9_10: %x' % sn_9_10) print ('找到 sn_1_8: %x' % (y-1))
// 找到 sn_9_10: E1,找到 sn_1_8: 75A29C09,因此第一段注册码(sn_1-10):“75A29C09E1”
六、分析第二段算法
// 上述解密后的动态申请的内存作为函数运行(DES+奇数位取反)
00402ABE C745 84 3139393>MOV DWORD PTR SS:[EBP-0x7C],0x33393931 00402AC5 50 PUSH EAX 00402AC6 8BC2 MOV EAX,EDX 00402AC8 C745 88 3039303>MOV DWORD PTR SS:[EBP-0x78],0x37303930 00402ACF 50 PUSH EAX ; 除前10位之外的其他注册码 00402AD0 8D45 84 LEA EAX,DWORD PTR SS:[EBP-0x7C] 00402AD3 56 PUSH ESI 00402AD4 50 PUSH EAX ; DES 密钥 "19930907" 00402AD5 F3: PREFIX REP: ; 多余的前缀 00402AD6 0F7F85 DCFEFFFF MOVQ QWORD PTR SS:[EBP-0x124],MM0 00402ADD FFD7 CALL EDI ; 调用刚才解密的代码,DES+奇数位取反
// 接下来跟进 CALL EDI,痛苦就此开始,第一眼判断可能位DES算法,使用工具计算结果不符,无奈跟进去分析,巨复杂
// 初步判断作者可能对DES的几个表做了调整,因此我花费了时间自己码了DES代码,对每一个DES步骤、表格进行检查(这工作量你懂得)
// 结果......DES解密过程没有问题,与标准的相同!!!真正的手脚在于DES末尾IP-1逆置换、交换左右32bit结束后,作者竟然将bit的奇数位取反!!!
// DES 解密末尾的坑:
003B2494 /79 05 JNS X003B249B ; 非负数跳走 003B2496 |4A DEC EDX 003B2497 |83CA FE OR EDX,0xFFFFFFFE 003B249A |42 INC EDX ; 即bit奇数位取反,偶数位不变 003B249B \75 11 JNZ X003B24AE ; 下标偶数不跳,下标奇数跳 003B249D 389C34 10020000 CMP BYTE PTR SS:[ESP+ESI+0x210],BL ; 比较当前bit是否为1 003B24A4 0F95C1 SETNE CL ; 为1时,CL=0;为0时,CL=1 003B24A7 888C34 10020000 MOV BYTE PTR SS:[ESP+ESI+0x210],CL ; 为1时就替换为0,为0时就替换为1,即取反 003B24AE 46 INC ESI ; 指向下一位 003B24AF 83FE 40 CMP ESI,0x40 003B24B2 ^ 7C D8 JL X003B248C
// 所以破解算法思路变成了将解密结果先bit奇数位取反,然后用“19930907”密钥进行DES加密,结果就是注册码的第二段
// CALL EDI 必须返回“!HelloHaniella!” 字符串
00402AF2 8A10 MOV DL,BYTE PTR DS:[EAX] 00402AF4 3A11 CMP DL,BYTE PTR DS:[ECX] 00402AF6 75 30 JNZ X11-ToBeB.00402B28 ; 比较"!HelloHaniella!" 00402AF8 84D2 TEST DL,DL 00402AFA 74 12 JE X11-ToBeB.00402B0E ; 末尾0x00结束符 00402AFC 8A50 01 MOV DL,BYTE PTR DS:[EAX+0x1] 00402AFF 3A51 01 CMP DL,BYTE PTR DS:[ECX+0x1] ; 比较"!HelloHaniella!" 00402B02 75 24 JNZ X11-ToBeB.00402B28 00402B04 83C0 02 ADD EAX,0x2 00402B07 83C1 02 ADD ECX,0x2 00402B0A 84D2 TEST DL,DL ; 末尾0x00结束符 00402B0C ^ 75 E4 JNZ X11-ToBeB.00402AF2
// 比如,要使 CALL EDI 返回:
21 48 65 6C 6C 6F 48 61 6E 69 65 6C 6C 61 21 00 !HelloHaniella! --> 0x2148656C6C6F48616E69656C6C612100
// 二进制
0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 1 1 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0
// --> 奇数位取反(下标偶数位)
1 0 0 0 1 0 1 1 1 1 1 0 0 0 1 0 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 0 1 1 1 1 0 0 0 1 0 1 1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 1 1 1 0 0 0 1 0 1 1 1 0 1 0 1 0 1 0
// 转回十六进制和字符串
--> 0x8BE2CFC6C6C5E2CBC4C3CFC6C6CB8BAA --> 字符串:“嬧掀婆馑拿掀扑嫪”
// 以“19930907”为密钥加密“嬧掀婆馑拿掀扑嫪”得到“80217C048420956C15DA309FF2B69170”
// 与第一段注册码连接:75A29C09E180217C048420956C15DA309FF2B69170
// 这就是正确注册码!
七、分析算法多解
由于作者没有对输入的字符串长度做明确要求,而是对DES+奇数位取反结果的字符串做比较,且以0x00作为比较终止符,终止符之后的数据忽略,因此存在无数解。前42位保持“75A29C09E180217C048420956C15DA309FF2B69170”不变,在后面添加任意符合DES解密规则的字符都可以,提交了包含140组答案的附件。
八、总结
一个设计理念跟我的(第三题)接近的作品,防护手段太显眼,算法存在太多解,可惜了一个好作品。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界