看雪.TSRC 2017CTF秋季赛 ctf2017_Fpc
自己做再加上分析别人的wp搞了一星期,这道题终于分析透彻了,感觉自己提升了很多。 从这道题中学到的新知识有:
scanf函数的缓冲区溢出漏洞
RUN跟踪去除花指令
Z3约束求解器的使用
基本工作 无壳的exe
运行一下
搜索字符串 进入Please input your code 运行后在输入字符串位置断下 所以接下来就是算法分析了
结构分析 IDA载入,找到主函数
main函数内部
dword_41B034的初值为2
可以看到判断是否成功前调用了三个call 401050,401090和4010E0 其中401050为打印题目并调用 scanf()
(注意,这很重要,后面有解释)
后两个看来就是解密函数了,分析一下
sub_401090
.text:00401090 ; =============== S U B R O U T I N E ======================================= .text:00401090 .text:00401090 ; Attributes: bp-based frame .text:00401090 .text:00401090 sub_401090 proc near ; CODE XREF: _main+1Cp .text:00401090 .text:00401090 var_C = dword ptr -0Ch .text:00401090 var_8 = dword ptr -8 .text:00401090 var_4 = dword ptr -4 .text:00401090 .text:00401090 push ebp .text:00401091 mov ebp, esp .text:00401093 mov ecx, [ebp+var_4] .text:00401096 test ecx, ecx .text:00401098 jz short loc_4010D7 .text:0040109A mov edx, [ebp+var_8] .text:0040109D test edx, edx .text:0040109F jz short loc_4010D7 .text:004010A1 mov eax, ecx .text:004010A3 sub eax, edx .text:004010A5 jz short loc_4010D7 .text:004010A7 mov [ebp+var_C], eax .text:004010AA imul eax, 5 .text:004010AD add ecx, eax .text:004010AF cmp ecx, 8F503A42h .text:004010B5 jnz short loc_4010D7 .text:004010B7 mov eax, [ebp+var_C] .text:004010BA imul eax, 0Dh .text:004010BD add edx, eax .text:004010BF cmp edx, 0EF503A42h .text:004010C5 jnz short loc_4010D7 .text:004010C7 mov edx, 543F30h .text:004010CC xor edx, 158F04h .text:004010D2 mov eax, [edx] .text:004010D4 dec eax .text:004010D5 mov [edx], eax .text:004010D7 .text:004010D7 loc_4010D7: ; CODE XREF: sub_401090+8j .text:004010D7 ; sub_401090+Fj ... .text:004010D7 mov esp, ebp .text:004010D9 pop ebp .text:004010DA retn .text:004010DA sub_401090 endp .text:004010DA .text:004010DA ; ---------------------------------------------------------------------------
方程
x!=0 y!=0 x!=y 5(y-x)+y=0x8F503A42 13(y-x)+x=0xEF503A42
sub_4010E0
.text:004010E0 ; =============== S U B R O U T I N E ======================================= .text:004010E0 .text:004010E0 ; Attributes: bp-based frame .text:004010E0 .text:004010E0 sub_4010E0 proc near ; CODE XREF: _main+21p .text:004010E0 .text:004010E0 var_C = dword ptr -0Ch .text:004010E0 var_8 = dword ptr -8 .text:004010E0 var_4 = dword ptr -4 .text:004010E0 .text:004010E0 push ebp .text:004010E1 mov ebp, esp .text:004010E3 mov ecx, [ebp+var_4] .text:004010E6 test ecx, ecx .text:004010E8 jz short loc_401127 .text:004010EA mov edx, [ebp+var_8] .text:004010ED test edx, edx .text:004010EF jz short loc_401127 .text:004010F1 mov eax, ecx .text:004010F3 sub eax, edx .text:004010F5 jz short loc_401127 .text:004010F7 mov [ebp+var_C], eax .text:004010FA imul eax, 11h .text:004010FD add ecx, eax .text:004010FF cmp ecx, 0F3A94883h .text:00401105 jnz short loc_401127 .text:00401107 mov eax, [ebp+var_C] .text:0040110A imul eax, 7 .text:0040110D add edx, eax .text:0040110F cmp edx, 33A94883h .text:00401115 jnz short loc_401127 .text:00401117 mov edx, 543F30h .text:0040111C xor edx, 158F04h .text:00401122 mov eax, [edx] .text:00401124 dec eax .text:00401125 mov [edx], eax .text:00401127 .text:00401127 loc_401127: ; CODE XREF: sub_4010E0+8j .text:00401127 ; sub_4010E0+Fj ... .text:00401127 mov esp, ebp .text:00401129 pop ebp .text:0040112A retn .text:0040112A sub_4010E0 endp .text:0040112A .text:0040112A ; ---------------------------------------------------------------------------
方程
x!=0 y!=0 x!=y 17(y-x)+y=0xF3A94883 7(y-x)+x=0x33A94883
这两个函数的方程要满足唯一解很难,并且也不是出题人本意,据说有人解了一天没解出来
更换思路 我们再回到主函数分析,再次看到这个scanf(),并且往后翻有大量未识别的数据,从00413131开始
于是我们可以考虑一下缓冲区溢出
可以看到,我们输入信息一开始保存在0012FF2C,栈清空后保存在0012FF3C,于是十六进制下3C-2C=10,也就是十进制下的16。因此如果要覆盖返回地址需要构造12字节的信息外加4个字节的返回地址。 我们事实上可以直接覆盖到You get it的地址,因为比赛限制flag只为字母和数字,因此需要另想其他办法
在IDA中往下翻翻,看到有一大串未识别的数据,于是猜想有可能应该溢出到这里 首地址为00413131 转换成ASCII为A11 因为数据在内存中为小段排序,于是应该转换为11A 所以我们可以使用测试数据1234567890ab11A来溢出到那段数据
成功跳入,然后右键分析->从模块中删除分析识别出正确代码。 看到一堆跳转
这里是花指令了
使用RUN跟踪去除花指令 断在0x413131处后, 点击菜单栏的”查看”, 选择”RUN跟踪”, 然后再点击”调试”, 选择”跟踪步入”, 程序会记录这段花指令执行的过程
我觉得应该是把最后一栏显示寄存器的指令提取出来 但目前程序显示错误 所以先从前向后记录这些有用指令,然后在下面发现一个奇怪的跳转 这个跳转后面没有short,也不显示寄存器,而且显然跳转之后导致了错误,因此我们把这里的跳转改掉或下断点修改标志位,重新跟踪 然后还有两个jnz
最后一个跳转修改完,跟踪会抛出一个异常
我们提取这之前的有效代码,一共三段验证
vaild_1: 00413131 83C4 F0 add esp,-0x10 00413150 33C0 xor eax,eax 00413184 A3 34B04100 mov dword ptr ds:[0x41B034],eax 004131BA 58 pop eax 004131EB 8BC8 mov ecx,eax 0041321F 58 pop eax 00413254 8BD8 mov ebx,eax 00413289 58 pop eax 004132B5 8BD0 mov edx,eax 004132AD 8BD0 mov edx,eax 004132E2 8BC1 mov eax,ecx 00413316 2BC3 sub eax,ebx 00413349 C1E0 02 shl eax,0x2 00413380 03C1 add eax,ecx 004133B5 03C2 add eax,edx 004133E9 2D E217F9EA sub eax,0xEAF917E2 00413420 /0F85 DD060000 jnz ctf2017_.00413B03 vaild_2: 00413455 03C1 add eax,ecx 00413489 2BC3 sub eax,ebx 004134BF 8BD8 mov ebx,eax 004134F3 D1E0 shl eax,1 00413525 03C3 add eax,ebx 00413559 03C1 add eax,ecx 0041358F 8BC8 mov ecx,eax 004135C3 03C2 add eax,edx 004135F7 2D C808F5E8 sub eax,0xE8F508C8 0041362E /0F85 CF040000 jnz ctf2017_.00413B03 vaild_3 00413665 8BC1 mov eax,ecx 0041365D 8BC1 mov eax,ecx 004136A7 2BC2 sub eax,edx 004136D8 2D 683C0A0C sub eax,0xC0A3C68 00413703 /75 04 jnz short ctf2017_.00413709
整理得到三个方程组
((x - y) << 2) + x + z == 0xEAF917E2 ((x - y) << 1) + (x - y) + x + z == 0xE8F508C8 ((x - y) << 1) + (x - y) + x - z == 0x0C0A3C68
利用z3约束求解器解方程 Z3 is a theorem prover from Microsoft Research. 相关介绍和安装方法: https://github.com/Z3Prover/z3
源码如下
#!/usr/bin/env python # coding=utf-8 from z3 import * x, y ,z = BitVecs('x y z', 64) #x = Real('x') #y = Real('y') #z = Real('z') solve(((x - y) << 2) + x + z == 0xEAF917E2,((x - y) << 1) + (x - y) + x + z == 0xE8F508C8,((x - y) << 1) + (x - y) + x - z == 0x0C0A3C68)
运行后得到结果[z = 1853187632, y = 1919903280, x = 1953723722]
转换为16进制 x = 0x7473754a y = 0x726f6630 z = 0x6e756630
连起来转换为字符串 倒过来就是Just0for0fun 再加上溢出要用的11A 所以 flag=Just0for0fun11A
题目源码 https://bbs.pediy.com/thread-222404.htm
主体(VC6) #include "stdafx.h" #ifdef __cplusplus extern "C" { #endif int __cdecl Add3(int, int); // proc in asm obj char * __cdecl very2(); // right verify proc, in asm obj, @413131 #ifdef __cplusplus } /* extern "C" */ #endif inline void G(){ printf("You get it!\n");} // show good msg, will not have a chance to run inline void B(){ printf("Bad register-code, keep trying.\n");} // show err msg char * InputKey(); // get register code, in stack void very1(); // fake verify proc 1 void very3(); // fake verify proc 3 int Flag=0x21; // register flag, 21 is a bad-guy extern "C" int cFlag=0x11; // anti-wrong jump flag <br> int main(int argc, char* argv[]) { printf("\n Crackme for CTF2017 @Pediy.\n"); // show welcome msg char Key0[10]; // local var, never use char *p1; // pointor to stack var, useless cFlag=2; p1=InputKey(); // get register code, it must over-flow to very2() //printf("%s\n", p1); very1(); // useless very3(); // useless if(cFlag==0) G(); else B(); // boom! bad cracker, just wonder where to verify it??? //getch(); return 0; } <br> char * InputKey() // get register code, it must over-flow to very2(), which is coded in ASM, with junk-code // dword1 dword2 dword3 11A (address of very2, @413131 ) { char Key[10]; char *p2; p2=(char *)&Key; printf(" Coded by Fpc.\n\n"); printf(" Please input your code: "); scanf("%s", Key); return p2; }
两个fake函数(asm) void __declspec(naked) very1() // useless { long int x, y, z; __asm { push ebp mov ebp, esp //mov eax, esp //sub eax, 8 //mov edx, [eax] //mov [ebp-4], edx //mov edx, [eax+4] //mov [ebp-8], edx mov ecx, [ebp-4] test ecx, ecx jz end1 mov edx, [ebp-8] test edx, edx jz end1 mov eax, ecx sub eax, edx jz end1 mov [ebp-0x0c], eax imul eax, 5 add ecx, eax cmp ecx, 0x08f503a42 jnz end1 mov eax, [ebp-0x0c] imul eax, 13 add edx, eax cmp edx, 0x0ef503a42 jnz end1 mov edx, 0x543f30 // dec flag, anti ida x-ref trick xor edx, 0x158f04 mov eax, [edx] dec eax mov [edx], eax //mov cFlag, eax //if (x==0) // goto end1; //if (y==0) // goto end1; //z=x-y; //if (z==0) // goto end1; //if( ((x+z*5)==0x08f503a42) && ((y+z*13)==0x0ef503a42) ) // G(); end1: mov esp, ebp pop ebp ret } //return; }
和
void __declspec(naked) very3() // useless { long int x, y, z; __asm { push ebp mov ebp, esp mov ecx, [ebp-4] test ecx, ecx jz end1 mov edx, [ebp-8] test edx, edx jz end1 mov eax, ecx sub eax, edx jz end1 mov [ebp-0x0c], eax imul eax, 17 add ecx, eax cmp ecx, 0x0f3a94883 jnz end1 mov eax, [ebp-0x0c] imul eax, 7 add edx, eax cmp edx, 0x033a94883 jnz end1 mov edx, 0x543f30 xor edx, 0x158f04 mov eax, [edx] dec eax mov [edx], eax end1: mov esp, ebp pop ebp ret } }
验证部分(masm) public c very2 ;声明引出的proc extern cFlag:near .code db 10000 dup(0) ; pushing addr of very2 to 413131 in exe-file db 2000h dup(0) db 0c3 ; ret, give some sign for crackerz ; very2 proc near c ; right verify proc here, at 413131 add esp, -10 ; restore esp, crack must input:0c bytes regcode and addr of very2 xor eax, eax mov DWORD ptr cFlag, eax ; ; check register code ; correct register code at stack: ; Just_for_fun + 11A(addr of very2) ; ;解个三元一次方程 ;5x-4y+z=A ;4x-3y+z=B ;4x-3y-z=C ; pop eax mov ecx, eax ;eax=x 7473754a pop eax ;eax=y 726f6630 mov ebx, eax pop eax ;z 6e756630 mov edx, eax mov eax, ecx ;x sub eax, ebx ;x-y 2040f1a shl eax, 2 ;4(x-y) 8103c68 add eax, ecx ;x=x+4(x-y) 7c83b1b2 add eax, edx ;x=x+4(x-y)+z eaf917e2 sub eax, 0eaf917e2 ;A jnz bad_boy add eax, ecx sub eax, ebx ;x-y mov ebx, eax shl eax, 1 add eax, ebx ;3(x-y) 60c2d4e add eax, ecx ;x+3(x-y) mov ecx, eax ;x+3(x-y) 7a7fa298 add eax, edx ;x+3(x-y)+z e8f508c8 sub eax, 0e8f508c8 ;B jnz bad_boy mov eax, ecx sub eax, edx ;x+3(x-y)-z c0a3c68 sub eax, 0c0a3c68 ;C jnz bad_boy ; ;eax=0 ;ebx=02040f1a ;ecx=7a7fa298 ;edx=6e756630 pop eax ; balance stack, eax=413131 ;set flag to 0, show good cracker msg xor eax, 8101 ;eax=41b030, addr of flag mov edi, eax xor eax, eax stosd ;set flag call @f ;buffer for good message: "You get it!", in 3 dwords dd 0050600e8, 1702,1702 @@: ; recover good message in code segment pop eax push eax mov edi, eax ;push 20756f59 push 4e000969 ;first dword pop eax ;use ebx,edx value xor eax, edx stosd ;push 20746567 xor eax, 10a3e ;second dword stosd ;push 217469 xor eax, ebx ;22706b8c, third dword xor eax, 22511e14 stosd ;mov eax, 401044 xor eax, 61642d jmp ret1 bad_boy: ;mov eax, 40103f pop eax ;eax=413131, addr of very2 xor eax, 1210e ret1: xor eax, DWORD ptr cFlag jmp eax nop ;ret very2 endp
完结