X1c@dM1n1$t

7d53ecd36a43d3d2

37e7dd633dcf8497

409EEC86B884A58B7E8A64E21AD3B8BB

x1login

过检测

首先我们先下载这个题目,因为我的手机开启了root权限所以直接退出了,这里我们直接看看这个,我第一想法是hook这个root检测和debugger检测

Java.perform(function() {
var MainActivity = Java.use('com.nctf.simplelogin.MainActivity'); // 根据实际类名修改

// Hook checkSecutity
MainActivity.checkSecutity.implementation = function() {
console.log('Bypassed Security Check');
// 不执行原方法,相当于直接跳过
};
console.log("Hook started!");
});

这样就可以直接hook掉了这个检测,可以看到叫我们输入用户名和密码,这里看看逻辑吧,其实可以看出是一个动态加载的dex

这里给一下简单的API

动态加载的dex

InMemoryDexClassLoader
这是 Android 9(API 28)引入的一个类加载器,用于从内存中的字节数组加载 Dex 文件。代码中通过 new InMemoryDexClassLoader(ByteBuffer.wrap(...), getClassLoader()) 来实现 Dex 的内存加载。使用这个 API 可以避免将 Dex 文件写入存储,从而减少被提取和逆向的风险。

自己实现的解密这些字符串

相当于字符串加密了,这里我们自己去调用这个native方法去实现解密就行(因为直接看native方法看不懂)

这里本地调用一下这个方法

var DecStr = Java.use('com.nctf.simplelogin.DecStr');

// Hook get 方法
DecStr.get.implementation = function(encryptedData) {
console.log("[*] DecStr.get called");

// 调用原始的 get 方法
var result = this.get(encryptedData);

console.log("[*] DecStr.get returned: " + result);

return result;
};

// 你可以在这里传入你知道的密文,假设它是某种字节数组或字符串
var encryptedUsername = "Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe="; // 你可以将密文作为字节数组或者字符串传入
var encryptedUsername1 = "zM1GzM4=";
var encryptedUsername2 = "agDYB3bJ";
// 调用 DecStr.get 获取解密后的用户名
var decryptedUsername = DecStr.get(encryptedUsername);
console.log("[*] Decrypted Username: " + decryptedUsername);
var decryptedUsername = DecStr.get(encryptedUsername1);
console.log("[*] Decrypted Username: " + decryptedUsername);
var decryptedUsername = DecStr.get(encryptedUsername2);
console.log("[*] Decrypted Username: " + decryptedUsername);

所以这个就是一个动态加载的这个check类,对于这种动态加载的类,我们的办法就是本地dump下这个动态加载的dex,然后去复制到sdcard,再给权限再去拖出来看

var InMemoryDexClassLoader = Java.use('dalvik.system.InMemoryDexClassLoader');

// Hook 构造函数 - 你要 hook 动态加载的类
var constructor = InMemoryDexClassLoader.$init.overload('[Ljava.nio.ByteBuffer;', 'java.lang.ClassLoader');

// Hook 该构造函数
constructor.implementation = function(byteBuffers, classLoader) {
console.log("[*] InMemoryDexClassLoader initialized");

// 获取 DEX 文件的字节数据
var dexBytes = byteBuffers[0].array(); // 获取 byteBuffer 中的字节数据

// 保存 DEX 到文件
saveDexToFile(dexBytes);

// 调用原始构造函数
return this.$init(byteBuffers, classLoader);
};

// 保存 DEX 文件的函数
function saveDexToFile(dexBytes) {
try {
// 获取文件路径(例如存储到 /data/data/your.package/files 目录)
var path = '/data/data/com.nctf.simplelogin/files/dynamic_login.dex'; // 你可以根据实际情况修改路径

// 获取 FileOutputStream 类
var FileOutputStream = Java.use('java.io.FileOutputStream');

// 创建 FileOutputStream 对象
var fileOutputStream = FileOutputStream.$new(path);

// 获取字节数组的长度
var byteArray = Java.array('byte', dexBytes); // 确保我们传递的是一个字节数组

// 使用正确的 write() 重载,将字节数组写入文件
fileOutputStream.write(byteArray); // 传入字节数组

fileOutputStream.close();

console.log("[*] DEX file saved to: " + path);
} catch (e) {
console.log("Error saving DEX: " + e.message);
}
}

这里看出大致逻辑就是对用户名进行判断了,根据我们前面的frida主动调用解密可以得到明文,所以用户名很快就能得到,之后就差密码了,这里的密码的逻辑就是这个用户名进行md5加密再用密码一起传入这个Secure.doCheck方法

[*] Decrypted Username: X1c@dM1n1$t

调用的就是native层的libnative.so文件了。

我们在导出表中没有看到这个doCheck方法,这里

看出应该是动态注册的方法,也看到了这个loaddex的方法,这里我们就看一下这个docheck方法吧

这里可以发现加密是Triple DES的加密的,这里我直接把这个密文拿去解密一直是不行的,后来才想到应该是大小端序的问题。

密钥应该就是我们输入的用户名经过md5加密后的字符串,用户名明文,可惜这里一直不行,后来想这个密钥是8字节,加密解密是不同的密钥,md5是16字节,那么我们按照8字节拆分

7d53ecd36a43d3d2

37e7dd633dcf8497

并且要是小端序,与之同时,我们的密文也要是小端序,每8字节

整理前的密文是409EEC86B884A58B7E8A64E21AD3B8BBDF4BFA1246453E52

所以整理的话密钥就是

d2d3436ad3ec537d9784cf3d63dde737d2d3436ad3ec537d

整理后的密文是

523E454612FA4BDFBBB8D31AE2648A7E8BA584B886EC9E40

就可以得到密码了,最后flag就是用户名_密码就行了。

Safe_program

第一次用超强9.0,这里的大致逻辑就先判断输入的格式和长度,之后进行加密,只不过这里调试就要飞,这里看看导出表发现有TLS回调函数,会在这个主函数加载之前就执行

发现有一个激起除零异常的函数,结合我们一调试就异常可以初步判断这个函数就是在反调试,这里直接apply patch是,改成强制跳转

这里可以看出就是SM4魔改的加密,因为直接用原版的解密方式是不行的,这里的方法就是去看,首先去出题人最喜欢的加密前后看看有没有其他的操作,比如对密钥进行异或,对明文数据进行异或,或者对加密的S盒进行替换,这里看出是对SM4的S盒进行的替换,我们其实可以先随便输入数据,然后调试去验证判断

这里的S盒是不一样的,密钥可以直接调试给出

#ifndef _SM4_H_
#define _SM4_H_
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define u8 unsigned char
#define u32 unsigned long
void four_uCh2uLong(u8* in, u32* out); //四字节转换成u32
void uLong2four_uCh(u32 in, u8* out); //u32转换成四字节
unsigned long move(u32 data, int length); //左移,保留丢弃位放置尾部
unsigned long func_key(u32 input); //先使用Sbox进行非线性变化,再将线性变换L置换为L'
unsigned long func_data(u32 input); //先使用Sbox进行非线性变化,再进行线性变换L
void print_hex(u8* data, int len); //无符号字符数组转16进制打印
void encode_fun(u8 len, u8* key, u8* input, u8* output); //加密函数
void decode_fun(u8 len, u8* key, u8* input, u8* output); //解密函数
/******************************定义系统参数FK的取值****************************************/
const u32 TBL_SYS_PARAMS[4] = {

0xa3b1bac6,
0x56aa3350,
0x677d9197,
0xb27022dc
};
/******************************定义固定参数CK的取值****************************************/
const u32 TBL_FIX_PARAMS[32] = {

0x00070e15,0x1c232a31,0x383f464d,0x545b6269,
0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,
0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,
0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,
0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};
/******************************SBox参数列表****************************************/
const u8 TBL_SBOX[256] = {

0xD1, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x17, 0xA9, 0x1C, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0x4F, 0xF3, 0x73, 0x71, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0xD6, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0xFC, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x78, 0x63, 0x58, 0x9F, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0xC9, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x5E, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48,
};


#endif
#include "sm4.h"
//4字节无符号数组转无符号long型
void four_uCh2uLong(u8* in, u32* out)
{

int i = 0;
*out = 0;
for (i = 0; i < 4; i++)
*out = ((u32)in[i] << (24 - i * 8)) ^ *out;
}
//无符号long型转4字节无符号数组
void uLong2four_uCh(u32 in, u8* out)
{

int i = 0;
//从32位unsigned long的高位开始取
for (i = 0; i < 4; i++)
*(out + i) = (u32)(in >> (24 - i * 8));
}
//左移,保留丢弃位放置尾部
u32 move(u32 data, int length)
{

u32 result = 0;
result = (data << length) ^ (data >> (32 - length));
return result;
}
//秘钥处理函数,先使用Sbox进行非线性变化,再将线性变换L置换为L'
u32 func_key(u32 input)
{

int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = {
0 };
u8 ucSboxValueList[4] = {
0 };
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{

ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 13) ^ move(ulTmp, 23);
return ulTmp;
}
//加解密数据处理函数,先使用Sbox进行非线性变化,再进行线性变换L
u32 func_data(u32 input)
{

int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = {
0 };
u8 ucSboxValueList[4] = {
0 };
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{

ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 2) ^ move(ulTmp, 10) ^ move(ulTmp, 18) ^ move(ulTmp, 24);
return ulTmp;
}
//加密函数(可以加密任意长度数据,16字节为一次循环,不足部分补0凑齐16字节的整数倍)
//len:数据长度(任意长度数据) key:密钥(16字节) input:输入的原始数据 output:加密后输出数据
void encode_fun(u8 len, u8* key, u8* input, u8* output)
{

int i = 0, j = 0;
u8* p = (u8*)malloc(50); //定义一个50字节缓存区
u32 ulKeyTmpList[4] = {
0 }; //存储密钥的u32数据
u32 ulKeyList[36] = {
0 }; //用于密钥扩展算法与系统参数FK运算后的结果存储
u32 ulDataList[36] = {
0 }; //用于存放加密数据
/***************************开始生成子秘钥********************************************/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));
ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];
for (i = 0; i < 32; i++) //32次循环迭代运算
{

//5-36为32个子秘钥
ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/***********************************生成32轮32位长子秘钥结束**********************************/
for (i = 0; i < len; i++) //将输入数据存放在p缓存区
*(p + i) = *(input + i);
for (i = 0; i < 16 - len % 16; i++)//将不足16位补0凑齐16的整数倍
*(p + len + i) = 0;
for (j = 0; j < len / 16 + ((len % 16) ? 1 : 0); j++) //进行循环加密,并将加密后数据保存(可以看出此处是以16字节为一次加密,进行循环,即若16字节则进行一次,17字节补0至32字节后进行加密两次,以此类推)
{

/*开始处理加密数据*/
four_uCh2uLong(p + 16 * j, &(ulDataList[0]));
four_uCh2uLong(p + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(p + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(p + 16 * j + 12, &(ulDataList[3]));
//加密
for (i = 0; i < 32; i++)
{

ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[i + 4]);
}
/*将加密后数据输出*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
free(p);
}
//解密函数(与加密函数基本一致,只是秘钥使用的顺序不同,即把钥匙反着用就是解密)
//len:数据长度 key:密钥 input:输入的加密后数据 output:输出的解密后数据
void decode_fun(u8 len, u8* key, u8* input, u8* output)
{

int i = 0, j = 0;
u32 ulKeyTmpList[4] = {
0 };//存储密钥的u32数据
u32 ulKeyList[36] = {
0 }; //用于密钥扩展算法与系统参数FK运算后的结果存储
u32 ulDataList[36] = {
0 }; //用于存放加密数据
/*开始生成子秘钥*/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));
ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];
for (i = 0; i < 32; i++) //32次循环迭代运算
{

//5-36为32个子秘钥
ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/*生成32轮32位长子秘钥结束*/
for (j = 0; j < len / 16; j++) //进行循环加密,并将加密后数据保存
{

/*开始处理解密数据*/
four_uCh2uLong(input + 16 * j, &(ulDataList[0]));
four_uCh2uLong(input + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(input + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(input + 16 * j + 12, &(ulDataList[3]));
//解密
for (i = 0; i < 32; i++)
{

ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[35 - i]);//与加密唯一不同的就是轮密钥的使用顺序
}
/*将解密后数据输出*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
}
//无符号字符数组转16进制打印
void print_hex(u8* data, int len)
{

int i = 0;
char alTmp[16] = {
'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
for (i = 0; i < len; i++)
{

printf("%c", alTmp[data[i] / 16]);
printf("%c", alTmp[data[i] % 16]);
putchar(' ');
}
putchar('\n');
}
/*在主函数中实现任意字节加密与解密,并且结果正确*/
int main(void)
{

u8 i, len;
unsigned char encode_Result[32] = {
0xFB, 0x97, 0x3C, 0x3B, 0xF1, 0x99, 0x12, 0xDF, 0x13, 0x30, 0xF7, 0xD8, 0x7F, 0xEB, 0xA0, 0x6C,
0x14, 0x5B, 0xA6, 0x2A, 0xA8, 0x05, 0xA5, 0xF3, 0x76, 0xBE, 0xC9, 0x01, 0xF9, 0x36, 0x7B, 0x46,
};
u8 decode_Result[50] = { 0 };

u8 key[] = "NCTF24nctfNCTF24";
//u8 Data_plain[18] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,0x01,0x23 };//定义18字节的原始输入数据(测试用)
//u8 Data_plain[32] = { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 };//定义32字节的原始输入数据(测试用)

u8 Data_plain[32] = {
0x06, 0x75, 0x19, 0x47, 0x16, 0x63, 0x88, 0x7C, 0x8B, 0x66,
0x55, 0xFF, 0x3F, 0x7D, 0x0D, 0x4A, 0xF5, 0xD2, 0x4E, 0x38,
0x3F, 0xE9, 0xC2, 0xDE, 0xDB, 0x7C, 0x7F, 0x6F, 0x74, 0xB1,
0x1F, 0x3C };//输入数据,密文或明文
len = 32;//长度

//len = 16 * (sizeof(Data_plain) / 16) + 16 * ((sizeof(Data_plain) % 16) ? 1 : 0);//得到扩充后的字节数(解密函数会用到)
//encode_fun(sizeof(Data_plain), key, Data_plain, encode_Result); //数据加密
/*printf("加密后数据是:\n");*/

/*注意:此处解密函数的输入数据长度应为扩展后的数据长度,即必为16的倍数*/
decode_fun(len, key, encode_Result, decode_Result);

for (i = 0; i < len; i++)
printf("%c", *(decode_Result + i));
system("pause");
return 0;
}

运行就可以得到flag了

ezDos

拿到题目发现是16位的汇编程序,那就是考验汇编能力,这里需要十分细心

拿到之后先去除花指令,都是一些简单的永真永假跳转,可以随意过掉,这里直接看逻辑

通过这里的判断逻辑以及不相等就直接跳飞,可以看出这是判断输入的长度

这里不断push再加,可以判断出应该就是一个将1-256数据压入到栈中

cx的判断循环的次数

之后就是把这个取出,这其实就是rc4加密的魔改,这里取出的逻辑是遵循栈的先进后出,所以这里的s初始化就是256-0,和s盒的初始化是相反

这里其实可以去看这个数据区发现有字符串,其实就是密钥,这里除余这个字符串的长度可以判断除这里就是密钥的初始操作

这里因为它调用的函数对返回地址进行了改变,所以需要pop,这里的逻辑就是对密钥进行了位移操作,然后前面除的数字再去数组中找key

and就是除余256,所以这个很容易看出是rc4

这个也是魔改点

所以这里的魔改点就是s盒倒序,这个key的位移操作,以及这个s盒值加1

#include <stdio.h>
#include <stdint.h>

// 循环左移3位
uint8_t rol3(uint8_t b) {
return ((b << 3) | (b >> 5)) & 0xFF;
}

// KSA过程
void KSA(uint8_t* S, uint8_t* key, uint32_t key_len) {
int i, j = 0;
uint8_t tmp;

// 倒序初始化
for (i = 0; i < 256; i++) {
S[i] = 255 - i;
}

for (i = 0; i < 256; i++) {
j = (j + S[i] + key[i % key_len]) % 256;
tmp = S[i];
S[i] = S[j];
S[j] = tmp;
}
}

// PRGA过程
void PRGA(uint8_t* S, uint8_t* keystream, uint32_t length) {
int i = 0, j = 0, t;
uint8_t tmp;

for (uint32_t k = 0; k < length; k++) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
tmp = S[i];
S[i] = S[j];
S[j] = tmp;
t = (S[i] + S[j]) % 256;
keystream[k] = (S[t] + 1) % 256;
}
}

int main() {
// 密钥
char key_str[] = "NCTf2024nctF";
uint32_t key_len = sizeof(key_str) - 1;
uint8_t key[12];
for (int i = 0; i < key_len; i++) {
key[i] = rol3((uint8_t)key_str[i]);
}

// 初始化 S 盒
uint8_t S[256];
KSA(S, key, key_len);

// 密文
uint8_t ciphertext[] = {
0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E, 0x3B, 0xB8, 0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43, 0xC5, 0x80, 0x45,
0x5B, 0x9A, 0x29, 0x24, 0x38, 0xA9, 0x5C, 0xCB, 0x7A, 0xE5, 0x93, 0x73, 0x0E, 0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C
};
uint32_t ciphertext_len = sizeof(ciphertext);

// 生成密钥流
uint8_t keystream[38];
PRGA(S, keystream, ciphertext_len);

// 解密
uint8_t plaintext[38];
for (uint32_t i = 0; i < ciphertext_len; i++) {
plaintext[i] = ciphertext[i] ^ keystream[i];
}


printf("Flag: ");
for (uint32_t i = 0; i < ciphertext_len; i++) {
if (plaintext[i] >= 32 && plaintext[i] <= 126) {
printf("%c", plaintext[i]);
}
else {
printf("\\x%02X", plaintext[i]);
}
}
printf("\n");

return 0;
}