xyctf2023-re-writeup

感觉第一周最好玩,没了。

聪明的信使

九位凯撒。
flag{Y0u_KnOw_Crypt0_14_v3ry_Imp0rt@nt!}

给阿姨倒一杯卡布奇诺

代码没去符号,一切都很明显,不多赘述
alt text
加密是魔改 xxtea。魔改了三处,其中还涉及一个全局变量

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
from ctypes import *

def encrypt(v, k):
    v0, v1 = c_uint32(v[0]), c_uint32(v[1])
    v0.value ^= 0x5F797274
    v1.value ^= 0x64726168
    delta = 0x6E75316C
    k0, k1, k2, k3 = k[0], k[1], k[2], k[3]
    total = c_uint32(0)
    for i in range(32):
        total.value += delta
        v0.value += ((v1.value << 4) + k0) ^ (v1.value +                                          total.value) ^ ((v1.value >> 5) + k1) ^ (total.value + i)
        v1.value += ((v0.value << 4) + k2) ^ (v0.value +                                        total.value) ^ ((v0.value >> 5) + k3) ^ (total.value + i)
        print(hex(v0.value), hex(v1.value))
    return v0.value, v1.value

data1 = 0x5F797274
data2 = 0x64726168
def decrypt(v, k):
    global data1
    global data2
    v0, v1 = c_uint32(v[0]), c_uint32(v[1])
    t_1 = v[0]
    t_2 = v[1]
    delta = 0x6E75316C
    k0, k1, k2, k3 = k[0], k[1], k[2], k[3]

    total = c_uint32(delta * 32)
    for i in range(31, -1, -1):
        v1.value -= ((v0.value << 4) + k2) ^ (v0.value +                                      total.value) ^ ((v0.value >> 5) + k3) ^ (total.value + i)
        v0.value -= ((v1.value << 4) + k0) ^ (v1.value +                                     total.value) ^ ((v1.value >> 5) + k1) ^ (total.value + i)
        total.value -= delta
        # print(hex(v0.value), hex(v1.value))
    v0.value ^= data1
    v1.value ^= data2
    data1 = t_1
    data2 = t_2
    return v0.value, v1.value

# test
if __name__ == "__main__":
    # value = [0x880012C7, 0x58EE54]
    value = [0x9B28ED45, 0x145EC6E9, 0x5B27A6C3, 0xE59E75D5,
             0xE82C2500, 0xA4211D92, 0xCD8A4B62, 0xA668F440]
    key = [0x65766967, 0x756F795F, 0x7075635F, 0x6165745F]

    # 解密
    flag2 = b""
    for i in range(0, len(value), 2):
        res = decrypt(value[i:i+2], key)
        flag2 += res[0].to_bytes(4, 'little') + res[1].to_bytes(4, 'little')
        print(hex(res[1]))
    print(flag2)

何须相思煮余年

数据开头是 0x55,结尾是 0xc3,可认为是汇编机器码
alt text
转化为 hex 数据后 ida 查看(注意这里如果复制到 010editor 后使用 paste from hex text 会吞一些数据。)
alt text
直接解即可。
alt text
b’XYCTF{5b3e07567a9034d06851475481507a75}’

今夕是何年

ida 和 die 都认为是未知架构。搜索架构码 or 搜索 cpu 特征可知可能是龙芯架构。
配置 qemu 环境模拟执行
alt text

馒头

alt text

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
tree = [0x8DE, 0x395, 0x1BE, 0x0D9, 0x06A, 0x033, 0x014, 0x00F, 0x011,

        0x0E5, 0x072, 0x010, 0x00B,

        0x1D7, 0x0E9, 0x074, 0x00E, 0x00D,

        0x0EE, 0x076, 0x00C, 0x007,

        0x549, 0x22D, 0x0F8, 0x07B, 0x006, 0x018,

        0x135, 0x089, 0x043, 0x003, 0x005,

        0x0AC, 0x054, 0x004, 0x001,

        0x31C, 0x17F, 0x0BA, 0x059, 0x002, 0x008,

        0x0C5, 0x061, 0x030, 0x017, 0x00A, 0x015,

        0x19D, 0x0CB, 0x065, 0x016, 0x009,

        0x0D2, 0x068, 0x013, 0x012]

fake_flag = "1"*24

print(fake_flag)

len_tree = len(tree)

print(len_tree)

flag = [0] * 25

data = 0

count = 0

dcount = 0

print(chr(0x65))

for i in range(len_tree-1, -1, -1):

    if tree[i] < 25:

        data = 0

        dcount = 1

        count += 1

        continue

    elif tree[i] < 0x7f:

        flag[tree[i+dcount]] = tree[i]

        data = tree[i]



        dcount += 2

        count -= 1



    elif count > 0:

        flag[tree[i+dcount]] = tree[i] - data

        # flag.append(tree[i] - data)

        data += tree[i]

        count -= 1

        dcount += 2

print(flag)

# print(bytes(flag), len(flag))

print(hex(sum(flag)))

flag = [0xac - 0x54, 0x59, 0x43, 0x54, 0x89-0x43, 0x7b, 0xee-0x76, 0xba-0x59, 0xcb-0x65, 0x61-0x30, 0xe5-0x72, 0x76, 0xe9-0x74,

        0x74, 0x6a-0x33, 0x72, 0xd9-0x6a, 0xd2-0x68, 0x68, 0x33, 0xc5-0x61, 0x65, 0x30, 0xf8-0x7b]

print(bytes(flag))

喵喵喵的 flag 碎了一地

推理游戏
alt text
alt text
同个函数有不被解析的部分
alt text

你是真的大学生吗?

汇编阅读。
相邻两位依次异或
alt text

1
2
3
4
5
6
7
8
9
cipher = [0x76, 0x0E, 0x77, 0x14, 0x60, 0x06, 0x7D, 0x04, 0x6B, 0x1E,

          0x41, 0x2A, 0x44, 0x2B, 0x5C, 0x03, 0x3B, 0x0B, 0x33, 0x05]

for i in range(len(cipher)-1, -1, -1):

    cipher[i] = (cipher[i] ^ cipher[i-1])

print(bytes(cipher))

输出会有一位偏移,修复一下即可。
alt text
xyctf{you_know_8086}

砸核桃

NsPack,esp 定律或者附加下断点脱壳
alt text
esp:
pushad 后对 esp 的地址下硬件访问断点,待到 popad 或代码到 0x401000+后使用 dump 插件 dump 并调整
alt text
最后发现是一个异或

1
2
3
4
5
6
7
8
9
10
11
key = b"this_is_not_flag"

cipher = [0x00000012, 0x00000004, 0x00000008, 0x00000014, 0x00000024, 0x0000005C, 0x0000004A, 0x0000003D, 0x00000056, 0x0000000A, 0x00000010, 0x00000067, 0x00000000, 0x00000041, 0x00000000, 0x00000001, 0x00000046, 0x0000005A, 0x00000044, 0x00000042, 0x0000006E, 0x0000000C,

          0x00000044, 0x00000072, 0x0000000C, 0x0000000D, 0x00000040, 0x0000003E, 0x0000004B, 0x0000005F, 0x00000002, 0x00000001, 0x0000004C, 0x0000005E, 0x0000005B, 0x00000017, 0x0000006E, 0x0000000C, 0x00000016, 0x00000068, 0x0000005B, 0x00000012, 0x00000000, 0x00000000]

for i in range(42):

    cipher[i] ^= key[i % len(key)]

print(bytes(cipher))

babyUnity

该题可直接用 il2cppdumper 脱
脱完后建议直接用 dnspy 分析 assembly,可看见几个函数的偏移
alt text
在 ida 里直接 ctrl+g 飞过去,可看得函数逻辑(有些是空函数,空就别管)

1
2
3
4
5
6
7
8
9
import base64


cipher = b"\x58\x49\x63\x4B\x59\x4A\x55\x38\x42\x75\x68\x3A\x55\x65\x56\x3A\x42\x4B\x4E\x7B\x55\x5B\x4A\x76\x55\x4C\x3F\x3F\x56\x75\x5A\x3F\x43\x58\x4A\x3B\x41\x58\x5E\x7B\x41\x65\x5D\x67\x41\x5B\x5D\x67\x55\x65\x63\x62\x40\x4B\x5D\x65\x69\x5E\x32\x32"
cipher = list(cipher)
for i in range(len(cipher)):
cipher[i] ^= 0xf
cipher = base64.b64decode(bytes(cipher))
print(cipher)

XYCTF{389f6900-e12d-4c54-a85d-64a54af9f84c}

DebugMe

alt text
JEB 打开,程序被 vm 混淆
MainActivity 里有暴露 cipher 数组,程序无输入,点击按键后默认只弹出无用信息,同时相邻部分的另一个 Toast 弹出有和 cipher 有关的参数。可推测需通过调试篡改到弹出 flag 的逻辑。
alt text
追踪调用:
alt text
分析 What 类或 x 的交叉引用树,可知该类是做了 tea 加解密类。x 调用了解密算法
key 的初始化:
alt text
tea 算法特征:
alt text
除此之外还进行了 base64 和稍微的变形,並修复了 cipher 的 base64 格式(有一个 url 格式的%2b 需替换为+)
alt text
这里我静态不知为何解不出,那么尝试用 frida 动态解。
frida 脚本思路是:hook 一个被执行的函数,篡改为 What.x 解密函数并使用修复后的密文作为参数。最终选择 hook 了 Debug 的 check 函数

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
// 检查java runtime可用性

// frida -U -f com.xyctf.ezapk -l .\hook_1.js

if (Java.available) {

    Java.perform(function () {

        var What = Java.use("com.xyctf.ezapk.What");

        var x = What.x;

        x.implementation = function (arg) {

            console.log("hook in");

            var retVal = this.x(arg);

            console.log(retVal);

            return retVal;

        };



        var Debug = Java.use("android.os.Debug");

        var isDebuggerConnected = Debug.isDebuggerConnected;

        isDebuggerConnected.implementation = function () {

            console.log("in debug check");

            var a = What.x(

                "WikFhRxyYjoSJ8mMbM3fRwty/74bc7Ip7ojqenHaSqc9wDv3JDG9XfV6xEiC7Eg1RWTUa4LaM+D0W+PKanSA5w=="

            );

            console.log("a");

            return false;

        };

    });

} else console.log("Java not available");

easy language

搜字符串容易判断是 aes ecb 加密,但 key 很难找,且没有 aes 常量暴露
alt text
但是,又发现有 CryptoAPI 的字样
alt text
猜测是动态注册调用,尝试 hook
alt text
确实使用了,但是 key 试了 RUUU 和 KSSM 都不对
但是又看到底下的 xyctf 和 abctf,长度刚好 16 位,满足 aeskey 的要求
尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import AES

import base64

cipher1 = "adaGb8kVFQpPVFMcwWQRybbInjf4Q9Iu+f6k9Nw="

cipher2 = "qBr4adaGb8kVFQpPVFMcwWQRybbInjf4Q9Iu+f6k9Nw="

cipher3 = "RZy/zVEWMFxaCbzChAg8x26XZYr51rNVnM+zBoBp3gya93L9QQXpFRin1JE33vyx"

tmp = "oVFXvP32lAb6S1bs5xj2mg=="

key = b"welcometoxyctf!!"

a = base64.b64decode(cipher3)

b = AES.new(key, AES.MODE_ECB)

print(b.decrypt(a)

XYCTF{y0u_@r3_v3ry_g00d_a7_E_l@ngu@ge}

ez_enc

本题的其它计算都不算重要,重要的是 mod 20.
加密带有 mod 20,会丢失信息,因此不能单纯逆推。
而事实上,mod 20 不仅是一个解题限制,更是解题线索.一个可见字符的大小范围是 0x20-0x7e。而当中 mod 20,同一个数字约有 5-6 种的可能。
同时加密算法中,flag[i]同时受 flag[i]自己和 flag[i+1]影响,也就是受前一位解密的影响。
也就是说我们从最后一位往前推,每一位都有约 5 种可能,而每一位都是上一位推理的继续。那么事实上,这个就是以 flag 最后一位为根节点,以 mod 20 为运算推理出的一个树,某一条能走到树最后一层的路径就是最终的 flag。
那么解题的关键找到了。剩下的就是让解题变为可行,这里分析题目有几个约束可用:
①flag[i]和 flag[i+1]强相关,如何 flag[i+1],也就是上一位的值不对,那 flag[i]大概率也不对
②flag[i]正确的情况为:flag[i]在推理时小于 20(这里的 flag[i]是由 flag[i+1]推出的 flag[i]%20)且 flag[i]推理完毕后>=0x20,<=0x7e
那么写一个递归算法实现这个思路:

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
cipher = bytes.fromhex(

    "2724170b5003c80c1f173655cb2de9320e1126020c07fc273d2ded3559eb3c3ee47d")

#lag{!_r3ea11y_w4nt_@_cu7e_s1$ter}

cipher = list(cipher)

key = b"IMouto"

flag = []

flag.append(cipher[-1])

def trace_back(flag:list,p:int):

    # 逆推求解cipher[p]的值

    #print(bytes(flag),p)

    if p == -1:

        flag.reverse()

        print(bytes(flag))

        flag.reverse()

        flag.pop()

        return

    a = cipher[p]

    a ^= key[p%6]

    a -= flag[-1] # 减去上一个推得的值,也就是减去cipher[p+1].此时的值是cipher[p]_原初%20

    if a >= 20 or a < 0: # 上一个值不合适,pop

        flag.pop()

        return

    else:

        for i in range(0,7):

            b = a + i * 20

            if b >= 0x20 and b <= 0x7f:

                #print(p,b)

                flag.append(b)

                trace_back(flag,p-1)

    # 当进行到最后的时候pop出来

    flag.pop()

trace_back(flag,len(cipher)-2)

alt text

ezcube

魔方,约束右手公式和 12 步
在网上搜到了同款初始情况:
alt text
魔方入门解法第 7 步:调整顶层棱块位置 - 爱魔方 (i-mofang.com)

ezmath

我认为出得很差的一个题。
python 3.8 打包为 exe,使用 python3.8 环境下的 pyinstxtractor.py 解包,使用 uncompyle6 反编译。

1
2
3
4
flag = [ord(i) for i in input('flag:')]
if len(flag) == 32:
if sum([flag[23] for _ in range(flag[23])]) + sum([flag[12] for _ in range(flag[12])]) + sum([flag[1] for _ in range(flag[1])]) - sum([flag[24] for _ in range(222)]) + sum([flag[22] for _ in range(flag[22])]) + sum([flag[31] for _ in range(flag[31])]) + sum([flag[26] for _ in range(flag[26])]) - sum([flag[9] for _ in range(178)]) - sum([flag[29] for _ in range(232)]) + sum([flag[17] for _ in range(flag[17])]) - sum([flag[23] for _ in range(150)]) - sum([flag[6] for _ in range(226)]) - sum([flag[7] for _ in range(110)]) + sum([flag[19] for _ in range(flag[19])]) + sum([flag[2] for _ in range(flag[2])]) - sum([flag[0] for _ in range(176)]) + sum([flag[10] for _ in range(flag[10])]) - sum([flag[12] for _ in range(198)]) + sum([flag[24] for _ in range(flag[24])]) + sum([flag[9] for _ in range(flag[9])]) - sum([flag[3] for _ in range(168)]) + sum([flag[8] for _ in range(flag[8])]) - sum([flag[2] for _ in range(134)]) + sum([flag[14] for _ in range(flag[14])]) - sum([flag[13] for _ in range(170)]) + sum([flag[4] for _ in range(flag[4])]) - sum([flag[10] for _ in range(142)]) + sum([flag[27] for _ in range(flag[27])]) + sum([flag[15] for _ in range(flag[15])]) - sum([flag[15] for _ in range(224)]) + sum([flag[16] for _ in range(flag[16])]) - sum([flag[11] for _ in range(230)]) - sum([flag[1] for _ in range(178)]) + sum([flag[28] for _ in range(flag[28])]) - sum([flag[5] for _ in range(246)]) - sum([flag[17] for _ in range(168)]) + sum([flag[30] for _ in range(flag[30])]) - sum([flag[21] for _ in range(220)]) - sum([flag[22] for _ in range(212)]) - sum([flag[16] for _ in range(232)]) + sum([flag[25] for _ in range(flag[25])]) - sum([flag[4] for _ in range(140)]) - sum([flag[31] for _ in range(250)]) - sum([flag[28] for _ in range(150)]) + sum([flag[11] for _ in range(flag[11])]) + sum([flag[13] for _ in range(flag[13])]) - sum([flag[14] for _ in range(234)]) + sum([flag[7] for _ in range(flag[7])]) - sum([flag[8] for _ in range(174)]) + sum([flag[3] for _ in range(flag[3])]) - sum([flag[25] for _ in range(242)]) + sum([flag[29] for _ in range(flag[29])]) + sum([flag[5] for _ in range(flag[5])]) - sum([flag[30] for _ in range(142)]) - sum([flag[26] for _ in range(170)]) - sum([flag[19] for _ in range(176)]) + sum([flag[0] for _ in range(flag[0])]) - sum([flag[27] for _ in range(168)]) + sum([flag[20] for _ in range(flag[20])]) - sum([flag[20] for _ in range(212)]) + sum([flag[21] for _ in range(flag[21])]) + sum([flag[6] for _ in range(flag[6])]) + sum([flag[18] for _ in range(flag[18])]) - sum([flag[18] for _ in range(178)]) + 297412 == 0:
print('yes')

简单格式处理后如下:
alt text
flag 长 32 位,
sum = 0
for i in range(32):
sum += flag[i]_ flag[i]
sum += flag[i]_ (100-300 的一个偶数)
sum = -297412
可能按照出题人的想法,这题的解法是依据题目简介的提示:初中数学,配平。
A^2 + 2AB +B^2 = X。这题每一位 flag 都有平方,也都有乘以一个偶数,那假设每位 flag 是 A,A 乘的那个偶数就是另外的 2B,由此可以推出 flag
alt text
那这不是很扯吗,题目的解是基于一个很低级的假设。大伙是来做逆向的,不是脑洞数学。

ezrand

随机种子随机数算法,因为随机种子根据 time64 得到,而 time64 返回值落在 0xffff 内,所以可爆。
中间随机数相关的复杂算法 ida 可能还原不正确,需要按汇编理解。本质上是 rand()%0xff

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int __cdecl main(int argc, const char **argv, const char **envp)

{

    unsigned long v3; // rbx

    unsigned int v4;  // ax

    int v5;           // edi

    long i;           // rsi

    int rand_value;   // eax

    int cipher[7];    // [rsp+20h] [rbp-50h]

    char v10;         // [rsp+3Ch] [rbp-34h]

    int v11;          // [rsp+3Dh] [rbp-33h]

    char *input;      // [rsp+40h] [rbp-30h] BYREF

    long v13;         // [rsp+50h] [rbp-20h]

    int v14;          // [rsp+58h] [rbp-18h]

    int v15;          // [rsp+5Ch] [rbp-14h]

    char v16;         // [rsp+5Eh] [rbp-12h]



    v13 = 0;

    input = 0;

    v14 = 0;

    v15 = 0;

    v16 = 0;

    cipher[0] = 0xEA6C0C5D;

    v11 = 0;

    v3 = -1;

    cipher[1] = 0x34FC1946;

    cipher[2] = 0x72362B2;

    cipher[3] = 0xFB6E2262;

    cipher[4] = 0xA9F2E8B4;

    cipher[5] = 0x86211291;

    cipher[6] = 0x43E98EDB;

    v10 = 77;

    v4 = _time64(0);

    srand(v4);

    v5 = 0;

    int rand_arr[29];

    // for (i = 0; i < 29; i++)

    //     rand_arr[i] = rand();

    v3 = 29;

    i = 0;



    for (int seed = 0; seed < 0xffff; seed++)

    {

        char flag[30] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

        srand(seed);

        for (i = 0; i < 29; i++)

        {

            int tmp = rand();

            int a = tmp >> 32 >> 7;

            int b = a >> 0x1f;

            int c = a + b;

            b = c * 0xff;

            int d = tmp - b;

            int tmp2 = *((char *)cipher + i) ^ d;

            // tmp2 = d;

            if (tmp2 > 0x7e)

                break;

            if (tmp2 < 0x20)

                break;

            flag[i] = tmp2; // printf("%c", tmp2);

        }

        if (strlen(flag) > 1)

            printf("%s\n", flag);

    }



    return 0;

}

ezUnity

此题略微难在 metadata 文件格式修复
可对比 babyUnity 或者其它 Unity 文件的 dll,修复后:
alt text
剩下操作类似 babyUnity
能通过 findcrypt 或者分析发现是 aes

1
2
3
cipher1 = "pNufkEIU9dHjKXYXWiFyrthHYFEfqJAWcPM/t8/zX1w="
key = "a216d5d34c2723f5"
iv = "9f68268f755b1363"

alt text
XYCTF{IL2CPP_1s_intere5t1ng}

Trustme

main 是一个没什么用的 rc4,并且和程序对不上。
alt text
康康别的类
alt text
有一个对 dex 解密并输出什么的。这里静态解太麻烦了,动态找到安卓机的 data/data 里对应的包,发现确实有东西
如:
alt text
分析 dex 目录的 apk,发现其主要做了一个数据库查找操作。
alt text
找到数据库:
alt text
XYCTF{And0r1d_15_V3ryEasy}

That’s this

lua 字节码
alt text
在线反编译
https://www.luatool.cn/index.php

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78

value = ""
output = ""
i = 1
while true do
local temp = string.byte(flag, i)
temp = string.char(Xor(temp, 8) % 256)
value = value .. temp
i = i + 1
if i > string.len(flag) then
break
end
end
for _ = 1, 1000 do
x = 3
y = x * 3
z = y / 4
w = z - 5
if w == 0 then
print("This line will never be executed")
end
end
for i = 1, string.len(flag) do
temp = string.byte(value, i)
temp = string.char(temp + 3)
output = output .. temp
end
result = output:rep(10)
invalid_list = {
1,
2,
3
}
for _ = 1, 20 do
table.insert(invalid_list, 4)
end
for _ = 1, 50 do
result = result .. "A"
table.insert(invalid_list, 4)
end
for i = 1, string.len(output) do
temp = string.byte(output, i)
temp = string.char(temp - 1)
end
for _ = 1, 30 do
result = result .. string.lower(output)
end
for _ = 1, 950 do
x = 3
y = x * 3
z = y / 4
w = z - 5
if w == 0 then
print("This line will never be executed")
end
end
for _ = 1, 50 do
x = -1
y = x * 4
z = y / 2
w = z - 3
if w == 0 then
print("This line will also never be executed")
end
end
require("base64")
obfuscated_output = to_base64(output)
obfuscated_output = string.reverse(obfuscated_output)
obfuscated_output = string.gsub(obfuscated_output, "g", "3")
obfuscated_output = string.gsub(obfuscated_output, "H", "4")
obfuscated_output = string.gsub(obfuscated_output, "W", "6")
invalid_variable = obfuscated_output:rep(5)
if obfuscated_output == "==AeuFEcwxGPuJ0PBNzbC16ctFnPB5DPzI0bwx6bu9GQ2F1XOR1U" then
print("You get the flag.")
else
print("F**k!")
end

存在一些混淆代码,但从密文逆推可以很轻松地找到核心逻辑:

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
import base64

a = list(b"==AeuFEcwxGPuJ0PBNzbC1WctFnPB5DPzI0bwxWbu9GQ2F1XOR1U")

a.reverse()

b = list(base64.b64decode(bytes(a)))

print(bytes(a))

print(base64.b64decode(bytes(a)), len(b))

flag = ""

for i in range(len(b)):

    for l in range(0x20, 0x7f):

        t = (l ^ 8) + 3

        if t == b[i]:

            flag += chr(l)

    # b[i] += 1

    # b[i] -= 3

    # # b[i] &= 0xff

    # b[i] ^= 8

print(flag, len(flag))

XYCTF{5dcbaed781363fbfb7d8647c1aee6c}