3
[World-Wide-CTF-2024] Flag Checker
题目提示关注main函数执行前的函数,看到有回调函数TlsCallback_0

这样下来Source的值是FAKE,说明有问题

看main函数,加载4-character的参数,储存在pbInput,用FindResourceW查找程序内嵌的资源(ID为101,类型为0xA),算参数的MD5,异或进行资源解密

尝试连接命名管道".\pipe\wwf1",用 CreateFile 打开管道(\.\pipe\wwf1),然后通过 WriteFile 发送数据,ReadFile 接收数据。
管道服务(\.\pipe\wwf1)是一种 进程间通信(IPC, Inter-Process Communication) 机制,允许不同程序(或同一程序的不同部分)通过 命名管道(Named Pipe) 交换数据。
在这里,真正的 flag 检查可能在服务端(也就是前面的资源),客户端只负责发送和接收结果。

可以用CFF Explorer 提取资源
命名管道的服务端通常是一个独立的可执行程序,而这些程序的最后都是00数据

0异或任何数都是这个数本身,可以用这个知道异或的MD5值是什么


那么参数应该是FLAG
import hashlib
md5 = hashlib.md5(b"FLAG").digest()
with open("ch1.exe", "rb") as f:
encrypted_data = f.read()
decrypted = bytes([encrypted_data[i] ^ md5[i % 16] for i in range(len(encrypted_data))])
with open("ch2.exe","wb") as f:
f.write(decrypted)
解密资源,再来分析资源


这里才是真正的check


分析函数


rc4解密得到aes的key和iv

命名管道(Named Pipe)是什么?
管道(Pipe) 是一种单向或双向的数据通道,类似于现实中的水管,数据像水流一样从一个进程流向另一个进程。
命名管道(Named Pipe) 是 Windows 提供的机制,允许 不同进程(甚至不同机器)通过 名称(如 \.\pipe\mypipe)通信。
它采用 客户端-服务器(Client-Server)模型:
服务端(Server) 创建管道并等待连接。
客户端(Client)(如这段代码)尝试连接并发送数据。
RCData 的常见用途
存储自定义数据(如配置文件、密钥、Shellcode、加密数据等)。
存储未加密的资源(如游戏素材、脚本、文本数据)。
存储加密/压缩的数据(需要程序运行时解密)。
[WolvCTF-2024]doubledelete's revenge

flag是48个数据,每四个组成一个32位的数据,再对其循环左移13位
就是移位和大小端序的问题
#include <stdio.h>
#include<string.h>
#include<stdint.h>
uint32_t ror(uint32_t val , int shift){
return (val >> shift) | val << (32-shift);
}
int main(){
unsigned int a[]={0xce,0xec,0x6e,0x8c,0x8b,0x6e,0x2f,0xed,0x6d,0x0d,0x2d,0xc6,0xa6,0xee,0x2b,0x0f,0x66,0xee,0xab,0x2d,0x6c,0xa6,0xce,0x8d,0x0e,0x46,0x6e,0x8e,0x0b,0x0d,0x86,0xec,0x4b,0x0e,0xe6,0xee,0x66,0x06,0x86,0x2e,0x46,0xee,0xeb,0x0e,0x4f,0x61,0xad,0xa6};
uint32_t b[12]={0};
int j=0;
for(int i=0;i<48;i+=4){
b[j] = a[i+3]<<24 | a[i+2] << 16| a[i+1] <<8|a[i];
b[j]=ror(b[j],13);
printf("%c%c%c%c",b[j],b[j]>>8,b[j]>>16,b[j]>>24);
j+=1;
}
}
//wctf{i_th1nk_y0u_m1sund3rst00d_h0w_r0t13_w0rk5}
[WolvCTF-2024]Assembled
手写的x86-64 Linux汇编,要先单步走才能看到这个xor

key的初始值为0x3148,根据奇偶更替key,然后还有一些其他操作形成真正的key,这里的汇编不太好看

简单一点,可以根据动调找到另一个xor的值,因为这个是单字节异或并检验,所以只能一个一个地看提取cl的值异或,再更换输入
[WolvCTF-2024]missing-resources
这道题挺有意思的,官方wp给了视频,主要是找dll和字体,dll下载搜索的时候不要加dll,太多广告了

提示缺失字体,但是下载的zip里面没有这个
字体其实不需要适配,随便一个字体将文件名修改了就行
[WolvCTF-2024]Game Graphics Debugging
题目描述是'The player is given a small "game" which when opened, displays a window containing some basic artwork and a colored box in the UI layer. Underneath that box is the flag, however, the flag cannot be viewed because it's being covered.'(感觉之前见过这种题,但是当时没做出来,当时是一个3D的盒子,不知道是不是这种)
用ida打开找不到信息

看到一份wp说用CE,没想到一下子就搜出来了(对CE的开发不足百分之一,遇到好多次CE能够直接秒的题了)

官方用的方法与GPU有关吧,用pix on windows
原来是CE和pix on windows都可以调试d3d
[京麒]Customize Virtual Machine

在给权限,可能会有smc或者是shellcode,后面看到是在和输入的flag进行异或,猜测是smc,但是因为flag未知,就不可能将函数还原,无法知道opcode

异或的另一个参数会跳转到这个地方,有很明显的重复数据
因为一个数异或0就为数的本身,猜测每个数据块中重复数据就是所求的flag,可以手动提取出来
[黄河]R
参考wp:
http://xherlock.top/index.php/archives/1903/#goforit
https://astralprisma.github.io/2025/05/26/huanghe_25/
都快是对着源码做了,动调能解决很多反编译不清晰的问题
已知这是rc4

动调看v12的数据是(0 ~ 7)
所以对key:
for(int i =0 ;i<8;i++){
key[i] ^= i
}

所以v25就是加密后的数据
sub_140003C80就是一个关键的加密函数

与标准的rc4加密对比一下,发现这里就是KSA算法,魔改的是异或0x66(其实感觉有点牵强)
一步一步走到这


加密:
def KSA(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + (key[i % len(key)] ^ 0x66 )) & 0xff
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
K_S = K >> 4 | K << 4
yield (K_S + 1) & 0xFF
def RC4Decrypt(key, text):
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(((char ^ next(keystream)) + 1) & 0xff)
return bytes(res)
key = [108,110,116,102,118,112,117,115]
for i in range(len(key)):
key[i] ^= i
plaintext = [97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97]
Rc4encrypt = RC4Decrypt(key, plaintext)
print(Rc4encrypt)
可以与v25对比(单字节)
解密:
def KSA(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + (key[i % len(key)] ^ 0x66 )) & 0xff
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
K_S = K >> 4 | K << 4
yield (K_S + 1) & 0xFF
def RC4Decrypt(key, text):
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
char -= 1
res.append(((char ^ next(keystream))) & 0xff)
return bytes(res)
key = [108,110,116,102,118,112,117,115]
for i in range(len(key)):
key[i] ^= i
plaintext = [0x29,0x5,0x13,0xC,0xe7,0xa5,0xd2,0xa2,0xa4,0x3a,0x3a,0x5a,0xbb,0x23,0x9c,0xe4,0xd7,0x2,0xbf]
Rc4encrypt = RC4Decrypt(key, plaintext)
print(Rc4encrypt)
flag:flag{Y0uKn0wRu5tV@ryW@1l}
[黄河]go for it
和rust不一样,这个难的是算法

异或部分:
加密:
e_a = a ^ c
e_b = e_a ^ c ^ b
e_c = c ^ e_b
解密:
c = e_c ^ e_b
a = c ^ e_a
b = e_b ^ a


加密:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h> // 用于PRIx64格式输出
int main() {
// 输入数据
char input[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
uint64_t cipher[4] = {0};
// 处理每个8字节块
for (int block = 0; block < 4; block++) {
uint8_t parts[8] = {0};
uint8_t result[8] = {0};
// ===== 第一阶段:XOR变换 =====
memcpy(parts, input + block*8, 8);
int i = 0;
while (i < 4) { // 处理前4组(3字节一组)
uint8_t tmp1 = parts[i] ^ parts[i + 2];
uint8_t tmp2 = tmp1 ^ parts[i + 2] ^ parts[i + 1];
parts[i] = tmp1;
parts[i + 1] = tmp2;
parts[i + 2] ^= tmp2;
i += 3;
}
// 转换为uint64_t
uint64_t stage1 = 0;
for (int i = 0; i < 8; i++) {
stage1 |= (uint64_t)parts[i] << ((7 - i) * 8);
}
// ===== 第二阶段:64轮大整数运算 =====
int64_t stage2 = stage1;
for (int i = 0; i < 64; i++) {
if (stage2 < 0) {
stage2 = (2 * stage2) ^ 0x2EF20D07161E85F7;
} else {
stage2 *= 2;
}
}
// ===== 第三阶段:位重组 =====
// 分解为字节数组
for (int i = 0; i < 8; i++) {
parts[i] = (stage2 >> (i * 8)) & 0xFF;
}
// 循环左移5位函数
auto rol = [](uint8_t v, int n) {
return ((v << n) | (v >> (8 - n))) & 0xFF;
};
// 位重组
memset(result, 0, 8);
for (int k = 0; k < 8; k++) {
for (int m = 0; m < 8; m++) {
if (((0x80 >> k) & rol(parts[m], 5)) != 0) {
result[k] |= 0x80 >> m;
}
}
}
// 转换回uint64_t
cipher[block] = 0;
for (int i = 0; i < 8; i++) {
cipher[block] |= (uint64_t)result[i] << (i * 8);
}
}
for (int i = 0; i < 4; i++) {
printf("0x%016" PRIx64 ",", cipher[i]);
}
return 0;
}
解密:
#include <stdint.h>
#include <stdio.h>
#include <string.h>
// 循环右移函数
uint8_t ror(uint8_t v, int n) {
return ((v >> n) | (v << (8 - n))) & 0xFF;
}
// 第一阶段解密:位重组逆操作
uint64_t dec3(uint64_t cipher) {
uint8_t parts[8] = {0};
uint8_t result[8] = {0};
uint64_t ret = 0;
// 将64位密文分解为字节数组
for (int i = 0; i < 8; i++) {
parts[i] = (cipher >> (i * 8)) & 0xFF;
}
// 位重组逆操作
for (int k = 0; k < 8; k++) {
for (int m = 0; m < 8; m++) {
if (parts[k] & (0x80 >> m)) {
result[m] |= ror(0x80 >> k, 5);
}
}
}
// 重组为64位整数
for (int i = 0; i < 8; i++) {
ret |= (uint64_t)result[i] << (i * 8);
}
return ret;
}
// 第二阶段解密:64轮大整数逆运算
uint64_t dec2(uint64_t cipher) {
int64_t tmp = cipher;
for (int i = 0; i < 64; i++) {
if (tmp & 1) {
tmp = (tmp ^ 0x2EF20D07161E85F7) / 2;
tmp |= 0x8000000000000000;
} else {
tmp = (uint64_t)tmp / 2;
}
}
return tmp;
}
// 第三阶段解密:XOR逆变换
void dec1(uint64_t cipher, char* plain) {
uint8_t tmp1, tmp2;
uint8_t parts[8] = {0};
// 将64位数据分解为字节数组(注意字节序)
for (int i = 0; i < 8; i++) {
parts[i] = (cipher >> ((7 - i) * 8)) & 0xFF;
}
// 3字节一组的XOR逆运算
int i = 0;
while (i < 4) {
tmp1 = parts[i];
tmp2 = parts[i + 1];
parts[i + 2] ^= tmp2;
parts[i + 1] = tmp1 ^ tmp2 ^ parts[i + 2];
parts[i] = tmp1 ^ parts[i + 2];
i += 3;
}
memcpy(plain, parts, 8);
}
int main() {
char flag[33] = {0}; // 32字符flag + 1个null终止符
uint64_t cipher[4] = {
0x08ADD5C04E5934C8,
0x199AC0E6DA4C2BC9,
0xFF83F5E87D5510B5,
0x58447D6AD4E38B74
};
// 对每个密文块进行解密
for (int i = 0; i < 4; i++) {
uint64_t step1 = dec3(cipher[i]); // 位重组逆操作
uint64_t step2 = dec2(step1); // 大整数逆运算
dec1(step2, flag + i * 8); // XOR逆变换
}
printf("%s", flag);
return 0;
}
好难。。。
最后这个算法嵌套变来变去的我没有完全搞清楚

浙公网安备 33010602011771号