首页
社区
课程
招聘
[原创] RCTF2019 DontEatMe 分析
2022-3-11 10:18 8792

[原创] RCTF2019 DontEatMe 分析

2022-3-11 10:18
8792

[RCTF2019]DontEatMe

0x00 去反调试

EXE文件,IDA反编译:

 

image-20220118163315821

 

通过srand固定时间戳生成伪随机数,然后在调用rand之前,有一段反调试:

 

拿到Ntdll的模块句柄,从句柄中查找ZwSetInformationThread的地址,然后调用这个函数去检查当前线程是否处于Debug模式下:

1
2
3
4
v3 = GetModuleHandleA("Ntdll");
ZwSetInformationThread = GetProcAddress(v3, "ZwSetInformationThread");
v5 = GetCurrentThread();
((void (__stdcall *)(HANDLE, int, _DWORD, _DWORD))ZwSetInformationThread)(v5, 17, 0, 0);

关于ZwSetInformationThread的类型:

1
2
3
4
5
6
typedef NTSTATUS(NTAPI* pZwSetInformationThread)(
    IN HANDLE ThreadHandle,                            // 线程对象句柄
    IN THREAD_INFO_CLASS ThreadInformaitonClass,    // 线程信息类型
    IN PVOID ThreadInformation,                        // 线程信息指针
    IN ULONG ThreadInformationLength                // 线程信息大小
);

线程信息类型为枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef enum _THREADINFOCLASS {
    ThreadBasicInformation,
    ThreadTimes,
    ThreadPriority,
    ThreadBasePriority,
    ThreadAffinityMask,
    ThreadImpersonationToken,
    ThreadDescriptorTableEntry,
    ThreadEnableAlignmentFaultFixup,
    ThreadEventPair,
    ThreadQuerySetWin32StartAddress,
    ThreadZeroTlsCell,
    ThreadPerformanceCount,
    ThreadAmILastThread,
    ThreadIdealProcessor,
    ThreadPriorityBoost,
    ThreadSetTlsArrayAddress,
    ThreadIsIoPending,
    ThreadHideFromDebugger
}THREAD_INFO_CLASS;

该程序中载入的参数就是ThreadHideFromDebugger,此时如果检测到处理Debugger模式就会将该线程强制分离出调试器,该函数与IsDebuggerPresent类似都是用于反调试的

 

image-20220121154513211

 

载入调试模式后在该call之后程序会因为Debug退出,为了不影响程序后续的正常运行,这里过反调试尽量不要有大的修改,该函数的功能根据的ThreadInformaitonClass这个参数,将该参数的值设为ThreadHideFromDebugger时便是处于反调试状态,那么过反调试只需要将ThreadHideFromDebugger 改成 ThreadBasicInformation即可,体现在汇编指令上就是将push 0x11改成push 0x00:

 

image-20220125112522906

 

image-20220125112613621

 

ps: 这里发现如果有其他的修改,可能会导致堆栈存在问题,影响后面strlen的结果,所以在Patch时以最小字节修改为最佳方式,针对该反调试的修改只修改了一个字节,即压入的参数值,对整体的程序无影响,也不影响堆栈的平衡

 

在前面的动态分析中可以发现call esi指向的是NtSetInformationThread的地址而不是ZwSetInformationThread的地址,这里再深入了解一下Zw和Nt函数的区别,首先关于这两个前缀的函数在Ring3Ring0都是有的,在Ring3中由ntdll.dll导出,可以发现两个函数指向同一个函数地址,从这里可以得到在Ring3情况下两个函数没有区别:

 

image-20220121162828516

 

在用户态下,Zw和Nt函数的EntryPoint相同,可以理解为是同一个函数,而我们通过x32dbg或者OllyDBG实际上都是用户态的调试,所以在调试中显示的调用的这两个函数实际上就是同一个函数,两种函数在内核态的系统调用下才会存在区别,具体参考文章https://www.cnblogs.com/seamanj/p/3181151.html,本文不再展开

 

回到该题,main函数拢共200+行代码,中间一部份都是些数据处理,猜测可能存在算法,FindCrypt查看,发现存在BlowFish算法:

 

image-20220118163531387

0x01 BLOWFISH

blowfish加密算法是一种对称的分组加密算法,对Plaintext进行分组,每组长度为64bit,而该加密的密钥为变长密钥,32bit-448bit都可以当作密钥进行加解密处理

 

密钥可以定义为Key数组,该数组的长度为[1, 14]

 

存在两个数组pBox和sBox,pBox为18个32位子密钥组成,sBox为4*256个32位子密钥组成,一般情况下,是用Π的小数点的16进制:

 

BlowFish加密过程

  1. 传入明文和密钥K,密钥K的长度为32bit-448bit,密钥K数组的单位为32bit,所以得到K = [K1, K2, ... , Kn],其中1≤n≤14
  2. 初始化子密钥,子密钥分为1个pBox和4个sBox,pBox的长度为18,sBox的长度为256,一般情况下,该子密钥的初始化为Π的16进制
  3. 子密钥预处理pBox:这里密钥数组K的长度最大只有14,所以存在轮换使用密钥的情况,假设密钥数组K的长度为14,首先是pBox与密钥数组K进行异或:
    • pBox[0] ^ K[0]
    • pBox[1] ^ K[1]
    • ...
    • pBox[13] ^ K[13]
    • pBox[14] ^ K[0]
  4. pBox变换:产生一个64bit全0的数据,然后调用BlowFish的主加密函数进行加密,得到一个64bit的数据替换pBox的数据,总共进行9轮,得到一个新的pBox
  5. sBox变换:此时沿用pBox中产生的leftpart和rightpart,再次通过BlowFish进行主加密函数进行加密,每次将处理后的leftpart和rightpart进行替换,替换得到新的sBox
  6. Plaintext加密:首先是对明文进行填充,若不足8个字节则填充至8个字节,填充为8的整数倍,然后分为前4字节与后4字节传入主加密函数进行加密,在主加密函数中进行加密得到最终的密文

主加密函数,总共为8轮加密,每轮都会从pBox中拿取64bit的数据分成2*32bit异或给leftpart和rightpart,然后再分别异或从sBox中查表得到的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void BF_Fn(ULONG& leftPart, ULONG& rightPart)
{
    for (int i = 0; i < 16; i += 2) {
        leftPart ^= p_box[i];
        rightPart ^= SearchTable(leftPart);
        rightPart ^= p_box[i + 1];
        leftPart ^= SearchTable(rightPart);
    }
 
    leftPart ^= p_box[16];
    rightPart ^= p_box[17];
 
    //swap
    ULONG temp = leftPart;
    leftPart = rightPart;
    rightPart = temp;
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned DWORD pBox[18] = {
    0x243F6A88L, 0x85A308D3L, 0x13198A2EL, 0x03707344L,
    0xA4093822L, 0x299F31D0L, 0x082EFA98L, 0xEC4E6C89L,
    0x452821E6L, 0x38D01377L, 0xBE5466CFL, 0x34E90C6CL,
    0xC0AC29B7L, 0xC97C50DDL, 0x3F84D5B5L, 0xB5470917L,
    0x9216D5D9L, 0x8979FB1BL
};
unsigned DWORD sBox[4][256] = {
    {
        0xD1310BA6L, 0x98DFB5ACL, 0x2FFD72DBL, 0xD01ADFB7L,
        0xB8E1AFEDL, 0x6A267E96L, 0xBA7C9045L, 0xF12C7F99L,
        0x24A19947L, 0xB3916CF7L, 0x0801F2E2L, 0x858EFC16L,
        0x636920D8L, 0x71574E69L, 0xA458FEA3L, 0xF4933D7EL,
 
        ......
 
        0x85CBFE4EL, 0x8AE88DD8L, 0x7AAAF9B0L, 0x4CF9AA7EL,
        0x1948C25CL, 0x02FB8A8CL, 0x01C36AE4L, 0xD6EBE1F9L,
        0x90D4F869L, 0xA65CDEA0L, 0x3F09252DL, 0xC208E69FL,
        0xB74E6132L, 0xCE77E25BL, 0x578FDFE3L, 0x3AC372E6L
    }
}

image-20220124155352654

 

image-20220124163343733

 

BlowFish 解密

 

根据Paul Kocher的源码,可以发现在循环处理中是逆序进行处理,逆序异或pBox,从pBox[17] - pBox[2],对leftpart进行异或,出循环后对rightpart异或pBox[1]leftpart异或pBox[0],整体流程跟加密一样,只是pBox的顺序不同:

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
#define N               16
typedef struct {
  unsigned long P[16 + 2];
  unsigned long S[4][256];
} BLOWFISH_CTX;
 
void Blowfish_Decrypt(BLOWFISH_CTX *ctx, unsigned long *xl, unsigned long *xr){
  unsigned long  Xl;
  unsigned long  Xr;
  unsigned long  temp;
  short       i;
 
  Xl = *xl;
  Xr = *xr;
 
  for (i = N + 1; i > 1; --i) {
    Xl = Xl ^ ctx->P[i];
    Xr = F(ctx, Xl) ^ Xr;
 
    /* Exchange Xl and Xr */
    temp = Xl;
    Xl = Xr;
    Xr = temp;
  }
 
  /* Exchange Xl and Xr */
  temp = Xl;
  Xl = Xr;
  Xr = temp;
 
  Xr = Xr ^ ctx->P[1];
  Xl = Xl ^ ctx->P[0];
 
  *xl = Xl;
  *xr = Xr;
}

0x02 BlowFish初始化

关于BLOWFISH的定位,发现在sub_401090中进行调用,但是该函数调用时并未传入flag相关的内容,所以猜测该函数应该不是对flag进行加密,通过前面了解到的BLOWFISH的特性,该函数应该是对pboxsbox进行预处理

 

image-20220118165317501

 

在预处理之前,发现存在srand和rand函数,但是该srand指定了时间戳,所以此时应该是伪随机数,将rand生成的数低8位存放到byte_4057A8中,通过测试可以发现,每次生成的数是相同的,顺序也是相同的:

 

image-20220119104531098

 

只取低8位,所以最终得到:a1 18 2c 3a 23 5f 33 cd

 

image-20220119104643179

 

pBox的地址-this指针的地址:

 

image-20220124174546490

 

在循环前发现对刚刚srand生成的值进行了覆盖,得到00 0F 1A 01 35 3A 3B 20

 

循环为18次,pBox进行异或,说明该位置就是对pBox的预处理,那么key就是覆盖后的值:

 

image-20220124180159831

 

异或得到:

 

image-20220124180648802

 

进入401000,pBox异或之后的值与eax继续异或:

 

image-20220125101914916

 

根据动态调试可以发现v3最开始指向的就是pBox的首地址,往下对pBox查表之后进行异或:

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
int *__usercall sub_401000@<eax>(int *a1@<edx>, _DWORD *pBox@<ecx>, int *a3)
{
  _DWORD *v3; // edi
  int v4; // eax
  unsigned int v5; // eax
  int v6; // edx
  bool v7; // zf
  int v8; // ecx
  int *result; // eax
  int v11; // [esp+14h] [ebp-8h]
  int v12; // [esp+18h] [ebp-4h]
 
  v11 = 16;
  v3 = pBox;
  v4 = *a1;
  v12 = *a3;
  do
  {
    v5 = *v3++ ^ v4;
    v6 = v5;
    v4 = v12 ^ (pBox[(unsigned __int8)v5 + 0x312]
              + (pBox[BYTE1(v5) + 0x212] ^ (pBox[BYTE2(v5) + 0x112] + pBox[HIBYTE(v5) + 0x12])));
    v7 = v11-- == 1;
    v12 = v6;
  }
  while ( !v7 );
  *a1 = v6 ^ pBox[17];
  v8 = v4 ^ pBox[16];
  result = a3;
  *a3 = v8;
  return result;
}

C实现的blowfish算法在searchTable查表是通过sBox查表的,根据内存分布,可以发现pBoxsBox是连续的,该题中反编译的结果是从pBox开始查,但其偏移已经添加了0x12,所以跟从sBox查表的结果相同:

1
2
3
4
5
6
// 从sBox开始查表的反编译结果
int __cdecl searchTable(unsigned int a1)
{
  __CheckForDebuggerJustMyCode(&unk_41D0A3);
  return sBox[(unsigned __int8)a1 + 0x300] + (sBox[BYTE1(a1) + 0x200] ^ (sBox[BYTE2(a1) + 0x100] + sBox[HIBYTE(a1)]));
}

通过对比源码可以发现sub_401000就是BlowFish下的轮函数,回到sub_401090,可以判断该函数就是blowFishInit

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
do                                            // 变换pBox
{
    BF_FN(&round, (_DWORD *)this, (int *)&v17);
    *(_DWORD *)(this + 4 * v10) = round;
    *(_DWORD *)(this + 4 * v10 + 4) = v17;
    v10 += 2;
}
while ( v10 < 18 );
v11 = (int **)(this + 76);
v16 = 4;
do                                            // 变换sBox
{
    v12 = 128;
    do
    {
        BF_FN(&round, (_DWORD *)this, (int *)&v17);
        *(v11 - 1) = (int *)round;
        result = v17;
        *v11 = v17;
        v11 += 2;
        --v12;
    }
    while ( v12 );
    --v16;
}
while ( v16 );

0x03 BlowFish解密

往下取flag,先&7,根据规律可以发现&7就是判断字符串的长度是否被8整除,如果不被整除则在后面格式化输入时进行填充,往下格式化字符串输入,转成16进制,根据这个可以知道输入的flag范围0-9A-Fa-f

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
flag_len = strlen(flag);
v9 = flag_len;
v49 = flag_len;
if ( (flag_len & 7) != 0 )
{
    v9 = 8 * (flag_len >> 3) + 8;
    v49 = v9;
}
v54 = (char *)calloc(v9, 1u);
res_0 = (unsigned __int8 *)calloc(v9 / 2, 1u);
idx_0 = 0;
v51 = res_0;
if ( v9 / 2 > 0 )
{
    v12 = flag;
    do
    {
        sscanf(v12, "%02x", idx_0 + (_BYTE)res_0);
        res_0 = v51;
        ++idx_0;
        v12 += 2;
    }
    while ( idx_0 < v9 / 2 );
    v9 = v49;
}

在进入第一个do-while之前会将指针指向第三个字节,然后可以看到第一个do-while循环中,取了8个字节分为上下两组进行或运算和移位运算,得到pre_dwordsuf_dwordv13[1] | ((*v13 | ((*(v13 - 1) | (*(v13 - 2) << 8)) << 8)) << 8)可以发现其实就是以dword形式输出:例如0x12,0x34, 0x56,0x78 -> 0x12345678,回到BLOWFISH,注意他的plain是以64位为一组,每组中又将64位拆分成上32位和下32位,第二个do-while循环中可以看到pre_dword异或v14之后给到suf_dword,而suf_dword进行几步异或之后给到pre_dword ,可以猜测该部分应该是BLOWFISH的加密或者解密:

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
v50 = 0;
v55 = v9 / 16;
if ( v9 / 16 <= 0 )
{
    v26 = v54;
}
else
{
    v13 = res_0 + 2;
    v53 = v13;
    do
    {
        v14 = v57;
        v52 = 16;
        pre_dword = v13[1] | ((*v13 | ((*(v13 - 1) | (*(v13 - 2) << 8)) << 8)) << 8);
        suf_dword = v13[5] | ((v13[4] | ((v13[3] | (v13[2] << 8)) << 8)) << 8);
        do
        {
            v17 = *(_DWORD *)v14 ^ pre_dword;
            v14 -= 4;
            v18 = v17;
            pre_dword = suf_dword ^ (*(_DWORD *)&v57[4 * (unsigned __int8)v17 + 3076]
                                     + (*(_DWORD *)&v57[4 * BYTE1(v17) + 2052] ^ (*(_DWORD *)&v57[4 * BYTE2(v17) + 1028]
                                                                                  + *(_DWORD *)&v57[4 * HIBYTE(v17) + 4])));
            v19 = v52-- == 1;
            suf_dword = v18;
        }
        while ( !v19 );
        v20 = v18 ^ v56[0];
        v21 = calloc(4u, 1u);
        *v21 = HIBYTE(v20);
        v21[1] = BYTE2(v20);
        v21[2] = BYTE1(v20);
        v21[3] = v20;
        v22 = v56[1];
        *(_DWORD *)&v54[8 * v50] = *(_DWORD *)v21;
        v23 = pre_dword ^ v22;
        v24 = calloc(4u, 1u);
        *v24 = HIBYTE(v23);
        v24[1] = BYTE2(v23);
        v25 = v23 >> 8;
        v24[3] = v23;
        v26 = v54;
        v24[2] = v25;
        *(_DWORD *)&v54[8 * v50 + 4] = *(_DWORD *)v24;
        v13 = v53 + 8;
        v53 += 8;
        ++v50;
    }
    while ( v50 < v55 );
}

变量较多,直接动态调试:

 

image-20220125150635662

 

第一次循环异或的是pBox[17],循环16次并且倒序异或pBox,最后再异或pBox[0]pBox[1],确定该部分应该是blowfish的解密:

 

image-20220125152210088

0x04 解迷宫

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
v58[0] = 0x4D746145746E6F44i64;
v27 = _byteswap_ushort(0x6F44u);
v28 = _byteswap_ushort(0x746Eu);
v29 = _byteswap_ushort(0x6145u);
v30 = _byteswap_ushort(0x4D74u);
v31 = &byte_40501A;
do
{
    *((_WORD *)v31 - 1) ^= v27;
    *(_WORD *)v31 ^= v28;
    *((_WORD *)v31 + 1) ^= v29;
    *((_WORD *)v31 + 2) ^= v30;
    v31 += 8;
}
while ( (int)v31 < (int)&unk_40503A );
v32 = dword_4053A8;
v33 = (unsigned __int16 *)&unk_405018;
do
{
    v34 = *v33;
    for ( j = 15; j > -1; --j )
    {
        v36 = (v34 & (1 << j)) >> j;
        *v32++ = v36;
    }
    ++v33;
}
while ( (int)v33 < (int)&unk_405038 );

首先是对v27-v30进行了字节序的调转:

 

image-20220119161746320

 

通过do-whilebyte_405018 - byte_40503A进行了异或处理

1
ff ff 3f 80 bf bf bf bf 07 bc f7 bd f7 bd 37 bc b7 bf b7 bf 37 80 f7 fb 07 f8 ff ff ff ff ff ff

image-20220119165755627

 

image-20220125163720261

 

对异或之后的结果进行移位运算和或运算,得到一个只有01的大矩阵:

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
00AC53A8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC53B8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC53C8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC53D8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC53E8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC53F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC5408 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5418 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5428 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5438 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5448 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5458 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5468 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5478 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5488 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5498 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC54A8 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC54B8 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
00AC54C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC54D8 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC54E8 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC54F8 01 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
00AC5508 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5518 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5528 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5538 01 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
00AC5548 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5558 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5568 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5578 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
00AC5588 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5598 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55A8 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55B8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55C8 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55D8 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55E8 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC55F8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5608 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5618 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5628 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC5638 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC5648 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5658 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5668 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5678 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5688 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5698 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC56A8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC56B8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC56C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00AC56D8 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC56E8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC56F8 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5708 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5718 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5728 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5738 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5748 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5758 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5768 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5778 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5788 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................
00AC5798 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 ................

根据switch-case以及前面得到的矩阵得到这部分就是解迷宫了,起点line=10, row=5,终点line=4, row=9

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
v37 = *decrypt_flag;
line = 10;
step = 0;
row = 5;
if ( *decrypt_flag )
{
    v41 = 160;
    while ( 1 )
    {
        switch ( v37 )
        {
            case 'a':
                --row;
                break;
            case 'd':
                ++row;
                break;
            case 's':
                ++line;
                v41 += 16;
                break;
            case 'w':
                --line;
                v41 -= 16;
                break;
            default:
                break;
        }
        if ( dword_4053A8[v41 + row] == 1 )
            break;
        v37 = decrypt_flag[++step];
        if ( !v37 )
        {
            if ( line == 4 && row == 9 && step < 17 )
            {
                printf("Congratulations! Here is your flag: RCTF{%s}", (char)flag);
                return 0;
            }
            break;
        }
    }
}
printf("Oh no.  Dont eat me!!!!", v48);

动态调试根据eax*4+AC53A8可以发现这里取值时是先将rowline相加,初始化时line160,而row5d往右走,所以得到160+5+1 = 0xA6,然后再乘4,找值,所以可以发现如果是左右走的话应该是每步加减4,而上下走每步应该是加减64

 

image-20220125170138429

 

根据生成的矩阵可以发现只在低byte有值,高3个byte都是0,所以可以把多余的0去掉方便找到路线:

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
unsigned char ida_chars[] =
{
    0x90, 0xBB, 0x4B, 0xEE, 0xDE, 0xFA, 0xF2, 0xCB, 0x68, 0xF8,
    0x83, 0xD3, 0x96, 0xF8, 0x7A, 0xC8, 0xD8, 0xFB, 0xC3, 0xD1,
    0x56, 0xC5, 0xBA, 0x8F, 0x68, 0xBC, 0x8B, 0x91, 0x9E, 0xBA,
    0xB2, 0x8B
};
 
short* res = (short*)malloc(16);
memset(res, 0, 16);
short v27 = _byteswap_ushort(0x6F44u);
short v28 = _byteswap_ushort(0x746Eu);
short v29 = _byteswap_ushort(0x6145u);
short v30 = _byteswap_ushort(0x4D74u);
 
for (int i = 0; i < sizeof(ida_chars) / 8; i++) {
    short* tmp = (short*)& ida_chars[i * 8];
    *(res + i * 4) = *tmp ^ v27;
    *(res + i * 4 + 1) = *(tmp + 1) ^ v28;
    *(res + i * 4 + 2) = *(tmp + 2) ^ v29;
    *(res + i * 4 + 3) = *(tmp + 3) ^ v30;
}
 
int* v32 = (int*)malloc(0x400);
int* metrix = v32;
for (int i = 0; i < 16; i++) {
    for (int j = 15; j > -1; --j) {
        *v32++ = (*(res + i) & (1 << j)) >> j;
    }
}
 
for (int i = 0; i < 0x100; i++) {
    if (i % 16 == 0) {
        printf("\n");
    }
    printf("%02x ", metrix[i]);
}

得到:

 

image-20220125174128662

 

根据起点和终点,并且步数不能超过16步,得到:ddddwwwaaawwwddd

 

image-20220125174700134

 

image-20220125180243499

 

代入加密算法:

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
ULONG SearchTable(ULONG leftPart)
{
    ULONG value;
    //查表操作 每一个字节查一次表 然后进行加和异或操作
    value = s_box[0][leftPart >> 24] + s_box[1][(leftPart >> 16) & 0xff];
    value ^= s_box[2][(leftPart >> 8) & 0xff];
    value += s_box[3][leftPart & 0xff];
    return value;
}
 
//BlowFish的主加密函数 明文的加密和对密钥的变换都是利用这个 迭代变换16
inline void BF_Fn(ULONG& leftPart, ULONG& rightPart)
{
    int i;
 
    for (i = 0; i < 16; i += 2) {
        leftPart ^= p_box[i];
        rightPart ^= SearchTable(leftPart);
        rightPart ^= p_box[i + 1];
        leftPart ^= SearchTable(rightPart);
    }
 
    leftPart ^= p_box[16];
    rightPart ^= p_box[17];
 
    //最后交换一下
    ULONG temp = leftPart;
    leftPart = rightPart;
    rightPart = temp;
 
}
 
//子密钥pBox, sBox预处理
void ExchangeBox(int* key)
{
    ULONG leftPart = 0, rightPart = 0;
 
    for (int i = 0; i < 18; i++) {//进行异或
        int tmp =  key[i % 2];
        p_box[i] ^= tmp;
    }
 
    leftPart = rightPart = 0;//产生一个64位全0数据
    for (int i = 0; i < 18; i += 2) {//变换pBox
        BF_Fn(leftPart, rightPart);
        p_box[i] = leftPart;
        p_box[i + 1] = rightPart;
    }
 
    for (int i = 0; i < 4; i++) {//变换sBox
        for (int j = 0; j < 256; j += 2) {//256 / 2 == 128
            BF_Fn(leftPart, rightPart);
            s_box[i][j] = leftPart;
            s_box[i][j + 1] = rightPart;
        }
    }
 
}
 
void BlowFish(int* key, byte* data)
{
    int i;
    int dataLen = strlen((const char*)data) / (sizeof(ULONG) * 2);//获取data长度
    byte* dataCopy = data;//建立一个data副本 方便进行指针偏移
    ULONG leftPart, rightPart;
    ExchangeBox(key);
 
    //至此就可以加密明文了 一次加密2 * 4字节大小
    for (i = 0; i < dataLen; i++) {
        leftPart = _byteswap_ulong(*((ULONG*)dataCopy));
        rightPart = _byteswap_ulong(*((ULONG*)dataCopy + 1));
        BF_Fn(leftPart, rightPart);
        *((ULONG*)dataCopy) = _byteswap_ulong(leftPart);
        *((ULONG*)dataCopy + 1) = _byteswap_ulong(rightPart);
        dataCopy += sizeof(ULONG) * 2;//指向下一个数据块
    }
 
    for (i = 0; i < strlen((const char*)data); i++) {
        printf("%02x", data[i]);
    }
 
}
 
int key[] = {0x000F1A010x353A3B20};
byte data[] = "ddddwwwaaawwwddd";
BlowFish(key, data);

得到:db824ef8605c5235b4bbacfa2ff8e087

 

校验:

 

image-20220125174920093

 

image-20220125181328783

0x05 总结以及参考文章

题目难度不高,整体流程也很简单:输入的flag的作为密文,通过blowfish算法解密得到的一串明文为迷宫的路线,涉及到的内容:

  • ZwSetInformationThread反调试技术
  • BlowFish算法在C语言下实现方式
  • 迷宫Puzzle

blowfish各版本源码:https://www.schneier.com/academic/blowfish/download/

 

参考文章:

 

[1] Blowfish Cipher 浅析: https://bbs.pediy.com/thread-256209.htm

 

[2] BlowFish加解密原理与代码实现: https://www.cnblogs.com/iBinary/p/14883752.html#tid-AwA6xn

 

[3] windows常用的反调试技术-静态反调试技术:https://bbs.pediy.com/thread-249572.htm

 

[4] blowfish加密算法(c实现): https://blog.csdn.net/qq_40890756/article/details/89256847

 

[5] Zw函数与Nt函数的分别与联系:https://www.cnblogs.com/seamanj/p/3181151.html


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-4-19 18:07 被Gushang编辑 ,原因: 忘记加title
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (1)
雪    币: 19584
活跃值: (60093)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2022-4-2 16:57
2
0
感谢分享,文中实例上传一下?
游客
登录 | 注册 方可回帖
返回