本站上的 XYCTF 2024 (baby_AIO) Writeup by 摸鱼
# 前言
好玩,爱玩。
原本看到出题人名单,就想着只来看一眼题目就走,后来和 luoingly 师傅组了队,虽然叫 “摸鱼”,但还是认真做题了。然后发现 Reverse 很多题目质量很高。下面的 Writeup 也按照我认为的题目质量排序,越靠前的题目越值得做。(但是 Misc 很多题目质量一言难尽)
比赛持续时间很长、题目数量很多,中间有三分之一以上的时间忙着准备长城杯半决赛、给校赛出题、准备 MSC 分享会去了,最后也没有来得及 AK Reverse,也没有来得及现学 Pwn。后续此 WP 将补上两道 Unity 和部分题目的预期解。打算之后 WP 做题法补 Pwn 题,然后看情况可能会更新一篇补题 WP。
最后摸鱼队排在第 8 名,主要是 Misc、Web、Reverse 接近 AK,但是 Crypto 和 Pwn 分别差十几题。摸鱼队的 Misc 和 Web 几乎都是 luoingly 师傅做的(orz),Reverse 是我做的。在摸鱼前面的队伍有熟悉的暨大 Xp0int 和深大 Aurora。W4terDr0p 的三个只打了半年 CTF 的新人,在前两周时也排进过前二十,orz。
我喜欢打新生赛,是因为新生赛能学到新东西,且相比于大比赛没那么 “功利”,也通常不需要在一道题上花很多时间。而且我想找回去年这个时间打第一场新手赛的感觉,那是我打 CTF 最快乐的一次。
没想到 XYCTF 参赛人数多达一千多支队伍,看得出来主办方和运维真的很辛苦,感谢。
# 舔狗四部曲 -- 我的白月光
不再如过去,真的要再见了。
出题人:@H1m
13 支队伍攻克
通过在 main 函数中调用 Windows API 实现的 GUI 程序。程序逻辑:
-
main 函数前 6 行初始化窗体
-
从 _mm_load_si128 到异或 0x66 是将大量数据复制到栈上并解密,解密结果看不出规律
-
八句 sub_140002630 在窗体上绘制八行以 UTF16-LE 编码的文字,最后一行是
flag{L0v3_(还有两个部分在等待你)
,并正常调用 MessageBoxA 🚩 -
从 GetModuleHandleA 到 VirtualProtect 之后是获得导入函数表的地址,遍历导入表,找到导入表中存放 MessageBoxA 地址的位置,修改其内存权限,并改为 sub_140001470 的地址,之后调用 MessageBoxA 就会调用 sub_140001470 了
-
如下操作进行 440 次:
- 从 _mm_load_si128 到异或 0x66 是对另一大段数据进行相同的解密过程,解密结果:
in the circus i know something are here, at the right time you can choose to skipfffffffffffffffffff
- 调用 MessageBoxA ,实际调用 sub_140001470
- 对另一大段数据进行相同的解密过程,解密结果:
please do not try to find her she do not belong to ago memories i will give you some flags:i8_a_k3y_and now go back please!
🚩 - 调用 MessageBoxW ,弹出 7 个对话框
- 返回 1
- 对另一大段数据进行相同的解密过程,解密结果:
- 将返回值最低位取反,写到栈上数组对应位置,实际上得到一个 440 字节的全为 0x00 的数组,如果不劫持 MessageBoxA 则是一个 440 字节的每个元素要么为 0x00 要么为 0x01 的数组
- 从 _mm_load_si128 到异或 0x66 是对另一大段数据进行相同的解密过程,解密结果:
-
在 for 循环中调用 440 次 sub_140001070 即 sprintf , IDA 反编译得到的实参列表不正确,右键 Set call type… 对函数调用增加一个参数,改为
__int64 (__fastcall *)(char *Buffer, size_t BufferCount, char *Format, unsigned int MoYuTeam)
这里的 v40 数组是上面的 440 字节的数组,对数组中每 8 个占 1 字节的布尔值合并成 1 字节,然后转为十六进制文本表示,并放到栈上另一个位置,最终得到 440 / 8 * 2 = 110 字符的十六进制文本
-
sub_140001290 是魔改的 Base64 算法,对这 110 字符的十六进制文本进行 Base64 并放到栈上另一个位置,魔改是输入 3 字节和输出 4 字节均使用小端序,相当于先把原文倒过来,然后进行正常 Base64 ,最后再将编码结果倒过来
-
最后将编码结果与字符串字面量比较,并输出比较结果。解码得:
🚩 flag{L0v3_i8_a_k3y_and_memory_never_go_done_finally_thankyou_xiaowangtongxue}
# 馒头
听说你数据结构与算法学的很好?
出题人:@Ea5y
31 支队伍攻克
从 “本地类型” 可以看到结构体定义
发现是用数组实现的哈夫曼树,整理一下反编译的代码,加上注释
typedef struct | |
{ | |
int data; | |
int weight; | |
int parent; | |
int lch; | |
int rch; | |
} htNode; | |
typedef htNode *huffmanTree; | |
int initHuffmanTree(huffmanTree *HT) | |
{ | |
*HT = (huffmanTree)malloc(960); | |
for (int i = 1; i <= 47; ++i) // 下标 0 留空不用,下标 1-24 是叶子节点,下标 25-47 是非叶子节点 | |
{ | |
(*HT)[i].parent = (*HT)[i].lch = (*HT)[i].rch = -1; | |
} | |
puts("please input flag:"); | |
for (int i_0 = 1; i_0 <= 24; ++i_0) // 叶子节点的 data 是 1-24,weight 是输入的字符 | |
{ | |
(*HT)[i_0].data = i_0; | |
(*HT)[i_0].weight = getchar(); | |
} | |
return 1; | |
} | |
void creatHuffmanTree(huffmanTree *HT, int n) | |
{ | |
int j; // [rsp+8h] [rbp-18h] | |
int rnode; // [rsp+Ch] [rbp-14h] | |
int min2; // [rsp+10h] [rbp-10h] | |
int lnode; // [rsp+14h] [rbp-Ch] | |
int min1; // [rsp+18h] [rbp-8h] | |
int i; // [rsp+1Ch] [rbp-4h] | |
if (n > 1) | |
{ | |
for (i = n + 1; i < 2 * n; ++i) // 对第 25 个节点到第 47 个节点赋值,data 未初始化但应该初始化为 0 才好 | |
{ | |
min1 = 0x7FFF; | |
lnode = -1; | |
min2 = 0x7FFF; | |
rnode = -1; | |
for (j = 1; i > j; ++j) // 找到两个最小的节点 | |
{ | |
if (min1 > (*HT)[j].weight && (*HT)[j].parent == -1) | |
{ | |
min2 = min1; | |
rnode = lnode; | |
min1 = (*HT)[j].weight; | |
lnode = j; | |
} | |
else if (min2 > (*HT)[j].weight && (*HT)[j].parent == -1) | |
{ | |
min2 = (*HT)[j].weight; | |
rnode = j; | |
} | |
} | |
(*HT)[lnode].parent = (*HT)[rnode].parent = i; | |
(*HT)[i].lch = lnode; | |
(*HT)[i].rch = rnode; | |
(*HT)[i].weight = (*HT)[lnode].weight + (*HT)[rnode].weight; | |
} | |
// 这时 (*HT)[47] 是根节点 | |
} | |
} | |
int ans1[58] = { 2270, 917, 446, 217, 106, 51, 20, 15, 17, 229, 114, 16, 11, 471, 233, 116, 14, 13, 238, 118, 12, 7, 1353, 557, 248, 123, 6, 24, 309, 137, 67, 3, 5, 172, 84, 4, 1, 796, 383, 186, 89, 2, 8, 197, 97, 48, 23, 10, 21, 413, 203, 101, 22, 9, 210, 104, 19, 18 }; | |
int check_flag(huffmanTree HT, int i) | |
{ | |
static int index = 0; | |
if (i <= 24) // 对于叶子,只比对下标 | |
{ | |
if (HT[i].data != ans1[index++]) | |
return 0; | |
} | |
else // 对于非叶子,只比对权值,如果左孩子是叶子,再比对左孩子的权值 | |
{ | |
if (HT[i].weight != ans1[index++]) | |
return 0; | |
if (HT[HT[i].lch].data <= 24 && HT[HT[i].lch].data > 0) | |
{ | |
if (HT[HT[i].lch].weight != ans1[index++]) | |
return 0; | |
} | |
} | |
if (HT[i].lch <= 0) // 递归终止条件 | |
return 1; | |
return check_flag(HT, HT[i].lch) && check_flag(HT, HT[i].rch); // 先序遍历 | |
} |
比对下标的时候,下标不会大于 24;而权值不会小于 32(ASCII 可打印字符)。可以据此区分开叶子和非叶子。
才 58 个数据,写解题程序如果出错了还要 debug,不如手撕🐶
🚩 XYCTF{xaf1svut7rojh3de0}
# 舔狗四部曲 -- 记忆的时光机
一切都晚了,开始你的时空探索。
出题人:@H1m
19 支队伍攻克
通过在栈上存放地址,每次执行一段操作后,读栈上保存的地址并跳转,实现函数内的控制流。动态调试一下
check 函数第一次 jmp 后可以知道 flag 长度为 48
走到 enc 被调用处,edi 寄存器是用户输入的字符串指针,esi 寄存器是即将比对的字符下标,每次调用 enc 将加密一个字符,手动记录关键逻辑如下
edi = (char*)Input | |
esi = (int)Idx | |
r10 = esi | |
esi += 6 | |
r11 = (char*)Key | |
r8d = (char)Input[Idx] | |
r8d ^= esi | |
r8d ^= 0x66 | |
r8d -= 6 | |
r9d = (char)Key[Idx] | |
r8d ^= r9d | |
eax = r8d |
#include <stdio.h> | |
unsigned char ans[] = | |
{ | |
0x69, 0x58, 0x61, 0x63, 0x67, 0x4C, 0x4D, 0x32, 0x98, 0x20, | |
0x4D, 0x51, 0x7B, 0x25, 0x75, 0x51, 0xA3, 0x58, 0x60, 0x72, | |
0x42, 0x62, 0x67, 0x66, 0x37, 0x6C, 0x30, 0x46, 0x66, 0x4F, | |
0x5D, 0x03, 0x5D, 0xA4, 0x66, 0x01, 0x43, 0x68, 0x7D, 0x7C, | |
0x55, 0x4F, 0x7A, 0x3F, 0x6C, 0x12, 0x21, 0x09}; | |
const char *key = "i_have_get_shell_but_where_is_you_my_dear_baby!!"; | |
int main() { | |
for (int i = 0; i < 48; i++) { | |
ans[i] ^= key[i]; | |
ans[i] += 6; | |
ans[i] ^= 0x66; | |
ans[i] ^= (i + 6); | |
} | |
printf(ans); | |
return 0; | |
} |
🚩 flag{Br0k3n_m3m0r1es_for3v3r_Sh1n@_1n_The_H3@$T}
# ez_rand
Release 太简单啦;flag 格式:XYCTF {}
出题人:@upsw1ng
65 支队伍攻克
动态调试发现:在不同的时刻随机生成的异或 key 不相同;
只取 time (0) 返回值的低 16 位(ax)传递给 srand,足够小,可以爆破;
rand 函数(看导入表知道是来自 api-ms-win-crt-utility-l1-1-0 的)总是返回一个范围是 16 位的整数。
为确保爆破用的 rand 同样是来自 api-ms-win-crt-utility-l1-1-0 的,这里同样使用 VS 2022 Release 配置(后面发现其实 Debug 或者 GCC 的结果都是相同的)。
#include <stdio.h> | |
#include <stdlib.h> | |
#define u8 unsigned char | |
#define u16 unsigned short | |
#define u32 unsigned int | |
#define u64 unsigned long long | |
u8 ida_chars[] = | |
{ | |
0x5D, 0x0C, 0x6C, 0xEA, 0x46, 0x19, 0xFC, 0x34, 0xB2, 0x62, | |
0x23, 0x07, 0x62, 0x22, 0x6E, 0xFB, 0xB4, 0xE8, 0xF2, 0xA9, | |
0x91, 0x12, 0x21, 0x86, 0xDB, 0x8E, 0xE9, 0x43, 0x4D}; | |
const char* tmpl = "XYCTF{XXXXXXXXXXXXXXXXXXXXXX}"; | |
int main() { | |
for(u32 i = 0; i <= 0xFFFF; i++) { | |
u32 flag = 1; | |
srand(i); | |
for(int j = 0; j < 29; j++) { | |
u32 eax = rand(); // 16 bits | |
u32 r8d = eax; | |
u64 mul = (u64)eax * (u64)0x80808081; | |
u32 edx = (mul >> 32) >> 7; | |
u32 ecx; | |
// always 0: | |
// ecx = edx >> 31; | |
// edx += ecx; | |
ecx = edx * 0xFF; | |
r8d -= ecx; | |
r8d ^= ida_chars[j]; | |
if (j < 6 || j == 28) { | |
if (r8d != tmpl[j]) { | |
flag = 0; | |
break; | |
} | |
} | |
// or: | |
// if (r8d < 32 || r8d >= 127) { | |
// flag = 0; | |
// break; | |
// } | |
} | |
if(flag) { | |
printf("Seed: %u\t", i); | |
srand(i); | |
for (int j = 0; j < 29; j++) { | |
u32 eax = rand(); | |
u32 r8d = eax; | |
u64 mul = (u64)eax * (u64)0x80808081; | |
u32 edx = (mul >> 32) >> 7; | |
u32 ecx; | |
ecx = edx * 0xFF; | |
r8d -= ecx; | |
r8d ^= ida_chars[j]; | |
printf("%c", r8d); | |
} | |
printf("\n"); | |
} | |
} | |
} | |
// Seed: 21308 XYCTF{R@nd_1s_S0_S0_S0_easy!} |
# 舔狗四部曲 -- 简爱
爱情就像 re,持之以恒,终得真爱;flag 格式:FLAG {} 难度:medium
出题人:@CDM258
20 支队伍攻克
file 可知给出的是可重定位目标文件(只编译了、没有链接),链接一下
gcc jianai -o jianai1 |
中文字符串是国标码,打印出来全是问号,坏(问了出题人说是用 Dev C++ 编辑的)
IDA 反编译的结果中,对几个字符串的复制与引用很混乱(问了出题人发现是因为用了栈上的可变长度数组),动态调试才知道每一处分别用的是哪个地址的字符串
TEA 是骗人的,真正的加密逻辑在 howtolove 函数(参数是用户输入原样),密文需要与 fake flag 一致,z3 梭了
from z3 import * | |
s = Solver() | |
temp = [BitVec(f'flag{i}', 8) for i in range(32)] | |
moyu_z3_vars = temp[:] | |
for i in range(32): | |
s.add(temp[i] >= 0x20, temp[i] <= 0x7e) | |
v2 = [0] * 0x1C20 | |
v2[32] = 2; | |
v2[65] = 2; | |
v2[66] = 4; | |
v2[98] = 2; | |
v2[99] = 5; | |
v2[185] = 2; | |
v2[186] = 2; | |
v2[187] = 1; | |
v2[188] = 1; | |
v2[189] = 1; | |
v2[190] = 1; | |
v2[191] = 1; | |
v2[192] = 1; | |
v2[193] = 1; | |
v2[194] = 1; | |
v2[195] = 1; | |
v2[196] = 1; | |
v2[197] = 1; | |
v2[198] = 1; | |
v2[199] = 1; | |
v2[200] = 1; | |
v2[201] = 1; | |
v2[202] = 1; | |
v2[203] = 1; | |
v2[204] = 1; | |
v2[205] = 1; | |
v2[206] = 1; | |
v2[207] = 1; | |
v2[208] = 1; | |
v2[209] = 1; | |
v2[210] = 1; | |
v2[211] = 1; | |
v2[212] = 1; | |
v2[213] = 1; | |
v2[214] = 1; | |
v2[215] = 1; | |
v2[216] = 1; | |
v2[217] = 1; | |
v2[218] = 1; | |
v2[219] = 1; | |
v2[220] = 1; | |
v2[221] = 1; | |
v2[222] = 1; | |
v2[223] = 1; | |
v2[224] = 1; | |
v2[225] = 1; | |
v2[226] = 1; | |
v2[227] = 1; | |
v2[228] = 1; | |
v2[229] = 2; | |
v2[232] = 2; | |
v2[256] = 2; | |
v2[257] = 5; | |
v2[303] = 1; | |
v2[304] = 1; | |
v2[305] = 1; | |
v2[306] = 1; | |
v2[307] = 2; | |
v2[308] = 5; | |
v2[328] = 1; | |
v2[329] = 1; | |
v2[330] = 1; | |
v2[331] = 1; | |
v2[332] = 1; | |
v2[333] = 1; | |
v2[334] = 1; | |
v2[335] = 1; | |
v2[336] = 1; | |
v2[337] = 1; | |
v2[338] = 1; | |
v2[339] = 1; | |
v2[340] = 1; | |
v2[341] = 1; | |
v2[342] = 2; | |
v2[353] = 2; | |
v2[354] = 5; | |
v2[430] = 2; | |
v2[431] = 2; | |
v2[432] = 5; | |
v2[523] = 2; | |
v2[524] = 5; | |
v2[564] = 2; | |
v2[565] = 5; | |
v2[627] = 2; | |
v2[628] = 1; | |
v2[629] = 1; | |
v2[630] = 1; | |
v2[631] = 1; | |
v2[632] = 1; | |
v2[633] = 1; | |
v2[634] = 1; | |
v2[635] = 1; | |
v2[636] = 1; | |
v2[637] = 1; | |
v2[638] = 1; | |
v2[639] = 1; | |
v2[640] = 1; | |
v2[641] = 1; | |
v2[642] = 1; | |
v2[643] = 1; | |
v2[644] = 1; | |
v2[645] = 1; | |
v2[646] = 1; | |
v2[647] = 2; | |
v2[648] = 4; | |
v2[649] = 1; | |
v2[650] = 1; | |
v2[651] = 1; | |
v2[652] = 1; | |
v2[653] = 2; | |
v2[680] = 2; | |
v2[687] = 2; | |
v2[688] = 4; | |
v2[698] = 2; | |
v2[766] = 2; | |
v2[767] = 5; | |
v2[818] = 2; | |
v2[819] = 1; | |
v2[820] = 2; | |
v2[827] = 2; | |
v2[828] = 5; | |
v2[846] = 2; | |
v2[847] = 5; | |
v2[890] = 2; | |
v2[891] = 1; | |
v2[892] = 1; | |
v2[893] = 1; | |
v2[894] = 1; | |
v2[895] = 1; | |
v2[896] = 1; | |
v2[897] = 1; | |
v2[898] = 1; | |
v2[899] = 1; | |
v2[900] = 1; | |
v2[901] = 1; | |
v2[902] = 1; | |
v2[903] = 1; | |
v2[904] = 1; | |
v2[905] = 1; | |
v2[906] = 1; | |
v2[907] = 1; | |
v2[908] = 1; | |
v2[909] = 1; | |
v2[910] = 1; | |
v2[911] = 1; | |
v2[912] = 1; | |
v2[913] = 1; | |
v2[914] = 1; | |
v2[915] = 1; | |
v2[916] = 1; | |
v2[917] = 1; | |
v2[918] = 1; | |
v2[919] = 1; | |
v2[920] = 1; | |
v2[921] = 1; | |
v2[922] = 1; | |
v2[923] = 1; | |
v2[924] = 1; | |
v2[925] = 1; | |
v2[926] = 1; | |
v2[927] = 1; | |
v2[928] = 1; | |
v2[929] = 1; | |
v2[930] = 1; | |
v2[931] = 1; | |
v2[932] = 1; | |
v2[933] = 2; | |
v2[934] = 5; | |
v2[989] = 2; | |
v2[994] = 2; | |
v2[995] = 1; | |
v2[996] = 1; | |
v2[997] = 1; | |
v2[998] = 1; | |
v2[999] = 1; | |
v2[1000] = 1; | |
v2[1001] = 1; | |
v2[1002] = 1; | |
v2[1003] = 1; | |
v2[1013] = 1; | |
v2[1014] = 1; | |
v2[1015] = 1; | |
v2[1016] = 1; | |
v2[1017] = 1; | |
v2[1018] = 1; | |
v2[1019] = 1; | |
v2[1020] = 1; | |
v2[1021] = 1; | |
v2[1022] = 1; | |
v2[1023] = 1; | |
v2[1024] = 1; | |
v2[1025] = 1; | |
v2[1026] = 1; | |
v2[1027] = 2; | |
v2[1028] = 3; | |
v4 = 0; | |
v3 = 0; | |
while ( 1 ): | |
while ( 1 ): | |
while ( 1 ): | |
while ( not v2[v3] ): | |
v3 += 1 | |
temp[v4] += 1 | |
if ( v2[v3] != 1 ): | |
break; | |
v3 += 1 | |
temp[v4] -= 1 | |
if ( v2[v3] != 2 ): | |
break; | |
v3 += 1 | |
v4 += 1 | |
if ( v2[v3] == 3 ): | |
break; | |
if ( v2[v3] == 4 ): | |
temp[v4] = temp[v4] + temp[v4 + 1] - 70; | |
v3 += 1 | |
elif ( v2[v3] == 5 ): | |
temp[v4] = temp[v4] - temp[v4 + 1] + 70; | |
v3 += 1 | |
for i in range(32): | |
s.add(temp[i] == ord('flag{Love_is_not_one_sided_Love}'[i])) | |
if s.check() == sat: | |
model = s.model() | |
print(''.join([chr(model[moyu_z3_vars[i]].as_long()) for i in range(32)])) |
🚩 FLAG{vm_is_A_3ecreT_l0Ve_revers}
# ez_cube
你会玩魔方吗?我反正不会。flag {What_you_input}
出题人:@Showmaker
98 支队伍攻克
4 种字符、长度最多 12,可爆
import itertools | |
from tqdm import tqdm | |
def reset(): | |
global pr, pb, pg, po, py, pw | |
pr = [0] * 9 | |
pb = [1] * 9 | |
pg = [2] * 9 | |
po = [3] * 9 | |
py = [4] * 9 | |
pw = [5] * 9 | |
pb[1] = 0 | |
pr[1] = 2 | |
pg[1] = 1 | |
def R(): | |
global pr, pb, pg, po, py, pw | |
v0 = pr[2]; | |
v1 = pr[5]; | |
v2 = pr[8]; | |
pr[2] = pw[2]; | |
pr[5] = pw[5]; | |
pr[8] = pw[8]; | |
pw[2] = po[6]; | |
pw[5] = po[3]; | |
pw[8] = po[0]; | |
po[0] = py[8]; | |
po[3] = py[5]; | |
po[6] = py[2]; | |
py[2] = v0; | |
py[5] = v1; | |
py[8] = v2; | |
v3 = pg[1]; | |
pg[1] = pg[3]; | |
pg[3] = pg[7]; | |
pg[7] = pg[5]; | |
pg[5] = v3; | |
v4 = pg[0]; | |
pg[0] = pg[6]; | |
pg[6] = pg[8]; | |
pg[8] = pg[2]; | |
pg[2] = v4; | |
def U(): | |
global pr, pb, pg, po, py, pw | |
v0 = pr[0]; | |
v1 = pr[1]; | |
v2 = pr[2]; | |
pr[0] = pg[0]; | |
pr[1] = pg[1]; | |
pr[2] = pg[2]; | |
pg[0] = po[0]; | |
pg[1] = po[1]; | |
pg[2] = po[2]; | |
po[0] = pb[0]; | |
po[1] = pb[1]; | |
po[2] = pb[2]; | |
pb[0] = v0; | |
pb[1] = v1; | |
pb[2] = v2; | |
v3 = py[1]; | |
py[1] = py[3]; | |
py[3] = py[7]; | |
py[7] = py[5]; | |
py[5] = v3; | |
v4 = py[0]; | |
py[0] = py[6]; | |
py[6] = py[8]; | |
py[8] = py[2]; | |
py[2] = v4; | |
def r(): | |
global pr, pb, pg, po, py, pw | |
v0 = pr[2]; | |
v1 = pr[5]; | |
v2 = pr[8]; | |
pr[2] = py[2]; | |
pr[5] = py[5]; | |
pr[8] = py[8]; | |
py[2] = po[6]; | |
py[5] = po[3]; | |
py[8] = po[0]; | |
po[0] = pw[8]; | |
po[3] = pw[5]; | |
po[6] = pw[2]; | |
pw[2] = v0; | |
pw[5] = v1; | |
pw[8] = v2; | |
v3 = pg[1]; | |
pg[1] = pg[5]; | |
pg[5] = pg[7]; | |
pg[7] = pg[3]; | |
pg[3] = v3; | |
v4 = pg[0]; | |
pg[0] = pg[2]; | |
pg[2] = pg[8]; | |
pg[8] = pg[6]; | |
pg[6] = v4; | |
def u(): | |
global pr, pb, pg, po, py, pw | |
v0 = pr[0]; | |
v1 = pr[1]; | |
v2 = pr[2]; | |
pr[0] = pb[0]; | |
pr[1] = pb[1]; | |
pr[2] = pb[2]; | |
pb[0] = po[0]; | |
pb[1] = po[1]; | |
pb[2] = po[2]; | |
po[0] = pg[0]; | |
po[1] = pg[1]; | |
po[2] = pg[2]; | |
pg[0] = v0; | |
pg[1] = v1; | |
pg[2] = v2; | |
v3 = py[1]; | |
py[1] = py[5]; | |
py[5] = py[7]; | |
py[7] = py[3]; | |
py[3] = v3; | |
v4 = py[0]; | |
py[0] = py[2]; | |
py[2] = py[8]; | |
py[8] = py[6]; | |
py[6] = v4; | |
if __name__ == '__main__': | |
a = [] | |
for l in range(2, 13): | |
a += itertools.product('RrUu', repeat=l) | |
for s in tqdm(a): | |
reset() | |
for c in s: | |
if c == 'R': | |
R() | |
elif c == 'r': | |
r() | |
elif c == 'U': | |
U() | |
elif c == 'u': | |
u() | |
if not all(all(x == t[0] for x in t) for t in [pr, pb, pg]): | |
continue | |
print(''.join(s)) |
🚩 flag{RuRURURuruRR}
# Findme
小叮当藏起来了,你能找到他吗?
出题人:@ShowMaker
15 支队伍攻克
Doraemon4 会初始化一个 512 字节的 BOX,然后用 BOX 加密 Doraemon3 得到 Doraemon1。由于 BOX 初始状态已知,正向顺序重新跑一次即可。复制反编译代码到新的 C 源文件,fputc 和 fgetc 互换,直接跑得到 Doraemon3。
#include <stdio.h> | |
#include <string.h> | |
const char *Str = "Find_Doraemon"; | |
unsigned char BOX[512]; | |
void init_box() | |
{ | |
char *v0; // rdi | |
__int64 i; // rcx | |
char v3[32]; // [rsp+0h] [rbp-20h] BYREF | |
char v4; // [rsp+20h] [rbp+0h] BYREF | |
unsigned int v5; // [rsp+24h] [rbp+4h] | |
char v6; // [rsp+44h] [rbp+24h] | |
char v7[532]; // [rsp+70h] [rbp+50h] BYREF | |
unsigned int j; // [rsp+284h] [rbp+264h] | |
int v9; // [rsp+2A4h] [rbp+284h] | |
int v10; // [rsp+2C4h] [rbp+2A4h] | |
v5 = strlen(Str); | |
memset(v7, 0, 0x200); | |
for (j = 0; j < 0x200; ++j) | |
{ | |
v6 = j; | |
BOX[j] = -(char)j; | |
v7[j] = Str[j % v5]; | |
} | |
v9 = 0; | |
v10 = 0; | |
while (v9 < 512) | |
{ | |
v10 = ((unsigned __int8)v7[v9] + BOX[v9] + v10) % 512; | |
unsigned char tmp = BOX[v9]; | |
BOX[v9] = BOX[v10]; | |
BOX[v10] = tmp; | |
++v9; | |
} | |
} | |
int main() | |
{ | |
size_t v3; // rax | |
int v5; // eax | |
FILE *fout; // [rsp+28h] [rbp+8h] | |
FILE *fin; // [rsp+48h] [rbp+28h] | |
int v8; // [rsp+64h] [rbp+44h] | |
int v9; // [rsp+84h] [rbp+64h] | |
int v10; // [rsp+A4h] [rbp+84h] | |
int v11; // [rsp+C4h] [rbp+A4h] | |
unsigned __int8 v12; // [rsp+104h] [rbp+E4h] | |
unsigned __int8 v13; // [rsp+144h] [rbp+124h] | |
int i; // [rsp+164h] [rbp+144h] | |
init_box(); | |
v8 = 0; | |
v10 = 0; | |
v11 = 0; | |
fout = fopen("Doraemon3", "wb"); | |
fin = fopen("Doraemon1", "rb"); | |
while (!feof(fin)) | |
{ | |
v10 = (v10 + 1) % 512; | |
v11 = (BOX[v10] + v11) % 512; | |
unsigned char tmp = BOX[v10]; | |
BOX[v10] = BOX[v11]; | |
BOX[v11] = tmp; | |
v13 = BOX[(unsigned __int8)((BOX[v11] + BOX[v10]) % 512)]; | |
v12 = v13 ^ fgetc(fin); | |
fputc(v12, fout); | |
srand(BOX[v8 % 512]); | |
v9 = rand() % 4; | |
for (i = 0; i < v9; ++i) | |
{ | |
v5 = rand(); | |
fgetc(fin); | |
} | |
++v8; | |
} | |
fclose(fout); | |
fclose(fin); | |
return 0; | |
} |
Doraemon3 本身已经是解密程序,解密 Doraemon2 得到 Here。输入密钥 Doraemon 直接跑得到 GIF89a 文件
# 舔狗四部曲 -- 相逢已是上上签
听说你 PE, 工具,算法都学的很好尊嘟假嘟?
出题人:@upsw1ng
18 支队伍攻克
蓝底处 PE 头位置修改为 100h
6 个变量 6 个方程,z3 启动
from z3 import * | |
s = Solver() | |
key = [BitVec(f'key_{i}', 8) for i in range(6)] | |
s.add(532 * key[5] | |
+ 829 * key[4] | |
+ 258 * key[3] | |
+ 811 * key[2] | |
+ 997 * key[1] | |
+ 593 * key[0] == 292512) | |
s.add(576 * key[5] | |
+ 695 * key[4] | |
+ 602 * key[3] | |
+ 328 * key[2] | |
+ 686 * key[1] | |
+ 605 * key[0] == 254496) | |
s.add(580 * key[5] | |
+ 448 * key[4] | |
+ 756 * key[3] | |
+ 449 * key[2] | |
+ 512 * key[1] | |
+ 373 * key[0] == 222479) | |
s.add(597 * key[5] | |
+ 855 * key[4] | |
+ 971 * key[3] | |
+ 422 * key[2] | |
+ 635 * key[1] | |
+ 560 * key[0] == 295184) | |
s.add(524 * key[5] | |
+ 324 * key[4] | |
+ 925 * key[3] | |
+ 388 * key[2] | |
+ 507 * key[1] | |
+ 717 * key[0] == 251887) | |
s.add(414 * key[5] | |
+ 495 * key[4] | |
+ 518 * key[3] | |
+ 884 * key[2] | |
+ 368 * key[1] | |
+ 312 * key[0] == 211260) | |
if s.check() == sat: | |
m = s.model() | |
print(''.join([chr(m[k].as_long()) for k in key])) |
key: XYCTF!
#include <stdio.h> | |
#define u32 unsigned int | |
char* Key = "XYCTF!"; | |
void sub_401000(u32 *a1, int a2) | |
{ | |
int v2; // ecx | |
int v3; // eax | |
int v4; // edx | |
int v5; // [esp+4h] [ebp-1Ch] | |
int v6; // [esp+Ch] [ebp-14h] | |
unsigned int v7; // [esp+10h] [ebp-10h] | |
unsigned int v8; // [esp+18h] [ebp-8h] | |
unsigned int i; // [esp+1Ch] [ebp-4h] | |
if (a2 > 1) | |
{ | |
v6 = 52 / a2 + 6; | |
v7 = 0; | |
v8 = a1[a2 - 1]; | |
do | |
{ | |
v7 -= 0x61C88647; | |
v5 = (v7 >> 2) & 5; | |
for (i = 0; i < a2 - 1; ++i) | |
{ | |
v2 = ((v8 ^ Key[v5 ^ i & 5]) + (a1[i + 1] ^ v7)) ^ (((16 * v8) ^ (a1[i + 1] >> 3)) + ((4 * a1[i + 1]) ^ (v8 >> 5))); | |
v3 = a1[i]; | |
a1[i] = v2 + v3; | |
v8 = v2 + v3; | |
} | |
v4 = (((v8 ^ Key[v5 ^ i & 5]) + (*a1 ^ v7)) ^ (((16 * v8) ^ (*a1 >> 3)) + ((4 * *a1) ^ (v8 >> 5)))) + a1[a2 - 1]; | |
a1[a2 - 1] = v4; | |
v8 = v4; | |
--v6; | |
} while (v6); | |
} | |
} | |
void decrypt(u32 *a1, int a2) | |
{ | |
int v5; // [esp+4h] [ebp-1Ch] | |
int v6; // [esp+Ch] [ebp-14h] | |
unsigned int v7; // [esp+10h] [ebp-10h] | |
unsigned int v8; // [esp+18h] [ebp-8h] | |
int i; // [esp+1Ch] [ebp-4h] | |
if (a2 > 1) | |
{ | |
v7 = (-0x61C88647) * (52 / a2 + 6); | |
for (v6 = 0; v6 < 52 / a2 + 6; ++v6) | |
{ | |
v5 = (v7 >> 2) & 5; | |
for (i = a2 - 1; i >= 0; --i) | |
{ | |
v8 = a1[(i - 1 + a2) % a2]; | |
a1[i] -= ((v8 ^ Key[v5 ^ i & 5]) + (a1[(i + 1) % a2] ^ v7)) ^ (((16 * v8) ^ (a1[(i + 1) % a2] >> 3)) + ((4 * a1[(i + 1) % a2]) ^ (v8 >> 5))); | |
} | |
v7 += 0x61C88647; | |
} | |
} | |
} | |
int main() { | |
int v6[8]; | |
v6[0] = 1718186609; | |
v6[1] = -1989270907; | |
v6[2] = -988247013; | |
v6[3] = 1924988163; | |
v6[4] = 1400902090; | |
v6[5] = 1302415020; | |
v6[6] = -2040328853; | |
v6[7] = -124282896; | |
decrypt(v6, 8); | |
printf("%s\n", v6); | |
return 0; | |
} |
🚩 XYCTF{XXTEA_AND_Z3_1s_S0_easy!!}
# easy language
易语言一定很 easy 吧 (玩的时候声音建议开小点)。
出题人:@yuro
28 支队伍攻克
看到一段 base64、故意用感叹号补齐的 16 个字符、明文字符串 AES-ECB,直接试。靠谱的方法以后再来探索吧
🚩 XYCTF{y0u_@r3_v3ry_g00d_a7_E_l@ngu@ge}
# 今夕是何年
只要运行就有 flag。都是兄弟,怎么会骗你呢。
出题人:@Suyun
47 支队伍攻克
既生「TCPL」,何生「今夕是何年」?
原本还以为 “今夕是何年” 意思是 “都 4202 年了还能遇到上古可执行文件”,直到丢 Ghidra 才知道是 “龙” 啊
Rust ,能看到明文 flag 头,但是逆不动一点
Ubuntu 22.04 的 apt 没有 qemu-loongarch64 ,自己编译整个 qemu 需要五六 GB 磁盘空间,被塞满了🤣👉🤡
# Trustme
什么漏洞,这么 EZ~
出题人:@JSTQ
126 支队伍攻克
在 ProxyApplication
中找到对 classes.dex
尾部数据异或 255 然后写回 APK 的逻辑
手动提取这部分数据异或 255 得到 APK 文件,找到对 mydb.db
异或 255 的逻辑
在 SQLite 二进制文件中看到 flag 字符串
🚩 XYCTF{And0r1d_15_V3ryEasy}
# 何须相思煮余年
为什么没有代码呀?
出题人:@upsw1ng
79 支队伍攻克
用 CyberChef 将文本存为二进制文件,拖到 IDA (32 位或 64 位都可以)发现包含一个完整的函数(有函数序言和函数尾声),P 键创建函数后可以反编译
enc = [88,88,134,87,74,118,318,101,59,92,480,60,65,41,770,110,73,31,918,39,120,27,1188,47,77,24,1352,44,81,23,1680,46,85,15,1870,66,91,16,4750] | |
for i in range(39): | |
if i % 4 == 0: enc[i] -= i | |
elif i % 4 == 1: enc[i] += i | |
elif i % 4 == 2: enc[i] //= i | |
elif i % 4 == 3: enc[i] ^= i | |
print(bytes(enc)) |
🚩 XYCTF{5b3e07567a9034d06851475481507a75}
# What's this
这是什么文件?
出题人:@ZhaoWu
69 支队伍攻克
DIE 可知 格式: Lua Bytecode (.LUAC)(v5.1)[LittleEndian]
找工具 https://www.52pojie.cn/thread-1224918-1-1.html
反编译得到源代码,包含大量无意义逻辑,只需追溯 output 变量的赋值过程即可
CyberChef 梭了
🚩 XYCTF{5dcbaed781363fbfb7d8647c1aee6c}
# 你是真的大学生吗? (luoingly)
你是冤种大学生吗?
出题人:@dev1l
326 支队伍攻克
MSDOS 程序,需要读汇编。
src = [0x76, 0x0e, 0x77, 0x14, 0x60, 0x06, 0x7d, 0x04, 0x6b, 0x1e, | |
0x41, 0x2a, 0x44, 0x2b, 0x5c, 0x03, 0x3b, 0x0b, 0x33, 0x05] | |
dst = [0x00] * 20 | |
for i in range(19): | |
dst[i] = src[i] ^ src[i + 1] | |
dst[19] = src[19] ^ dst[0] | |
print("".join([chr(i) for i in dst])) |
🚩 xyctf{you_know_8086}
# ez_enc
你们要的签到题。
出题人:@Showmaker
89 支队伍攻克
通过字符串引用定位到主要逻辑在 sub_140011960 ,搓个 z3
from z3 import * | |
s = Solver() | |
items = [BitVec('flag[%d]' % i, 8) for i in range(34)] | |
moyu_z3_decls = items[:] | |
for i in range(34): | |
s.add(items[i] < 127) | |
s.add(items[i] >= 32) | |
enc = [ | |
0x27, 0x24, 0x17, 0x0B, 0x50, 0x03, 0xC8, 0x0C, 0x1F, 0x17, | |
0x36, 0x55, 0xCB, 0x2D, 0xE9, 0x32, 0x0E, 0x11, 0x26, 0x02, | |
0x0C, 0x07, 0xFC, 0x27, 0x3D, 0x2D, 0xED, 0x35, 0x59, 0xEB, | |
0x3C, 0x3E, 0xE4, 0x7D | |
] | |
for i in range(33): | |
items[i] = b'IMouto'[i % 6] ^ (items[i + 1] + items[i] % 20) & 0xff | |
for i in range(34): | |
s.add(items[i] == enc[i]) | |
if s.check() == sat: | |
m = s.model() | |
print(''.join(chr(m[moyu_z3_decls[i]].as_long()) for i in range(34))) | |
# Rlag{!_r3ea11y_w4nt_@_cu7e_s1$ter} | |
# flag{!_r3ea11y_w4nt_@_cu7e_s1$ter} |
# 砸核桃
来帮 zhaowu 开个核桃 非恶意文件!!!
出题人:@ZhaoWu
159 支队伍攻克
DIE 可知 打包工具: NsPacK (3.x)[-]
x32dbg 运行到 0x40641D 处,脱壳
脱壳后整理一下反编译结果
解密
🚩 flag{59b8ed8f-af22-11e7-bb4a-3cf862d1ee75}
# ezmath
上过初中就会写
出题人:@dev1l
64 支队伍攻克
pydumpck 一把解开,下面两行可以等效替换,这件事用 IDE 的一些神奇的查找替换功能就能做到
sum((lambda .0: [ <参数1> for _ in .0 ])(range(<参数2>))) | |
<参数1> * <参数2> |
整理一下,发现 flag 中的每个字符都平方然后减去一个倍数
(((((((((((((((((((((flag[23] * flag[23] + flag[12] * flag[12] + flag[1] * flag[1] - flag[24] * 222) + flag[22] * flag[22] + flag[31] * flag[31] + flag[26] * flag[26] - flag[9] * 178 - flag[29] * 232) + flag[17] * flag[17] - flag[23] * 150 - flag[6] * 226 - flag[7] * 110) + flag[19] * flag[19] + flag[2] * flag[2] - flag[0] * 176) + flag[10] * flag[10] - flag[12] * 198) + flag[24] * flag[24] + flag[9] * flag[9] - flag[3] * 168) + flag[8] * flag[8] - flag[2] * 134) + flag[14] * flag[14] - flag[13] * 170) + flag[4] * flag[4] - flag[10] * 142) + flag[27] * flag[27] + flag[15] * flag[15] - flag[15] * 224) + flag[16] * flag[16] - flag[11] * 230 - flag[1] * 178) + flag[28] * flag[28] - flag[5] * 246 - flag[17] * 168) + flag[30] * flag[30] - flag[21] * 220 - flag[22] * 212 - flag[16] * 232) + flag[25] * flag[25] - flag[4] * 140 - flag[31] * 250 - flag[28] * 150) + flag[11] * flag[11] + flag[13] * flag[13] - flag[14] * 234) + flag[7] * flag[7] - flag[8] * 174) + flag[3] * flag[3] - flag[25] * 242) + flag[29] * flag[29] + flag[5] * flag[5] - flag[30] * 142 - flag[26] * 170 - flag[19] * 176) + flag[0] * flag[0] - flag[27] * 168) + flag[20] * flag[20] - flag[20] * 212) + flag[21] * flag[21] + flag[6] * flag[6] + flag[18] * flag[18] - flag[18] * 178) + 297412 == 0 |
有个 flag [31] * 250 ,而 flag [31] 为右大括号即 125 ,直接猜两倍
flag = [0] * 32 | |
flag[24] = 222 | |
flag[9] = 178 | |
flag[29] = 232 | |
flag[23] = 150 | |
flag[6] = 226 | |
flag[7] = 110 | |
flag[0] = 176 | |
flag[12] = 198 | |
flag[3] = 168 | |
flag[2] = 134 | |
flag[13] = 170 | |
flag[10] = 142 | |
flag[15] = 224 | |
flag[11] = 230 | |
flag[1] = 178 | |
flag[5] = 246 | |
flag[17] = 168 | |
flag[21] = 220 | |
flag[22] = 212 | |
flag[16] = 232 | |
flag[4] = 140 | |
flag[31] = 250 | |
flag[28] = 150 | |
flag[14] = 234 | |
flag[8] = 174 | |
flag[25] = 242 | |
flag[30] = 142 | |
flag[26] = 170 | |
flag[19] = 176 | |
flag[27] = 168 | |
flag[20] = 212 | |
flag[18] = 178 | |
print(''.join([chr(flag[i] // 2) for i in range(32)])) | |
# XYCTF{q7WYGscUuptTYXjnjKoyUTKtG} |
后续:看提示说配方,下面两行可以等效替换
flag[1] * flag[1] - flag[1] * 178 | |
(flag[1] - 89) ** 2 - 89 ** 2 |
减去的这些常数合起来应该刚好是 297412 ,也就是 32 个平方项之和等于 0 ,所以每个平方项都等于 0 。合理(话说这题为什么放在 Reverse)
# 给阿姨倒一杯卡布奇诺
到点了,该喝茶了。flag 格式:XYCTF {}
70 支队伍攻克
经典 TEA ,注意静态变量 data1 和 data2 的处理
#include <stdio.h> | |
#define uint32_t unsigned int | |
void decrypt(uint32_t *v, uint32_t *key) | |
{ | |
static uint32_t data1 = 0x5F797274; | |
static uint32_t data2 = 0x64726168; | |
int i; // [rsp+20h] [rbp-10h] | |
uint32_t sum; // [rsp+24h] [rbp-Ch] | |
uint32_t v1; // [rsp+28h] [rbp-8h] | |
uint32_t v0; // [rsp+2Ch] [rbp-4h] | |
sum = 0x6E75316C * 32; | |
uint32_t data1_tmp = v[0]; | |
uint32_t data2_tmp = v[1]; | |
v0 = v[0]; | |
v1 = v[1]; | |
for (i = 31; i >= 0; i--) | |
{ | |
v1 -= ((v0 >> 5) + key[3]) ^ (v0 + sum) ^ (key[2] + 16 * v0) ^ (sum + i); | |
v0 -= ((v1 >> 5) + key[1]) ^ (v1 + sum) ^ (key[0] + 16 * v1) ^ (sum + i); | |
sum -= 0x6E75316C; | |
} | |
v[0] = v0 ^ data1; | |
v[1] = v1 ^ data2; | |
data1 = data1_tmp; | |
data2 = data2_tmp; | |
} | |
int main() | |
{ | |
uint32_t key[4]; // [rsp+60h] [rbp-40h] BYREF | |
uint32_t array[8]; // [rsp+70h] [rbp-30h] | |
array[0] = 0x9B28ED45; | |
array[1] = 0x145EC6E9; | |
array[2] = 0x5B27A6C3; | |
array[3] = 0xE59E75D5; | |
array[4] = 0xE82C2500; | |
array[5] = 0xA4211D92; | |
array[6] = 0xCD8A4B62; | |
array[7] = 0xA668F440; | |
key[0] = 0x65766967; | |
key[1] = 0x756F795F; | |
key[2] = 0x7075635F; | |
key[3] = 0x6165745F; | |
for (int i = 0; i <= 7; i += 2) | |
{ | |
decrypt(array + i, key); | |
} | |
for(int i=0; i<32; i++) | |
{ | |
printf("%c", ((char*)array)[i]); | |
} | |
return 0; | |
} | |
// 133bffe401d223a02385d90c5f1ca377 |
🚩 XYCTF{133bffe401d223a02385d90c5f1ca377}
# DebugMe
康康你的 debugger
出题人:@yuro
189 支队伍攻克
复现一下 NewStarCTF 2023 Week 2 AndroDbgMe 即可
yixinBC ✌️ 的视频非常好 BV182421T7BS 从 27:22 开始
解包,添加可调试配置,打包签名,安装, adb 启动调试, JEB 附加
# 喵喵喵的 flag 碎了一地 (ClearWine)
小猫把 flag 给咬碎了,你能还原吗?
出题人:@ZhaoVVu
661 支队伍攻克
🚩 flag{My_fl@g_h4s_br0ken_4parT_Bu7_Y0u_c@n_f1x_1t!}
# 聪明的信使 (ClearWine)
传统加密,童叟无欺。
出题人:@ZhaoWu
793 支队伍攻克
对字符串中的字母进行凯撒加密,key=9
🚩 flag{Y0u_KnOw_Crypt0_14_v3ry_Imp0rt@nt!}