[原创] CTF2017 第2题 CrackMe 破解分析
本文就纯OD手工完成算法分析,过程比较累,你有什么更好的办法请不吝分享。如果有猪蹄汤、排骨汤应该提前服用。
为了节省篇幅,本文直接拿事后算出来的注册码作为测试输入,更直观。功力有限,有些地方分析不到位,请见谅。
另外,我对本题作者无任何不敬之意,权当学习交流了。
一、首先,算法很绕,能破解出来也有一定的运气成分。看到群里有人提到是否可以直接修改CrackMe汇编代码,让它自己完成穷举,我觉得这个思路值得探索,事后补上了这章。开头先讲讲如何利用ODbgScript实现自动化穷举代码的写入和结果显示,就当开拓思维吧,以后在其他场合还是有用武之地的。
// Ctf.pediy.com crackme 2# Ollydbg 穷举脚本 // 本例测试穷举效率不高,每秒几千到一万多次,可能跟Crackme的算法很绕有关 // 所以直接通过本Crackme自身写入代码进行穷举需要几个小时时间才能完成 // OdbgScript 1.83 // Written by 爱琴海 // 2017/06/04 CMP $VERSION, "1.82" //比较是否大于1.82版,脚本基于1.82版本以上 JAE Initialize MSG "需要1.82版本以上的ODbgScript才能支持本程序,请更新!" RET Initialize: //定义变量 VAR Sn_address //储存注册码地址 VAR Eip_bak //备份恢复原始Eip PUSHA //高版本OdbgScript才支持该指令,保存寄存器现场 Set_break: //设置相关断点,提示用户 BP 4010BC BPGOTO 4010BC,Catch_break //设置4010BC断点,接管算法流程 BP 401257 BPGOTO 401257,Succes //设置401257断点,找到注册码时候接管流程 MSG "请在CrackMe输入窗口输入8位非0数字并回车继续" RUN Catch_break: //捕捉到4010BC断点,让程序自己完成一些初始化工作再接管 BC 4010BC //取消4010BC断点,有始有终 MOV Eip_bak,eip //保存当前eip,穷举结束时得恢复eip Start: //循环穷举主程序,为提高运行效率采用补丁方式,交给程序自己完成 MOV Sn_address,ecx //储存注册码地址,成功时需要调用显示 MOV [ecx],#313131313131313000# //我已经测试CrackMe自身穷举效率较低,穷举需要几个小时才能完成 //穷举初值11111110(接下里的累加1,将实际初值变为11111111) Inc_check: //写入代码,修改程序流程,实现穷举注册码功能 MOV [4010BC],#E95F660000909090909090# // JMP 00407720 以及 nop 填充,接管算法流程 MOV [40122B],#909090909090E976FEFFFF# // NOP JE 004010FA // JMP 004010AC 错误时继续 MOV [407700],#60B80800000083F8017413807C08FF397609C64408FF31FE4408FE48EBE861C3FE4107E8D8FFFFFF8039397601C3E99499FFFF# //累加、检查进位子程序,选择407700位置写入代码 // 407700: // PUSHAD // MOV EAX,0x8 // CMP EAX,0x1 // JE 0040771E // CMP BYTE PTR DS:[EAX+ECU-0x1],0x39 不是ECU而是ECX,显示乱码搞不懂 // JBE 0040771B // MOV BYTE PTR DS:[EAX+ECU-0x1],0x31 不是ECU而是ECX,显示乱码搞不懂 // INC BYTE PTR DS:[EAX+ECU-0x2] 不是ECU而是ECX,显示乱码搞不懂 // DEC EAX // JMP 00407706 // POPAD // RETN // 407720: // INC BYTE PTR DS:[ECX+0x7] // CALL 00407700 // CMP BYTE PTR DS:[ECX],0x39 最高位超过9就结束 // JBE 0040772E // RETN // 40772E: // JMP 004010C7 RUN Succes: //成功穷举到注册码 BC 401257 //取消401257断点,有始有终 MSG [Sn_address] //显示注册码 End: //收尾工作 POPA //高版本OdbgScript才支持该指令,彻底恢复寄存器现场 MOV eip,Eip_bak //穷举结束恢复eip MSG "穷举结束" ret //结束脚本
你猜结果如何? 虽然能穷举到注册码,但每秒几千次到上万次,效率比较低,需进一步分析算法实现破解。这章事后补充的,希望能有启发。

二、查壳,Microsoft Visual C/C++(6.0)[libc],木有壳。
三、OD载入,查找字符串,定位到:“WELL DONE!”,往上翻翻就到了主要代码。
四、在401053设断点,运行。动态跟踪时发现有检测GetTickCount,记得设置返回为0,避免干扰分析。
00401021 |. 68 C0904000 PUSH 2.004090C0 ; =======PEDIY CTF 2017=======\n
00401026 |. E8 B50B0000 CALL 2.00401BE0
0040102B |. 68 A0904000 PUSH 2.004090A0 ; -----CrackMe by: lelfei-----\n
00401030 |. E8 AB0B0000 CALL 2.00401BE0
00401035 |. 68 8C904000 PUSH 2.0040908C ; Please input KEY:
0040103A |. E8 A10B0000 CALL 2.00401BE0
0040103F |. 68 E0904000 PUSH 2.004090E0 ; \n
00401044 |. 8D4424 18 LEA EAX,DWORD PTR SS:[ESP+0x18]
00401048 |. 68 04010000 PUSH 0x104
0040104D |. 50 PUSH EAX
0040104E |. E8 7D0A0000 CALL 2.00401AD0 ; 输入 "97654321"
00401053 |. 8D7C24 20 LEA EDI,DWORD PTR SS:[ESP+0x20]
五、输入“97654321”之后回车断下,一步步跟踪:
00401057 |. 83C9 FF OR ECX,0xFFFFFFFF
0040105A |. 33C0 XOR EAX,EAX
0040105C |. 83C4 18 ADD ESP,0x18
0040105F |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401061 |. F7D1 NOT ECX
00401063 |. 83C1 FE ADD ECX,-0x2
00401066 |. 83F9 08 CMP ECX,0x8
00401069 |. 0F8C 0B020000 JL 2.0040127A ; 低于8位完蛋
0040106F |. 83F9 14 CMP ECX,0x14
00401072 |. 0F8F 02020000 JG 2.0040127A ; 高于20位完蛋
00401078 |. 33F6 XOR ESI,ESI
0040107A |. 33D2 XOR EDX,EDX
0040107C |. 85C9 TEST ECX,ECX
0040107E |. 88440C 08 MOV BYTE PTR SS:[ESP+ECX+0x8],AL
00401082 |. 7E 28 JLE X2.004010AC
00401084 |> 8A4414 08 /MOV AL,BYTE PTR SS:[ESP+EDX+0x8]
00401088 |. 3C 30 |CMP AL,0x30
0040108A |. 7E 04 |JLE X2.00401090 ; 非0,且>0
0040108C |. 3C 39 |CMP AL,0x39
0040108E |. 7E 01 |JLE X2.00401091 ; <=9
00401090 |> 46 |INC ESI
00401091 |> 42 |INC EDX
00401092 |. 3BD1 |CMP EDX,ECX
00401094 |.^ 7C EE \JL X2.00401084 ; 字符串格式检查 1-9
00401096 |. 85F6 TEST ESI,ESI
00401098 |. 74 12 JE X2.004010AC
0040109A |. 68 74904000 PUSH 2.00409074 ; key format error...\n
0040109F |. E8 3C0B0000 CALL 2.00401BE0
004010A4 |. 83C4 04 ADD ESP,0x4
004010A7 |. E9 DC010000 JMP 2.00401288
要求注册码为:8-20位,且1-9(非0数字)
六、跟踪到4010CF:
004010AC |> \8D8C24 0C010000 LEA ECX,DWORD PTR SS:[ESP+0x10C]
004010B3 |. E8 08020000 CALL 2.004012C0 ; 0x400位乱序填充
004010B8 |. 8D4C24 08 LEA ECX,DWORD PTR SS:[ESP+0x8]
004010BC |. C78424 34410000 0000000>MOV DWORD PTR SS:[ESP+0x4134],0x0
004010C7 |. 51 PUSH ECX
004010C8 |. 8D8C24 10010000 LEA ECX,DWORD PTR SS:[ESP+0x110]
004010CF |. E8 0C040000 CALL <2.change_decimal_1> ; 将用户输入字符串倒置按十进制逐位转换,12345679
004010D4 |. 8D8C24 0C010000 LEA ECX,DWORD PTR SS:[ESP+0x10C]
004010DB |. E8 D0010000 CALL <2.nothing>
此时感觉 CALL 4014E0 可疑,单步进入:
004014E0 <2.change>/$ 53 PUSH EBX ; 将输入字符当做十进制,逐位存放,逐位检查格式、进位
004014E1 |. 55 PUSH EBP
004014E2 |. 8B6C24 0C MOV EBP,DWORD PTR SS:[ESP+0xC]
004014E6 |. 56 PUSH ESI
004014E7 |. 57 PUSH EDI
004014E8 |. 8BD9 MOV EBX,ECX
004014EA |. 8BFD MOV EDI,EBP ; -1
004014EC |. 83C9 FF OR ECX,0xFFFFFFFF
004014EF |. 33C0 XOR EAX,EAX
004014F1 |. 33F6 XOR ESI,ESI ; 0
004014F3 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
004014F5 |. F7D1 NOT ECX
004014F7 |. 49 DEC ECX ; 位数
004014F8 |. 8973 04 MOV DWORD PTR DS:[EBX+0x4],ESI
004014FB |. 8BF9 MOV EDI,ECX
004014FD |. 3BFE CMP EDI,ESI ; 输入的位数全部转换为止
004014FF |. 7E 2E JLE X2.0040152F
00401501 |> 8A042E /MOV AL,BYTE PTR DS:[ESI+EBP]
00401504 |. 3C 30 |CMP AL,0x30 ; 1-9
00401506 |. 7C 22 |JL X2.0040152A
00401508 |. 3C 39 |CMP AL,0x39
0040150A |. 7F 1E |JG X2.0040152A
0040150C |. 0FBEC0 |MOVSX EAX,AL
0040150F |. 83E8 30 |SUB EAX,0x30 ; 转为所见十进制
00401512 |. 74 16 |JE X2.0040152A
00401514 |. 85F6 |TEST ESI,ESI
00401516 |. 74 0A |JE X2.00401522
00401518 |. 8BCE |MOV ECX,ESI
0040151A |> 8D0480 |/LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; 10*第2位 100*第3位 依次类推
0040151D |. 03C0 ||ADD EAX,EAX
0040151F |. 49 ||DEC ECX
00401520 |.^ 75 F8 |\JNZ X2.0040151A
00401522 |> 50 |PUSH EAX ; 所见十进制数
00401523 |. 8BCB |MOV ECX,EBX
00401525 |. E8 56000000 |CALL 2.00401580 ; 将用户输入的字符当做十进制进行每位转换,EAX可以看到
0040152A |> 46 |INC ESI
0040152B |. 3BF7 |CMP ESI,EDI
0040152D |.^ 7C D2 \JL X2.00401501 ; 没完,继续
0040152F |> 8BCB MOV ECX,EBX
00401531 |. E8 3A040000 CALL 2.00401970 ; 检查格式,进位什么的
00401536 |. 5F POP EDI
00401537 |. 5E POP ESI
00401538 |. 5D POP EBP
00401539 |. 5B POP EBX
0040153A \. C2 0400 RETN 0x4
原来此段代码是将输入字符当做十进制,逐位存放,逐位检查格式、进位,出题作者自己实现了最大0x400位十进制计算功能。
比如输入“97654321”得到 12345679。 CALL 00401970 各位可以自己跟踪看看,这里省点篇幅以免过于Up嗦。
七、返回到上一步,继续:
004010E0 |. 6A 09 PUSH 0x9
004010E2 |. 8D8C24 10010000 LEA ECX,DWORD PTR SS:[ESP+0x110]
004010E9 |. E8 42060000 CALL 2.00401730 ; 乘法 12345679 * 9 = 111111111
004010EE |. 8D8C24 0C010000 LEA ECX,DWORD PTR SS:[ESP+0x10C]
004010F5 |. E8 B6010000 CALL <2.nothing>
此处 CALL 401730 明显得跟踪进入:
00401730 /$ 6A FF PUSH -0x1 ; 乘法
00401732 |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00401738 |. 68 D6754000 PUSH 2.004075D6
0040173D |. 50 PUSH EAX
0040173E |. B8 20400000 MOV EAX,0x4020
00401743 |. 64:8925 00000000 MOV DWORD PTR FS:[0],ESP
0040174A |. E8 B1070000 CALL 2.00401F00
0040174F |. 53 PUSH EBX
00401750 |. 55 PUSH EBP
00401751 |. 56 PUSH ESI
00401752 |. 8BE9 MOV EBP,ECX
00401754 |. 57 PUSH EDI
00401755 |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+0x10]
00401759 |. E8 62FBFFFF CALL 2.004012C0 ; 填充
0040175E |. 33FF XOR EDI,EDI
00401760 |. 8D8C24 20200000 LEA ECX,DWORD PTR SS:[ESP+0x2020]
00401767 |. 57 PUSH EDI
00401768 |. 89BC24 3C400000 MOV DWORD PTR SS:[ESP+0x403C],EDI
0040176F |. E8 9CFBFFFF CALL 2.00401310 ; 填充
00401774 |. 8B8424 40400000 MOV EAX,DWORD PTR SS:[ESP+0x4040]
0040177B |. C68424 38400000 01 MOV BYTE PTR SS:[ESP+0x4038],0x1
00401783 |. 3BC7 CMP EAX,EDI
00401785 |. 74 59 JE X2.004017E0 ; 到了没
00401787 |> 99 /CDQ
00401788 |. B9 0A000000 |MOV ECX,0xA
0040178D |. F7F9 |IDIV ECX ; 取余
0040178F |. 8BF2 |MOV ESI,EDX
00401791 |. 8BD8 |MOV EBX,EAX
00401793 |. 85F6 |TEST ESI,ESI
00401795 |. 74 42 |JE X2.004017D9
00401797 |. 55 |PUSH EBP
00401798 |. 8D4C24 14 |LEA ECX,DWORD PTR SS:[ESP+0x14]
0040179C |. E8 9FFDFFFF |CALL 2.00401540 ; 读取十进制各位,检查格式
004017A1 |. 57 |PUSH EDI
004017A2 |. 8D4C24 14 |LEA ECX,DWORD PTR SS:[ESP+0x14]
004017A6 |. E8 B5FEFFFF |CALL 2.00401660
004017AB |. 56 |PUSH ESI
004017AC |. 8D4C24 14 |LEA ECX,DWORD PTR SS:[ESP+0x14]
004017B0 |. E8 2BFFFFFF |CALL 2.004016E0 ; 乘法,得跟进去看看
004017B5 |. 8D5424 10 |LEA EDX,DWORD PTR SS:[ESP+0x10]
004017B9 |. 8D8C24 20200000 |LEA ECX,DWORD PTR SS:[ESP+0x2020]
004017C0 |. 52 |PUSH EDX
004017C1 |. E8 1AFEFFFF |CALL 2.004015E0 ; 加法,得跟进去看看
004017C6 |. 8D8C24 20200000 |LEA ECX,DWORD PTR SS:[ESP+0x2020]
004017CD |. E8 CEFBFFFF |CALL <2.len()> ; 检查乘法之后的位数
004017D2 |. 3D 00040000 |CMP EAX,0x400 ; 0x400
004017D7 |. 7D 48 |JGE X2.00401821
004017D9 |> 8BC3 |MOV EAX,EBX
004017DB |. 47 |INC EDI
004017DC |. 85C0 |TEST EAX,EAX
004017DE |.^ 75 A7 \JNZ X2.00401787
004017E0 |> 8D8424 20200000 LEA EAX,DWORD PTR SS:[ESP+0x2020]
跟踪 CALL 00401540:
00401540 /$ 56 PUSH ESI ; 刚才的余,读取刚才用户输入的十进制各位
00401541 |. 8B7424 08 MOV ESI,DWORD PTR SS:[ESP+0x8]
00401545 |. 33D2 XOR EDX,EDX
00401547 |. 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4] ; 8 9
0040154A |. 85C0 TEST EAX,EAX
0040154C |. 8941 04 MOV DWORD PTR DS:[ECX+0x4],EAX
0040154F |. 7E 28 JLE X2.00401579
00401551 |. 53 PUSH EBX
00401552 |. 55 PUSH EBP
00401553 |. 57 PUSH EDI
00401554 |. 8BFE MOV EDI,ESI
00401556 |. 8D81 08100000 LEA EAX,DWORD PTR DS:[ECX+0x1008]
0040155C |. 2BF9 SUB EDI,ECX
0040155E |> 8B1C07 /MOV EBX,DWORD PTR DS:[EDI+EAX] ; 2f2
00401561 |. 8B28 |MOV EBP,DWORD PTR DS:[EAX]
00401563 |. 42 |INC EDX
00401564 |. 83C0 04 |ADD EAX,0x4
00401567 地址 |. 8B5C9E 08 |MOV EBX,DWORD PTR DS:[ESI+EBX*4+0x8] ; 取刚才结果
0040156B 密码 |. 895CA9 08 |MOV DWORD PTR DS:[ECX+EBP*4+0x8],EBX
0040156F |. 8B59 04 |MOV EBX,DWORD PTR DS:[ECX+0x4]
00401572 |. 3BD3 |CMP EDX,EBX
00401574 |.^ 7C E8 \JL X2.0040155E ; 完了么
00401576 |. 5F POP EDI
00401577 |. 5D POP EBP
00401578 |. 5B POP EBX
00401579 |> 5E POP ESI
0040157A \. C2 0400 RETN 0x4
留意到401567、40156B分别处理了当前计算结果所储存的地址和内容,为了方便后续分析,在这两处分别下条件记录断点记录
ECX+EBP*4+0x8、EBX,并命名“地址、密码”:

继续跟进 CALL 004016E0 定位核心功能:
004016E0 /$ 57 PUSH EDI
004016E1 |. 8B7C24 08 MOV EDI,DWORD PTR SS:[ESP+0x8]
004016E5 |. 85FF TEST EDI,EDI
004016E7 |. 7C 3D JL X2.00401726
004016E9 |. 83FF 0A CMP EDI,0xA
004016EC |. 7D 38 JGE X2.00401726
004016EE |. 8B41 04 MOV EAX,DWORD PTR DS:[ECX+0x4]
004016F1 |. 33D2 XOR EDX,EDX
004016F3 |. 85C0 TEST EAX,EAX
004016F5 |. 7E 24 JLE X2.0040171B
004016F7 |. 53 PUSH EBX
004016F8 |. 56 PUSH ESI
004016F9 |. 8DB1 08100000 LEA ESI,DWORD PTR DS:[ECX+0x1008]
004016FF |> 8B06 /MOV EAX,DWORD PTR DS:[ESI]
00401701 |. 83C6 04 |ADD ESI,0x4
00401704 |. 8B5C81 08 |MOV EBX,DWORD PTR DS:[ECX+EAX*4+0x8]
00401708 x |. 8D4481 08 |LEA EAX,DWORD PTR DS:[ECX+EAX*4+0x8]
0040170C y |. 0FAFDF |IMUL EBX,EDI ; *9 *7 *6 ......*3 *2 *1
0040170F x * y |. 8918 |MOV DWORD PTR DS:[EAX],EBX
00401711 |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+0x4]
00401714 |. 42 |INC EDX
00401715 |. 3BD0 |CMP EDX,EAX
00401717 |.^ 7C E6 \JL X2.004016FF ; 都乘完了么
00401719 |. 5E POP ESI
0040171A |. 5B POP EBX
0040171B |> E8 50020000 CALL 2.00401970 ; 格式检查,进位
00401720 |. 33C0 XOR EAX,EAX
00401722 |. 5F POP EDI
00401723 |. C2 0400 RETN 0x4
00401726 |> B8 01000000 MOV EAX,0x1
0040172B |. 5F POP EDI
0040172C \. C2 0400 RETN 0x4
可以看到:
0040170C y |. 0FAFDF |IMUL EBX,EDI ; *9 *7 *6 ......*3 *2 *1
这句就是作者实现自定义乘法的“乘法之根本法”,后续频繁调用这个子程序,为了减少劳动量,请设置401708、40170C、40170F条件记录断点,永不中断,分别永远记录EBX、EDI、EBX值,并标明表达式名称为“x、y、x * y”,以免后续搞混了。
在4017B5下断,清空记录窗口,运行,查看记录窗口:
Log data
地址 消息
00401567 COND: 地址 = 001289B0
0040156B COND: 密码 = 00000009
00401567 COND: 地址 = 00128664
0040156B COND: 密码 = 00000007
00401567 COND: 地址 = 0012823C
0040156B COND: 密码 = 00000006
00401567 COND: 地址 = 00128D00
0040156B COND: 密码 = 00000005
00401567 COND: 地址 = 00128234
0040156B COND: 密码 = 00000004
00401567 COND: 地址 = 001284E0
0040156B COND: 密码 = 00000003
00401567 COND: 地址 = 00127F94
0040156B COND: 密码 = 00000002
00401567 COND: 地址 = 00128C14
0040156B COND: 密码 = 00000001
00401708 COND: x = 00000009
0040170C COND: y = 00000009
0040170F COND: x * y = 00000051
00401708 COND: x = 00000007
0040170C COND: y = 00000009
0040170F COND: x * y = 0000003F
00401708 COND: x = 00000006
0040170C COND: y = 00000009
0040170F COND: x * y = 00000036
00401708 COND: x = 00000005
0040170C COND: y = 00000009
0040170F COND: x * y = 0000002D
00401708 COND: x = 00000004
0040170C COND: y = 00000009
0040170F COND: x * y = 00000024
00401708 COND: x = 00000003
0040170C COND: y = 00000009
0040170F COND: x * y = 0000001B
00401708 COND: x = 00000002
0040170C COND: y = 00000009
0040170F COND: x * y = 00000012
00401708 COND: x = 00000001
0040170C COND: y = 00000009
0040170F COND: x * y = 00000009
004017B5 断点位于 2.004017B5
取消4017B5断点,保留条件记录断点,跟踪 CALL 004015E0:
004015E0 /$ 51 PUSH ECX
004015E1 |. 8B51 04 MOV EDX,DWORD PTR DS:[ECX+0x4]
004015E4 |. 53 PUSH EBX
004015E5 |. 56 PUSH ESI
004015E6 |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+0x10]
004015EA |. 895424 08 MOV DWORD PTR SS:[ESP+0x8],EDX
004015EE |. 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4] ; [123d9c]=9 [127DE4]=9
004015F1 |. 3BD0 CMP EDX,EAX
004015F3 |. 7D 06 JGE X2.004015FB
004015F5 |. 8BD0 MOV EDX,EAX
004015F7 |. 895424 08 MOV DWORD PTR SS:[ESP+0x8],EDX
004015FB |> 33DB XOR EBX,EBX
004015FD |. 85D2 TEST EDX,EDX
004015FF |. 7E 50 JLE X2.00401651
00401601 |. 55 PUSH EBP
00401602 |. 57 PUSH EDI
00401603 |. 8BFE MOV EDI,ESI
00401605 |. 8D81 08100000 LEA EAX,DWORD PTR DS:[ECX+0x1008]
0040160B |. 2BF9 SUB EDI,ECX
0040160D |> 8B51 04 /MOV EDX,DWORD PTR DS:[ECX+0x4] ; 0 1
00401610 |. 3BDA |CMP EBX,EDX
00401612 |. 7C 1B |JL X2.0040162F
00401614 |. 8B2C07 |MOV EBP,DWORD PTR DS:[EDI+EAX] ; 2f2
00401617 |. 8B9491 08100000 |MOV EDX,DWORD PTR DS:[ECX+EDX*4+0x1008]
0040161E |. 8B6CAE 08 |MOV EBP,DWORD PTR DS:[ESI+EBP*4+0x8] ; 读取乘法结果
00401622 |. 896C91 08 |MOV DWORD PTR DS:[ECX+EDX*4+0x8],EBP
00401626 |. 8B51 04 |MOV EDX,DWORD PTR DS:[ECX+0x4]
00401629 |. 42 |INC EDX
0040162A |. 8951 04 |MOV DWORD PTR DS:[ECX+0x4],EDX
0040162D |. EB 14 |JMP X2.00401643
0040162F |> 3B5E 04 |CMP EBX,DWORD PTR DS:[ESI+0x4]
00401632 |. 7D 0F |JGE X2.00401643
00401634 |. 8B10 |MOV EDX,DWORD PTR DS:[EAX]
00401636 |. 8B2C07 |MOV EBP,DWORD PTR DS:[EDI+EAX]
00401639 |. 8B6CAE 08 |MOV EBP,DWORD PTR DS:[ESI+EBP*4+0x8]
0040163D a |. 8D5491 08 |LEA EDX,DWORD PTR DS:[ECX+EDX*4+0x8]
00401641 b |. 012A |ADD DWORD PTR DS:[EDX],EBP ; 加法
00401643 EDX > 040>|> 8B5424 10 |MOV EDX,DWORD PTR SS:[ESP+0x10]
00401647 |. 43 |INC EBX
00401648 |. 83C0 04 |ADD EAX,0x4
0040164B |. 3BDA |CMP EBX,EDX
0040164D |.^ 7C BE \JL X2.0040160D
0040164F |. 5F POP EDI
00401650 |. 5D POP EBP
00401651 |> E8 1A030000 CALL 2.00401970 ; 检查格式,进位
00401656 |. 5E POP ESI
00401657 |. 5B POP EBX
00401658 |. 59 POP ECX
00401659 \. C2 0400 RETN 0x4
可以看到:
00401641 b |. 012A |ADD DWORD PTR DS:[EDX],EBP ; 加法
这句就是作者实现自定义加法的“加法之根本法”,后续频繁调用这个子程序,为了减少劳动量,请设置40163D、401641、401643条件记录断点,永不中断,分别永远记录EBP、DWORD PTR DS:[EDX]、条件EDX>400记录DWORD PTR DS:[EDX]值,并标明表达式名称为“a、b、a + b”,以免后续搞混了。

此时还没到用到加法的时候,所以你也记录不到加法信息,别着急,保留条件记录断点,回到4010EE下断点,清空记录窗口,运行。
断下之后,查看记录窗口:
00401567 COND: 地址 = 0012CAF0
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C7A4
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C37C
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012CE40
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C374
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C620
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C0D4
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012CD54
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012C4C0
0040156B COND: 密码 = 00000001
004010EE 断点位于 2.004010EE
通过上述记录信息,很快就可以理解本步骤算法含义:12345679 * 9 = 111111111
八、从上文 4010EE 继续:
004010FA |> /8D5424 08 /LEA EDX,DWORD PTR SS:[ESP+0x8]
004010FE |. |8D8C24 1C210000 |LEA ECX,DWORD PTR SS:[ESP+0x211C]
00401105 |. |52 |PUSH EDX
00401106 |. |E8 45020000 |CALL <2.change_decimal_2> ; 将用户输入字符串按十进制逐位转换,
12345679
0040110B |. |8D8424 1C210000 |LEA EAX,DWORD PTR SS:[ESP+0x211C]
00401112 |. |8D8C24 0C010000 |LEA ECX,DWORD PTR SS:[ESP+0x10C]
00401119 |. |50 |PUSH EAX
0040111A |. |C68424 38410000 01 |MOV BYTE PTR SS:[ESP+0x4138],0x1
00401122 |. |E8 19070000 |CALL 2.00401840 ; 完成逐位乘法、加法,结果,
12345678987654321
00401127 |. |6A 09 |PUSH 0x9
00401129 |. |8D8C24 10010000 |LEA ECX,DWORD PTR SS:[ESP+0x110]
00401130 |. |8BF0 |MOV ESI,EAX
00401132 |. |E8 F9050000 |CALL 2.00401730 ; 乘法 * 9
00401137 |. |8D8C24 0C010000 |LEA ECX,DWORD PTR SS:[ESP+0x10C]
0040113E |. |03F0 |ADD ESI,EAX
00401140 |. |E8 6B010000 |CALL <2.nothing>
CALL 401840 和 CALL 401730 是算法的核心,分别实现了逐位乘法、加法,累加得到最终结果。(CALL 401730 在上文中已经分析过了)
00401840 /$ 6A FF PUSH -0x1
00401842 |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00401848 |. 68 F6754000 PUSH 2.004075F6
0040184D |. 50 PUSH EAX
0040184E |. B8 24400000 MOV EAX,0x4024
00401853 |. 64:8925 00000000 MOV DWORD PTR FS:[0],ESP
0040185A |. E8 A1060000 CALL 2.00401F00
0040185F |. 53 PUSH EBX
00401860 |. 55 PUSH EBP
00401861 |. 56 PUSH ESI
00401862 |. 8BF1 MOV ESI,ECX
00401864 |. 57 PUSH EDI
00401865 |. 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+0x14]
00401869 |. E8 52FAFFFF CALL 2.004012C0 ; 填充
0040186E |. 33FF XOR EDI,EDI
00401870 |. 8D8C24 24200000 LEA ECX,DWORD PTR SS:[ESP+0x2024]
00401877 |. 57 PUSH EDI
00401878 |. 89BC24 40400000 MOV DWORD PTR SS:[ESP+0x4040],EDI
0040187F |. E8 8CFAFFFF CALL 2.00401310 ; 填充
00401884 |. 8B9C24 44400000 MOV EBX,DWORD PTR SS:[ESP+0x4044]
0040188B |. 8B4E 04 MOV ECX,DWORD PTR DS:[ESI+0x4]
0040188E |. C68424 3C400000 01 MOV BYTE PTR SS:[ESP+0x403C],0x1 ; 1
00401896 |. 8B43 04 MOV EAX,DWORD PTR DS:[EBX+0x4] ; 8
00401899 |. 03C8 ADD ECX,EAX
0040189B |. 81F9 00040000 CMP ECX,0x400 ; 8+9=17,检查可能的位数
004018A1 |. 7E 1F JLE X2.004018C2
004018A3 |> 8D8C24 24200000 LEA ECX,DWORD PTR SS:[ESP+0x2024]
004018AA |. E8 E1FAFFFF CALL 2.00401390
004018AF |. 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+0x14]
004018B3 |. E8 D8FAFFFF CALL 2.00401390
004018B8 |. B8 01000000 MOV EAX,0x1
004018BD |. E9 90000000 JMP 2.00401952
004018C2 |> 3BC7 CMP EAX,EDI
004018C4 |. 7E 5F JLE X2.00401925 ; 结束了么
004018C6 |. 8D93 08100000 LEA EDX,DWORD PTR DS:[EBX+0x1008]
004018CC |. 895424 10 MOV DWORD PTR SS:[ESP+0x10],EDX
004018D0 |> 8B4424 10 /MOV EAX,DWORD PTR SS:[ESP+0x10]
004018D4 |. 56 |PUSH ESI
004018D5 |. 8B08 |MOV ECX,DWORD PTR DS:[EAX]
004018D7 |. 8B6C8B 08 |MOV EBP,DWORD PTR DS:[EBX+ECX*4+0x8] ; 9 7 6 5 4 3 2 1
004018DB |. 8D4C24 18 |LEA ECX,DWORD PTR SS:[ESP+0x18]
004018DF |. E8 5CFCFFFF |CALL 2.00401540 ; 检查格式
004018E4 |. 57 |PUSH EDI
004018E5 |. 8D4C24 18 |LEA ECX,DWORD PTR SS:[ESP+0x18]
004018E9 |. E8 72FDFFFF |CALL 2.00401660 ; -1
004018EE |. 55 |PUSH EBP
004018EF |. 8D4C24 18 |LEA ECX,DWORD PTR SS:[ESP+0x18]
004018F3 |. E8 38FEFFFF |CALL 2.00401730 ; 乘法
004018F8 |. 8D5424 14 |LEA EDX,DWORD PTR SS:[ESP+0x14]
004018FC |. 8D8C24 24200000 |LEA ECX,DWORD PTR SS:[ESP+0x2024]
00401903 |. 52 |PUSH EDX
00401904 |. E8 D7FCFFFF |CALL 2.004015E0 ; 加法
00401909 |. 817E 04 00040000 |CMP DWORD PTR DS:[ESI+0x4],0x400
00401910 |.^ 7D 91 |JGE X2.004018A3
00401912 |. 8B4C24 10 |MOV ECX,DWORD PTR SS:[ESP+0x10]
00401916 |. 8B43 04 |MOV EAX,DWORD PTR DS:[EBX+0x4]
00401919 |. 47 |INC EDI
0040191A |. 83C1 04 |ADD ECX,0x4
0040191D |. 3BF8 |CMP EDI,EAX ; 8位结束了么
0040191F |. 894C24 10 |MOV DWORD PTR SS:[ESP+0x10],ECX
00401923 |.^ 7C AB \JL X2.004018D0
00401925 |> 8BCE MOV ECX,ESI
00401927 |. E8 34010000 CALL 2.00401A60
0040192C |. 8D8424 24200000 LEA EAX,DWORD PTR SS:[ESP+0x2024]
00401933 |. 8BCE MOV ECX,ESI
00401935 |. 50 PUSH EAX
00401936 |. E8 05FCFFFF CALL 2.00401540
0040193B |. 8D8C24 24200000 LEA ECX,DWORD PTR SS:[ESP+0x2024]
00401942 |. E8 49FAFFFF CALL 2.00401390
00401947 |. 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+0x14]
0040194B |. E8 40FAFFFF CALL 2.00401390
00401950 |. 33C0 XOR EAX,EAX
00401952 |> 8B8C24 34400000 MOV ECX,DWORD PTR SS:[ESP+0x4034]
00401959 |. 5F POP EDI
0040195A |. 5E POP ESI
0040195B |. 5D POP EBP
0040195C |. 5B POP EBX
0040195D |. 64:890D 00000000 MOV DWORD PTR FS:[0],ECX
00401964 |. 81C4 30400000 ADD ESP,0x4030
0040196A \. C2 0400 RETN 0x4
清空记录窗口,在 401137 设置断点,运行,此时查看记录窗口正在迅速回显记录,条数很多,此时静下心来逐条查看计算记录,
因为条数太多,这里只罗列最后结果那几行,剩下的记录请见附件log:
00401567 COND: 地址 = 0012C728
0040156B COND: 密码 = 00000001
00401567 COND: 地址 = 0012CCFC
0040156B COND: 密码 = 00000002
00401567 COND: 地址 = 0012C584
0040156B COND: 密码 = 00000003
00401567 COND: 地址 = 0012C48C
0040156B COND: 密码 = 00000004
00401567 COND: 地址 = 0012C154
0040156B COND: 密码 = 00000005
00401567 COND: 地址 = 0012C3EC
0040156B COND: 密码 = 00000006
00401567 COND: 地址 = 0012CC8C
0040156B COND: 密码 = 00000007
00401567 COND: 地址 = 0012C220
0040156B COND: 密码 = 00000008
00401567 COND: 地址 = 0012C188
0040156B COND: 密码 = 00000009
00401567 COND: 地址 = 0012C740
0040156B COND: 密码 = 00000008
00401567 COND: 地址 = 0012BFD0
0040156B COND: 密码 = 00000007
00401567 COND: 地址 = 0012C8D8
0040156B COND: 密码 = 00000006
00401567 COND: 地址 = 0012C11C
0040156B COND: 密码 = 00000005
00401567 COND: 地址 = 0012C0BC
0040156B COND: 密码 = 00000004
00401567 COND: 地址 = 0012CBEC
0040156B COND: 密码 = 00000003
00401567 COND: 地址 = 0012C598
0040156B COND: 密码 = 00000002
00401567 COND: 地址 = 0012BFEC
0040156B COND: 密码 = 00000001
00401137 断点位于 2.00401137
整理计算记录、步骤:
1、12345679*9=111111111
2、111111111*9=999999999
3、1111111110*7=7777777770
4、7777777770+999999999=8777777769
5、11111111100*6=66666666600
6、66666666600+8777777769=75444444369
7、111111111000*5=555555555000
8、555555555000+75444444369=630999999369
9、1111111110000*4=4444444440000
10、4444444440000+630999999369=5075444439369
11、11111111100000*3=33333333300000
12、33333333300000+5075444439369=38408777739369
13、111111111000000*2=222222222000000
14、222222222000000+38408777739369=260630999739369
15、1111111110000000*1=1111111110000000
16、1111111110000000+260630999739369=1371742109739369
17、1371742109739369*9=12345678987654321
输出:12345678987654321
根据追踪记录,不难看出本作品是以用户输入倒置为十进制数,自定义了0x400位十进制计算乘法、加法(从低位到高位,逐位乘法、加法并进行进位)
Python 实现该部分算法:
#计算用户输入转为十进制之后进行乘法和累加 def calchash(input): input_str = str(input) calchash1 = input * 9 calchash2 = 0 count2 = 1 while count2 <= len(input_str): calchash2 = calchash2 + calchash1 * (input % 10) calchash1 *= 10 input //= 10 count2 += 1 calchash2 *= 9 return calchash2
先在Python中测试:
输入“11111111”--> 11111111 -- > 16位:9999999800000001
输入“87654321”--> 12345678 -- > 17位:12345676987654404
输入“99999999”--> 99999999 -- > 18位:809999983800000081
输入8位-->16-18位
输入9位-->18-21位
依次类推,先得到大概输入、输出位数的范围,便于后续选择。
九、跟踪关键比较程序:
0040114D |. 8D8C24 0C010000 |LEA ECX,DWORD PTR SS:[ESP+0x10C]
00401154 |. E8 47020000 |CALL <2.len()> ; 读取结果位数
00401159 |. 25 01000080 |AND EAX,0x80000001
0040115E |. 79 05 |JNS X2.00401165 ; 最高位非0时跳转
00401160 |. 48 |DEC EAX
00401161 |. 83C8 FE |OR EAX,0xFFFFFFFE
00401164 |. 40 |INC EAX
00401165 |> 83F8 01 |CMP EAX,0x1 ; 位数必须为奇数,不能跳
00401168 |. 0F85 A7000000 |JNZ 2.00401215 ; 当输入8位数时,结果16-18位,所以可以假设
17位
0040116E |. 8D8C24 0C010000 |LEA ECX,DWORD PTR SS:[ESP+0x10C]
00401175 |. E8 26020000 |CALL <2.len()> ; 取结果位数
0040117A |. D1F8 |SAR EAX,1 ; 算术右移1位,相当于/2取整,如果输入8位,
则结果8
0040117C |. 50 |PUSH EAX
0040117D |. 8D8C24 10010000 |LEA ECX,DWORD PTR SS:[ESP+0x110] ; 0012BF20 -- 计算结果
00401184 |. E8 27020000 |CALL 2.004013B0 ; 如果17位结果,则取第9位
00401189 |. 6A 00 |PUSH 0x0
0040118B |. 8D8C24 20210000 |LEA ECX,DWORD PTR SS:[ESP+0x2120] ; 0012DF30 -- 用户输入十进制
00401192 |. 8BF8 |MOV EDI,EAX
00401194 |. E8 17020000 |CALL 2.004013B0 ; 用户输入的第1位(十进制的个位)
00401199 |. 3BF8 |CMP EDI,EAX ; 用户输入的第1位(十进制个位)== 计算结果
的中间位
0040119B |. 8D8C24 1C210000 |LEA ECX,DWORD PTR SS:[ESP+0x211C]
004011A2 |. 75 78 |JNZ X2.0040121C ; 不能跳
004011A4 |. 6A 00 |PUSH 0x0
004011A6 |. E8 F5010000 |CALL <2.len()> ; 用户输入十进制位数,比如8位
004011AB |. 48 |DEC EAX ; 8-1 = 7
004011AC |. 8D8C24 20210000 |LEA ECX,DWORD PTR SS:[ESP+0x2120] ; 0012DF30 -- 用户输入十进制
004011B3 |. 50 |PUSH EAX
004011B4 |. 6A 01 |PUSH 0x1
004011B6 |. E8 E5010000 |CALL <2.len()> ; 用户输入十进制位数,比如8位
004011BB |. BE 01000000 |MOV ESI,0x1
004011C0 |. 8D8C24 18010000 |LEA ECX,DWORD PTR SS:[ESP+0x118] ; 0012BF20 -- 计算结果
004011C7 |. 2BF0 |SUB ESI,EAX ; 1-8 = -7
004011C9 |. E8 D2010000 |CALL <2.len()> ; 计算结果的位数
004011CE |. 03F0 |ADD ESI,EAX ; 17-7=10
004011D0 |. 8D8C24 28210000 |LEA ECX,DWORD PTR SS:[ESP+0x2128] ; 0012DF30 -- 用户输入十进制
004011D7 |. 56 |PUSH ESI ; 10
004011D8 |. 51 |PUSH ECX ; 0012DF30 -- 用户输入十进制
004011D9 |. 8D8C24 20010000 |LEA ECX,DWORD PTR SS:[ESP+0x120] ; 0012BF20 -- 计算结果
004011E0 |. E8 FB010000 |CALL 2.004013E0 ; 关键比较1,逐位比较用户输入的第2-8位是否
与计算结果的第7-1位相等
004011E5 |. 6A 01 |PUSH 0x1
004011E7 |. 8D8C24 20210000 |LEA ECX,DWORD PTR SS:[ESP+0x2120]
004011EE |. 8BF0 |MOV ESI,EAX
004011F0 |. E8 AB010000 |CALL <2.len()>
004011F5 |. 48 |DEC EAX
004011F6 |. 8D9424 20210000 |LEA EDX,DWORD PTR SS:[ESP+0x2120]
004011FD |. 50 |PUSH EAX
004011FE |. 6A 01 |PUSH 0x1
00401200 |. 6A 00 |PUSH 0x0
00401202 |. 52 |PUSH EDX
00401203 |. 8D8C24 20010000 |LEA ECX,DWORD PTR SS:[ESP+0x120]
0040120A |. E8 D1010000 |CALL 2.004013E0 ; 关键比较2,逐位比较用户输入的第8-2位是否
与计算结果的17-11位相等
0040120F |. 03F0 |ADD ESI,EAX
00401211 |. 74 44 |JE X2.00401257 ; 正确出口
其中:
00401168 |. /0F85 A7000000 |JNZ 2.00401215 ; 当输入8位数时,结果16-18位,所以可以假设
17位
提示了计算结果极有可能是奇数位。
004011A2 |. /75 78 |JNZ X2.0040121C ; 不能跳
提示了用户输入的第1位(十进制个位)== 计算结果的中间位。
CALL 004013E0 关键比较,逐位比较用户输入的第2-8位是否与计算结果的第7-1位相等;用户输入的第8-2位是否与计算结果的17-11位相等:
004013E0 /$ 51 PUSH ECX ; 0012BF20 -- 计算结果
004013E1 |. 8B5424 14 MOV EDX,DWORD PTR SS:[ESP+0x14] ; 用户输入的十进制位数-1,比如输入8位,则
这里是7
004013E5 |. 56 PUSH ESI ; A 怎么来的呢,计算结果位数用户输入位
数=178=10
004013E6 |. 57 PUSH EDI ; 计算结果中间位(计算结果奇数位)
004013E7 |. 8BF9 MOV EDI,ECX ; 0012BF20 -- 计算结果
004013E9 |. 8B4C24 10 MOV ECX,DWORD PTR SS:[ESP+0x10] ; 0012DF30 -- 用户输入十进制
004013ED |. C74424 08 00000000 MOV DWORD PTR SS:[ESP+0x8],0x0 ; 12BDF8 -- 初值0
004013F5 |. 85D2 TEST EDX,EDX
004013F7 |. 7E 7E JLE X2.00401477
004013F9 |. 8B4424 18 MOV EAX,DWORD PTR SS:[ESP+0x18] ; 注意 12BE08 初值 1
004013FD |. 53 PUSH EBX
004013FE |. 55 PUSH EBP
004013FF |. 8B6C24 1C MOV EBP,DWORD PTR SS:[ESP+0x1C] ; 第A位(其实A+1,下标0)
00401403 |. 8D5C10 FF LEA EBX,DWORD PTR DS:[EAX+EDU-0x1] ; 11
00401407 |. 8BF0 MOV ESI,EAX
00401409 |. 895C24 24 MOV DWORD PTR SS:[ESP+0x24],EBX ; ebx=11
0040140D |. 2BE8 SUB EBP,EAX ; 10-1
0040140F |> 8B5F 04 /MOV EBX,DWORD PTR DS:[EDI+0x4] ; 计算结果位数17
00401412 |. 8D042E |LEA EAX,DWORD PTR DS:[ESI+EBP] ; 1+9 2+9 ...
00401415 |. 3BC3 |CMP EAX,EBX
00401417 |. 7C 0D |JL X2.00401426 ; 计算结果的比较完了么
00401419 |. 8B4424 10 |MOV EAX,DWORD PTR SS:[ESP+0x10]
0040141D |. 40 |INC EAX
0040141E |. 33DB |XOR EBX,EBX
00401420 |. 894424 10 |MOV DWORD PTR SS:[ESP+0x10],EAX
00401424 |. EB 02 |JMP X2.00401428
00401426 |> 8BD8 |MOV EBX,EAX
00401428 |> E8 73FFFFFF |CALL <2.len()> ; 用户输入十进制位数
0040142D |. 3BF0 |CMP ESI,EAX
0040142F |. 7C 0D |JL X2.0040143E ; 用户输入的比较完了么
00401431 |. 8B4424 10 |MOV EAX,DWORD PTR SS:[ESP+0x10]
00401435 |. 40 |INC EAX
00401436 |. 894424 10 |MOV DWORD PTR SS:[ESP+0x10],EAX
0040143A |. 33C0 |XOR EAX,EAX
0040143C |. EB 0E |JMP X2.0040144C
0040143E |> 8A4424 28 |MOV AL,BYTE PTR SS:[ESP+0x28] ; 注意 12BE10 0
00401442 |. 84C0 |TEST AL,AL
00401444 |. 8B4424 24 |MOV EAX,DWORD PTR SS:[ESP+0x24]
00401448 |. 75 02 |JNZ X2.0040144C
0040144A |. 8BC6 |MOV EAX,ESI
0040144C |> 8B9C9F 08100000 |MOV EBX,DWORD PTR DS:[EDI+EBX*4+0x1008] ; 计算结果第7、6、5、4、3、2、1位;第17、
16、15、14、13、12、11位
00401453 |. 8B8481 08100000 |MOV EAX,DWORD PTR DS:[ECX+EAX*4+0x1008] ; 用户输入第2、3、4、5、6、7、8位;第8、7
、6、5、4、3、2位
0040145A |. 8B5C9F 08 |MOV EBX,DWORD PTR DS:[EDI+EBX*4+0x8]
0040145E |. 3B5C81 08 |CMP EBX,DWORD PTR DS:[ECX+EAX*4+0x8] ; 关键比较,逐位比较用户输入的第2-8位是否
与计算结果的第7-1位相等;用户输入的第8-2位是否与计算结果的17-11位相等
00401462 |. 74 04 |JE X2.00401468
00401464 |. FF4424 10 |INC DWORD PTR SS:[ESP+0x10]
00401468 |> 8B4424 24 |MOV EAX,DWORD PTR SS:[ESP+0x24]
0040146C |. 48 |DEC EAX
0040146D |. 46 |INC ESI
0040146E |. 4A |DEC EDX
0040146F |. 894424 24 |MOV DWORD PTR SS:[ESP+0x24],EAX
00401473 |.^ 75 9A \JNZ X2.0040140F
00401475 |. 5D POP EBP
00401476 |. 5B POP EBX
00401477 |> 8B81 0C200000 MOV EAX,DWORD PTR DS:[ECX+0x200C]
0040147D |. 8BB7 0C200000 MOV ESI,DWORD PTR DS:[EDI+0x200C]
00401483 |. 8B4C24 08 MOV ECX,DWORD PTR SS:[ESP+0x8]
00401487 |. 33C6 XOR EAX,ESI
00401489 |. C1E8 07 SHR EAX,0x7
0040148C |. 5F POP EDI
0040148D |. 03C1 ADD EAX,ECX
0040148F |. 5E POP ESI
00401490 |. 59 POP ECX
00401491 \. C2 1400 RETN 0x14
至此算法部分基本清楚了。
十、算法逆向:
输入:“97654321”--> 12345679
输出:12345678987654321
满足条件1:每个输入字符为1-9数字
满足条件2:输出结果奇数位
满足条件3:输出结果中间位必须等于输入第1位(十进制末位)
满足条件4:用户输入的第2-8位(十进制7-1位)必须与计算结果的第7-1位相等
满足条件5:用户输入的第8-2位(十进制1-7位)必须与计算结果的17-11位相等
考虑先从8位数开始穷举:(也许算法深入分析后会有一些简便运算方法或者效率提升,但是人生苦短,只要出答案即可,不浪费时间)
Python Keygen:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Ctf.pediy.com ctf2017 crackme 2 keygen
# python 3.6.0 32bit
# written by 爱琴海
# 2017/06/14
import time
def main():
start_time = time.clock()
for sn in range(11111111,100000000):
if check_input(sn) == 1:
if check_answer(sn,calchash(sn)) == 1:
print ('found sn:',str(sn)[::-1])
break
print ('use time: %.3f second' % (time.clock()-start_time))
return
#校验计算结果,假设输入8位,如果测试更多位数需要对程序做完善,这里图省事
def check_answer(input1,out1):
input1_str = str(input1)
out1_str = str(out1)
count = len(out1_str) // 2
if len(out1_str) % 2 == 1:
if out1_str[count] == input1_str[7]:
if out1_str[0:7] == input1_str[0:7]:
input1_str = input1_str[::-1]
if out1_str[10:17] == input1_str[1:8]:
return 1
else:
return 0
else:
return 0
else:
return 0
#检查输入是否包含0
def check_input(input2):
input2_str = str(input2)
count1 = 1
while count1 <= len(input2_str):
if input2 % 10 == 0:
return 0
else:
input2 //= 10
count1 += 1
return 1
#计算用户输入转为十进制之后进行乘法和累加
def calchash(input):
input_str = str(input)
calchash1 = input * 9
calchash2 = 0
count2 = 1
while count2 <= len(input_str):
calchash2 = calchash2 + calchash1 * (input % 10)
calchash1 *= 10
input //= 10
count2 += 1
calchash2 *= 9
return calchash2
if __name__=="__main__":
main()如果换好一些的电脑、不采用脚本计算,速度应该会有显著提升。

十一、总结:
本题作者自定义实现了最大0x400位十进制乘法、加法运算,注册码算法对原文和密文都有关联,一时半会儿我认为穷举是最合适的。
本题难点在于对众多汇编指令的跟踪、理解、总结,对模拟大数运算的了解,以及调试方法的恰当应用,否则会把你绕晕。


