首页
社区
课程
招聘
[原创][2021][KCTF] 第七题 千里寻根 wp
2021-5-23 10:45 7293

[原创][2021][KCTF] 第七题 千里寻根 wp

ccfer 活跃值
16
2021-5-23 10:45
7293

2021 KCTF 第七题 千里寻根


首先用x64dbg调试,发现有壳,很多smc,一层套一层,而且好像有反调试,不能直接跑起来

不想脱壳,直接运行exe,等待输入序列号的时候,再用调试器附加

通过ReadFile回溯定位到读取输入序列号的位置:

00000001400045C7  | 48 | lea rdx,qword ptr ds:[14027E065]          | 输入缓冲区
00000001400045CE  | 48 | lea rcx,qword ptr ds:[14027E01C]          | "%500s"
00000001400045D5  | E8 | call crackme.14027CA68                    |

往下单步看一看,找找感觉,走到处理输入的地方:

0000000140002E06  | 48 | lea rcx,qword ptr ds:[14027E065]          | 
0000000140002E0D  | E8 | call crackme.140001000                    | 输入字符16进制换

继续走看到这个:

0000000140002FA2  | 0F | rdtsc                                     | 随机数
0000000140004787  | 48 | mov rcx,6400000                           |
000000014000478E  | 48 | div rcx                                   |
。。。
0000000140002A94  | FC | cld                                       |
0000000140002A95  | F3 | rep movsq                                 | 把代码拷到140280000区段内的一个随机偏移处
0000000140002A98  | 48 | sub rcx,rcx                               |
0000000140002A9B  | 48 | or rcx,rax                                |
0000000140002A9E  | 48 | and rcx,7                                 |
0000000140002AA2  | F3 | rep movsb                                 |
。。。
0000000140004672  | 65 | mov rsi,qword ptr gs:[10]                 | 堆栈

后面就是利用gs:[10]复制堆栈数据并切换堆栈,跳转到新代码空间执行,销毁前一轮代码,smc解码,一般可能会只夹杂一行有效代码,

然后重新rdtsc取随机偏移,拷贝代码和数据,切换新堆栈,进入下一轮smc

感觉对我来说都是体力劳动,实在也没啥太多技术性的描述

可以对gs:[10]下了个硬件写断点,每次修改后意味着要进入下一轮了

根据硬件断点的命中次数,应该是循环了2021轮


旅途无比艰辛啊,直接瞻仰一下最后一轮的代码吧:

0000000140501331  | B9 | mov ecx,11                                | 比较长度
0000000140501336  | E8 | call crackme.14050133B                    | 
000000014050133B  | 5E | pop rsi                                   |
000000014050133C  | 48 | add rsi,616C                              | 相对寻址到用序列号解码出的用户名
0000000140501343  | E8 | call crackme.140501348                    | 
0000000140501348  | 5F | pop rdi                                   |
0000000140501349  | 48 | add rdi,16                                | 相对寻址到输入的用户名
000000014050134D  | F3 | repe cmpsb                                | 比较结果
000000014050134F  | 85 | test ecx,ecx                              |
0000000140501351  | E9 | jmp crackme.1405000B2                     |
00000001405000B2  | 9C | pushfq                                    |
00000001405000B3  | E8 | call crackme.1405000B8                    | 
00000001405000B8  | 5F | pop rdi                                   |
00000001405000B9  | 48 | add rdi,DD                                |
00000001405000C0  | 48 | mov rax,C3C3C3C3C3C3C3C3                  | 
00000001405000CA  | BA | mov edx,130A                              |
00000001405000CF  | 8B | mov ecx,edx                               |
00000001405000D1  | C1 | shr ecx,3                                 |
00000001405000D4  | FC | cld                                       |
00000001405000D5  | F3 | rep stosq                                 | 销毁部分代码
00000001405000D8  | 8B | mov ecx,edx                               |
00000001405000DA  | 83 | and ecx,7                                 |
00000001405000DD  | F3 | rep stosb                                 |
00000001405000DF  | 9D | popfq                                     |
00000001405000E0  | 75 | jne crackme.140500122                     | 分支显示结果正确或错误


最后就是到每一轮中去找出那一行有效逻辑代码,感觉自动化识别我两三天也写不好,我选择肉眼识别

毕竟原始代码的执行是要恢复到真实堆栈位置的,可以利用rsp的位置辅助判断


过程很漫长,实际可写的内容却不多,下面描述调试过程中记录下来的验证算法逻辑:

16进制转换后的序列号是32个字节,平分成两半,用一个什么算法分别解密,两次输入排序不同,算法很短,眼熟却想不起名字了

解密出来的32个字节看上去都是可显ascii,确认后是个变种base64的结果

base64解码后得到24个字节,前8个字节经过xor操作刚好等于用户名的前8个字符

后面16个字节又是用前面那种算法解密,只是排序不同

最后解密出的16个字节就是完整的用户名了


用户名:KCTF

序列号:B91AE5FCDA57D87406968CBDB8829799790A77302D7E8754B705894489B37A10


keygen:

void encode(WORD *t,WORD *o)
{
	WORD u,v,w,y,k,e,n,h;

	y = t[4] - t[5];
	v = t[2] + t[3];
	u = t[0] ^ t[1];
	w = u & v;
	h = ((~u & y) | w);
	k = get_bits1(t[6] ^ t[7]);
	n = (h * u >> k) + 0x18;
	e = (h | n) & (y ^ n) | (h & n);

	o[6] = rotl16(t[7], 16 - k) ^ h;
	o[7] = rotl16(t[6], 16 - k) ^ h;

	o[4] = t[4] - n;
	o[5] = t[5] - n;

	o[3] = t[2] - (y ^ n);
	o[2] = t[3] + (y ^ n);
	
	o[0] = e ^ t[1];
	o[1] = e ^ t[0];
}

void en1(BYTE *t, BYTE *o)
{
	BYTE k[] = {0x3C,0x14,0xEA,0xB0,0xD9,0xF5,0x67,0x82};
	BYTE a[8];
	DWORD i;

	for (i=0;i<8;i++)
	{
		a[i] = k[i] ^ t[i];
	}

	o[3] = a[0];
	o[2] = o[3] ^ a[2];
	o[1] = o[2] ^ a[1];
	o[0] = o[1] ^ a[3];
	o[7] = a[4];
	o[6] = o[7] ^ a[6];
	o[5] = o[6] ^ a[5];
	o[4] = o[5] ^ a[7];
}

void x1(BYTE *t)
{
	BYTE x[0x10];

	x[0x09] = t[0];
	x[0x0A] = t[1];
	x[0x0B] = t[2];
	x[0x0C] = t[3];
	x[0x05] = t[4];
	x[0x06] = t[5];
	x[0x07] = t[6];
	x[0x08] = t[7];
	x[0x03] = t[8];
	x[0x04] = t[9];
	x[0x01] = t[10];
	x[0x02] = t[11];
	x[0x0D] = t[12];
	x[0x0E] = t[13];
	x[0x0F] = t[14];
	x[0x00] = t[15];

	memcpy(t, x, 16);
}

void x2(BYTE *t)
{
	BYTE x[0x10];

	x[0x06] = t[0];
	x[0x09] = t[1];
	x[0x08] = t[2];
	x[0x0B] = t[3];
	x[0x02] = t[4];
	x[0x05] = t[5];
	x[0x04] = t[6];
	x[0x07] = t[7];
	x[0x00] = t[8];
	x[0x03] = t[9];
	x[0x0E] = t[10];
	x[0x01] = t[11];
	x[0x0A] = t[12];
	x[0x0D] = t[13];
	x[0x0C] = t[14];
	x[0x0F] = t[15];

	memcpy(t, x, 16);
}

void x3(BYTE *t)
{
	BYTE x[0x10];

	x[0x0E] = t[0];
	x[0x0D] = t[1];
	x[0x0C] = t[2];
	x[0x0B] = t[3];
	x[0x02] = t[4];
	x[0x01] = t[5];
	x[0x00] = t[6];
	x[0x0F] = t[7];
	x[0x04] = t[8];
	x[0x03] = t[9];
	x[0x06] = t[10];
	x[0x05] = t[11];
	x[0x0A] = t[12];
	x[0x09] = t[13];
	x[0x08] = t[14];
	x[0x07] = t[15];

	memcpy(t, x, 16);
}

void base64en(BYTE *o, BYTE *p)
{
	DWORD i;

	memset(o, 0, 0x20);

	for (i=0;i<8;i++)
	{
		o[i*4+0] |= (p[i*3+0] & 0x03);
		o[i*4+0] |= (p[i*3+2] & 0x0F) << 2;

		o[i*4+1] |= (p[i*3+0] & 0x0C) >> 2;
		o[i*4+1] |= (p[i*3+1] & 0x3C);

		o[i*4+2] |= (p[i*3+0] & 0xF0) >> 4;
		o[i*4+2] |= (p[i*3+1] & 0x03) << 4;

		o[i*4+3] |= (p[i*3+1] & 0xC0) >> 2;
		o[i*4+3] |= (p[i*3+2] & 0xF0) >> 4;
	}

	BYTE *s = (BYTE *)"0O/+KuChaNiUB1FAckL4Hot9zlWEMpm2TG5Sb6Ixdeq8YJPjygXQsRvwVZr73Dnf";
	for (i=0;i<0x20;i++)
	{
		o[i] = s[o[i]];
	}
}

void kg()
{
	BYTE x[0x10] = {0};
	BYTE y[0x20] = {0};
	BYTE z[0x20] = {0};
	BYTE o[0x20] = {0};
	DWORD i;

	for (i=0;i<0x10;i++)
	{
		x[i] = (BYTE)(0x1F - i);
	}

	memcpy(x, "KCTF", 5);
	en1(x, y);

	encode((WORD *)&x[0], (WORD *)&y[8]);
	x3(&y[8]);

	base64en(z, y);

	encode((WORD *)&z[0], (WORD *)&o[0]);
	x1(&o[0]);

	encode((WORD *)&z[0x10], (WORD *)&o[0x10]);
	x2(&o[0x10]);

	for (i=0;i<32;i++)
	{
		printf("%02X", o[i]);
	}
}



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2021-5-23 10:53 被ccfer编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 2745
活跃值: (1826)
能力值: ( LV12,RANK:298 )
在线值:
发帖
回帖
粉丝
KuCha128 1 2021-5-23 12:49
2
0
强!膜拜!
游客
登录 | 注册 方可回帖
返回