首页
论坛
课程
招聘
[原创]MRCTF2021逆向题解
2021-4-13 13:22 4280

[原创]MRCTF2021逆向题解

2021-4-13 13:22
4280

个人解题赛,做完了逆向题,总排名第5。

Reverse

Real_CHECKIN

换表base64加密,找到表:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/

 

密文:tvjdvez7D0vSyZbnzv90mf9nuKnurL8YBZiXiseHFq==

 

简单写一下换表脚本:

1
2
3
4
5
6
7
8
9
10
import base64
 
s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
enc = "tvjdvez7D0vSyZbnzv90mf9nuKnurL8YBZiXiseHFq=="
ans = enc.translate(str.maketrans(table, s))
 
print(base64.b64decode(ans))
 
#MRCTF{wElc0Me_t0_MRCTF_2o21!!!}

Dynamic Debug

来到main函数,首先对输入的字符的长度进行了一个判断:

 

 

到关键函数sub_402500(),不能直接反编译,那就简单的分析一下汇编,开始是花指令之类的东西,后面也就是简单比较输入是否为:MRCTF{IS_THIS_REAL?ASK_YOURSELF},很明显这是假的。

 

调试一波找到对代码修改的地方:其实不关心细节,直接动调跟进我们输入字符后的函数,直接就是解密后的代码,修复一下即可。这里我做题时ida和犯病了一样,动调得到那个解密后的代码怎么都不对,,后面单步步入找原因,再次到达解密后的代码又对了,现在想模拟一下当时出错的情况,看原因,又一直不错(。。

 

 

这里手动patch不太方便,还要dump出异或流。就直接动调得到,一个清晰明了的tea结构加密:

 

 

解密:

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
#include <stdio.h>
 
unsigned char ENC[] = {153, 161, 13385, 10493, 130, 12657,   0,
                           77, 14867, 105, 114, 113,   66781, 106,
                            0, 17320756313, 210, 10021, 219,
                           55, 159
                        };
 
unsigned int v5 = 0x6B696C69,
             v4 = 0x79645F65,
             v3 = 0x696D616E,
             v2 = 0x67626463;
 
unsigned int get_delat()
{
    int i = 0;
    unsigned int ans = 0, delat = 0x9E3779B9;
 
    for(i = 0; i < 32; i++)
        ans += delat;
 
    return ans;
}
 
void tea_decode()
{
    int i = 0, j = 0;
 
    for(i = 0; i < 4; i++)
    {
        unsigned int *enc = (unsigned int *)(ENC+8*i);
        unsigned int delat = get_delat();
        for(j = 31; j >= 0; j--)
        {
            enc[1] -= (enc[0] + delat) ^ (enc[0]*16+v3) ^ ((enc[0] >> 5)+v2);
            enc[0] -= (enc[1] + delat) ^ (enc[1]*16+v5) ^ ((enc[1] >> 5)+v4);
            delat -= 0x9E3779B9;
        }
    }
}
 
int main(void)
{
    int i = 0;
 
    tea_decode();
    for(i = 0; i < 32; i++)
    {
        printf("%c", ENC[i]);
    }
 
    return 0;
}
//MRCTF{Dyn4m1c_d3buG_1s_a_ki11eR}

MR_Register

考点:Debug Blocker技术。

 

程序的关键在子进程。分析方法很多,但绝大多数,只要找到这个技术关键点,静态分析和动调父进程看子进程反馈就好了,如果数据复杂了,可以附加调试看内存加以辅助。

 

首先根据函数的特征结构找到main函数,

 

来看main函数,刚刚开始看的时候疑惑了一会儿,为什么这个在调试状态下才执行程序的关键逻辑,经过后面的创建进程函数发现:其实这里的if else语句就区别了父进程与子进程执行不同的语句,因为创建出的子进程是调试模式运行的。这里关注一下创建进程的dwCreationFlags参数,分析程序是将其转换一下枚举的含义好看一些。

 

dwCreationFlags标识了影响新进程创建方式的标志:

DEBUG_PROCESS:如果这个标志被设置,调用进程将被当作一个调试程序,并且新进程会被当作被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。

DEBUG_ONLY_THIS_PROCESS:如果这个标志被设置,调用进程将被当作一个调试程序,并且新进程会被当作被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。

上面2个不同点在于DEBUG_PROCESS会调试被调试进程以及它的所有子进程,而DEBUG_ONLY_THIS_PROCESS只调试被调试进程,不调试它的子进程。

 

继续就是分析后面父进程处理子进程异常的部分sub_40188D():

 

 

关键是看DebugEvent.dwDebugEventCode == 1的活动:接受处理来自子进程的异常,进而修改子进程代码。

 

所以现在关键就是要去寻找子进程要执行函数中出现触发异常地方,进而对着父进程处理模块分析。这里我直接调试了下父进程,记录它处理子进程异常的位置和处理方式,随后自己用idapython去patch一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
第一次交互:通过除0异常触发
for ( i = 374; i >= 0; --i )
    Buffer[i] ^= Buffer[i + 1] ^ i;             // 对表进行了一个简单异或运算。
  Buffer[375] = 120;
    rip += 2;
 
 
第二次交互:通过int3交互,解密代码。
地址:0x0000401E1C
for ( i = 0; i <= 0x57D; ++i )
    *((_BYTE *)v5 + i) ^= i;
 
    rip += 2;
 
第三次,在patch后的代码第一次遇到int3,触发异常交互:
    rip += 2

patch文件,并nop无用的代码:

随后分析程序的关键流程,也就是子进程执行的,先创建一个文件,将输入进行加密后的数据写入该文件中,最后取出文件中的数据与指定编码数据对比。

 

而关键加密函数就是之前patch后的:就是用我们的输入可见字符,因为最高为0,所以只用了7位,分成3 3 1三部分作为index,置换表的过程。这里的email其实并没有作为比较,看到最后用了有一个异或操作,而在最后的比对过程中也有一个同样的操作,就还原了,所以密文就是不经过异或加密后的结果。

 

最后先爆破,再进行一个bytes.fromhex(),最后一个减法。比赛时赶时间写的,比较水,用python处理起来方便一些。

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
#include <stdio.h>
 
char enc[] = {71, 90, 53, 121, 69, 120, 71, 105, 71, 88, 69, 120, 53, 122, 71, 88, 69, 120, 71, 87, 72, 108, 72, 108, 69, 120, 71, 88, 53, 119, 71, 86, 69, 120, 71, 90, 53, 119, 71, 89, 69, 120, 71, 107, 53, 118, 71, 106, 69, 120, 53, 120, 72, 108, 53, 121, 69, 120, 71, 87, 71, 90, 53, 118, 71, 87, 69, 120, 71, 88, 71, 87, 72, 108, 53, 121, 69, 120, 71, 89, 71, 106, 53, 118, 71, 107, 69, 120, 71, 105, 72, 108, 71, 106, 71, 90, 69, 120, 72, 108, 53, 84, 72, 108, 71, 86, 69, 120, 53, 122, 72, 108, 71, 90, 71, 89, 69, 120, 71, 87, 71, 107, 72, 108, 71, 88, 71, 107, 69, 120, 71, 88, 71, 106, 71, 86, 72, 109, 53, 121, 69, 120, 71, 89, 53, 121, 72, 109, 71, 88, 71, 106, 69, 120, 71, 106, 71, 89, 53, 118, 71, 89, 72, 108, 69, 120, 53, 118, 71, 87, 71, 89, 53, 120, 71, 105, 69, 120, 71, 87, 71, 86, 71, 90, 53, 122, 71, 105, 53, 120, 69, 120, 71, 87, 53, 118, 71, 106, 71, 88, 71, 105, 71, 88, 69, 120, 71, 88, 53, 118, 53, 119, 71, 87, 71, 88, 71, 88, 69, 120, 71, 90, 71, 105, 71, 87, 71, 89, 53, 119, 71, 89, 69, 120, 71, 106, 53, 84, 53, 120, 71, 105, 71, 89, 71, 90, 69, 120, 53, 119, 71, 90, 53, 121, 72, 109, 71, 105, 71, 105, 69, 120, 71, 87, 71, 88, 71, 90, 72, 109, 53, 122, 53, 119, 72, 109, 69, 120, 71, 87, 53, 121, 72, 109, 71, 107, 72, 108, 71, 106, 53, 121, 69, 120, 71, 88, 53, 84, 53, 122, 71, 87, 71, 107, 72, 109, 53, 121, 69, 120, 71, 90, 53, 121, 71, 107, 72, 109, 71, 86, 71, 106, 53, 119, 69, 120, 71, 107, 53, 121, 71, 105, 53, 118, 72, 108, 71, 90, 71, 87, 69, 120, 53, 120, 53, 118, 53, 121, 71, 89, 72, 108, 53, 120, 53, 121, 69, 120, 71, 87, 71, 90, 72, 108, 71, 88, 53, 122, 71, 87, 72, 108, 53, 119, 69, 120};
 
 
char table[9][9] = {"ABCDEFGH", "12345678", "0IJKLMNO", "+OPQRStu",
                    "\\vwxyzTU", "abcdefgh", "VWXYZijk", "lmnopqrs"};
 
int main(void)
{
    int i, j, v25, v24, v23, ans1, ans2;
 
    for(i = 12; i < sizeof(enc)-1; i += 2)
    {
        for(j = 32; j < 127; j++)
        {
            char ch = j;
            v25 = (ch >> 6) & 1;
            v24 = (ch >> 3) & 7;
            v23 =  ch & 7;
            ans1 = table[v25][v24];
            ans2 = table[v24][v23];
 
            if(ans1 == enc[i] && ans2 == enc[i+1])
            {
                putchar(j);
                break;
            }
        }
    }
 
    //printf("%d %d", ans1, ans2);   
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
unsigned int enc[100] =
{
  0x4d, 0x52, 0xe2, 0x188, 0x2b0, 0x4b3, 0x7a6, 0xc8d, 0x14a1, 0x218d, 0x36a7, 0x5864, 0x8f80, 0xe843, 0x17827, 0x2609d, 0x3d926, 0x63a38, 0xa13c5, 0x104e5c, 0x1a6252, 0x2ab122, 0x4513b3, 0x6fc534, 0xb4d955, 0x1249eb9, 0x1d9786d, 0x2fe179d, 0x4d7906b, 0x7d5a841, 0xcad38cd, 0x1482e18b
};
char flag[100];
 
int main(void)
{
    int i = 0;
    int init = enc[0]+enc[1];
 
    flag[0] = enc[0], flag[1] = enc[1];
    for(i = 2; i < 100; i++)
    {
        flag[i] = enc[i]-enc[i-1]-enc[i-2];
    }
 
    for(i = 0; i < 100; i++)
        putchar(flag[i]);
}
//MRCTF{C4n_y0u_d3bug_1t?_n0_wa9!}

EzGame

游戏真好玩。

 

之前嘶吼CTF做过一个魔塔的游戏,也是有几个通关条件但更苛刻,但是那个并不是unit