avatar

目录
看雪.TSRC 2017CTF秋季赛 ctf2017_Fpc

看雪.TSRC 2017CTF秋季赛 ctf2017_Fpc

自己做再加上分析别人的wp搞了一星期,这道题终于分析透彻了,感觉自己提升了很多。
从这道题中学到的新知识有:

  1. scanf函数的缓冲区溢出漏洞
  2. RUN跟踪去除花指令
  3. Z3约束求解器的使用

基本工作

无壳的exe

运行一下
mark

搜索字符串
mark
进入Please input your code
运行后在输入字符串位置断下
mark
所以接下来就是算法分析了

结构分析

IDA载入,找到主函数
mark

main函数内部

mark

dword_41B034的初值为2

可以看到判断是否成功前调用了三个call 401050,401090和4010E0
其中401050为打印题目并调用 scanf() (注意,这很重要,后面有解释)

后两个看来就是解密函数了,分析一下

sub_401090

mark
mark

Code
.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 ;

---------------------------------------------------------------------------

mark

方程

x!=0
y!=0
x!=y
5(y-x)+y=0x8F503A42
13(y-x)+x=0xEF503A42

sub_4010E0
mark
mark

Code
.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 ;

---------------------------------------------------------------------------

mark

方程

x!=0
y!=0
x!=y
17(y-x)+y=0xF3A94883
7(y-x)+x=0x33A94883

这两个函数的方程要满足唯一解很难,并且也不是出题人本意,据说有人解了一天没解出来

更换思路

我们再回到主函数分析,再次看到这个scanf(),并且往后翻有大量未识别的数据,从00413131开始
mark

于是我们可以考虑一下缓冲区溢出

mark

mark
mark

可以看到,我们输入信息一开始保存在0012FF2C,栈清空后保存在0012FF3C,于是十六进制下3C-2C=10,也就是十进制下的16。因此如果要覆盖返回地址需要构造12字节的信息外加4个字节的返回地址。
我们事实上可以直接覆盖到You get it的地址,因为比赛限制flag只为字母和数字,因此需要另想其他办法

在IDA中往下翻翻,看到有一大串未识别的数据,于是猜想有可能应该溢出到这里
mark
首地址为00413131
转换成ASCII为A11
因为数据在内存中为小段排序,于是应该转换为11A
所以我们可以使用测试数据1234567890ab11A来溢出到那段数据

成功跳入,然后右键分析->从模块中删除分析识别出正确代码。
看到一堆跳转
mark

这里是花指令了

使用RUN跟踪去除花指令

断在0x413131处后, 点击菜单栏的”查看”, 选择”RUN跟踪”, 然后再点击”调试”, 选择”跟踪步入”, 程序会记录这段花指令执行的过程
mark

我觉得应该是把最后一栏显示寄存器的指令提取出来
但目前程序显示错误
mark
所以先从前向后记录这些有用指令,然后在下面发现一个奇怪的跳转
mark
这个跳转后面没有short,也不显示寄存器,而且显然跳转之后导致了错误,因此我们把这里的跳转改掉或下断点修改标志位,重新跟踪
然后还有两个jnz
mark
mark

最后一个跳转修改完,跟踪会抛出一个异常
mark

我们提取这之前的有效代码,一共三段验证

Code
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

源码如下

Code
#!/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

连起来转换为字符串
mark
倒过来就是Just0for0fun
再加上溢出要用的11A
所以
flag=Just0for0fun11A

mark

题目源码

https://bbs.pediy.com/thread-222404.htm

主体(VC6)

Code
#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)

Code
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;
}

Code
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)

Code
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

完结

文章作者: kabeor
文章链接: https://kabeor.github.io/%E7%9C%8B%E9%9B%AA.TSRC%202017CTF%E7%A7%8B%E5%AD%A3%E8%B5%9B%20ctf2017_Fpc/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 K's House

评论