-
-
[原创]CTF2016第18题ELyt's CrackMe分析(详细,虚拟指令还原)
-
发表于: 2016-12-7 15:45 5373
-
Elyt作者你好,看着代码简短,忍不住想出手,怎想是VM解码
解题过程:
一、第一天吃了中饭午休,看了题目没有加壳也没有反调试,对于我这样忙于工作的人来说,只要不太费心费力的题目就会感兴趣分析,研究算法很有意思,其他的看到就恶心,因为真的是忙里偷闲,不太可能花费连续几个小时时间去琢磨。如果作品直奔算法去的,应该点赞!
接入正题,消息断点,第一部分注册码算法流程:
00401205 E8 E6300000 CALL Crackme.004042F0 ; 取长度
0040120A 83C4 04 ADD ESP,0x4
0040120D 8945 B4 MOV DWORD PTR SS:[EBP-0x4C],EAX
00401210 837D B4 0D CMP DWORD PTR SS:[EBP-0x4C],0xD ; 小于13位转移
00401214 0F8C 11010000 JL <Crackme.fail>
0040121A C745 AC 0000000>MOV DWORD PTR SS:[EBP-0x54],0x0
00401221 C745 B0 0D00000>MOV DWORD PTR SS:[EBP-0x50],0xD
00401228 EB 09 JMP XCrackme.00401233
0040122A 8B55 B0 MOV EDX,DWORD PTR SS:[EBP-0x50]
0040122D 83C2 01 ADD EDX,0x1
00401230 8955 B0 MOV DWORD PTR SS:[EBP-0x50],EDX
00401233 8B45 B0 MOV EAX,DWORD PTR SS:[EBP-0x50]
00401236 3B45 B4 CMP EAX,DWORD PTR SS:[EBP-0x4C] ; 小于等于13位时转移
00401239 7D 27 JGE XCrackme.00401262
0040123B 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+0xC]
0040123E 034D B0 ADD ECX,DWORD PTR SS:[EBP-0x50]
00401241 0FBE11 MOVSX EDX,BYTE PTR DS:[ECX]
00401244 83FA 30 CMP EDX,0x30
00401247 7C 0E JL XCrackme.00401257 ; 小于“0”时跳转
00401249 8B45 0C MOV EAX,DWORD PTR SS:[EBP+0xC]
0040124C 0345 B0 ADD EAX,DWORD PTR SS:[EBP-0x50]
0040124F 0FBE08 MOVSX ECX,BYTE PTR DS:[EAX]
00401252 83F9 39 CMP ECX,0x39 ; 小于等于“9”时跳转
00401255 7E 09 JLE XCrackme.00401260
00401257 C745 AC 0100000>MOV DWORD PTR SS:[EBP-0x54],0x1
0040125E EB 02 JMP XCrackme.00401262
思考1:注册码必须大于等于13位,并且13位之后的字符必须为0-9的数字
00401278 83C2 01 ADD EDX,0x1 ; 指针+1
0040127B 8955 B0 MOV DWORD PTR SS:[EBP-0x50],EDX
0040127E 837D B0 08 CMP DWORD PTR SS:[EBP-0x50],0x8 ; 循环结束9次
00401282 7F 1F JG XCrackme.004012A3
00401284 8B45 0C MOV EAX,DWORD PTR SS:[EBP+0xC] ; 读SN
00401287 0345 B0 ADD EAX,DWORD PTR SS:[EBP-0x50]
0040128A 0FBE08 MOVSX ECX,BYTE PTR DS:[EAX] ; 读第1字节
0040128D 8B55 B0 MOV EDX,DWORD PTR SS:[EBP-0x50]
00401290 0FBE4415 9C MOVSX EAX,BYTE PTR SS:[EBP+EDX-0x64] ; 读密表1字节
00401295 33C8 XOR ECX,EAX ; 与密表 XOR
00401297 83E9 41 SUB ECX,0x41 ; 异或结果减去0X41
0040129A 8B55 B0 MOV EDX,DWORD PTR SS:[EBP-0x50]
0040129D 894C95 B8 MOV DWORD PTR SS:[EBP+EDX*4-0x48],ECX ; 结果储存
004012A1 ^ EB D2 JMP XCrackme.00401275
004012A3 837D B8 01 CMP DWORD PTR SS:[EBP-0x48],0x1 ; 密文第1字节大于1才行
004012A7 7F 07 JG XCrackme.004012B0
004012A9 C745 AC 0100000>MOV DWORD PTR SS:[EBP-0x54],0x1 ; 注意可能时标志位为1就死
004012B0 C745 B0 0000000>MOV DWORD PTR SS:[EBP-0x50],0x0
004012B7 EB 09 JMP XCrackme.004012C2
思考2:注册码前9字节与密表“0x33 0x21 0x22 0x21 0x35 0x7c 0x62 0x65 0x6e”分别异或,并减去0x41,得出密文,密文第一位必须大于0x1
004012D2 3B4495 BC CMP EAX,DWORD PTR SS:[EBP+EDX*4-0x44] ; 比较密文由小到大排列
004012D6 7C 09 JL XCrackme.004012E1
00401302 837D B0 09 CMP DWORD PTR SS:[EBP-0x50],0x9 ; 循环计数比较
00401306 7D 10 JGE XCrackme.00401318
00401308 8B55 B0 MOV EDX,DWORD PTR SS:[EBP-0x50]
0040130B 8B45 A8 MOV EAX,DWORD PTR SS:[EBP-0x58] ; 初值1
0040130E 0FAF4495 B8 IMUL EAX,DWORD PTR SS:[EBP+EDX*4-0x48] ; 相乘
00401313 8945 A8 MOV DWORD PTR SS:[EBP-0x58],EAX ; 结果替换初值
00401316 ^ EB E1 JMP XCrackme.004012F9
00401318 B9 86204C0D MOV ECX,0xD4C2086 ; ECX=0xD4C2086
0040131D 2B4D A8 SUB ECX,DWORD PTR SS:[EBP-0x58] ; 相减
00401320 B8 64000000 MOV EAX,0x64 ; EAX=0x64
00401325 99 CDQ
00401326 F7F9 IDIV ECX ; 0x64 / ECX
00401328 8945 A8 MOV DWORD PTR SS:[EBP-0x58],EAX ; 商放在[EBP-0X58]
思考3:9字节密文相乘,结果必须为 magic key 0xD4C2086,因为显性流程中没有成功分支,显然必须构造除数为0异常,进入SEH才行。对 magic key 分解因子:2、3、5、7、11、13、17、19、23(十进制,都是质数,刚好9字节,如果不是质数或者不为9字节,则可能出现多解)。利用因子倒推注册码前9位:pediy2016
0040112A |. 68 18454000 PUSH Crackme.00404518 ; SE 处理程序安装
0040459B |. FF548F 08 |CALL DWORD PTR DS:[EDI+ECX*4+0x8] ; Crackme.0040133E
思考4:跟进SEH处理,进入40133E,再跟进4013B0
004013B0 $ 55 PUSH EBP
004013B1 . 8BEC MOV EBP,ESP
004013B3 . B8 F4020100 MOV EAX,0x102F4 ; 66292 字节
004013B8 . E8 63360000 CALL Crackme.00404A20
004013BD . 53 PUSH EBX
004013BE . 56 PUSH ESI
004013BF . 57 PUSH EDI
004013C0 . 8DBD 0CFDFEFF LEA EDI,DWORD PTR SS:[EBP+0xFFFEFD0C]
004013C6 . B9 BD400000 MOV ECX,0x40BD ; 0x40BD
004013CB . B8 CCCCCCCC MOV EAX,0xCCCCCCCC
004013D0 . F3:AB REP STOS DWORD PTR ES:[EDI] ; 用 0xcc 填充66292字节空间
004013D2 . C785 68FEFEFF>MOV DWORD PTR SS:[EBP+0xFFFEFE68],0x101
004013DC . C685 64FDFEFF>MOV BYTE PTR SS:[EBP+0xFFFEFD64],0x1 ; VM 指令码
004041C4 > \8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4] ; 8字节变量地址,用来存放类似寄存器的内容
004041C7 . 66:C740 04 00>MOV WORD PTR DS:[EAX+0x4],0x6000 ; EIP 初值 0x6000
004041CD . 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+0xC] ; 读取注册码地址
004041D0 . 51 PUSH ECX ; /压栈
004041D1 . 8D95 FC1FFFFF LEA EDX,DWORD PTR SS:[EBP+0xFFFF1FFC] ; |地址121960
004041D7 . 52 PUSH EDX ; |压栈
004041D8 . E8 53070000 CALL <Crackme.strcpy> ; \拷贝注册码到地址,比如121960
004041DD . 83C4 08 ADD ESP,0x8
004041E0 . 8B85 68FEFEFF MOV EAX,DWORD PTR SS:[EBP+0xFFFEFE68]
004041E6 . 50 PUSH EAX ; /size 0x101
004041E7 . 8D8D 64FDFEFF LEA ECX,DWORD PTR SS:[EBP+0xFFFEFD64] ; |
004041ED . 51 PUSH ECX ; |src 比如 11F6C8
004041EE . 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4] ; |
004041F1 . 33C0 XOR EAX,EAX ; |
004041F3 . 66:8B42 04 MOV AX,WORD PTR DS:[EDX+0x4] ; |
004041F7 . 8D8C05 FCFFFE>LEA ECX,DWORD PTR SS:[EBP+EAX+0xFFFEFFFC] ; |
004041FE . 51 PUSH ECX ; |dest 比如 125960
004041FF . E8 EC030000 CALL <Crackme.memcpy> ; \memcpy VM指令码拷贝
思考5:这里作者模拟了汇编指令,自己定义了一套指令规则,相当于一个精简的VM,指令码加操作数一共257字节
00125960 01 00 02 00 03 00 96 00 20 21 0A 00 29 01 2B 01 ...? !..)+
00125970 27 F6 FF 81 14 21 04 00 00 01 2E 12 00 80 01 63 '??!....€c
00125980 02 09 8B 00 20 21 04 00 00 01 6F 2A 01 8B 00 20 .? !..o*?
00125990 21 04 00 00 2A 01 8B 00 20 21 04 00 00 01 6C 2A !..*? !..l*
001259A0 01 8B 00 20 21 04 00 00 01 2E 03 01 14 00 80 01 ? !....€
001259B0 31 2A 01 8B 00 20 21 04 00 00 0B 00 20 29 D0 08 1*? !... )?
001259C0 45 45 1B 2A 01 0B 00 20 29 D0 08 45 45 1B 2A 01 EE*. )?EE*
001259D0 0B 00 20 29 D0 08 45 45 1D 2D 1D 2D 1B 02 0D 0B . )?EE--.
001259E0 00 20 29 D0 41 64 2A 01 11 00 20 2B D0 43 0A 2D . )蠥d*. +蠧.-
001259F0 2A 01 11 00 20 2B D0 2D 1D 85 21 04 00 00 01 2E *. +??...
00125A00 03 02 14 00 80 2A 01 0B 00 20 29 D0 41 0A 2A 01 .€*. )蠥.*
00125A10 11 00 20 2B D0 2D 1B 2A 01 11 00 20 2B D0 43 0A . +?*. +蠧.
00125A20 2A 01 0B 00 20 29 D0 30 1E 04 2D 42 04 43 02 2F *. )?-BC/
00125A30 81 23 26 12 00 82 5E 26 0D 00 01 2E 03 03 14 00 ?&.俕&....
00125A40 80 27 04 00 00 02 00 01 00 03 2E 98 00 80 26 05 €'.....?€&
00125A50 00 29 01 2A 01 82 04 26 F4 FF 81 04 26 04 00 99 .)*?&??&.
代码附近随意浏览,发现成功弹框提示分支,地址4041A3
004041A3 . 8BF4 MOV ESI,ESP
004041A5 . 6A 00 PUSH 0x0 ; /Style = MB_OK|MB_APPLMODAL
004041A7 . 68 CC804000 PUSH Crackme.004080CC ; |Title = "Success!"
004041AC . 68 CC804000 PUSH Crackme.004080CC ; |Text = "Success!"
004041B1 . 8B55 08 MOV EDX,DWORD PTR SS:[EBP+0x8] ; |
004041B4 . 52 PUSH EDX ; |hOwner
004041B5 . FF15 9C804000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>] ; \MessageBoxA
VM的解码执行:
00404207 . 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
0040420A . C642 06 FF MOV BYTE PTR DS:[EDX+0x6],0xFF ; 8字节空间的第6字节赋值0xFF
0040420E > 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4]
00404211 . 33C9 XOR ECX,ECX
00404213 . 66:8B48 04 MOV CX,WORD PTR DS:[EAX+0x4] ; 取EIP变量(初值0x6000)
00404217 . 33D2 XOR EDX,EDX
00404219 . 8A940D FCFFFE>MOV DL,BYTE PTR SS:[EBP+ECX+0xFFFEFFFC] ; 取指令码
00404220 . 8995 60FDFEFF MOV DWORD PTR SS:[EBP+0xFFFEFD60],EDX
00404226 . 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4]
00404229 . 66:8B48 04 MOV CX,WORD PTR DS:[EAX+0x4]
0040422D . 66:83C1 01 ADD CX,0x1 ; EIP指向下一条(初值0x6000)
00404231 . 8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
00404234 . 66:894A 04 MOV WORD PTR DS:[EDX+0x4],CX ; 更新EIP
00404238 . 8B85 60FDFEFF MOV EAX,DWORD PTR SS:[EBP+0xFFFEFD60] ; 按取出的字节*4+0xFFFEFD60,推算成功时字节为0x99
0040423E . FFA485 6CFEFE>JMP DWORD PTR SS:[EBP+EAX*4+0xFFFEFE6C] ; 执行
思考6:因为午休时间结束了,今天不太可能继续分析,直接将指令码修改为“0x99”,爆破。
二、第一天下班后要陪伴家人,追剧《锦绣未央》,腹黑女被未央扳倒了,哈哈,没有时间分析作品。第二天,午休时间开始了,接昨天:看来需要分析作者定义的VM指令码和操作数,以及这257字节对应的程序流程(不一定要全部分析完),只好跟踪流程,这里省略中间分析过程,因为太长了,贴出算法相关的所有指令码含义(根据我自己的理解,不一定准确,但不影响算法的分析)
VM指令码:(操作数根据指令码很好理解,此处eip,esp,ebp都是便于理解的虚拟概念,要与实机区别)
00 : ret
01 : mov var1,数据
02 : mov var2,数据
03 : mov var3,数据
04 : mov var1,var2
08 : mov var3,var1
0B : mov var1,[ebp+操作数+var2]
11 : mov var3,[ebp+操作数+var2]
12 : mov [ebp+操作数],var1
14 : mov [ebp+操作数+var3],var1
1B : push var1 (dec esp)
1D : pop var3 (inc esp)
1E : pop var2 (inc esp)
21 : je eip+操作数-1 (jz)
26 : jne eip+操作数-1 (jnz)
27 : jmp eip+操作数-1
29 : add var1,数据
2A : add var2,数据
2B : add var3,数据
2D : add var1,var3
2F : add var2,var3
30 : add var3,var1
41 : imul var1,数据
42 : imul var2,数据
43 : imul var3,数据
45 : imul var1,var3
81 : cmp var1,数据
82 : cmp var2,数据
85 : cmp var1,var3
8B : cmp var1,[ebp+操作数+var2]
96 : cmp [ebp+操作数+var3],0x0
98 : cmp [ebp+操作数+var2],var3
99 : msgbox_success
P-CODE(纯手工) 排版太累,点这里下载
根据上述VM指令码定义和P-CODE流程,很方便可以整理算法流程:
1、81 14 : 定义了注册码长度为0x14字节(20位)
2、01 63 : 定义了注册码第10位为“c"
3、01 6F : 定义了注册码第11位,12位为“o”(两个"o")
4、01 6C : 定义了注册码第13位为“l”
5、01 31 : 定义了注册码第14位为“1”
6、注册码第15-16位,需满足 0x1+(0xD0+Sn15)^3 +(0xD0+Sn16)^3 == 0x64+(0xD0+Sn15)*0xA+(0xD0+Sn16),注意byte运算会有溢出,仅保留byte结果,根据注册码0-9的数字范围,很容易穷举出结果为:"5",“3”
穷举代码:
unsigned char Sn15,Sn16,Temp1,Temp2;
for (Sn15=0x30; Sn15 <= 0x39; Sn15++)
{
for (Sn16=0x30; Sn16 <= 0x39; Sn16++)
{
Temp1=0x1+pow((0xD0+Sn15),3)+pow((0xD0+Sn16),3);
Temp2=0x64+(0xD0+Sn15)*0xA+(0xD0+Sn16);
if (Temp1==Temp2)
{
printf ("%c,%c\n",Sn15,Sn16);
}
}
}
7、注册码第17-20位,需满足 (0xD0+Sn17)*0xA+(0xD0+Sn18)+(0xD0+Sn19)*0xA+(0xD0+Sn20) == 0x23,以及 ((0xD0+Sn17)*0xA+(0xD0+Sn18))*4+((0xD0+Sn19)*0xA+(0xD0+Sn20))*2 == 0x5E,注意byte运算会有溢出,仅考虑byte结果,根据注册码0-9的数字范围,也容易穷举出结果为:“1”,“2”,“2”,“3”
穷举代码:
unsigned char Sn17,Sn18,Sn19,Sn20,Temp1,Temp2;
for (Sn17=0x30; Sn17 <= 0x39; Sn17++)
{
for (Sn18=0x30; Sn18 <= 0x39; Sn18++)
{
for (Sn19=0x30; Sn19 <= 0x39; Sn19++)
{
for (Sn20=0x30; Sn20 <= 0x39; Sn20++)
{
Temp1=(0xD0+Sn17)*0xA+(0xD0+Sn18)+(0xD0+Sn19)*0xA+(0xD0+Sn20);
Temp2=((0xD0+Sn17)*0xA+(0xD0+Sn18))*4+((0xD0+Sn19)*0xA+(0xD0+Sn20))*2;
if (Temp1==0x23 && Temp2==0x5E)
{
printf ("%c,%c,%c,%c\n",Sn17,Sn18,Sn19,Sn20);
}
}
}
}
}
8、将所有注册码连接就是答案:pediy2016cool1531223
三、将分析过程写下来,同时把CrackMe放在附件,欢迎各位同学研究。总结该作品采用了SEH隐藏算法流程,采用VM防止Cracker轻易理解算法,采用异或,加法,减法、乘法、除法作为算法基本运算规则,在此应感谢Elyt给大家带来了这么一个有意思的作品。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)