TGCTF2025_Reverse_XTEA_writeup

赛后复现

初步分析

下载文件,给了一个附件和 readme.txt

  • readme.txt
1
压缩包密码:2654435769

压缩包密码有点奇怪,其实 hex 一下就可以发现就是默认的 DELTA 值,后续没有发现 DELTA 没有 deifne, 用默认的其实也可以解开。

1
2
3
v1 = 2654435769
print(hex(v1))
# 0x9e3779b9

解压后 exeinfo 查看文件: 64bit exe 文件,无壳, IDA64打开文件分析
很多函数,搜索 main 或者 start 找不到入口点,Shift+F12 找到了 Welcome to HZNUCTF!!! , Ctrl + X 交叉引用进入 Main 函数
可以先对 printfscanf 等函数进行一些重命名

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
__int64 sub_140011BE0()
{
char *v0; // rdi
__int64 i; // rcx
__int64 v2; // rax
__int64 v3; // rdi
char v5[32]; // [rsp+0h] [rbp-20h] BYREF
char v6; // [rsp+20h] [rbp+0h] BYREF
unsigned int v7[9]; // [rsp+24h] [rbp+4h] BYREF
void *Block; // [rsp+48h] [rbp+28h]
void *v9; // [rsp+68h] [rbp+48h]
int v10[15]; // [rsp+88h] [rbp+68h] BYREF
int j; // [rsp+C4h] [rbp+A4h]
int k; // [rsp+E4h] [rbp+C4h]
int l; // [rsp+104h] [rbp+E4h]
int v14; // [rsp+1D4h] [rbp+1B4h]

v0 = &v6;
for ( i = 66i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 += 4;
}
sub_1400113A2(&unk_1400220A7);
srand(0x7E8u);
sub_140011181();
printf("Welcome to HZNUCTF!!!\n");
printf("Plz input the cipher:\n");
v7[0] = 0;
if ( (unsigned int)scnaf("%d", v7) == 1 ) //判断delta值是否相等
{
printf("Plz input the flag:\n");
Block = malloc(0x21ui64);
v9 = malloc(0x10ui64);
if ( (unsigned int)scnaf("%s", (const char *)Block) == 1 ) //输入flag
{
for ( j = 0; j < 32; j += 4 )
{
v14 = *((char *)Block + j + 3) | (*((char *)Block + j + 2) << 8) | (*((char *)Block + j + 1) << 16) | (*((char *)Block + j) << 24); //处理字符串
v10[j / 4] = v14;
}
sub_1400110B9(v9);
for ( k = 0; k < 7; ++k )
sub_140011212(v7[0], &v10[k], &v10[k + 1], v9); //关键加密函数
for ( l = 0; l < 8; ++l )
{
if ( v10[l] != dword_14001D000[l] )
{
printf("wrong_wrong!!!");
exit(1);
}
}
printf("Congratulation!!!");
free(Block);
free(v9);
v2 = 0i64;
}
else
{
printf("Invalid input.\n");
free(Block);
free(v9);
v2 = 1i64;
}
}
else
{
printf("Invalid input.\n");
v2 = 1i64;
}
v3 = v2;
sub_14001133E((__int64)v5, (__int64)&unk_14001AD40);
return v3;
}
  • sub_140011212
1
2
3
4
5
// attributes: thunk
__int64 __fastcall sub_140011212(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
return sub_140011A10(a1, a2, a3, a4);
}

继续跟进

  • sub_140011A10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int *__fastcall sub_140011A10(int a1, unsigned int *a2, unsigned int *a3, __int64 a4)
{
unsigned int *result; // rax
unsigned int v5; // [rsp+24h] [rbp+4h]
unsigned int v6; // [rsp+44h] [rbp+24h]
unsigned int v7; // [rsp+64h] [rbp+44h]
int i; // [rsp+84h] [rbp+64h]

sub_1400113A2(&unk_1400220A7);
v5 = *a2;
v6 = *a3;
v7 = 0;
for ( i = 0; i < 32; ++i )
{
v5 += (*(_DWORD *)(a4 + 4i64 * (v7 & 3)) + v7) ^ (v6 + ((v6 >> 5) ^ (16 * v6)));
v7 -= a1;
v6 += (*(_DWORD *)(a4 + 4i64 * ((v7 >> 11) & 3)) + v7) ^ (v5 + ((v5 >> 5) ^ (16 * v5)));
}
*a2 = v5;
result = a3;
*a3 = v6;
return result;
}

分析

很正常的一个 xtea 加密,分析一下传入的参数:

  • int a1: 从 v7 -= a1; 可以看出来 a1 应该就是 delta
  • unsigned int *a2, unsigned int *a3: 应该是要解密的数据
  • __int64 a4 : 最后剩下一个一定是 key

处理数据

先找到和处理数据:

  • main
1
2
3
4
5
6
7
...
if ( v10[l] != dword_14001D000[l] )
{
printf("wrong_wrong!!!");
exit(1);
}
...

看到 v10dword_14001D000 作了比较,如果不相等就输出 wrong_wrong!!! ,双击进入 dword_14001D000

  • dword_14001D000
    对着 dword_14001D000 按两下 D , 用 LazyIDA 提取数据 (选取数据,右键,Convert,Convert to C/C++ array (DWORD))
1
2
3
unsigned int byte_14001D000[8] = {
0x8CCB2324, 0x09A7741A, 0xFB3C678D, 0xF6083A79, 0xF1CC241B, 0x39FA59F2, 0xF2ABE1CC, 0x17189F72
};

找 key

  • sub_1400110B9(v9)
1
2
3
4
5
// attributes: thunk
__int64 __fastcall sub_1400110B9(__int64 a1)
{
return sub_140011B70(a1);
}

继续跟进

  • sub_140011B70(a1)
1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_140011B70(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+24h] [rbp+4h]

result = sub_1400113A2(&unk_1400220A7);
for ( i = 0; i < 4; ++i )
{
*(_DWORD *)(a1 + 4i64 * i) = rand();
result = (unsigned int)(i + 1);
}
return result;
}

可以看到是一个伪随机数生成函数

0x0A 本地 code 得到 key

入口函数开头看到定义了 srand:

1
srand(0x7E8u);

编写代码

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

int main() {
    srand(0x7E8u);
    for (int i = 0; i < 4; ++i )
    {
        printf("%d,",rand());
    }
    return 0;
}
// 6648,4542,2449,13336,
0x0B 动态调试得到 key
1
2
srand(0x7E8u);
sub_140011181();
  • sub_140011181()
1
2
3
4
5
// attributes: thunk
__int64 sub_140011181(void)
{
return sub_140012130();
}

跟进 sub_140012130()

1
2
3
4
5
6
void sub_140012130()
{
sub_1400113A2(&unk_1400220A7);
if ( (unsigned int)sub_140011230() )
srand(0x7E9u);
}

sub_140011230() 中定义了 return IsDebuggerPresent(); , 那就很明显了:
如果被发现在动调调试,那就把 srand 的值改为 0x7E9 , 那出来的 key 肯定是错的; 不过可以简单绕过:

  • 关闭 DLL CAN MOVE , 防止基址随机化
    CFF Explorer VIII -> Optional Header -> DllCharacteristics , 把 Dll can move 的勾去掉就可以了

  • X64DBG 打开文件 ,做一些绕过反调试的操作, 直接用 ScyllaHide 就可以绕过
    插件 -> ScyllaHide -> options -> 选择 Basic 就可以
    140011B70 (就是 sub_140011B70函数) 处打断点,然后一直 F9 一直执行到要求输入或断点

第一次要求输入 Plz input the cipher: ,那就给他 DELTA 值 0x9e3779b9

1
if ( (unsigned int)sub_140011217("%d", v7) == 1 )

第二次就断在了 140011B70 处, F8步过,进入循环体

1
2
3
4
5
6
7
8
9
10
0000000140011B9 | 8B45 04                | mov eax,dword ptr ss:[rbp+4]                            |
0000000140011B9 | FFC0 | inc eax |
0000000140011B9 | 8945 04 | mov dword ptr ss:[rbp+4],eax |
0000000140011BA | 837D 04 04 | cmp dword ptr ss:[rbp+4],4 |
0000000140011BA | 7D 16 | jge xtea.140011BBD |
0000000140011BA | FF15 4BF70000 | call qword ptr ds:[<rand>] |
0000000140011BA | 48:634D 04 | movsxd rcx,dword ptr ss:[rbp+4] |
0000000140011BB | 48:8B95 00010000 | mov rdx,qword ptr ss:[rbp+100] |
0000000140011BB | 89048A | mov dword ptr ds:[rdx+rcx*4],eax |
0000000140011BB | EB DC | jmp xtea.140011B99 |

可以看到存入了 rdx 寄存器所指的内存地址,等待循环结束,ret 前查看 rdx 内存地址的数据
RDX 0000000000572540

1
2
3
0000000000572530  10 00 00 00 00 00 00 00 59 00 00 00 FD FD FD FD  ........Y...ýýýý  
0000000000572540 F8 19 00 00 BE 11 00 00 91 09 00 00 18 34 00 00 ø...¾........4..
0000000000572550 FD FD FD FD 31 00 5C 00 7B FA 28 4B 77 0E 00 80 ýýýý1.\.{ú(Kw...

可以看到 F8 19 00 00, BE 11 00 00, 91 09 00 00, 18 34 00 00
转换一下

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

int main() {
    int v1[] = {0x000019F8, 0x000011BE, 0x00000991, 0x00003418};
    //`F8 19 00 00`, `BE 11 00 00`, `91 09 00 00`, `18 34 00 00`
    for (int i = 0; i < 4; i++)
    {
        printf("%d,",v1[i]);
    }
    return 0;
}

// 6648,4542,2449,13336,

得到的数据和本地编写的代码是一样的,两种方法都可以得到。

解密 XTEA

直接套 xtea 的解密脚本,改几个数据就可以解密出来了
先把解密函数改成我们容易看懂的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int *__fastcall sub_140011A10(int delta, unsigned int *a2, unsigned int *a3, __int64 key)
{
unsigned int *result; // rax
unsigned int data; // [rsp+24h] [rbp+4h]
unsigned int data_1; // [rsp+44h] [rbp+24h]
unsigned int v7; // [rsp+64h] [rbp+44h]
int i; // [rsp+84h] [rbp+64h]

sub_1400113A2(&unk_1400220A7);
data = *a2;
data_1 = *a3;
v7 = 0;
for ( i = 0; i < 32; ++i )
{
data += (*(_DWORD *)(key + 4i64 * (v7 & 3)) + v7) ^ (data_1 + ((data_1 >> 5) ^ (16 * data_1)));
v7 -= delta;
data_1 += (*(_DWORD *)(key + 4i64 * ((v7 >> 11) & 3)) + v7) ^ (data + ((data >> 5) ^ (16 * data)));
}
*a2 = data;
result = a3;
*a3 = data_1;
return result;
}

解密脚本(C):

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
#include <stdio.h>
#include <stdint.h>

int main() {
    uint32_t data[] = {
        0x8CCB2324, 0x09A7741A, 0xFB3C678D, 0xF6083A79, 0xF1CC241B, 0x39FA59F2, 0xF2ABE1CC, 0x17189F72
    };
    uint32_t key[] = {6648, 4542, 2449, 13336}; // key
    int delta = 0x9E3779B9;
    for (int i = 6; i >= 0; i--) {
        int sum = 32 * -delta;  //sum += delta; 最后一定是等于0, 所以这里改为 -delta

        for (int j = 0; j < 32; j++ ) {  
            data[i+1] -= (key[((sum >> 11) & 3)] + sum) ^ (data[i] + ((data[i] >> 5) ^ (16 * data[i])));  //注意变符号
            sum += delta;
            data[i] -= (key[(sum & 3)] + sum) ^ (data[i+1] + ((data[i+1] >> 5) ^ (16 * data[i+1])));
        }
    }
    // 输出解密后的字符
    for (int i = 0; i < 8; i++) {
        for (int j = 3; j >= 0; j--) {
            printf("%c", (data[i] >> (j * 8)) & 0xFF);
        }
    }
    return 0;
}

// HZNUCTF{ae6-9f57-4b74-b423-98eb}