这是我参加的第一个CTF比赛,参与的是逆向方向。作为一个入门

逆向工程入门指北

反编译后即可看到

image-20260922085159471

moectf{C0ngr4tuLati0N_On_find1n9_your_1st_RE_f1aggggg!!!}

Assembly

一段汇编语言,看不懂,丢ai让它一步步讲解

了解到寄存器,基本指令

发现flag由两部分组成,一部分是直接转ASCII码,另一部分和0x42按位进行异或

1

1.1

2

2.1

组合起来写个小代码则成flag

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>
using namespace std;
int main(){
char a[]={0x6d,0x6f,0x65,0x63,0x74,0x66,0x7b,0x41,0x73,0x73,0x65,0x6d,0x62,0x31,0x79,0x5f,0x4c,0x34,0x6e,0x67,0x75,0x61,0x67,0x65,0x5f};
char b[]={0x73,0x11,0x1d,0x21,0x2d,0x72,0x2d,0x72,0x0d,0x2d,0x2d,0x2e,0x63,0x63,0x3f};
for(int i=0;i<25;i++)
cout << a[i];
for(int i=0;i<15;i++)
cout << (char)(b[i] ^ 0x42);
}

moectf{Assemb1y_L4nguage_1S_co0o0Oool!!}

bbxor

简单的异或

image-20260922093929759

image-20260922094555367

利用异或的对称性得到

moectf{B4sic_x0r_cha1lenge_solved!!}

反方向的 RC4

依旧对称加密算法

手解

通过这个了解了一下rc4算法

想用cyberchef解,但是解不出来。

主要是dump的数据不知道咋搞,image-20260922102511077

这里面说是20个,但是按dumphex后只有17个数据image-20260922102655069

去网上找了个脚本

image-20260922102728516

也不知道为啥,后面构造一下蒙过了。

moectf{OH~Dyn4mic!}

到时候再看吧

动调

通过这道题也学着调配了一下wsl和linux调试环境

image-20260922103407081

在call那里下断点,再F8然后去RDI那里

image-20260922103455503

得到flag

moectf{OH~Dyn4mic!}

超级土豆大爆炸

image-20260922104426182

用die查到有一层upx壳,通过这个学了一下手脱和upx -d(好像linux的不能用xdbg)

image-20260922103614410

碰到一串字符串,直接丢一把梭里,一不小心解出来了

image-20260922104850529

一点点看吧,base58编码

主要是下面这层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
__int64 __fastcall sub_401216(const char *a1, __int64 a2, unsigned __int64 a3, unsigned __int64 *a4)
{
__int64 v5; // rax
__int64 v6; // rax
char s[140]; // [rsp+20h] [rbp-E0h] BYREF
int v10; // [rsp+ACh] [rbp-54h]
char *v11; // [rsp+B0h] [rbp-50h]
unsigned __int64 v12; // [rsp+B8h] [rbp-48h]
size_t v13; // [rsp+C0h] [rbp-40h]
unsigned __int64 ii; // [rsp+C8h] [rbp-38h]
unsigned __int64 n; // [rsp+D0h] [rbp-30h]
__int64 v16; // [rsp+D8h] [rbp-28h]
unsigned __int64 m; // [rsp+E0h] [rbp-20h]
int k; // [rsp+E8h] [rbp-18h]
int v19; // [rsp+ECh] [rbp-14h]
unsigned __int64 j; // [rsp+F0h] [rbp-10h]
unsigned __int64 i; // [rsp+F8h] [rbp-8h]

memset(s, 0, 0x80uLL);
v13 = strlen(a1);
for ( i = 0LL; a1[i] == '1'; ++i )
;
for ( j = 0LL; j < v13; ++j )
{
v11 = strchr("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", a1[j]);
if ( !v11 )
return 0xFFFFFFFFLL;
v19 = v11 - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
for ( k = 127; k >= 0; --k )
{
v10 = 58 * (unsigned __int8)s[k] + v19;
s[k] = v10;
v19 = v10 >> 8;
}
if ( v19 )
return 0xFFFFFFFFLL;
}
for ( m = 0LL; m <= 0x7F && !s[m]; ++m )
;
v12 = i - m + 128;
if ( a3 < v12 )
return 0xFFFFFFFFLL;
v16 = 0LL;
for ( n = 0LL; n < i; ++n )
{
v5 = v16++;
*(_BYTE *)(a2 + v5) = 0;
}
for ( ii = m; ii <= 0x7F; ++ii )
{
v6 = v16++;
*(_BYTE *)(v6 + a2) = s[ii];
}
*a4 = v12;
return 0LL;
}

搜一下特征

Base58 特征

  1. 字母表固定
    123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
    共 58 个字符。
  2. 去掉了易混淆字符
    没有 0、O、I、l,也没有 +、/、=。
  3. 前导 1 表示前导零字节
    解码时,开头的每个 1 对应一个 0x00 字节。
  4. 本质是大整数进制转换
    把字节串当成大整数,按 58 进制编码/解码:
    num = num * 58 + index
  5. 无填充,长度可变
    不像 Base64 有 = 补位。
  6. 常见用途
    比特币地址、IPFS、Ripple、CTF 逆向题中的隐藏编码。
  7. 代码识别特征
    • 出现那串 58 字符常量
    • strchr 查字母表
    • 循环里乘 58
    • 大整数数组(常见 128 字节)
    • 统计前导 '1' 数量
  8. 与 Base64 区别
    • Base64:有 + / =,含 0OIl
    • Base58:无符号,无易混淆字符,纯字母数字
  9. CyberChef 解法
    搜索 From Base58,输入字符串即可解码。

flag:

moectf {EaSy_UPX_anD_Base58}

奇怪的 APP

apk逆向,找到一个工具JADX

image-20260922105249018

依旧找main函数,发现可疑字符串

image-20260922105349936

继续找拼图

image-20260922105453401

这里面的不是

image-20260922105619612

moectf{Apk_REv3rse_1sghT?!!!}

请你喝茶

第一次碰到比较复杂的题目

做了两次,第一次跟着填空,第二次自己写了代码

image-20260922105944882

两部分tea

主要就是这些代码的理解

image-20260922110105112

然后写逆向就倒过来

首先先改名改类型

根据上下代码改好使程序变得好读

比如说v6其实就是a1[0],v5是a1[1],v4这种改成sum

还有后面_DWORD那块,改a2类型

image-20260922110530535

瞬间好读很多

image-20260922110547857

然后补全代码

C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <bits/stdc++.h>
using namespace std;
unsigned int tea_key[4] = {
0x12345678, 0x87654321, 0x13572468, 0x24681357
};
unsigned int xtea_key[4] = {
0xDEADBEEF, 0xCAFEBABE, 0x11223344, 0x55667788
};
unsigned int tea_cipher[4] = {
0xB3E7E33E, 0xB4114672, 0x8E088C0C, 0x14B2C329
};
unsigned int xtea_cipher[4] = {
0x60EC68AB, 0x940251CE, 0xB427534C, 0xDF435416
};

void untea(unsigned int *v, unsigned int *k)
{
unsigned int v0 = v[0], v1 = v[1];
unsigned int delta = 1640531527;
unsigned int sum = -delta * 32; // 0xC6EF3720

for (int i = 0; i < 32; i++) {
// 先恢复 v1(对应加密的第二步)
v1 -= (v0 + sum) ^ ((v0 << 4) + k[2]) ^ ((v0 >> 5) + k[3]);
// 再恢复 v0(对应加密的第一步)
v0 -= (v1 + sum) ^ ((v1 << 4) + k[0]) ^ ((v1 >> 5) + k[1]);
sum += delta;
}
v[0] = v0;
v[1] = v1;
}

void unxtea(unsigned int *v, unsigned int *k)
{
unsigned int v0 = v[0], v1 = v[1];
unsigned int delta = 1640531527;
unsigned int sum = -delta * 32;

for (int i = 0; i < 32; i++) {
// 先恢复 v1(对应加密的第二步)
v1 -= (((v0 >> 5) ^ (16 * v0)) + v0) ^ (k[(sum >> 11) & 3] + sum);
sum += delta;
// 再恢复 v0(对应加密的第一步)
v0 -= (((v1 >> 5) ^ (16 * v1)) + v1) ^ (k[sum & 3] + sum);
}
v[0] = v0;
v[1] = v1;
}

int main()
{
// TEA解密
untea(tea_cipher, tea_key);
untea(tea_cipher + 2, tea_key);

// XTEA解密
unxtea(xtea_cipher, xtea_key);
unxtea(xtea_cipher + 2, xtea_key);

// 输出flag(直接输出解密后的内存)
cout <<"大家好" <<(char*)tea_cipher << (char*)xtea_cipher << endl;

return 0;
}

输出

moectf{Wh4t_a_n1ce_cup_0f_TEA!!}

Tell Me No Lies

这题当时直接看爆了

后面慢慢静下来认真读找到感觉,也通过这道题了解了很多知识点

然而后面还是问了AI

分析代码

image-20260922112336869

从上往下:首先是v7,通过下面ifelse可以猜得v7必须是false,进入函数IsDebuggerPresent()查看

image-20260922115701203

可知这是一个反调试口,需要手动改rax或者patch

问题1:这个/status是什么,文件?

答:Linux 里记录当前进程状态的文件

读取进程状态,碰到Tracerpid开始内部语句

问题2:strtoul()函数含义是什么,有何作用?

答:string to unsigned long(字符串转无符号长整型),把字符串里开头的数字抠出来,变成整数。一碰到其他字符就退出,如果没有值的话返回0

作用:判断有无数值,这里用来判断是否有调试进程,有就返回真值,用来反调试

image-20260922120755383

往后走,三个函数

build_required_token(s),acquire_operator_token(),compare_operator_token(v8, s)

第一个build_required_token(s)

1
2
3
4
5
6
7
8
__int64 result; // rax
unsigned __int64 i; // [rsp+10h] [rbp-8h]

for ( i = 0LL; i <= 0x13; ++i )
*(_BYTE *)(a1 + i) = *(_BYTE *)(i + 4203008) ^ 0x5A;
result = a1 + 20;
*(_BYTE *)(a1 + 20) = 0;
return result;

问题1:看到类型提示想改参数类型,改成int *a1报错了

答:要改成char *a1

具体不知道为啥

因为反编译里 *(_BYTE *)(a1 + i) = ... 的意思是:a1 是一个地址,往 a1 + i 这个字节位置写数据。如果你把它改成 __int64 *a1,那 a1 + i 就变成“跳过 i 个 int64”了,语义完全变了。这里的 a1 应该理解为 char \*a1(字符串首地址),反编译工具偷懒没给你标清楚类型而已。

第二个acquire_operator_token()

1
2
3
4
if ( (getpid() & 1) != 0 )
return "MOE-LEVEL-1-GUEST";
else
return "MOE-LEVEL-3-TECH";

问题:&按位与作用

答:

  • 奇数的最低位是 1 → getpid() & 1 == 1
  • 偶数的最低位是 0 → getpid() & 1 == 0

作用:判断奇偶

第三个就是简单比较数据,使v8和s相等

然后进行自揭密程序self_driven_decrypt(v7, v6, v4)

然后程序结束

操作

动调跳过

改寄存器rax的值为0跳到else分支image-20260922120111075

patch

选中v7 = isdebuggerpresent那一行

edit->patch program->assemble

因为我们想让它直接为0,并且原先有5个数

image-20260922120445712

所以改成

1
2
3
4
xor eax, eax
nop
nop
nop

也就是31 C0 90 90 90

因为eax和自身异或结果就是0,达到了我们的目的


我们继续动调,看看if(v6)那块

当在acquire处下断点后F8后,RDI中显示这一串

image-20260922122632605

与其他俩都不符合,所以又得patch

同样的操作把后面v6的if改了

后面就是自解密了,不用我们上手,直接下断点到printf

image-20260922122845421

出了

moectf{Wow~h0w_do_Y0u_f1nd_IT?}

成都好室友(未解)

这道题是pyc的逆向,首先先反汇编,用

python -m xdis.disasm chall15.pyc > chall15_disasm.txt

这条命令,当然要先装一个pip install xdis库

看不懂,于是丢ai,发现这是一个生成图片的程序

发现是一个缺角二维码,补角发现扫不出来。卡关了

(ai题解懒得看了,好像说是还要颜色反转)

让我们说中文

image-20260922124302355

在里面乱点,观察到

image-20260922124332380

这是base64改表的特征,直接上cyberchef(或者网上找个python脚本)

image-20260922124829995

得出一串字符串

丢到一把梭里试试

image-20260922124920630

确实让我们说中文

最后结果

1
moectf{Spy4KA_y@$w_W07ds}

Mewtype

一个py文件

打开来发现乱七八糟,一条蛆

慎开

1
exit(1 - (lambda myk43: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(len(myk43) == 54))(lambda: (lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk19: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((myk19 * myk19 * myk19 - myk19) % 6 == 0))(len(myk43)))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk39: lambda vol40: lambda myk41: lambda vol42: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk41 >= len(vol40) - 1))(lambda: lambda myk3: lambda vol4: myk3)(lambda: (lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol18: (lambda myk15: lambda vol16: myk15((lambda vol10: lambda myk11: lambda vol12: vol10(vol12)(myk11))(vol16))(vol16))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((vol18 * vol18 + vol18) % 2 == 0))(lambda myk5: lambda vol6: vol6))(myk41))(lambda: (lambda vol22: lambda myk23: lambda vol24: lambda myk25: lambda vol26: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol20: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol20 == 0))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 + myk23 + vol24 * vol24 == vol26))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk21: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk21 == 1))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * myk23 + vol24 == vol26))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * vol22 + myk23 * myk23 + vol24 * vol24 * vol24 == vol26)))())(ord(vol40[myk41]))(ord(vol40[myk41 + 1]))(ord(vol40[(myk41 + 17) % len(vol40)]))(vol42)((10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686, 9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472, 9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566, 13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540, 15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995, 1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119)[myk41] ^ 90))(lambda: (lambda vol22: lambda myk23: lambda vol24: lambda myk25: lambda vol26: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol20: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol20 == 0))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 + myk23 + vol24 * vol24 == vol26))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk21: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk21 == 1))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * myk23 + vol24 == vol26))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * vol22 + myk23 * myk23 + vol24 * vol24 * vol24 == vol26)))())(ord(vol40[myk41 + 1]))(ord(vol40[myk41]))(ord(vol40[(myk41 + 17) % len(vol40)]))(vol42)((10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686, 9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472, 9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566, 13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540, 15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995, 1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119)[myk41] ^ 90))())(myk39(vol40)(myk41 + 1)((vol42 + 1) % 3)))())(myk43)(0)(0))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk27: lambda vol28: lambda myk29: 0 if myk29 == len(vol28) else ord(vol28[myk29]) + myk27(vol28)(myk29 + 1))(myk43)(0) == 5138))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda vol30: lambda myk31: lambda vol32: 0 if vol32 == len(myk31) else ord(myk31[vol32]) ^ vol30(myk31)(vol32 + 1))(myk43)(0) == 26))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk33: lambda vol34: lambda myk35: 0 if myk35 == len(vol34) else ord(vol34[myk35]) * ord(vol34[myk35]) + myk33(vol34)(myk35 + 1))(myk43)(0) == 520600))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda vol36: lambda myk37: lambda vol38: 0 if vol38 == len(myk37) - 1 else ord(myk37[vol38]) * ord(myk37[vol38 + 1]) + vol36(myk37)(vol38 + 1))(myk43)(0) == 467644)))))))(lambda: lambda myk5: lambda vol6: vol6)())(input())(1)(0))

我都不知道如果没有ai怎么整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import sys

def check(s: str) -> bool:
# 1. 长度必须为 54
if len(s) != 54:
return False

# 2. 所有字符 ord 值之和
if sum(ord(c) for c in s) != 5138:
return False

# 3. 所有字符 ord 值异或结果
xor_result = 0
for c in s:
xor_result ^= ord(c)
if xor_result != 26:
return False

# 4. 所有字符 ord 值平方和
if sum(ord(c) ** 2 for c in s) != 520600:
return False

# 5. 相邻字符 ord 值乘积之和
if sum(ord(s[i]) * ord(s[i + 1]) for i in range(53)) != 467644:
return False

# 6. 逐位置的三元组校验
targets = (
10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686,
9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472,
9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566,
13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540,
15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995,
1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119
)

for i in range(53):
a = ord(s[i])
b = ord(s[i + 1])
c = ord(s[(i + 17) % 54])
target = targets[i] ^ 90
mode = i % 3

if mode == 0:
if a + b + c * c != target:
return False
elif mode == 1:
if a * b + c != target:
return False
else: # mode == 2
if a * a + b * b + c * c * c != target:
return False

return True


if __name__ == "__main__":
user_input = input().strip()
if check(user_input):
sys.exit(0)
else:
sys.exit(1)

看出来它要满足这些条件。

可以用Z3约束求解器,正好学下它的语法。。

新写了re-Z3约束求解器的文章,里面写了遇到的挫折

下面是自己复现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from z3 import *

s = [BitVec(f's{i}',32)for i in range(54)]
# Define the problem
solver = Solver()

prefix = "moectf{"
for i, ch in enumerate(prefix):
solver.add(s[i] == ord(ch))
solver.add(s[53] == ord('}'))


total = BitVecVal(0, 32)
for v in s:
total += v
solver.add(total == 5138)

xoresult = BitVecVal(0, 32)
for v in s:
xoresult ^=v
solver.add(xoresult == 26)

squri = BitVecVal(0, 32)
for v in s:
squri += v*v
solver.add(squri == 520600)

lrj_sum = BitVecVal(0, 32)
for v in range(53):
lrj_sum += s[v] * s[v+1]
solver.add(lrj_sum == 467644)


targets = (
10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686,
9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472,
9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566,
13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540,
15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995,
1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119
)
for i in range(53):
a = s[i]
b = s[i + 1]
c = s[(i + 17) % 54]
target = targets[i] ^ 90
mode = i % 3
if mode == 0:
solver.add(a + b + c * c == target)
elif mode == 1:
solver.add(a * b + c == target)
else:
solver.add(a * a + b * b + c * c * c == target)

for v in s:
solver.add(v >= 32 , v <= 126)


if solver.check() == sat:
m = solver.model()
result = ''.join(chr(m[v].as_long()) for v in s)
print(result)
else:
print("No solution found")

得出下列flag

moectf{l@mbd4_c@lcu1us_4r3_h4rd_but_z3_s0lv3r_1s_3asy}

Ultato Polosion Xptra

image-20260922133246602

image-20260929095450150

这一题无法直接upx-d解壳,又因为不是windowsPE文件,也不能xdbg手脱。想法是用010看看文件是不是被改动过

010patch

image-20260929131228313

当时第一次接触010什么都看不出来,只能复制全部然后丢到deepseek里胡乱分析,并没有卵用

image-20260929131443841

后面想到打包一个正常文件然后对比

依旧是不熟悉010,只能是一点一点比对着分析,然后也不知道哪些是壳哪些是正常的不同文件

过了几天后发现有个功能。。

image-20260929131734452

image-20260929131751250

这样就容易看出来了,把正常打包文件的信息丢上去,注意位数要一样。然后另存为

解包成功

image-20260929132040546

进入题目

image-20261001212319453

依旧,一顿操作之后比对静态数据。

反向解出input即flag

重点是encrypt()函数,点进去

image-20261001212521069

纵览一下,一顿操作,函数,加减法,异或,换位

从下面看起,换位的写出来是这样

00a5821ce16d4d8a639e897f678d4812

即可写出

1
2
3
4
5
6
7
8
uint64_t old_v1=v4;
uint64_t old_v2=v1;
uint64_t old_v3=v5;
uint64_t old_v4=v2;
uint64_t old_v5=v6;
uint64_t old_v6=v3;
v1 = old_v1; v2 = old_v2; v3 = old_v3;
v4 = old_v4; v5 = old_v5; v6 = old_v6;

异或是对称的,所以从下往上就行了,加减法不涉及多个量,换号即可

主要是rol64函数

image-20260929172421612

这个程序怎么反向写解密?思考它是有两种可能性还是什么的。

却忽略了输入的数据本身就是定死的,直接可以知道n是多少。

encrypt()就看完了,现在还有main中最上面的str_to_u64()要处理

image-20261001213445599

不知道怎么处理,问了deepseek,说是大端序存储了,用

1
2
3
4
5
6
7
uint64_t vs[6] = {v1, v2, v3, v4, v5, v6};
for (int k = 0; k < 6; k++) {
uint64_t x = vs[k];
for (int i = 0; i < 8; i++) {
flag[k * 8 + i] = (x >> (56 - 8 * i)) & 0xFF;
}
}

处理。但是我复现不出来,想不到。对底层,32位int,64位int,还有最终uint64数据转char还不太了解,memcpy还是什么的用法不了解

写代码中遇到的问题

  • 交换值时要用中间值
  • 多数据return,可以直接用全局变量,用void即可

请 fanchai 喝茶

是请你喝茶的升级版

xxtea改版,需要先理解算法然后写代码

其实这道题在开学前就已经上线了,但是当时没有长代码分析的经验,一看到这么长都吓死了。看都不敢再看这道题,但是后面反复摸然后一点点理解,辅以ai帮助,写出了这道题目

难懂在哪

难懂的是这几个块

image-20260927144940668

1.开头部分:好奇怪啊

他把v3复制到v4,然后向前4字节,v4也前进4字节,v5-1。从上文看,v5等于16,而fgets了66,也就是实际文本是64,分4段变

这一段其实就是把输入变成了 16 个 32 位字,承担了memcpy函数的功能

感觉不是主体部分的就不用多去想


image-20260927145650339

两段搭配在一起

image-20260927152130078

image-20260927152508006

2.赋值部分:这一段是在赋值,要注意赋值顺序,对理解代码很有效

emm,其实我还是不太懂_mm_or_si128哪几个的作用是什么

问了ai,他说不用管,密文是静态数据,比较是运行时逻辑;解 flag 只关心密文,不关心比较。


3.主体加密部分

image-20260927153838517

image-20260927153855937

当时就是看这里太复杂了,一个是函数名似乎没什么规律,另一个是这加密算法不知道怎么去写解密


分析主体部分

首先就是对赋值那块,和主题解密这块变量的改名

image-20260927154451068

瞬间舒服多了,然后开始找规律。

先看框架

某个字 += (一堆移位、异或、加法) ^ (另一堆)

共用这些:

sum(每轮都变,轮中不变),key[](一直不变)

再看不同

image-20260927154906041

结尾这里有点奇怪,异或1,2,~,空,5,6,7,空,发现每隔4行异或归0

查询ai后发现,这里可以提取出 p & 3

image-20260927155739238

“每隔几行重复一次” → 周期 → 周期长度对应 p & (周期长度-1) → 提取变量 → 写成循环。

那我们现在就可以写出通用公式了

1
2
3
MX = ((z << 4) ^ (y >> 3))\+ ((y << 2) ^ (z >> 5))^ ((sum ^ y) + (z ^ k));

v[i] = v[i] -MX

然而写解密时还要注意加密了谁,加密用的数据是已经加密过的还是没有加密过的。还原的时候怎么用值

加密时 v[0] 先更新,然后 v[1] 用新的 v[0] 算 MX。
所以解密时,v[15] 要先还原,并且它用的 y 是“还没还原的 v[0]”;
还原完 v[15] 后,v[14] 用的 y 正好是刚还原的 v[15]。
这就是 y 从头传递到尾的“接力”。

写出代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<bits/stdc++.h>
using namespace std;
uint32_t key[4] =
{
0x4D4F4543, 0x54463230, 0x32365858, 0x54454121
};
uint32_t cytext[16] =
{
0x84ebdfdd, 0x646be358, 0x37778398, 0xc4d0a558,
0x8eea087c, 0x99f3cee5, 0x60aa5af3, 0x8d9f4d27,
0x89f425f4, 0x59779497, 0x45f537c3, 0x229b9954,
0x824ad1e8, 0xc3fee9e5, 0xc4e5eae9, 0x08608800
};

void unxxtea()
{
unsigned int sum = 0x58F228D7;

while(sum > 0)
{
unsigned int e = (sum >> 2) & 3;
unsigned int y = cytext[0];
for(int j = 15; j>=0; j--)
{
unsigned int z = cytext[(j - 1 + 16) % 16];
unsigned int k = key[(j & 3) ^ e];
unsigned int MX = ((z << 4) ^ (y >> 3))
+ ((y << 2) ^ (z >> 5))
^ ((sum ^ y) + (z ^ k));
cytext[j] -= MX;
y = cytext[j];
}
sum -= 0x5F3759DF;
}
char flag[65];
memcpy(flag, cytext, 64);
flag[64] = 0;
printf("%s\n", flag);
//这四行是问ai写的,没有搞清楚memcpy用法
}

int main()
{
unxxtea();
return 0;
}

结尾总结

这其实是XXTEA算法的改版,只要认出它以后就好办了

XXTEA特征

最外层:轮循环(for r = 0..rounds)
中间层:字循环(for p = 0..n-1)
最内层:一个 MX 函数(用左右邻居 + sum + key 算一个值)

flag

moectf{Tre4t_f4ncha1_W1tH_xxtea_Wh4t_A_g00d_Id3a_HahaHAh4hAH4ha}

广晨风语录(未解)

apk,还是不太会分析

2B 小模型(未解)

这题ai能出来,但是我看不懂emm,没交flag

MCb4ckd00r(未解)

好像是个流量分析?没什么思路啊,网上有一题目是ftp中含压缩包的,跟这个没关系,这里只有http和json之类的,没怎么了解

九转蛇肠(未解)

反思

ai太方便了,有了ai的辅助我们能轻松拿到一些题目的flag,但是当闭卷再做的时候,还是要走那条必须走过的路,还是要花费那些必须花费的时间。

还是得沉下心去认认真真写

站内搜索

没有找到内容!