-
-
[原创]2017秋季赛第二题Writeup
-
2017-10-27 12:04 2731
-
感慨一下,作者声东击西的思路真**清奇。简单说一下解题思路,程序真正的校验逻辑被隐藏了,解题的关键是找到真正的校验过程,然后反推key。
0x00 主函数
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp) .text:00401000 _main proc near ; CODE XREF: start+AF↓p .text:00401000 .text:00401000 argc = dword ptr 4 .text:00401000 argv = dword ptr 8 .text:00401000 envp = dword ptr 0Ch .text:00401000 .text:00401000 push offset aCrackmeForCtf2 ; "\n Crackme for CTF2017 @Pediy.\n" .text:00401005 call sub_413D42 .text:0040100A add esp, 4 .text:0040100D mov key_41B034, 2 .text:00401017 call sub_401050 ; 读取输入 .text:0040101C call checkA_401090 ; 假的校验分支1 .text:00401021 call checkB_4010E0 ; 假的校验分支2 .text:00401026 mov eax, key_41B034 .text:0040102B test eax, eax .text:0040102D jnz short loc_40103F .text:0040102F push offset aYouGetIt ; "You get it!\n" .text:00401034 call sub_413D42 .text:00401039 add esp, 4 .text:0040103C xor eax, eax .text:0040103E retn .text:0040103F ; --------------------------------------------------------------------------- .text:0040103F .text:0040103F loc_40103F: ; CODE XREF: _main+2D↑j .text:0040103F push offset aBadRegisterCod ; "Bad register-code, keep trying.\n" .text:00401044 call sub_413D42 .text:00401049 add esp, 4 .text:0040104C xor eax, eax .text:0040104E retn .text:0040104E _main endp
主函数的逻辑很清晰,但这是作者给少年们下的套,因为checkA_401090处的限制条件和checkB_4010E0处的限制条件是自相矛盾的,所以找不到解能通过这段校验。
0x01 栈溢出
这是第一个线索,读取用户输入的代码存在栈溢出,并且可以劫持返回地址,所以程序的执行流是可以重定向的,这也暗示了另一个校验过程的存在:
.text:00401050 sub_401050 proc near ; CODE XREF: _main+17↑p .text:00401050 .text:00401050 var_C = dword ptr -0Ch .text:00401050 .text:00401050 sub esp, 0Ch .text:00401053 push offset aCodedByFpc ; " Coded by Fpc.\n\n" .text:00401058 call sub_413D42 .text:0040105D add esp, 4 .text:00401060 push offset aPleaseInputYou ; " Please input your code: " .text:00401065 call sub_413D42 .text:0040106A add esp, 4 .text:0040106D lea eax, [esp+0Ch+var_C] .text:00401071 push eax .text:00401072 push offset aS ; "%s" .text:00401077 call _scanf ; 使用scanf直接向栈上拷贝数据,造成了溢出 .text:0040107C lea eax, [esp+14h+var_C] .text:00401080 add esp, 14h .text:00401083 retn .text:00401083 sub_401050 endp
0x02 可显示字符和隐藏的代码
相信很多少年都使用不可显示字符本地过了校验,但按照比赛规则,最终的flag只能包含字母和数字,所以无法提交。
其实可显示字符这一个条件指出了隐藏代码所在的区域。猜测作者把真正的校验逻辑藏在某个地址,然后通过栈溢出漏洞劫持控制流,跳转过去执行,由于覆盖的地址来自用户输入,所以跳转的地址一定可以看成数字/字母ASCII编码的组合。这是第二个线索。
然后很蛋疼的事情就来了,在公司加班,搜索各种可疑指令的机器码,找了一晚上,没找到...
回家之后又玩了一会儿,笔记本上只有windbg,反正没有反调试就走起了。突然想起来IDA里有一块很可疑的区域:
.text:00413131 db 83h, 0C4h, 0F0h .text:00413134 dd 20712A70h, 0F1C75F2h, 28741C71h, 2E0671DDh, 870F574h .text:00413134 dd 74F17169h, 0DC167002h, 0EA74C033h, 0DC261275h, 0F471E771h .text:00413134 dd 6903740Fh, 0EB75EB70h, 0FDF7069h, 22712C70h, 0B8261F7Dh .text:00413134 dd 2B741E71h, 3E067169h, 870F57Ch, 7CF17169h, 0DC197002h .text:00413134 dd 41B034A3h, 75E77400h, 0E571DC12h, 7CDCF271h, 0E9706903h .text:00413134 dd 6965E97Dh, 70B8DC70h, 3E1D7127h, 710F1971h, 0DD257019h .text:00413134 dd 0F6700571h, 71DD0870h, 700270F2h, 70580F14h, 0F1171ECh ......
0x31是数字“1”的ASCII编码,0x41是字母“A”的ASCII编码,这地址来的太诡异了,像是精心设计的,正好匹配第二条线索。并且递归下降的IDA没有识别出.text段的这段内容,说明正常执行流程很可能不会光顾这里,这又很符合第一条线索。
其实,我看这段东西不爽很久了...
很自然的,把溢出地址换成“11A”(0x413131)试了一下,发现跳转后的指令先调整了esp寄存器,并且正好保护了我们的输入:
00413131 83c4f0 add esp,0FFFFFFF0h 00413134 702a jo image00400000+0x13160 (00413160) 00413136 7120 jno image00400000+0x13158 (00413158)
后面两个条件跳转,有很明显的花指令特征。
然后......就没有然后了。
写到这儿感觉差不多了,基本玩躲猫猫的整个心路历程都在这里了。感谢作者的精心设计,厘清思路之后,感觉真的蛮好玩的。
0x03反推和解方程
这段就简略的过了,动态调试,真正的校验过程和之前那个伪造的很像,只是需要解三个方程。写个Python脚本计算一下,输入key:
#!/usr/bin/env python #-*- coding:utf-8 -*- A = 0xeaf917e2 B = 0xe8f508c8 C = 0xc0a3c68 c = (B - C)/2 b_a = A - B b = C + c - 3*b_a a = b - b_a key = "" for n in (b, a, c): key += chr(n & 0x000000ff) key += chr((n & 0x0000ff00) >> 8) key += chr((n & 0x00ff0000) >> 16) key += chr((n & 0xff000000) >> 24) print key
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界