您现在的位置是:首页 >技术教程 >2024熵密杯-第二环节-GITEA(逆向+爆破)网站首页技术教程
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 1000
从2进制可以很明显的看出,明文[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)
爆破代码就不贴上来了,网上多的是





QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
U8W/U8W-Mini使用与常见问题解决
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结