首页
社区
课程
招聘
[原创]看雪CTF-2017秋季赛第二题
2017-10-29 09:54 5160

[原创]看雪CTF-2017秋季赛第二题

2017-10-29 09:54
5160
总的来说这道题设计的比较巧妙吧。使用了声东击西的套路。首先放了一个烟雾弹,让人误以为关键点在某个地方,然而那里根本不是核心点。只是一个假“据点”。过多的在那里纠缠就只会浪费你的时间。

首先看下程序。没又任何保护可以直接调试:


然后ida打开。由于没有任何保护措施,所以直接搜索字符串,查找关键字符:


如上图所示,发现了关键位置。然后直接跟上去。可以发现关键逻辑点:


在这个关键跳转前面能发现三个函数。第一个是输入输出函数。用于获取用户的输入已经显示程序的信息。下面两个函数就是“加密”函数了。为什么要打引号?因为后面会降到。这只是个幌子,逗你玩的。在这里纠结那你就中计了:)

现在大概说一下目前看到的“关键逻辑”(假逻辑):


首先有一个变量:dword_41B034。他的初始值是2。然后跟进伪加密函数1可以发现一些逻辑:


如图可知,伪加密函数1的逻辑就是进行一系列的if判断然后如果满足条件则变量dword_41B034自减1。第二个“加密函数”也是一样的原理:


这两个伪加密函数最后就构成一个方程组。现在整理如下:

5 * (y - x) + y == 0x8F503A42

13 * (y - x) + x == 0xEF503A42

17 * (y - x) + y == 0xF3A94883

7 * (y - x) + x == 0x33A94883

其中x!=y且x和y均不为0。看到这里,没有经验的小白就已经要开始打草稿然后手解方程组了。稍微“专业”一点的新手会写个程序枚举或者编个程序解方程组。然后等一天都算不出来。。。呵呵,其实冷静下来观察一下不难发现。这个方程式是无解的,这很好判断,根本不需要用什么复杂的逻辑定理来证明。因为:两个未知数,但是却要满足四个方程。这很明显是无解的(这只是经验推断。并不是某个数学定理)。那么现在没有解,该怎么办?仔细观察输入输出函数。发现了scanf:

看到scanf你想到了什么?作为一个搞安全的人。看到scanf第一反应应该是缓冲区溢出。这是本能反应。如果你没这个意识,这说明你还是需要再学习一个…..

既然想到了缓冲区溢出。那么就可以试着构造。溢出的原理很简单:


如上图所示。可以发现很多信息:

a) 函数依靠eax返回参数。Eax的值就是一个指针,指向一个buffer,这个buffer保存着我们输入的信息(就是“aaaabbbb”)

b) 函数的堆栈显示了输入信息所保存的方式。以及函数的返回地址。根据栈的排列方式不难想到,可以通过构造输入来覆盖返回地址,然后就达到了溢出的目的——让程序执行我们想让他执行的代码

c) 综合上图的“%s”可以发现,所有的输入是被当作字符串来保存的。因此输入的字母a和字母b都被当作了字符串。因此这里需要考虑ascii码的转换问题。同时数据在内存中是小端排序(这个是常识了)

接下来按照上述原理。直接构造输入。我们看到在程序清空堆栈之前,我们输入的信息保存的地址是0x18ff38.然后返回地址保存在0x18ff48。这两个地址相减得:0x10(十进制16)所以这很容易算出。如果要覆盖返回地址需要构造12字节的信息外加4个字节的返回地址。也就是说输入结构如下:A+B+C+返回地址。然后ida上可以看到成功分支的地址是:0x0040102F。因此对照ascii表构造输入如下:


如图所示这样构造其实也是可以解决问题的。但是比赛规则限制,正确的注册码只能由字母+数字组成。这说明此题还有其他的解。因此断定一定还有我们没有发现的逻辑。所以搜索特征值进行寻找。对于加密解密来说。一些经典的特征如下:

a) JMP EAX

b) shl等各自位移指令

c) XOR运算指令

上述这些指令都是加密算法精彩用到的。很容易就能找到关键点。(用最笨的方法。Ida直接向下翻,就可以找到啦:))其次,由于这是比赛。比赛的程序其实都很小。这其实大大降低了逆向的难度。不管你这个程序用了什么花哨的技术。你这么小一个程序,他逆向的难度和复杂度始终都不会高到哪里去。因此在这里多说几句。各位逆向界的新手们,千万要正确对待creakme比赛。不要以为自己能破解creakme。就沾沾自喜。就以为自己已经算是学会了,入门了。相信我。在一个只有几KB大小的creakme程序中。不管使用了多么复杂的反调试,反分析技术。相对都是很容易破解的。因为代码量级在那摆着。但是如果你真的进入这个行业接手真的破解项目。你会恍然大悟:什么ctf,什么creakme。简直就是too simple。Sometimes naive。现在随便一个程序都是几百兆,N个G。在这种量级的代码中进行逆向那才是真的复杂。即使作者不做任何保护。你分析它都非常困难。在海量的汇编代码中如果没有经验和技巧。很容易就迷失在代码的海洋中啦。如果作者又加上各自反调试。那难度呈指数级上涨。这才是真正的逆向工程。我们现在的crf比赛真的是小儿科,连入门都算不上:)

按照上文的方法。果然可以找到一段数据:

这其实就已经非常明显了。这么小的一个程序为什么会有一大段数据?(当然如果是商业级的软件,几百上千兆的程序,那这一点都不奇怪,很正常)可以猜测。这段数据要么就是程序运行时解密的密文数据。要么就是一段被ida当成数据的代码。然而,结果比想象中的要简单。这不是密文,而是明文的代码。只不过是被ida当成了数据。重新分析后如下:

如图可见。这段shellcode的代码地址是0x 00413131。而且可以看到很多跳转指令。熟悉的人一眼就明白。这里其实是花指令。而且是最简单的花指令。就是来回跳一跳,吓唬吓唬你而已。相信我。所有的花指令都是纸老虎。复杂的花指令可以用插件记录程序流程分析一下脱花。简单的花指令(就像本题中遇到的这种)根本不用鸟它。完全是战五渣。直接肛正面……

最后我整理了一下脱花后的代码如下:

// input:D1+D2+D3+11A

xor eax,eax;
mov dword ptr ds:[0x41B034], eax	;[0x41B034]=0x2--->[0x41B034]=0x0
pop eax		;eax=D1
mov ecx,eax  	;ecx=D1
pop eax		;eax = D2
mov ebx,eax		;ebx=D2
pop eax		;eax=D3
mov edx,eax		;edx=D3

mov edx,eax		;edx=D3
mov eax,ecx		;eax=D1
sub eax,ebx		;eax=D1-D2
shl eax,0x02	;eax=(D1-D2)*4
add eax,ecx		;eax=(D1-D2)*4+D1
add eax,edx		;eax=(D1-D2)*4+D1+D3
sub eax,0xEAF917E2		;eax=((D1-D2)*4+D1+D3)-0xEAF917E2

if (eax!=0)
{
	pop eax			; eax=return_addr(0x413131)
	xor eax,0x1210e		;eax=return_addr(0x413131)^0x1210e=0x40103f
	xor eax, dword ptr ds:[0x41B034]		;eax=return_addr(0x413131)^0x1210e^0=0x40103f
	jmp eax		;jmp 0x40103f
}
else	//zf=1
{
	add eax,ecx		;eax=((D1-D2)*4+D1+D3)-0xEAF917E2+D1=D1
	sub eax,ebx		;eax=D1-D2
	mov ebx,eax		;ebx=eax=D1-D2
	shl eax,1		;eax=(D1-D2)*2
	add eax,ebx		;eax=(D1-D2)*2+(D1-D2)
	add eax,ecx		;eax=(D1-D2)*2+(D1-D2)+D1
	mov ecx,eax		;ecx=eax=(D1-D2)*2+(D1-D2)+D1
	add eax,edx		;eax=(D1-D2)*2+(D1-D2)+D1+D3
	sub eax,0xE8F508C8	;eax=(D1-D2)*2+(D1-D2)+D1+D3-0xE8F508C8
	if (eax==0)	//zf=1
	{
		mov eax,ecx		;eax=(D1-D2)*2+(D1-D2)+D1
		sub eax,edx		;eax=(D1-D2)*2+(D1-D2)+D1-D3
		sub eax,0xc0a3c68		;eax=(D1-D2)*2+(D1-D2)+D1-D3-0xc0a3c68
		if (eax==0)	//zf=1
		{
			pop eax			; eax=return_addr(0x413131)
			xor eax,0x8101	; eax=0041B030
			mov edi,eax		; edi=0041B030
			xor eax,eax		;
			stos [edi]		;edi=0041B034
			jmp eax
		}
	}
	
}

如上文所属。整理下来,其实就是三个方程组:

(D1-D2)*4+D1+D3 = 0xEAF917E2

(D1-D2)*2+D1-D2+D1+D3 = 0xE8F508C8                                                                                                    

(D1-D2)*2+D1-D2+D1-D3 = 0x0C0A3C68

其中D1,D2,D3就是我们输入的信息。大小为DWORD。

现在确定了这里才是真正的加密函数。那么要做的就是解方程并且构造输入信息。

首先解方程的方法很多。我是自己写的代码。代码如下:

如上文所属。整理下来,其实就是三个方程组:

(D1-D2)*4+D1+D3 = 0xEAF917E2

(D1-D2)*2+D1-D2+D1+D3 = 0xE8F508C8                                                                                                    

(D1-D2)*2+D1-D2+D1-D3 = 0x0C0A3C68

其中D1,D2,D3就是我们输入的信息。大小为DWORD。

现在确定了这里才是真正的加密函数。那么要做的就是解方程并且构造输入信息。

首先解方程的方法很多。我是自己写的代码。代码如下:

如上文所属。整理下来,其实就是三个方程组:

(D1-D2)*4+D1+D3 = 0xEAF917E2

(D1-D2)*2+D1-D2+D1+D3 = 0xE8F508C8                                                                                                    

(D1-D2)*2+D1-D2+D1-D3 = 0x0C0A3C68

其中D1,D2,D3就是我们输入的信息。大小为DWORD。

现在确定了这里才是真正的加密函数。那么要做的就是解方程并且构造输入信息。

首先解方程的方法很多。我是自己写的代码。代码如下:

// 解方程组
unsigned char table[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
unsigned int count = 62;
int _tmain(int argc, _TCHAR* argv[])
{
	// D1[4],D2[4],D3[4] 分别为4个byte
	// 如下算法每次确认一个byte,只需要运行4次就行了
	for (unsigned int iv1 = 0; iv1 < count; ++iv1)
	{
		unsigned int cv1_ = table[iv1];
		unsigned int off = 8+8+8;// 比如第一次就是 0,第二次就是8,以此类推,后面d2,d3也是一样
		unsigned int cv1 = (cv1_ << (off)) + 0x73754a;// 比如第一次,就是0x4a,第二次就是0x754a
		for (unsigned int iv2 = 0; iv2 < count; ++iv2)
		{
			unsigned int cv2_ = table[iv2];
			unsigned int cv2 = (cv2_ << (off))+0x6f6630;
			for (unsigned int iv3 = 0; iv3 < count; ++iv3)
			{
				unsigned int cv3_ = table[iv3];
				unsigned int cv3 = (cv3_ << (off)) + 0x756630;

				unsigned int c = (4 * (cv1 - cv2) + cv1 + cv3) & 0xFFFFFFFF;
				unsigned int ch1 = 0xeaf917e2;
				if (c == ch1)
				{
					//printf("[1]cv1=%c(0x%02x) cv2=%c(0x%02x) cv3=%c(0x%02x) \n", cv1, cv1, cv2, cv2, cv3, cv3);
					unsigned int ch2 = 0xe8f508c8;
					c = (2 * (cv1 - cv2) + cv1 - cv2 + cv1 + cv3) & 0xFFFFFFFF;
					if (c == ch2)
					{
						//printf("[2]cv1=%c(0x%02x) cv2=%c(0x%02x) cv3=%c(0x%02x) \n", cv1, cv1, cv2, cv2, cv3, cv3);
						unsigned int ch3 = 0x0c0a3c68;
						c = (2 * (cv1 - cv2) + cv1 - cv2 + cv1 - cv3) & 0xFFFFFFFF;
						if (c == ch3)
							printf("[3]cv1=%c(0x%02x) cv2=%c(0x%02x) cv3=%c(0x%02x) \n", cv1, cv1, cv2, cv2, cv3, cv3);
					}
				}
			}
		}
	}
	return 0;
}

最后结果是:

D1=0x7473754a

D2=0x726f6630

D3=0x6e756630

最后将这三段信息组合起来就得到了结果:D1+D2+D3= 4A 75 73 74 30 66 6F 72 30 66 75 6E=Just0for0fun

现在还差临门一脚——构造溢出地址。那就更简单了。正如前文所示。Shellcode的其实地址是:0x00413131。那么对照ascii表可以得出相应的字符为:0x11A(主要字节排序)。

所以,综上所属,我们这道题的答案就是:Just0for0fun11A

最后结果是:

D1=0x7473754a

D2=0x726f6630

D3=0x6e756630

最后将这三段信息组合起来就得到了结果:D1+D2+D3= 4A 75 73 74 30 66 6F 72 30 66 75 6E=Just0for0fun

现在还差临门一脚——构造溢出地址。那就更简单了。正如前文所示。Shellcode的其实地址是:0x00413131。那么对照ascii表可以得出相应的字符为:0x11A(主要字节排序)。

所以,综上所属,我们这道题的答案就是:Just0for0fun11A

最后结果是:

D1=0x7473754a

D2=0x726f6630

D3=0x6e756630

最后将这三段信息组合起来就得到了结果:D1+D2+D3= 4A 75 73 74 30 66 6F 72 30 66 75 6E=Just0for0fun

现在还差临门一脚——构造溢出地址。那就更简单了。正如前文所示。Shellcode的其实地址是:0x00413131。那么对照ascii表可以得出相应的字符为:0x11A(主要字节排序)。

所以,综上所属,我们这道题的答案就是:Just0for0fun11A






[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (9)
雪    币: 7101
活跃值: (2614)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 13 2017-10-29 15:04
2
0
文章很认真  分析比较到位  给予精华鼓励!
雪    币: 2259
活跃值: (3355)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
奔跑的阿狸 1 2017-10-29 15:20
3
0
路过,挺厉害的小伙
雪    币: 202
活跃值: (16)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
skygarth 2017-10-29 15:57
4
0
首先膜拜大神,另外斗胆提一问  怎么知道ASCII  0x10  就是^P呢,  哪里有相关资料呢,  我找了好久
雪    币: 6977
活跃值: (1775)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
TopC 2017-10-30 13:59
5
0
讲的很透彻,自己跟着做了一遍,受益匪浅
雪    币: 5
活跃值: (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
无头骑士 2017-10-30 19:47
6
0
自己跟着做了一遍,但是花指令那块不太懂。。。
雪    币: 424
活跃值: (40)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
何小龙 2 2017-10-30 21:26
7
0
无头骑士 自己跟着做了一遍,但是花指令那块不太懂。。。
这是最基础的东西啦。我已经说了。这个花指令就是吓唬人的。并没有什么卵用。你别管那些跳转就好啦。。。。其实花指令这个技术本身就是吓唬人的。他只是垃圾代码,本身不会影响程序运行。
雪    币: 424
活跃值: (40)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
何小龙 2 2017-10-30 21:31
8
0
skygarth 首先膜拜大神,另外斗胆提一问 怎么知道ASCII 0x10 就是^P呢, 哪里有相关资料呢, 我找了好久[em_5]
这个我就不解答啦。你稍微动点脑筋还是可以解决的这是基础
雪    币: 4269
活跃值: (1911)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
cqzhou 2017-10-30 22:20
9
0
IDA  怎么把哪个转换成花指令的
雪    币: 28
活跃值: (52)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
zuoyang 2017-10-31 02:08
10
0
cqzhou IDA 怎么把哪个转换成花指令的
在IDA中直接摁字母C就可以了
游客
登录 | 注册 方可回帖
返回