Home (CTF) 2022 hxp required writeup
Post
Cancel

(CTF) 2022 hxp required writeup

rank: 68

required ( 182 pt, 43 solve )

1
2
3
4
5
6
7
8
9
10
f=[...require('fs').readFileSync('./flag')]
require('./28')(753,434,790)
require('./157')(227,950,740)
require('./736')(722,540,325)
require('./555')(937,26,229)
require('./394')(192,733,981)
require('./446')(635,887,709)
require('./394')(751,446,832)
...
require('./37')(f)

It requires numerous .js files and calls exported functions. Some of those functions modify __proto__ to change the require path ( i’m not sure it really works ), while others do operations that modify flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os
import re
## get current files *.js
files = os.listdir(os.getcwd())

new_files = []
## get each file execution
for file in files:
    if file.endswith('.js'):
        with open(file, 'r') as f:
            execution = f.read()
            ## if f\[.\] is in execution
            if re.search(r'f\[.\]', execution):
                new_files.append(file)
for file in new_files:
    with open(file, 'r') as f:
        contents = f.read()
        execution = contents[:]
        execution = re.findall(r'f\[.\].*\)$', execution)[0][:-1]
        execution = re.sub(r'(f\[(.)\])',r'f[${\2}]',execution)
        print(execution)
    with open(file, 'w') as f:
        data = contents[:-1] + ",console.log(`"+execution+"`))"
        f.write(data)

The files that modify the flag use f variable. So I modify those .js files to print each operations

1
2
3
4
5
6
7
8
9
files node required.js 
f[17]+=f[5],f[17]&=0xff
f[29]=~f[29]&0xff
f[3]^=f[11]
f[6]=f[6]<<7&0xff|f[6]>>1
f[2]=~f[2]&0xff
f[20]=f[20]<<7&0xff|f[20]>>1
f[23]=f[23]^(f[23]>>1)
...

It prints well

1
2
3
4
5
6
7
8
## + -> -
## - -> +
## ~ -> ~
## f[x]^=f[y] -> f[x]^=y
## ror -> rol
## rol -> ror
## f[XX]^(f[XX]>>1) -> xor(f[XX])
## f[XX]=(((f[XX]*0x0802&0x22110)|(f[XX]*0x8020&0x88440))*0x10101>>16)&0xff -> inv(f[XX])

There are 7 major types of operations. +, -, ~, ror, rol, xor can be easily reversed. In case of xoring with itself after rsh 1, MSB can be found, so inverse calculation is possible if xor in turn. I made a separate function to calculate the inverse. The last bit operation can be reversed by creating a table for values up to 0xff in a one-to-one correspondence.

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
import re
ans = 0xd19ee193b461fd8d1452e7659acb1f47dc3ed445c8eb4ff191b1abfa7969
ans = list(ans.to_bytes(30,byteorder='big'))

def xor(x):
    mask = 0b10000000
    prev = mask & x
    out = prev
    for _ in range(7):
        mask >>= 1
        prev >>=1
        out |= (prev ^ x) & mask
        prev = out & mask
    return out

inv = {}
for i in range(0x100):
    inv[(((i*0x0802&0x22110)|(i*0x8020&0x88440))*0x10101>>16)&0xff] = i

code = """f[17]+=f[5]
f[17]&=0xff
...
f[0]=~f[0]&0xff""".split('\n')

code = code[::-1]

new_code = []
for line in code:
    if line.find('+=') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        line = line.replace('+=', '-=')
        line += "\n"
        line += f"{var}&= 0xff"
    elif line.find('-=') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        line = line.replace('-=', '+=')
        line += "\n"
        line += f"{var} &= 0xff"
    elif line.find('^') != -1 and line.find('>>') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        line = f"{var} = xor({var})"
    elif line.find('0x88440') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        line = f"{var} = inv[{var}&0xff]"
    elif line.find('|') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        if line.find('<<1') != -1: ## to ror
            line = f"{var} = {var}>>1|({var}<<7&0xff)"
        else: ## to rol
            line = f"{var} = {var}>>7|({var}<<1&0xff)"
    elif line.find('~') != -1:
        var = re.match(r'(f\[[0-9]+\])',line)[0]
        line = f"{var} = ~{var}&0xff"
    elif line.find('^') != -1:
        pass
    else:
        continue
    new_code.append(line)
f = ans[:]
for line in new_code:
    exec(line)
print(bytes(f))

After collecting and inverting the output code, converting each into an inverse and executing it with the exec function, you can see that the flag is decrypted.

hxp{Cann0t_f1nd_m0dule_‘fl4g’}

This post is licensed under CC BY 4.0 by the author.
Trending Tags