您现在的位置是:首页 >技术教程 >2024熵密杯-第二环节-GITEA(逆向+爆破)网站首页技术教程

2024熵密杯-第二环节-GITEA(逆向+爆破)

达沙迪奥 2026-06-18 12:01:04
简介2024熵密杯-第二环节-GITEA(逆向+爆破)

看了大量WP,都是给出payload,但没有说为什么这样做,像我这种没学多久的,还不会C++的人看着就很迷茫,正所谓知其然也要知其所以然,因此我准备花大量文本来解释逆推的过程、思路。

题目大义

给了一段密文和加密代码,要求解密出明文,题目和第一届的第二题类似,但难度比第一届要大一些。

关键代码

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

#define ROUND 16

//S-Box 16x16
int sBox[16] =
        {
                2, 10, 4, 12,
                1, 3, 9, 14,
                7, 11, 8, 6,
                5, 0, 15, 13
        };


// 将十六进制字符串转换为 unsigned char 数组
void hex_to_bytes(const char* hex_str, unsigned char* bytes, size_t bytes_len) {
    size_t hex_len = strlen(hex_str);
    if (hex_len % 2 != 0 || hex_len / 2 > bytes_len) {
        fprintf(stderr, "Invalid hex string length.
");
        return;
    }

    for (size_t i = 0; i < hex_len / 2; i++) {
        sscanf(hex_str + 2 * i, "%2hhx", &bytes[i]);
    }
}


// 派生轮密钥
void derive_round_key(unsigned int key, unsigned char *round_key, int length) {

    unsigned int tmp = key;
    for(int i = 0; i < length / 16; i++)
    {
        memcpy(round_key + i * 16,      &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 4,  &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 8,  &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 12, &tmp, 4);   tmp++;
    }
}


// 比特逆序
void reverseBits(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i++) {
        unsigned char byte = 0;
        for (int j = 0; j < 8; j++) {
            byte |= ((state[i] >> j) & 1) << (7 - j);
        }
        temp[15 - i] = byte;
    }
    for (int i = 0; i < 16; i++) {
        state[i] = temp[i];
    }
}


void sBoxTransform(unsigned char* state) {
    for (int i = 0; i < 16; i++) {
        int lo = sBox[state[i] & 0xF];
        int hi = sBox[state[i] >> 4];
        state[i] = (hi << 4) | lo;
    }
}


void leftShiftBytes(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i += 4) {
        temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);
        temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3);
        temp[i + 2] = state[i + 0] >> 5 | (state[i + 3] << 3);
        temp[i + 3] = state[i + 1] >> 5 | (state[i + 0] << 3);
    }
    for (int i = 0; i < 16; i++)
    {
        state[i] = temp[i];
    }
}


// 轮密钥加
void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) {
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 8; j++) {
            state[i] ^= ((roundKey[i + round * 16] >> j) & 1) << j;
        }
    }
}

// 加密函数
void encrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) {
    unsigned char roundKeys[16 * ROUND] = {}; //

    // 生成轮密钥
    derive_round_key(key, roundKeys, 16 * ROUND);

    // 初始状态为16字节的口令
    unsigned char state[16]; // 初始状态为16字节的密码
    memcpy(state, password, 16); // 初始状态为密码的初始值

    // 迭代加密过程
    for (int round = 0; round < ROUND; round++)
    {
        reverseBits(state);
        sBoxTransform(state);
        leftShiftBytes(state);
        addRoundKey(state, roundKeys, round);
    }

    memcpy(ciphertext, state, 16);
}

void main() {
    unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:开头,16字节的口令
    unsigned int key = 0xF0FFFFFF; // 4字节的密钥
    unsigned char ciphertext[16]; // 16字节的状态

    printf("Password: 
");
    printf("%s
", password);

    encrypt(password, key, ciphertext);

    // 输出加密后的结果
    printf("Encrypted password:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", ciphertext[i]);
    }
    printf("
");
}

题目分析

找到关键加密代码

for (int round = 0; round < ROUND; round++)
    {
        reverseBits(state);
        sBoxTransform(state);
        leftShiftBytes(state);
        addRoundKey(state, roundKeys, round);
    }
  • 这个循环一共调用了4个函数,我们让4个函数在执行后都输出state值,方便我们理解
  • 缩减计算量,ROUND默认是16,也就是说他要执行16次,我们将循环去掉,只执行一次,方便我们阅读
    最终我们修改后的代码
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

#define ROUND 16

//S-Box 16x16
int sBox[16] =
{
        2, 10, 4, 12,
        1, 3, 9, 14,
        7, 11, 8, 6,
        5, 0, 15, 13
};

// 将十六进制字符串转换为 unsigned char 数组
void hex_to_bytes(const char* hex_str, unsigned char* bytes, size_t bytes_len) {
    size_t hex_len = strlen(hex_str);
    if (hex_len % 2 != 0 || hex_len / 2 > bytes_len) {
        fprintf(stderr, "Invalid hex string length.
");
        return;
    }

    for (size_t i = 0; i < hex_len / 2; i++) {
        sscanf(hex_str + 2 * i, "%2hhx", &bytes[i]);
    }
}

// 派生轮密钥
void derive_round_key(unsigned int key, unsigned char* round_key, int length) {

    unsigned int tmp = key;
    for (int i = 0; i < length / 16; i++)
    {
        memcpy(round_key + i * 16, &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 4, &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 8, &tmp, 4);   tmp++;
        memcpy(round_key + i * 16 + 12, &tmp, 4);   tmp++;
    }
    //printf("roundKey
");
    //for (int i = 0; i < 16; i++) {
    //    printf("%02X", round_key[i]);
    //}
    //printf("
");
}

// 比特逆序
void reverseBits(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i++) {
        unsigned char byte = 0;
        for (int j = 0; j < 8; j++) {
            byte |= ((state[i] >> j) & 1) << (7 - j);
        }
        temp[15 - i] = byte;
    }
    for (int i = 0; i < 16; i++) {
        state[i] = temp[i];
    }
    printf("en1:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

void sBoxTransform(unsigned char* state) {
    for (int i = 0; i < 16; i++) {
        int lo = sBox[state[i] & 0xF];
        int hi = sBox[state[i] >> 4];
        state[i] = (hi << 4) | lo;
    }
    printf("en2:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    } 
    printf("
");
}

void leftShiftBytes(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i += 4) {
        temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);
        temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3);
        temp[i + 2] = state[i + 0] >> 5 | (state[i + 3] << 3);
        temp[i + 3] = state[i + 1] >> 5 | (state[i + 0] << 3);
    }
    for (int i = 0; i < 16; i++)
    {
        state[i] = temp[i];
    }
    printf("en3:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

// 轮密钥加
void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) {
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 8; j++) {
            state[i] ^= ((roundKey[i + round * 16] >> j) & 1) << j;
        }
    }
    printf("en4:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

// 加密函数
void encrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) {
    unsigned char roundKeys[16 * ROUND] = {}; //

    // 生成轮密钥
    derive_round_key(key, roundKeys, 16 * ROUND);

    // 初始状态为16字节的口令
    unsigned char state[16]; // 初始状态为16字节的密码
    memcpy(state, password, 16); // 初始状态为密码的初始值

    // 迭代加密过程
    reverseBits(state);
    sBoxTransform(state);
    leftShiftBytes(state);
    addRoundKey(state, roundKeys, 0);
    /*for (int round = 0; round < ROUND; round++)
    {
        reverseBits(state);
        sBoxTransform(state);
        leftShiftBytes(state);
        addRoundKey(state, roundKeys, round);
    }*/

    memcpy(ciphertext, state, 16);
}

void main() {
    unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:开头,16字节的口令
    unsigned int key = 0xF0FFFFFF; // 4字节的密钥
    unsigned char ciphertext[16]; // 16字节的状态

    printf("Password: 
");
    printf("%s
", password);

    encrypt(password, key, ciphertext);

    // 输出加密后的结果
    printf("Encrypted password:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", ciphertext[i]);
    }
    printf("
");
}

输出结果

Password:
pwd:xxxxxxxxxxxx
en1:
1E1E1E1E1E1E1E1E1E1E1E1E5C26EE0E
en2:
AFAFAFAFAFAFAFAFAFAFAFAF3549FF2F
en3:
7D7D7D7D7D7D7D7D7D7D7D7D4FF979AA
en4:
8282828D7D7D7D8C7C7D7D8C4DF9795B
Encrypted password:
8282828D7D7D7D8C7C7D7D8C4DF9795B

我们先来看第一个函数

// 比特逆序
void reverseBits(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i++) {
        unsigned char byte = 0;
        for (int j = 0; j < 8; j++) {
            byte |= ((state[i] >> j) & 1) << (7 - j);
        }
        temp[15 - i] = byte;
    }
    for (int i = 0; i < 16; i++) {
        state[i] = temp[i];
    }
    printf("en1:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

这个函数将明文

7077643A787878787878787878787878(pwd:xxxxxxxxxxxx)

计算成en1

1E1E1E1E1E1E1E1E1E1E1E1E5C26EE0E

简单来说,就是把16进制数转换成2进制,然后逆转、再把数组位置逆序。
比如明文第一个16进制数70
他的二进制是0111 0000
逆转后就是 0000 1110
然后放到最后一个(0E)
明文第二个16进制数77
二进制 0111 0111
逆转后1110 1110
放到倒数第二个(EE)

这个函数不需要编写专门的解密函数,因为再执行一次就能恢复原样
比如密文第一个1E,
二进制 0001 1110
逆转后 0111 1000
放到最后一个(78)
如果熟悉C++的甚至还能优化这个函数,加快执行速度


然后看第二个函数

//S-Box 16x16
int sBox[16] =
{
        2, 10, 4, 12,
        1, 3, 9, 14,
        7, 11, 8, 6,
        5, 0, 15, 13
};
void sBoxTransform(unsigned char* state) {
    for (int i = 0; i < 16; i++) {
        int lo = sBox[state[i] & 0xF];
        int hi = sBox[state[i] >> 4];
        state[i] = (hi << 4) | lo;
    }
    printf("en2:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    } 
    printf("
");
}

将en1

1E1E1E1E1E1E1E1E1E1E1E1E5C26EE0E

计算成en2

AFAFAFAFAFAFAFAFAFAFAFAF3549FF2F

就是个S盒
例如1E,就是取S盒[1](10)和S盒[E](15),再将他们拼接(AF)
函数不需要写专门的解密函数,但S盒需要
目前的S盒是这样(黄色是序号,绿色是数值)
在这里插入图片描述
我们想编写解密的S盒,直接把序号和数值反转就好了(比如之前序号是0的数值是2,反转后变成序号是2的数值是0)
在这里插入图片描述
得到了我们新的S盒

int sBox2[16] =
{
        13, 4, 0, 5,
        2, 12, 11, 8,
        10, 6, 1, 9,
        3, 15, 7, 14
};

第三个函数

void leftShiftBytes(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i += 4) {
        temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);
        temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3);
        temp[i + 2] = state[i + 0] >> 5 | (state[i + 3] << 3);
        temp[i + 3] = state[i + 1] >> 5 | (state[i + 0] << 3);
    }
    for (int i = 0; i < 16; i++)
    {
        state[i] = temp[i];
    }
    printf("en3:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

将en2

AFAFAFAFAFAFAFAFAFAFAFAF3549FF2F

变成en3

7D7D7D7D7D7D7D7D7D7D7D7D4FF979AA

这个就需要编写解密函数了,先上答案

void rightShiftBytes(unsigned char* state) {
    unsigned char temp[16];
    for (int i = 0; i < 16; i += 4) {
        temp[i + 0] = (state[i + 2] << 5) | (state[i + 3] >> 3);
        temp[i + 1] = (state[i + 3] << 5) | (state[i + 0] >> 3);
        temp[i + 2] = (state[i + 0] << 5) | (state[i + 1] >> 3);
        temp[i + 3] = (state[i + 1] << 5) | (state[i + 2] >> 3);
    }
    for (int i = 0; i < 16; i++)
    {
        state[i] = temp[i];
    }
}

这个是花费我最多时间的,因为当时看不懂为什么加密的时候是temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);解密时就变成了temp[i + 0] = (state[i + 2] << 5) | (state[i + 3] >> 3);,去问了下AI,结果AI叽叽咕咕半天,最后告诉我无法理解
在这里插入图片描述
最后逼得我一步步手算,才搞清楚原理,理解后发现好像还挺简单的
为了方便理解,我们直接演示最后一个分组是怎么计算的

35 49 FF 2F -> 4F F9 79 AA

temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);
4F = FF >> 5 | 49 << 3
0100 1111 = 0000 0111 | 0100 10002进制可以很明显的看出,明文[2]FF的前3位和明文[1]49的前5位组成密文[0]4F
所以如果我们想要反向计算出明文[2],我们就要截取密文[0]的后3位,也就是密文[0]<<5(1110 0000)
现在我们有了明文[2]的前3位,还差后5位,我们就看哪个等式用到了明文[2]的后5位
temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3);
F9 = 2F >> 5 | FF << 3
1111 1001 = 0000 0001 | 1111 1000
我们将密文[1]右移3位,是不是就得到了明文[2]的后5位了
所以我们就得到了等式
密文[0] << 5 | 密文[1] >> 3 = 明文[2]
4F << 5 | F9 >> 3 = FF
以此类推,就可以得到我们的解密函数了

第四个函数

// 轮密钥加
void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) {
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 8; j++) {
            state[i] ^= ((roundKey[i + round * 16] >> j) & 1) << j;
        }
    }
    printf("en4:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

将en3

7D7D7D7D7D7D7D7D7D7D7D7D4FF979AA

计算成en4

8282828D7D7D7D8C7C7D7D8C4DF9795B

这个就比较简单了,其实就是异或,将明文和轮秘钥进行异或
根据异或性质,A⊕B=C A⊕C=B B⊕C=A
所以将密文和轮秘钥异或就可以得到明文,不需要写解密函数
不过可以优化,多了个没什么意义的位运算,可以去掉

void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) {
    for (int i = 0; i < 16; i++) {
        state[i] ^= roundKey[i + round * 16];
    }
    printf("en4:
");
    for (int i = 0; i < 16; i++) {
        printf("%02X", state[i]);
    }
    printf("
");
}

至此,我们4个加密函数都找到了反编译方法~
另外编写解密函数的时候要注意,之前的加密函数用到了round值,顺序是0-15
所以我们的解密函数就要反着来,从15-0

for (int round = 15; round >= 0; round--)
{
    addRoundKey(state, roundKeys, round);
    rightShiftBytes(state);
    sBoxTransform2(state);
    reverseBits(state);
}

解密函数编写完成,就可以解密密文了,但我们还缺少1个条件,我们不知道key
这就只能爆破了,爆破出明文前4个字符是:pwd:就成功了
之前优化算法也是为了增加爆破的速度,另外也给出提示,key第一位是F(Fxxxxxxx)
爆破代码就不贴上来了,网上多的是

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。