day1聚会去了直接摸鱼,结果队里的爹们直接全给干完了,day2跟师傅们一起把reAK了,只能说OIer多少带点傻逼的

image-20240212150940629

babycom

调试可以发现释放了一个dll文件在 C:\Users\Username\AppData\Local\Temp

继续调试到

image-20240212151315226

再 F7 可以进入加密函数

创建函数反编译后发现是一堆 XTEA 加密

image-20240212151358020

key是这个

image-20240212152615536

最后还有一个 advapi32_CryptEncrypt

image-20240212152041259

密文是Buf1

image-20240212152844296

解密即可,参考 CryptEncrypt 函数 (wincrypt.h) - Win32 apps | Microsoft Learn

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
#include<iostream>
#include"Windows.h"
#include<windef.h>
#include<stdint.h>
unsigned char enc[] =
{
0x0B, 0xAF, 0x51, 0x21, 0x9C, 0x52, 0x10, 0x89, 0x3F, 0x2C,
0x34, 0x30, 0x87, 0x13, 0xC1, 0x4C, 0xC1, 0x7F, 0x81, 0x6E,
0xBA, 0xBD, 0xDF, 0x43, 0x1A, 0xF0, 0xD7, 0xDE, 0x8E, 0x66,
0xB9, 0x7C
};

void xtea_decrypt(uint32_t v[2], uint32_t key[4])
{
unsigned int i;
unsigned int num_rounds = 32;
uint32_t v0=v[0], v1=v[1], delta=0x114514, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

int main() {
BYTE pbData[] = "\xEA\x3E\xD4\x1Cp\xCB\xD7G\x98^\xCA\xDBS\f9+";
DWORD pdwDataLen = 32;
HCRYPTPROV phProv = 0;
HCRYPTHASH phHash = 0;
HCRYPTKEY phKey = 0;
CryptAcquireContextA(&phProv, 0, 0, 0x18u, 0xF0000000);
CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash);
CryptHashData(phHash, pbData, 0x10u, 0);
CryptDeriveKey(phProv, 0x660Eu, phHash, 1u, &phKey);
std::cout << std::hex << phKey << std::endl;
CryptDecrypt(phKey, 0, 0, 0, enc, &pdwDataLen);
uint32_t k[4]={0x1CD43EEA, 0x47D7CB70, 0x0DBCA5E98, 0x2B390C53};
for(int i = 0; i < 32; i += 8)xtea_decrypt((uint32_t*)(enc + i), k);
for(int i = 0; i < 32; i++)printf("%c", enc[i]);
}

解出flag L3HCTF{C0M_Th3C0d3_1s_FuN!!!!!!}

babyRust

查个属性,发现是 Tauri Apps,Tauri 是一个开源框架,用于构建轻量级、高性能的桌面应用程序。它是使用 Web 技术(如 HTML、CSS 和 JavaScript)来创建用户界面,同时利用 Rust 语言提供的api功能。Tauri 的目标是提供一种更高效、更安全的方式来开发跨平台的桌面应用程序。

根据 Tauri 框架的静态资源提取方法探究 | yllhwa’s blog

再IDA里找到 /assets/index-tWBcqYh-.js

image-20240219132657259

先dump下来

1
2
3
4
5
6
7
import brotli

content = open("dump.br", "rb").read()
print(len(content))

decompressed = brotli.decompress(content)
open("dump", "wb").write(decompressed)

格式化JS代码以后,逻辑在最下面

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
const nl = ve("button", {
type: "submit"
}, "go!", -1),
sl = {
__name: "Greet",
setup(e) {
const t = ts(""),
n = ts("");

function s(o, i = "secret") {
for (var c = "", u = i.length, d = 0; d < o.length; d++) {
var h = o.charCodeAt(d),
x = i.charCodeAt(d % u),
w = h ^ x;
c += String.fromCharCode(w)
}
return c
}
async function r() {
if (n.value === "") {
t.value = "Please enter a name.";
return
}
btoa(s(n.value)) === "JFYvMVU5QDoNQjomJlBULSQaCihTAFY=" ? t.value = "Great, you got the flag!" : t.value = "No, that's not my name."
}
return (o, i) => (mr(), br(ge, null, [ve("form", {
class: "row",
onSubmit: Qi(r, ["prevent"])
}, [Mo(ve("input", {
id: "greet-input",
"onUpdate:modelValue": i[0] || (i[0] = c => n.value = c),
placeholder: "Enter a name..."
}, null, 512), [
[zi, n.value]
]), nl], 32), ve("p", null, Nr(t.value), 1)], 64))
}
},
rl = (e, t) => {
const n = e.__vccOpts || e;
for (const [s, r] of t) n[s] = r;
return n
},
Kn = e => (bo("data-v-bacdabd6"), e = e(), yo(), e),
ol = {
class: "container"
},
il = Kn(() => ve("h1", null, "Welcome to L3HCTF!", -1)),
ll = Kn(() => ve("p", null, "Hope you have a good time playing.", -1)),
cl = Kn(() => ve("p", null, "Now please tell me a name.", -1)),
fl = {
__name: "App",
setup(e) {
return (t, n) => (mr(), br("div", ol, [il, ll, cl, Ae(sl)]))
}
},
ul = rl(fl, [
["__scopeId", "data-v-bacdabd6"]
]);
ki(ul)
.mount("#app");

先异或再base64,解密即可

1
2
3
4
5
6
7
8
9
import base64
enc = "JFYvMVU5QDoNQjomJlBULSQaCihTAFY="
enc = base64.b64decode(enc).decode('utf-8')
key = 'secret'
flag = ''
for i in range(0, len(enc)):
flag += chr(ord(enc[i]) ^ ord(key[i % 6]))
print(flag)
# W3LC0M3_n0_RU57_AnyM0r3

DAG

字节码逆向,chatGPT + 手动修改(func1 GPT生成的缩进错完了痛失三血)

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
# func1
def func1(arr, lss, i, j):
if arr[i * len(lss) + j] != -1:
return arr[i * len(lss) + j]

s1 = list(lss[i])
s2 = list(lss[j])
l1 = len(s1)
l2 = len(s2)

flag = True
n = 0

if l1 - l2 == 1:
for m in range(l1):
if s1[m] != s2[n]:
if flag:
flag = False
else:
arr[i * len(lss) + j] = 0
return arr[i * len(lss) + j]
else:
n += 1
if n == l2:
break
else:
arr[i * len(lss) + j] = 0
return arr[i * len(lss) + j]

arr[i * len(lss) + j] = 1
return 1


def abc(abcarray, arr, lss, i):
if abcarray[i] > 0:
return abcarray[i]
else:
m = 1
for index, word in enumerate(lss):
if func1(arr, lss, i, index) == 1:
m = max(m, abc(abcarray, arr, lss, index) + 1)
abcarray[i] = m
return m


def solution(lss):
abcarray = [-1] * len(lss)
arr = [-1] * (len(lss) ** 2)
ans = 1
for i in range(len(lss)):
ans = max(ans, abc(abcarray, arr, lss, i))
abcarray[i] = ans
return ans


def func2(n):
a, b = 1, 1
for i in range(n - 1):
a, b = b, a + b
return a


import random


def calc(nums):
num1, num2, num3 = nums[0], nums[1], nums[2]
num1 = 2023 + (num1 & 15) - (num1 & 240)
num2 += 7
num2 = func2(num2)
random.seed(num3)
flag = f"{num1}{num2}{num3}{random.gauss(num2, 0.2)}".replace('.', 'x')
print(f"flag={flag}")
return flag


def encode(s):
ret = []
ls = list(s)
for i in range(0, len(ls), 2):
num1 = ord(ls[i])
num2 = ord(ls[i + 1])
numa = (num1 & 248) >> 3
numb = ((num1 & 7) << 3) | ((num2 & 240) >> 4)
numc = num2 & 15
ret += [numa, numb, numc]
return ret


if __name__ == '__main__':
assert encode(str1) == [12, 22, 1], "AssertionError"
assert encode(str2) == [12, 14, 2], "AssertionError"
assert encode(str3) == [12, 22, 3], "AssertionError"
assert encode(str4) == [12, 30, 2], "AssertionError"
assert encode(str5) == [12, 22, 4, 12, 30, 1], "AssertionError"
assert encode(str6) == [12, 22, 1, 12, 30, 4], "AssertionError"
assert encode(str7) == [12, 22, 2, 12, 22, 2], "AssertionError"
assert encode(str8) == [12, 14, 3, 12, 38, 2], "AssertionError"
num1 = solution([
'a', str1, str2, str3, str4, 'bda', str5, str6, str7, str8,
'bcdef', 'aabcc', 'acbac', 'bdcaa', 'bbbbcc', 'babccc', 'abaccc'
])
num2 = solution([xxx])
num3 = solution([xxx])
flag = calc([num1, num2, num3])


把几个str爆破一下,然后进行连边,看v1能不能通过删除一个字母到达v2,最后跑一个最长路,注意 ans 初始化是 1

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
138
import sys
def func1(arr, lss, i, j):
if arr[i * len(lss) + j] != -1:
return arr[i * len(lss) + j]

s1 = list(lss[i])
s2 = list(lss[j])
l1 = len(s1)
l2 = len(s2)

flag = True
n = 0

if l1 - l2 == 1:
for m in range(l1):
if s1[m] != s2[n]:
if flag:
flag = False
else:
arr[i * len(lss) + j] = 0
return arr[i * len(lss) + j]
else:
n += 1
if n == l2:
break
else:
arr[i * len(lss) + j] = 0
return arr[i * len(lss) + j]

arr[i * len(lss) + j] = 1
return 1


def abc(abcarray, arr, lss, i):
if abcarray[i] > 0:
return abcarray[i]
else:
m = 1
for index, word in enumerate(lss):
if func1(arr, lss, i, index) == 1:
m = max(m, abc(abcarray, arr, lss, index) + 1)
abcarray[i] = m
return m


def can_transform(s1, s2):
if len(s1) - len(s2) != 1:
return False
for i in range(len(s1)):
if s1[:i] + s1[i + 1:] == s2:
return True
return False


def build_graph(lss):
graph = {}
for i, word1 in enumerate(lss):
for j, word2 in enumerate(lss):
if can_transform(word1, word2):
if i not in graph:
graph[i] = []
graph[i].append(j)
return graph


def longest_path(graph, node, memo):
if node in memo:
return memo[node]
max_length = 1
if node in graph:
for neighbor in graph[node]:
path_length = 1 + longest_path(graph, neighbor, memo)
max_length = max(max_length, path_length)
memo[node] = max_length
return max_length


def solution(lss):
graph = build_graph(lss)
memo = {}
max_path = 1
for node in graph:
max_path = max(max_path, longest_path(graph, node, memo))
return max_path

def func2(n):
a, b = 1, 1
for i in range(n - 1):
a, b = b, a + b
return a


import random


def calc(nums):
num1, num2, num3 = nums[0], nums[1], nums[2]
num1 = 2023 + (num1 & 15) - (num1 & 240)
num2 += 7
num2 = func2(num2)
random.seed(num3)
flag = f"{num1}{num2}{num3}{random.gauss(num2, 0.2)}".replace('.', 'x')
print(f"flag={flag}")
return flag


def encode(s):
ret = []
ls = list(s)
for i in range(0, len(ls), 2):
num1 = ord(ls[i])
num2 = ord(ls[i + 1])
numa = (num1 & 248) >> 3
numb = ((num1 & 7) << 3) | ((num2 & 240) >> 4)
numc = num2 & 15
ret += [numa, numb, numc]
return ret


if __name__ == '__main__':
sys.setrecursionlimit(10000)
str1 = "ba"
str2 = "ab"
str3 = "bc"
str4 = "cb"
str5 = "bdca"
str6 = "bacd"
str7 = "bbbb"
str8 = "acdb"
num1 = solution([
'a', str1, str2, str3, str4, 'bda', str5, str6, str7, str8,
'bcdef', 'aabcc', 'acbac', 'bdcaa', 'bbbbcc', 'babccc', 'abaccc'
])
num2 = ...
num3 = ...
calc([num1, num2, num3])
# 202817711117711x25763695063

ez_stack

虚拟机,dispatchersub_4017A1, 字节码在 dispatcher 函数调用之前解密出来,sys_mmap 是 Linux 系统调用,开空间用的。

一开始就开辟了三个栈空间 406040, 406050, 406060,作用分别是:

406040:存储待执行的指令字节码,相当于 IR

406050:存储栈空间中的变量

406060:存储数据,相当于通用寄存器,并用于循环

格式为:

1
2
3
4
5
6
struct stack
{
unsigned char * base;
unsigned int top;
unsigned int max_size;
}

image-20240502171342836

然后进入循环取字节码

image-20240502171642709

sub_40170Dpop 操作,该函数的参数也是一个 stack 结构体

image-20240502172052781

sub_4015AEpush 操作如果栈空间大小不够,还会用 sys_mmap 函数扩展栈空间

image-20240502172132880

然后对取出来的 opcode 进行一些解析,v15 是对指令的操作次数

image-20240502172303977

然后就是虚拟机分析,取高四位进行 dispatch

  • 0xF0: 退出虚拟机

  • 0xE0: 从 406050 依次 pop opcode_num 个字节,然后 sys_write() 写出来

  • 0xD0: 读入 opcode_num 个字节,push 进栈 50

  • 0xC0: 一坨位移操作

  • 0xB0: or 运算,拿出 406050 顶部的 2 * opcode_num 个元素,前 i 个元素与第 i + opcode_num 个元素 or,再按顺序放回去

  • 0xA0: and 运算,和 or 差不多

  • 0x90: xor 运算,和 or 差不多

  • 0x80: sub 运算,v9 保存借位,其它和 or 差不多;上一个如果是负下一个结果得减 1,先算靠近栈底的那边

  • 0x70: add 运算,v9 保存进位,其它和 or 差不多;上一个如果进位下一个结果得加 1,先算靠近栈底的那边

  • 0x60: 复制 406050 顶部第 opcode_num 个元素,然后把它 push 到栈顶

  • 0x50: 删除 406050 顶部第 opcode_num 个元素

  • 0x40: 把之前 op_big_endian 那仨数据 push 到栈 50

  • 0x30: 控制流指令 jmp

  • 0x20: 从 406060 中取出之前push进去的旧指令,实现向前跳转,循环的功能

  • 0x10: 从解密字节码中解压 z 条指令,并压入 406040 的栈