请问你今天要来杯茶吗?🍵

1. TEA 加密简介

TEA(Tiny Encryption Algorithm)由剑桥计算机实验室的 Roger Needham 和 David Wheeler 于 1994 年发明。它是一种分组加密算法,速度快,实现简单,占用资源少,常见于嵌入式系统等。一个具体应用是 QQ 和微信的一部分加密协议。

TEA 使用 64 bits (8 bytes) 的明文分组和 128 bits (16 bytes) 的密钥进行加密。采用 Feistel 分组密码结构和迭代形式,将明文分为两部分,每一轮迭代后 “交换” 位置再执行运算。该算法推荐的迭代次数为 64 轮,最少不少于 32 轮。下图示意 TEA 的两轮迭代(或计为一个循环 (Cycle)),其中密钥被分为四个子密钥 KiK_i

(来源:Tiny Encryption Algorithm - Wikipedia

阅读说明:(绿色)田字格表示 “加法”,(红色)田字圆表示异或。

TEA 引入了一个神秘常数 δ\delta,每轮迭代使用 δ\delta 的不同倍数以保证每一轮加密都不相同。这个神秘常数的推荐数值为 232ϕ,ϕ=1+52\lfloor \frac{2^{32}}{\phi} \rfloor, \phi=\frac{1+\sqrt{5}}{2},以十六进制表示为 0x9E3779B9。这个常数的值可以替换,但采用推荐值可以避免一些算法问题。另外,这个常数在迭代过程中不能更改。

TEA 存在密钥表攻击的问题。为了解决算法的缺陷,设计者提出了更先进的算法:从 XTEA 和 Block TEA,到 XXTEA。

  • XTEA:使用与 TEA 相近的运算,但四个子密钥采取不太正规的方式进行混合,密钥的移位、异或与加法运算也经过重新组合。

    (来源:XTEA - Wikipedia

  • Block TEA:和 XTEA 同时提出,可以对 32 位的任意整数倍长度的明文块进行加解密操作。该算法将 XTEA 中的轮循函数依次应用于块中的每个字 (32 bits),并且将它与最左侧的邻字进行加法组合。

  • XXTEA:是 Block TEA 的改良版(所以它的英文全称为 “Corrected Block TEA”),换用具有两个输入量的 MX 函数作为轮循函数,在处理块中的每个字时利用了相邻的两个字。

    (来源:XXTEA - Wikipedia

2. TEA 系列算法加密流程分析

Tip

强调一下变量的类型一定要是无符号的,如果是有符号的话数字的大小是不正确的(会考虑符号位),所以一定要无符号才能跑的出结果
——[原创] 逆向魔改 tea + 简单异或 + z3 运算

2.1 TEA

以下是 David Wheeler 和 Roger Needham 公开发布的 TEA 加密和解密的 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
#include <stdint.h>

void encrypt (uint32_t v[2], const uint32_t k[4]) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9E3779B9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}

void decrypt (uint32_t v[2], const uint32_t k[4]) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up; sum is (delta << 5) & 0xFFFFFFFF */
uint32_t delta=0x9E3779B9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}

(来源:Tiny Encryption Algorithm - Wikipedia

  • 在加密函数 encrypt 中,
    • 数组 v 是明文,分为 v[0]v[1] 两部分。数组 k 是密钥,分为 k[0] ~ k[3] 四个子密钥。sum 对应 delta 的不同倍数。
    • 整数类型使用 uint32_t,即无符号的 32 位整数。由于范围限制,sum 会遇到整数溢出的情况,程序截断数值后会改变 sum 的值,对 sum 进行混淆。
    • 接下来是 32 次循环。
    • 循环结束,将密文写回数组 v
  • 在解密函数 decrypt 中,
    • 数组 v 是密文,分为 v[0]v[1] 两部分。数组 k 是密钥,分为 k[0] ~ k[3] 四个子密钥。
    • 解密是加密的逆操作。此时要从最后一次迭代开始逆向迭代,逐步获取明文。这里 sum 的初始值由 (delta << 5) & 0xFFFFFFFF 确定,计算得到 0xC6EF3720。(也可以直接用 delta * 32 得到)

2.2 XTEA

此 C 语言源代码,改编自 David Wheeler 和 Roger Needham 发布到公共领域的参考代码,使用 XTEA 进行加密和解密:

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

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

(来源:XTEA - Wikipedia


2.3 XXTEA

以下 C 语言源代码根据 David Wheeler 和 Roger Needham 发布的原始源码改进而来,使用 XXTEA 进行加密和解密:

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
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
// n是明文块数量,p对应密钥下标索引,e是sum>>2

if (n > 1) { /* Coding Part */
rounds = 6 + 52/n; // 控制迭代轮数,兼顾效率和安全性
sum = 0;
z = v[n-1]; // 明文的最后一个分块
do {
sum += DELTA;
e = (sum >> 2) & 3; // 计算每轮的密钥
for (p=0; p<n-1; p++) {
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
} while (--rounds);
}
else if (n < -1) { /* Decoding Part */
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--) {
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}

(来源:XXTEA - Wikipedia

Note

PS:也不知道是哪个神仙发明的算法,自学起来如此晦涩难懂。

——Tea 系列算法学习 - St4rdd3n’s blog

😂😂😂
TEA 算法及其背后的结构原理是很经典的,因此你能看到一些千奇百怪的加密算法都有 TEA 系列的影子。进阶一点的 CTF 比赛有很多就是拿 TEA 算法魔改的。所以…… 加油吧💪

3. TEA 加密特征识别

  • 可能存在针对 64 bits 以及 128 bits 数字的操作(分别是输入的 messagekey),一般会以 uint32_t 的数组表示

  • 存在先移位、后异或的类似操作。比如 ((z >> 5 ^ x <<4) ^ (y << 4 & q >> 5)) 等混合变换就是 XXTEA 加密的特征

  • 前面一个复杂的混合变换的结果可能会叠加到另一个值上,两者相互叠加(即 Feistel 结构)

  • 获取密钥的时候,会使用某一个常量值作为下标(如 key[(sum >> 11) & 3]),并且出现轮换形式,可以猜测是 XTEA 或 XXTEA。

  • 一开始会在算法定义一个 delta(特别是值为 0x9E3779B9 时),并且不断参与算法,但是不会受到输入的影响。

  • 常见代码片段:

    • TEA

      1
      2
      3
      sum += delta;
      v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
      v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    • XTEA

      1
      2
      3
      v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
      sum += delta;
      v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
    • XXTEA

      1
      2
      3
      4
      5
      #define MX ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z))

      sum += delta;
      e = (sum >> 2) & 3;
      ``

4. TEA 常见魔改方法

  1. 更改 delta 的值,不再是 0x9E3779B9
  2. 在每轮的迭代加密中添加可逆运算
  3. 在迭代完成后赋值时添加可逆运算

5. TEA 逆向方法

基本思路为通过提取关键数据编写脚本进行解密。

如果反编译出来的算法不是那么标准,那么尽力写一个正向的加密算法出来,然后将测试数据借用目标程序进行加密,通过比对结果来检验我们写的算法的正确性。由于解密是加密的逆运算,因此正向的加密解决了,逆向的解密也不成问题。

在解密 TEA 时,我们需要:

  • 提取常数 delta 的值,一般是一个 4 字节(32 位)大小的整数
  • 查看每一轮的迭代加密是否添加了其他可逆运算
  • 循环结束赋值回去前对密文有没有进一步加密

TEA 加密最好准备一个解密模板,当遇到 TEA 加密时就可以直接套数据,而不是复现整个解密流程。

6. 习题示例

西电 CTF 终端 - 练习场 - 强壮逆向人 - ezTea

本题为源码逆向,要求借助加密函数写出与之配套的解密函数。

源码:

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

void encrypt (uint32_t* v, uint32_t* k) { // 主要加密函数,试着搞定它
uint32_t v0 = v[0], v1 = v[1], sum = 0;
uint32_t delta = 0xd33b470;
for (int i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>5) + k[1]);
v1 += ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>5) + k[3]);
}
v[0] = v0;
v[1] = v1;
}

int main() {
uint32_t k[4] = {1, 2, 3, 4};
int8_t input[33] = {0};

scanf("%32s", input);
for (int i = 0; i < 32; i+=8) {
uint32_t v[2] = {*(uint32_t *)&input[i], *(uint32_t *)&input[i+4]};
encrypt(v, k);
for (int j = 0; j < 2; j++) { // 这一段主要是把 v 按单字节输出,另外可以了解一下 “大小端序” 在这题是如何体现的
for (int k = 0; k < 4; k++) {
printf("%#x, ", v[j] & 0xff); // x:将无符号整数以十六进制形式输出,字母使用小写;#:表示输出时添加前缀 0x
v[j] >>= 8;
}
}
}
return 0;
}

原题所给 tutorial:

你需要做的是看看 main 函数中给 encrypt 传入的参数是什么,然后去分析加密的行为,并试着写出解密函数。

当你输入正确的 flag 时会输出这样的数据:

1
2
3
0x17, 0x65, 0x54, 0x89, 0xed, 0x65, 0x46, 0x32, 0x3d, 0x58, 0xa9, 0xfd, 0xe2, 0x5e,
0x61, 0x97, 0xe4, 0x60, 0xf1, 0x91, 0x73, 0xe9, 0xe9, 0xa2, 0x59, 0xcb, 0x9a, 0x99,
0xec, 0xb1, 0xe1, 0x7d

脚本:

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

void decrypt(uint32_t *v, uint32_t *k) {
uint32_t delta = 0xd33b470;
uint32_t v0 = v[0], v1 = v[1], sum = (32) * delta;
for (int i = 31; i >= 0; i--) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}

int main(void) {
uint32_t k[4] = {1, 2, 3, 4};
int8_t input[] = {0x17, 0x65, 0x54, 0x89, 0xed, 0x65, 0x46, 0x32,
0x3d, 0x58, 0xa9, 0xfd, 0xe2, 0x5e, 0x61, 0x97,
0xe4, 0x60, 0xf1, 0x91, 0x73, 0xe9, 0xe9, 0xa2,
0x59, 0xcb, 0x9a, 0x99, 0xec, 0xb1, 0xe1, 0x7d};
for (int i = 0; i < 32; i += 8) {
uint32_t v[2] = {*(uint32_t *)&input[i], *(uint32_t *)&input[i + 4]};
decrypt(v, k);
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 4; k++) {
printf("%c", v[j] & 0xff);
v[j] >>= 8;
}
}
}
}

解密函数完全是加密函数的逆运算,所有的运算顺序都倒置了。


NewStar CTF 2024 - Week2 - Reverse 逆向工程 - drink_tea

先用 DIE 看看

64 位,无壳

然后用 IDA,直接跳到 main 函数处

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // r8
__int64 v6; // rdx
__int64 v7; // r8
int i; // [rsp+20h] [rbp-28h]
__int64 v9; // [rsp+28h] [rbp-20h]

sub_140001070(aPleaseInput, argv, envp);
sub_140001120("%32s", byte_140004700);
v9 = -1i64;
do
++v9;
while ( byte_140004700[v9] );
if ( v9 == dword_140004078 )
{
for ( i = 0; i < dword_140004078; i += 8 )
sub_140001180(&byte_140004700[i], aWelcometonewst);
if ( !memcmp(byte_140004700, &unk_140004080, dword_140004078) )
sub_140001070(aRight, v6, v7);
else
sub_140001070(aWrong_0, v6, v7);
return 0;
}
else
{
sub_140001070(aWrong, v3, v4);
return 0;
}
}

我们分析一下这个程序

  • sub_140001120 函数后面出现了很明显的格式化字符串,应该是 scanf

  • aPleaseInput 点进去对应字符串 Please Input:。运行附件程序,发现这个是欢迎语,因此 sub_140001070 函数应为 printf

  • byte_140004700 是用户输入。dword_140004078 点进去发现其数值为 32。结合 for 语句的条件 i += 8,可以知道这里将明文分成四等份(每份 8 字节)来处理。

  • unk_140004080 点进去发现疑似为一个数组,用 LazyIDA 提取后发现其长度为 32

  • 重头戏是函数 sub_140001180。点进去:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    __int64 __fastcall sub_140001180(unsigned int *a1, _DWORD *a2)
    {
    __int64 result; // rax
    unsigned int v3; // [rsp+0h] [rbp-38h]
    unsigned int v4; // [rsp+4h] [rbp-34h]
    int v5; // [rsp+8h] [rbp-30h]
    unsigned int i; // [rsp+Ch] [rbp-2Ch]

    v3 = *a1;
    v4 = a1[1];
    v5 = 0;
    for ( i = 0; i < 32; ++i )
    {
    v5 -= 0x61C88647;
    v3 += (a2[1] + (v4 >> 5)) ^ (v5 + v4) ^ (*a2 + 16 * v4);
    v4 += (a2[3] + (v3 >> 5)) ^ (v5 + v3) ^ (a2[2] + 16 * v3);
    }
    *a1 = v3;
    result = 4i64;
    a1[1] = v4;
    return result;
    }

    发现先移位、后异或的操作,并且使用 32 次循环。

    这里的 delta 似乎是 0x61C88647,但这里是递减。通过计算,发现 v5 -= 0x61C88647v5 += 0x9E3779B9 等价,所以真正的 delta 仍然是 0x9E3779B9

    推理:

    0x61C88647 + 0x9E3779B9 = 0xFFFFFFFF + 1 = 0x100000000

    由于 delta 是一个 uint32_t 型常量,因此计算结果被截断,实际存储为 0

    0x9E3779B9 = -0x61C88647

    v5 = v5 - 0x61C88647 等价于 v5 = v5 + 0x9E3779B9

    这里用到的 a2 数组应该为密钥(因为被分成四份了),对应到 main 函数里是 aWelcometonewst,也就是 WelcomeToNewStar(16 字节)。这里还需要将字符串转成十六字节来让脚本处理。

    可以初步确定是 TEA 算法, 因为在对明文处理时没有在下标计算时使用 v5

如果使用 IDA 的 Findcrypt 插件,可以直接 一眼丁真,鉴定为:TEA_DELTA_1400011C3

接下来着手写解密脚本(官方 WriteUp

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

// 解密函数
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], i;
uint32_t delta = 0x9E3779B9;
uint32_t sum = (32)*delta;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) { // 解密时将加密算法的顺序倒过来,+= 变为 -=
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1; // 解密后再重新赋值
}


unsigned char keys[] = "WelcomeToNewStar";
unsigned char cipher[] = { 0x78,0x20,0xF7,0xB3,0xC5,0x42,0xCE,0xDA,0x85,0x59,0x21,0x1A,0x26,0x56,0x5A,0x59,0x29,0x02,0x0D,0xED,0x07,0xA8,0xB9,0xEE,0x36,0x59,0x11,0x87,0xFD,0x5C,0x23,0x24 };
int main()
{
uint32_t *v = (uint32_t*)cipher;
uint32_t *k = (uint32_t*)keys;
// v 为要加密的数据是 n 个 32 位无符号整数
// k 为加密解密密钥,为 4 个 32 位无符号整数,即密钥长度为 128 位

for (int i = 0; i < 8; i += 2)
{
decrypt(v + i, k);
// printf("解密后的数据:%u %u\n", v[i], v[i+1]);
}

for (int i = 0; i < 32; i++) {
printf("%c", cipher[i]);
}

return 0;
}

得到

1
flag{There_R_TEA_XTEA_and_XXTEA}

7. XXTEA 逆向分析实例

以下是一个 XXTEA 加密函数的逆向分析实例,展示了如何从反汇编代码中识别和分析 XXTEA 算法的特征。

7.1 逆向代码分析

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
bool __fastcall xxteaEncrypt(unsigned int *a1, int i, __int64 a3)
{
unsigned int *v3; // rax
unsigned int *v4; // rax
bool result; // al
unsigned int *v6; // rax
int v7; // [rsp+8h] [rbp-18h]
unsigned int v8; // [rsp+8h] [rbp-18h]
int v9; // [rsp+Ch] [rbp-14h]
int v10; // [rsp+Ch] [rbp-14h]
unsigned int k; // [rsp+10h] [rbp-10h]
int j; // [rsp+10h] [rbp-10h]
unsigned int v13; // [rsp+14h] [rbp-Ch]
unsigned int v14; // [rsp+14h] [rbp-Ch]
unsigned int v15; // [rsp+18h] [rbp-8h]
unsigned int v16; // [rsp+18h] [rbp-8h]
unsigned int v17; // [rsp+18h] [rbp-8h]
unsigned int v18; // [rsp+1Ch] [rbp-4h]
unsigned int v19; // [rsp+1Ch] [rbp-4h]
int v20; // [rsp+38h] [rbp+18h]

if ( i <= 1 )
{
if ( i < -1 )
{
v20 = -i;
v10 = 52 / -i + 6;
v14 = 1050114489 * v10;
v19 = *a1;
do
{
v8 = (v14 >> 2) & 3;
for ( j = v20 - 1; j; --j )
{
v16 = a1[j - 1];
v6 = &a1[j];
*v6 -= (((4 * v19) ^ (v16 >> 5)) + ((v19 >> 3) ^ (16 * v16)))
^ ((v19 ^ v14) + (v16 ^ *(4LL * (v8 ^ j & 3) + a3)));
v19 = *v6;
}
v17 = a1[v20 - 1];
*a1 -= (((4 * v19) ^ (v17 >> 5)) + ((v19 >> 3) ^ (16 * v17))) ^ ((v19 ^ v14) + (v17 ^ *(4LL * v8 + a3)));
v19 = *a1;
v14 -= 1050114489;
result = --v10 != 0;
}
while ( v10 );
}
}
else
{
v9 = 52 / i + 6;
v13 = 0;
v15 = a1[i - 1];
do
{
v13 += 1050114489;
v7 = (v13 >> 2) & 3;
for ( k = 0; i - 1 > k; ++k )
{
v18 = a1[k + 1];
v3 = &a1[k];
*v3 += (((4 * v18) ^ (v15 >> 5)) + ((v18 >> 3) ^ (16 * v15)))
^ ((v18 ^ v13) + (v15 ^ *(4LL * (v7 ^ k & 3) + a3)));
v15 = *v3;
}
v4 = &a1[i - 1];
*v4 += (((4 * *a1) ^ (v15 >> 5)) + ((*a1 >> 3) ^ (16 * v15))) ^ ((*a1 ^ v13) + (v15 ^ *(4LL * (v7 ^ k & 3) + a3)));
v15 = *v4;
result = --v9 != 0;
}
while ( v9 );
}
return result;
}

7.2 关键特征识别

通过分析上述逆向代码,可以识别出以下 XXTEA 算法的关键特征:

  1. Delta 常数: 1050114489 (十进制) = 0x3E6779B9 (十六进制)

    • 这个值实际上是 XXTEA 中使用的 delta 值,虽然与标准的 0x9E3779B9 不同,但遵循了相同的模式
  2. 轮数计算: 52 / n + 6

    • 这是 XXTEA 的典型轮数计算公式,其中 n 是数据块的数量
  3. MX 函数的特征运算:

    1
    2
    (((4 * v18) ^ (v15 >> 5)) + ((v18 >> 3) ^ (16 * v15)))
    ^ ((v18 ^ v13) + (v15 ^ *(4LL * (v7 ^ k & 3) + a3)))

    这对应 XXTEA 中的 MX 宏定义中的核心运算

  4. 密钥索引计算: (v7 ^ k & 3)

    • 对应 XXTEA 中的 (p & 3) ^ e 密钥索引计算
  5. 双向操作: 代码包含加密 (i > 1) 和解密 (i < -1) 两个分支

7.3 参数分析

  • a1: 指向数据块数组的指针 (对应 XXTEA 中的 v)
  • i: 数据块数量,正数表示加密,负数表示解密 (对应 XXTEA 中的 n)
  • a3: 指向密钥数组的指针 (对应 XXTEA 中的 key)

7.4 逆向要点

在逆向分析此类 XXTEA 变种时,需要注意:

  1. 提取 delta 值: 识别常数 1050114489 作为 delta
  2. 确认轮数公式: 52/n + 6 是标准的 XXTEA 轮数计算
  3. 分析密钥使用方式: 通过 *(4LL * (v7 ^ k & 3) + a3) 的模式识别密钥索引
  4. 理解数据流: 每个数据块都会受到相邻块的影响,这是 XXTEA 的核心特征

7.5 解密实现

基于上述逆向分析,我们可以编写对应的解密函数。以下是完整的解密实现:

7.5.1 关键差异分析

逆向代码与标准 XXTEA 的主要差异:

  1. Delta 值不同

    • 逆向代码:1050114489 (0x3E9779B9)
    • 标准 XXTEA:0x9E3779B9
  2. MX 函数实现

    • 逆向代码使用:((4 * v18) ^ (v15 >> 5)) 等价于 ((v18 << 2) ^ (v15 >> 5))
    • 标准实现使用:((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4))
  3. 移位操作

    • 逆向代码:16 * v15 等价于 v15 << 4
    • 逆向代码:v19 >> 3 对应标准的右移 3 位操作

7.5.2 完整解密示例

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

// 针对逆向代码的解密函数
void customXXTEADecrypt(uint32_t *data, int n, uint32_t *key) {
if (n <= 1) return;

uint32_t rounds = 52 / n + 6;
uint32_t sum = 1050114489 * rounds;
uint32_t y = data[0];
uint32_t z, e;
int p;

do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = data[p - 1];
data[p] -= (((z << 2) ^ (y >> 5)) + ((y >> 3) ^ (z << 4)))
^ ((y ^ sum) + (z ^ key[(e ^ p) & 3]));
y = data[p];
}
z = data[n - 1];
data[0] -= (((z << 2) ^ (y >> 5)) + ((y >> 3) ^ (z << 4)))
^ ((y ^ sum) + (z ^ key[e]));
y = data[0];
sum -= 1050114489;
} while (--rounds);
}

int main() {
// 示例:假设有密文数据和密钥
uint32_t ciphertext[] = {0x12345678, 0x9ABCDEF0, 0x11111111, 0x22222222};
uint32_t key[] = {0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD};
int n = 4; // 数据块数量

printf("密文: ");
for (int i = 0; i < n; i++) {
printf("0x%08X ", ciphertext[i]);
}
printf("\n");

// 解密
customXXTEADecrypt(ciphertext, n, key);

printf("明文: ");
for (int i = 0; i < n; i++) {
printf("0x%08X ", ciphertext[i]);
}
printf("\n");

// 转换为字符串查看结果
printf("明文字符串: ");
char *str = (char*)ciphertext;
for (int i = 0; i < n * 4; i++) {
if (str[i] >= 32 && str[i] <= 126) { // 可打印字符
printf("%c", str[i]);
} else {
printf(".");
}
}
printf("\n");

return 0;
}

7.5.4 使用要点

  1. 确定参数

    • data: 指向密文数据的指针
    • n: 数据块数量(每块 4 字节)
    • key: 密钥数组(4 个 32 位整数)
  2. 注意事项

    • 确保数据长度是 4 的倍数
    • 注意字节序问题(大端 / 小端)
    • 验证密钥的正确性
  3. 调试技巧

    • 可以先用已知的明文 - 密文对验证算法实现
    • 逐步跟踪每轮解密的中间结果
    • 对比标准 XXTEA 实现找出差异

这个逆向实例展示了 XXTEA 算法在实际应用中的变种形式,通过仔细分析逆向代码的特征,我们可以准确还原其解密逻辑并编写对应的解密工具。