首页
论坛
专栏
课程

[原创]看雪秋季赛第2题出题思路

2017-10-30 17:04 3523

[原创]看雪秋季赛第2题出题思路

Fpc
4
2017-10-30 17:04
3523

看雪秋季赛第2题出题思路


by Fpc


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


    首先,第2题完赛了,近1万的关注度,99人过关,从防守方的角度看是很理想的,有参与,而且付出努力(通篇看代码)后可解,努力就可能有收获。


    ctf防守方有多种选择,比如之前比赛有强度很大的壳,无人攻破,是一种思路;或者明码比较,这也是一种思路。年中赛时,有的题令人印象深刻,爱琴海vb pcode加了smc改关键运算代码,readyu数论或密码学应用,还有令人感叹的围绕brainfucker出题和解题技巧,等等,各有侧重,不管能否解出与否,相信参与者都会在相关方向上有所学习和收获。


    防守方最难是题目难度与通过率的估计,出得太难,难以说这是防守方的成功;出得太易,攻防双方都觉得不爽。个人理解,出题解题就是制迷与猜迷的过程,要达到一个平衡很不容易,最终目标即是完成一个较好的心理攻防游戏。


    这道题的思路也是很偶然。大家知道,任何一本C语言教材,开始介绍输入输出时就会涉及到printf, scanf,但一般到高级应用部分才会告诉你scanf有栈溢出漏洞,缓冲区要够用才行。《软件漏洞分析》一书中,failwest也是从strcpy(比scanf应用范围更广)这个函数入手,逐步介绍栈溢出知识的。于是就想,为何不用scanf溢出来控制程序流程,让它走到真正的验证处,实现明暗的转换,对验证处再加以适当的花指令保护。


    题目的设计主体是VC6作个框架,以masm作辅助。有网友怀疑我改了系统函数,这个要求对我是有点高了


   主程序是VC6,流程比较简单。设了两个flag,实际有用的一处,模拟注册成功过程


#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


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

    开赛前夕,稍有调整,主要是在挖坑部分,让主流程的两个验证函数(原来是不能正常工作的)更像一些。这里用到了一点心理诱导(ransomware就是这样做的):近在眼前的东西,但却不能得到时最痛苦。这题的坑、抓人的地方正在于此。原谅作者吧。有坑才好玩 。

    这两个fake check function是用asm写的,原因是C的代码用到的寄存器会入栈,而栈里面一个字节的多余空间都没有了,不能再允许push任何数据了

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写的,加了花,编译成obj,加入VC工程,编译后IDA里面分析,再手动跟一遍,确定和修正里面与地址有关的数据

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


    实际上,这题是在合理利用规则,如果输入内容不限制为数字和字母,即使防守的代码上一点漏洞没有也一定是多解的,即输入 “任意数据”+“Addr(jmp esp)”+ shellcode(未限制长度,可手动构造注册成功信息,修改注册标志位等等),所以这题天然只能在有限定条件下求解。

    其实这题最值得怀疑之处就是前面一段代码之后,放了大段的0,这可以说是为推进到41XXXX处的无奈之举,逆向者就是要具有合理怀疑的想法。如果作者空地里面添加垃圾数据,很有可能大家就找到不答案了,失去了游戏的意义,当然另一方面更多的数据也意味多解的可能性增加了(比如有rop可用)


    最后感谢众位解题大神,有的分析已经超出作者的预想,我也学到很多;感谢林版、netwind等各位版主辛苦地维护论坛;感谢kanxue长期以来为大家提供这一方乐土。希望以后还有机会将游戏进行下去。



2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最新回复 (13)
泪落晨曦 2017-10-30 17:51
2
0
支持下出题大佬
灬阑珊灬 2017-10-30 18:28
3
0
支持下出题大佬
杨森纳YSN 2017-10-30 18:38
4
0
受益匪浅,谢谢大佬
聖blue 2017-10-30 19:04
5
0
不错!
demoLin 2017-10-30 19:38
6
0
支持出题大佬
骑驴的大叔 2017-10-31 20:39
7
0
感谢楼主
dragonwang 2017-10-31 21:48
8
0
能问一下用什么工具或方法加花指令的么
Fpc 4 2017-11-1 08:54
9
0
dragonwang 能问一下用什么工具或方法加花指令的么
这个只能自己编程实现了,你可以找找相关花指令还有变形的资料
BlackTrace 2017-11-1 08:55
10
0
又涨了一个脑洞,谢谢大佬,分享出题思路
HackWolfer 2017-11-6 22:21
11
0
thank  大佬,分享思路,有助于提高技术
水火相容 2017-11-8 14:28
12
0
支持大佬
锦衣夜行ch 2017-11-13 14:04
13
0
(•̀ㅂ•́)و✧
锦衣夜行ch 2017-11-14 11:00
14
0
请问getinput函数是如何编译,这个函数既没有普通函数都有的push  ebp,mov  ebp,esp  操作,而且还是使用esp引用局部变量,请问这一点是如何办到的
游客
登录 | 注册 方可回帖
返回