This page looks best with JavaScript enabled

ByteCTF 2021 Re 部分题解

 ·  ☕ 4 min read · 👀... views

languages binding

GO+LUA 整了个类似解释器的东西。可以看出程序是GO编译的,并且混淆了符号。没有符号的Go,实现了Lua的虚拟机,我只能说非常恐怖。

随便往文件里输点啥 总能通过报错得到一段输出
languagesbinding_1

对比源文件内输入和报错输出,发现就是逐位字节异或了85。然后对文件解密,发现文件少了头。现场学习lua文件结构

Lua chunk文件结构

修文件头前6字节,尝试用unluac对文件进行反编译,发现还是会报错,瞒猜改了啥,估计是字节码什么的改过了。后面发现可以用disassemble参数勉强看些符号

java -jar .\unluac_2021_09_23.jar --disassemble .\new_lang_script.decrypt --nodebug >> dis.txt

然后在里面发现了这个
languagesbinding_2

瞒猜一手flag长度是29,一测发现雀石是29(我只能说很抽象

后面就是无尽的动调追码,输入然后断下然后CE搜,然后硬断,追到0x738DFE,这里是第一段flag校验,其实就是将第一段flag和下标做了异或,要求结果为9negozc9a
languagesbinding_3
可得第一段flag是1golcwm6q

第二段就很恶心了,往下一直追不下去,往外走,来来回回看了半天,感觉这里像译码入口
languagesbinding_4

然后硬断是能追到这个位置对第二段flag做了读操作,取了一字节
languagesbinding_5

再往下走会找到这个位置,是做比较,但他们中间夹杂了什么东西属实是看不懂了。
languagesbinding_6

对比发现大部分是在做明文比较,直接把数据扣出来就可以,但是其中两个位置会做固定值的加减

flag[17] + 11
flag[23] - 1

这两个位置会做一个简单的加减,多做了几次输入,对照着看发现每次加减的值都相同。然后就可以得到第二段flag:_ymz7fm0dfx

最后还是在这个位置,会做flag格式比较,要求flag头是 ByteCTF 。全部组合,得到 ByteCTF{1golcwm6q_ymz7fm0dfx}

moderncpp

这题其实不是很难,不需要逆那恶心的C++数据结构,反正都是逐字节的操作,打表取巧可以很快的解决。

IDA大砍看到代码很乱,C++和一堆try结构。sub_4027D5会先对数据做长度检查和格式检查,长度要为41。然后往下追,在sub_40211C里,会逐字节的对输入做一系列操作,具体是怎么操作的实在是逆不动,代码太恶心了。但多次测试动调发现,对同一字符的输入,输出是一样的,很像查hash表。然后就手工将所有可见字符输入进去查表(其中大写字母和某些符号没有,被恶心了一手),可以打一个表出来:

{’!’: ‘00110110’, ‘#’: ‘101001’, ‘%’: ‘00010’, ‘&’: ‘10100011100’, ‘(’: ‘010011’, ‘)’: ‘111100’, ‘*’: ‘0110010’, ‘+’: ‘10111’, ‘-’: ‘10100010’, ‘;’: ‘1010000’, ‘=’: ‘000000’, ‘@’: ‘1110011’, ‘[’: ‘110011’, ‘]’: ‘00100’, ‘^’: ‘01111’, ‘_’: ‘01010’, ‘{’: ‘10001’, ‘}’: ‘1010001111’, ‘a’: ‘100101’, ‘b’: ‘00001’, ‘c’: ‘01110’, ’d’: ‘11011’, ’e’: ‘0011010’, ‘f’: ‘010010’, ‘g’: ‘111011’, ‘h’: ‘01000’, ‘i’: ‘10110’, ‘j’: ‘00110111’, ‘k’: ‘1111010’, ’l’: ‘110010’, ’m’: ‘00011’, ’n’: ‘10000’, ‘o’: ‘10100011101’, ‘p’: ‘0110011’, ‘q’: ‘011000’, ‘r’: ‘111110’, ’s’: ‘01011’, ’t’: ‘11000’, ‘u’: ‘11110110’, ‘v’: ‘000001’, ‘w’: ‘111000’, ‘x’: ‘00101’, ‘y’: ‘10011’, ‘z’: ‘101000110’, ‘1’: ‘100100’, ‘2’: ‘111111’, ‘3’: ‘01101’, ‘4’: ‘11010’, ‘5’: ‘11110111’, ‘6’: ‘001100’, ‘7’: ‘111010’, ‘8’: ‘00111’, ‘9’: ‘10101’, ‘0’: ‘1110010’}

然后程序会把输出的这堆二进制字符串转化成数字再转化成字符类型后,拼接到一个数组里构造string
moderncpp

然后再把这个数组做TEA加密,key是"etybftc-clew~emo",最后就是比较了

 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
// 解密TEA加密后的密文
#include <cstdio>
#include <stdlib.h>
#include <windows.h>
BYTE ciphers[] = {
    0x9F, 0x66, 0xD3, 0xC5, 0x1A, 0x17, 0x17, 0xB9, 0x19, 0x7B, 
    0xB3, 0xB4, 0x5F, 0x0C, 0xE8, 0x0A, 0x7F, 0x30, 0x80, 0x8D, 
    0x80, 0x28, 0x52, 0x21, 0x89, 0x05, 0xD8, 0x34, 0xD1, 0x83, 
    0x6C, 0xDE, 0x18, 0x36, 0xB7, 0x59, 0x35, 0x5D, 0xE6, 0xC6
};

//解密函数  
void decrypt (DWORD* v, DWORD* k) {  
    DWORD v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */  
    DWORD delta=0x9e3779b9;                     /* a key schedule constant */  
    DWORD k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */  
    for (i=0; i<32; i++) {                         /* basic cycle start */  
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);  
        sum -= delta;  
    }                                              /* end cycle */  
    v[0]=v0; v[1]=v1;  
} 

union data{
    BYTE raw[40];
    DWORD data[10];
};

int main() {
    BYTE key[] = "etybftc-clew~emo";

    for(int i=0;i<5;++i) {
        decrypt(&reinterpret_cast<union data*>(ciphers)->data[2*i], reinterpret_cast<DWORD*>(key));
    }
    for(int i=0;i<40;++i)
        printf("%x ", reinterpret_cast<union data*>(ciphers)->raw[i]);

    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 通过解密后内容转化成二进制字符串,再从表中匹配明文
ans = [0xc, 0xf0, 0x69, 0xd8, 0x4a, 0x32, 0xfb, 0x62, 0x8e, 0xa4, 0xcc, 0xc, 0xc0, 0x22, 0x63, 0xe5, 0xb6, 0xfd, 0x7, 0x5e, 0xe6, 0xfe, 0xc6, 0x8d, 0xfd, 0x8d, 0x51, 0xad, 0xe4, 0x68, 0xfa, 0x14, 0x78]
table = {'!': '00110110', '#': '101001', '%': '00010', '&': '10100011100', '(': '010011', ')': '111100', '*': '0110010', '+': '10111', '-': '10100010', ';': '1010000', '=': '000000', '@': '1110011', '[': '110011', ']': '00100', '^': '01111', '_': '01010', '{': '10001', '}': '1010001111', 'a': '100101', 'b': '00001', 'c': '01110', 'd': '11011', 'e': '0011010', 'f': '010010', 'g': '111011', 'h': '01000', 'i': '10110', 'j': '00110111', 'k': '1111010', 'l': '110010', 'm': '00011', 'n': '10000', 'o': '10100011101', 'p': '0110011', 'q': '011000', 'r': '111110', 's': '01011', 't': '11000', 'u': '11110110', 'v': '000001', 'w': '111000', 'x': '00101', 'y': '10011', 'z': '101000110', '1': '100100', '2': '111111', '3': '01101', '4': '11010', '5': '11110111', '6': '001100', '7': '111010', '8': '00111', '9': '10101', '0': '1110010'}

flag = ""

ans = "".join([bin(x).replace('0b', '').zfill(8) for x in ans])

while ans != "000":
    for i, j in table.items():
        if ans.startswith(j):
            flag += i
            ans = ans[len(j):]
            print(flag)
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer