X-nuca 2020

第一次打这个比赛,是中国科学院信息工程研究所出的题,题目都偏难吧。。做了一个arm架构的题,然后UnravelMFC这个题,比赛时学长师傅做了,就没做了,赛后把它复现了下。

链接:https://pan.baidu.com/s/1zD_x5Z_2ktIWjFzoOKn_5A
提取码:mflo
复制这段内容后打开百度网盘手机App,操作更方便哦

babyarm

进入程序直接看到假的程序流程。。看了下函数,然后gdb开一个端口不断调试跟找到关键函数。其实从ELF Initialization Function Table 也可以看见关键加密函数:sub_114D8

image-20201105163344611

确定关键函数后不断调试。。从调试看到,首先对输入字符hex,然后再byes.fromhex(),所以最后就是我们输入的字符。

剩下就是慢慢整理数据,解密。。循环加密数据。。

加密中2中取已经编码数据的表达式需要注意一下,动调可以很明白。

(_DWORD *)(&v40 + (v17 & 0xC) - 20)

(*((_DWORD *)&v40 + (((unsigned __int8)(v17 >> 2) ^ 1) & 3) - 5)

首先取出最后比较的密文变成16个4字节数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>

unsigned char ida_chars[] =
{
0x13, 0xF0, 0x61, 0xB0, 0x7E, 0x56, 0xC8, 0xB3, 0xC7, 0xA3,
0x52, 0x99, 0x3F, 0x2D, 0x1C, 0x45, 0x67, 0x22, 0xE3, 0x3E,
0x3E, 0x2B, 0xE2, 0xE3, 0x50, 0xA2, 0xE5, 0x43, 0xD0, 0x8E,
0xB2, 0x59, 0xDC, 0x49, 0x86, 0x0F, 0x83, 0xD0, 0xF4, 0x9B,
0x10, 0x81, 0x57, 0x8A, 0x4F, 0xEC, 0x04, 0x86, 0x7F, 0xA2,
0xB5, 0x2E, 0xF3, 0xDD, 0x17, 0x12, 0x53, 0xB2, 0xC9, 0x93,
0x43, 0x8E, 0x7F, 0xDC
};

int main(void)
{
int i = 0;

for(i = 0; i < 64; i += 4)
{
printf("%#x ", *(unsigned int *)(ida_chars+i));
}
return 0;
}
/*unsigned int v32 = 0xb061f013;
unsigned int v28 = 0xb3c8567e;
unsigned int v25 = 0x9952a3c7;
unsigned int v29 = 0x451c2d3f;
unsigned int v26 = 0x3ee32267;
unsigned int v27 = 0xe3e22b3e;
unsigned int v10 = 0x43e5a250;
unsigned int v30 = 0x59b28ed0;
unsigned int v31 = 0xf8649dc;
unsigned int v11 = 0x9bf4d083;
unsigned int v12 = 0x8a578110;
unsigned int v13 = 0x8604ec4f;
unsigned int v14 = 0x2eb5a27f;
unsigned int v15 = 0x1217ddf3;
unsigned int v16 = 0x93c9b253;
unsigned int v33 = 0xdc7f8e43;*/

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>

unsigned int v32 = 0xb061f013;
unsigned int v28 = 0xb3c8567e;
unsigned int v25 = 0x9952a3c7;
unsigned int v29 = 0x451c2d3f;
unsigned int v26 = 0x3ee32267;
unsigned int v27 = 0xe3e22b3e;
unsigned int v10 = 0x43e5a250;
unsigned int v30 = 0x59b28ed0;
unsigned int v31 = 0xf8649dc;
unsigned int v11 = 0x9bf4d083;
unsigned int v12 = 0x8a578110;
unsigned int v13 = 0x8604ec4f;
unsigned int v14 = 0x2eb5a27f;
unsigned int v15 = 0x1217ddf3;
unsigned int v16 = 0x93c9b253;
unsigned int v33 = 0xdc7f8e43;
//unsigned int v33 = 0;
unsigned int v17 = 0;
unsigned int v40 = 0;
unsigned int v18 = 0;
char ans[100] = {0};


int ans1[100] = {0};


int main()
{
int v19;
int v34 = 0;

ans[12] = 4;
ans[8] = 3;
ans[4] = 2;
ans[0] = 2;

ans1[3] = 4;
ans1[2] = 3;
ans1[1] = 2;
ans1[0] = 2;

do{
v17 = 0x8FF34781;
do
{
*(_int8 *)&v18 = v17 >> 2; //LOBYTE
v19 = (((v32 >> 3) ^ 16 * v16) + (4 * v32 ^ (v16 >> 5))) ^ ((*((_int32 *)ans1 + 5
+ (((unsigned __int8)v18 ^ 0xF) & 3)
- 5) ^ v16)
+ (v32 ^ v17));
v33 = v33-v19;
v16 -= (((v33 >> 3) ^ 16 * v15) + (4 * v33 ^ (v15 >> 5))) ^ ((*((_int32 *)ans1 + 5
+ (((unsigned __int8)v18 ^ 0xE) & 3)
- 5) ^ v15)
+ (v33 ^ v17));

v15 -= (((v16 >> 3) ^ 16 * v14) + (4 * v16 ^ (v14 >> 5))) ^ ((*((_int32 *)ans1 + 5
+ (((unsigned __int8)v18 ^ 0xD) & 3)
- 5) ^ v14)
+ (v16 ^ v17));
v14 -= ((*((_int32 *)ans1 + 5 + ((v17 >> 2) & 3) - 5) ^ v13) + (v15 ^ v17)) ^ (((v15 >> 3) ^ 16 * v13)
+ (4 * v15 ^ (v13 >> 5)));
v13 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)v18 ^ 0xB) & 3) - 5) ^ v12) + (v14 ^ v17)) ^ (((v14 >> 3) ^ 16 * v12) + (4 * v14 ^ (v12 >> 5)));
v12 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)v18 ^ 0xA) & 3) - 5) ^ v11) + (v13 ^ v17)) ^ (((v13 >> 3) ^ 16 * v11) + (4 * v13 ^ (v11 >> 5)));


v11 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 9) & 3) - 5) ^ v31) + (v12 ^ v17)) ^ (((v12 >> 3) ^ 16 * v31) + (4 * v12 ^ (v31 >> 5)));////
v31 -= (((*((_int32 *)ans1 + 5 + ((v17 >> 2) & 3) - 5) ^ v30) + (v11 ^ v17)) ^ (((v11 >> 3) ^ 16 * v30)
+ (4 * v11 ^ (v30 >> 5))));
//v18 = v31 + (((*((_int32 *)ans1 + 5 + ((v17 >> 2) & 3) - 5) ^ v30) + (v11 ^ v17)) ^ (((v11 >> 3) ^ 16 * v30) + (4 * v11 ^ (v30 >> 5))));
v30 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 7) & 3) - 5) ^ v10) + (v31 ^ v17)) ^ (((v31 >> 3) ^ 16 * v10) + (4 * v31 ^ (v10 >> 5)));
v10 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 6) & 3) - 5) ^ v27) + (v30 ^ v17)) ^ (((v30 >> 3) ^ 16 * v27) + (4 * v30 ^ (v27 >> 5)));
v27 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 5) & 3) - 5) ^ v26) + (v10 ^ v17)) ^ (((v10 >> 3) ^ 16 * v26) + (4 * v10 ^ (v26 >> 5)));
v26 -= ((*((_int32 *)ans1 + 5 + ((v17 >> 2) & 3) - 5) ^ v29) + (v27 ^ v17)) ^ (((v27 >> 3) ^ 16 * v29) + (4 * v27 ^ (v29 >> 5)));
v29 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 3) & 3) - 5) ^ v25) + (v26 ^ v17)) ^ (((v26 >> 3) ^ 16 * v25) + (4 * v26 ^ (v25 >> 5)));
v25 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 2) & 3) - 5) ^ v28) + (v29 ^ v17)) ^ (((v29 >> 3) ^ 16 * v28) + (4 * v29 ^ (v28 >> 5)));
v28 -= ((*((_int32 *)ans1 + 5 + (((unsigned __int8)(v17 >> 2) ^ 1) & 3) - 5) ^ v32) + (v25 ^ v17)) ^ (((v25 >> 3) ^ 16 * v32) + (4 * v25 ^ (v32 >> 5)));
v32 -= (((v28 >> 3) ^ 16 * v33) + (4 * v28 ^ (v33 >> 5))) ^ ((v33 ^ *(_int32 *)(ans + 20 + (v17 & 0xC) - 20)) + (v28 ^ v17));
v17 += 0x61C88647;
}while(v17 != 0);
v34++;
}while(v34 != 16);


printf("good!");

return(0);
}

直接通过调试窗口取出的解密数据,然后打印出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main(void)
{
unsigned int a[] = {0x67616c66, 0x6330447b, 0x37306261,0x35346146, 0x36623241,
0x62376646, 0x41364541, 0x41624261, 0x64354635,
0x43336263, 0x43446639, 0x66613545, 0x34354665,
0x38434144, 0x30354138, 0x7d413339};
int i = 0, j = 0;
for(i = 0; i < 16; i++)
{
for(j = 0; j < 4; j++)
printf("%c", *(((char *)&a[i])+j));
}
}
//flag{D0cab07Fa45A2b6Ff7bAE6AaBbA5F5dcb3C9fDCE5afeF54DAC88A5093A}

其中是sub_11E40这个函数对程序流程进行了一些骚操作,好像是hook。

ps:后面看别人的wp学习我才知道自己逆向的这个算法是一个魔改的tea,然后那个骚操作是hook了程序的memcmp函数。

UnravelMFC

这个题对于我我现在的知识储备感觉考的东西还是挺多的。

首先算法:RC4,base64的简单改动,改了delta的tea。

然后说说程序的一些坑,开始使用如下的函数执行一个函数指针数组的所有函数,里面包括了smc,变换数据,和反调试检测(简单的*(_BYTE *)(__readfsdword(0x30u) + 2)):

image-20201212112326826

另外就是一个反调试我找了好久,一度怀疑是ida版本的bug,每次调试到一个地方就闪退,一直跟最后跟到库里的一个创建窗口函数,然后就退出了,但为什么呢。。后面才发现是在一个函数初始化窗口时的函数用了*(_BYTE *)(__readfsdword(0x30u) + 2)检测调试,如果处于调试状态就不会初始化这个窗口了,然后调用ExitProcess退出程序。

image-20201213155507714

从这个函数向上找调用:可以看到消息值0x110,一些RegisterWindowsMessageW函数后出现对话框过程处理函数(DialogFunc)。

image-20201213160502370

image-20201213155933860

此时我又想到正在做的另外一个MFC程序,那找主要的对话框初始化函数是不是也可以找呢,试了一下:确实可行。

image-20201213161554990

接着上面那个用函数指针调用的函数:一个简单的反调试,虽然简单,但感觉不好发现。。

image-20201212114250004

image-20201212114424506

上面说到那个反调试不好发现,但其实有一个查程序退出原因的办法,那就是不断跟踪:下面是退出前我跟到的一个API:

image-20201213163744884

函数解释,其实这个函数就类似DialogBoxParamA()函数,用对话框的模板创建模态对话框。而窗口过程函数指针在第四个参数。。这也是我们关心的关键函数,下个断点。

image-20201213163854906

然后就是为什么程序要输入字符长度为66才会enable确定按钮,但为什么只能输入63个字符呢,,这也是我在做题时一直想的问题,然后当时也就不想逆下去了。

而当我在输入框输入正确的flag时这个输入长度又可以是66,难道还有检查机制。。找了半天没有找到,后面发现是那个文本框是根据我输入的字符宽度来决定能输入多少内容。。。因为比赛时我输入的全是1,而字符1比较宽,这里输入全是i的话能容纳的就多了。这应该是设置了输入框的一个属性吧。

image-20201212115832156

剩下对于程序的就是mfc消息对应的处理函数的学习。然后逆向2部分解密。

这里只说一下我在python逆tea加密时遇到的坑,因为python不像C语言,int型数据的话就固定是4个字节,python就是作为一个大数处理,所以每次的数据我们都要&0xffffffff取低4字节数据。

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from z3 import *
import base64
from Crypto.Util.number import *

s = Solver()
v13, v16, v17 = BitVecs('v13 v16 v17', 64)

v14 = 0x2F9970FF
v15 = 0xDF3634AE
s.add(v16 - v13 == 0x3F66B755B4490579)
s.add(v13 + v17 == 0x162F924623D2CAE0)
s.add(v17 - v16 == 0x7C3C71F1B295D77F)
if s.check() == sat:
for i in v13, v16, v17:
print(hex(s.model()[i].as_long()))
else:
print('usat')

enc2 = [0x2d46347f, 0x5e79f6f4, 0xDF3634AE, 0x2F9970FF, 0x6cacebd5, 0x12c2fc6d, 0xe8e95dc6, 0xc558d3ec]

"""
def delta():
a = 0
for i in range(32):
a += 0x2433B95A
a &= 0xffffffff
return a
print(hex(delta()))
"""

key = [0x0D9610D02, 0x2AADA57D, 0x0A37537F1, 0x0C29E3913, 0x0D5942CE8, 0x608CCE66, 0x6D593422, 0x21E5D6F2, 0x0ED3A9235, 0x9DAD62C4, 0x3856641B, 0x71F75B9D, 0x0DCDEDAE8, 0x0EAD2D1A0, 0x0BAC4F564, 0x0DA4772AC]
enc1 = [36, 72, 77, 37, 47, 78, 69, 88, 44, 55,
57, 80, 66, 78, 92, 67, 47, 66, 81, 76,
86, 83, 87, 44, 42, 47, 39, 56, 84, 35,
85, 77, 67, 52, 37, 69, 71, 64, 64, 64,
44, 46, 37, 53]
in_put = 'asfa2asf1asfsa13asfsfasasfas313ssljlslgjlasjlsagljlagaaslagsafsasa'
a = [0x21, 0x5B, 0xD0, 0x3D, 0xE1, 0xE5, 0x2F, 0x7C, 0xD1, 0x79,
0x8E, 0x59, 0x2E, 0xAF, 0xC7, 0x88, 0x5C, 0x35, 0x25, 0xF1,
0x1C, 0xC8, 0x2F, 0x82, 0x97, 0x2E, 0x9C, 0xD6, 0x8A, 0x08,
0x48, 0xA7, 0x8D]
xor = []
for i in range(33):
xor += [ord(in_put[i])^a[i]]

#---------------------------1--------------------
base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for i in range(len(enc1)):
enc1[i] -= 35
flag1 = ''
for i in enc1:
flag1 += base[i]
flag1 = base64.b64decode(flag1.encode())
flag1 = list(flag1)
for i in range(len(flag1)):
flag1[i] ^= xor[i]
flag1 = ''.join(map(chr, flag1))
print(flag1)

#----------------------------2--------------------

def de_tea(a, b, key):
sum = 0x86772b40
for i in range(32):
b -= (a+sum) ^ ((a<<4)+key[2]) ^ ((a>>5)+key[3])
b &= 0xffffffff
a -= (b+sum) ^ ((b<<4)+key[0]) ^ ((b>>5)+key[1])
a &= 0xffffffff
sum -= 0x2433B95A
sum &= 0xffffffff
return [a, b]

ans = []
flag2 = b''
for i in range(0, len(enc2), 2):
ans += de_tea(enc2[i], enc2[i+1], key[4*(i//2):])

for i in ans:
flag2 += long_to_bytes(i)[::-1]
print(flag2)
print(flag1+'f'+flag2.decode())

最后消息映射表的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct AFX_MSGMAP{
AFX_MSGMAP * pBaseMessageMap;
AFX_MSGMAP_ENTRY * lpEntries;
}

struct AFX_MSGMAP_ENTRY{
UINT nMessage; //Windows Message
UINT nCode //Control code or WM_NOTIFY code
UINT nID; //control ID (or 0 for windows messages)
UINT nLastID; //used for entries specifying a range of control id's
UINT nSig; //signature type(action) or pointer to message
AFX_PMSG pfn; //routine to call (or specical value)
}
-------------本文结束感谢您的阅读-------------