Home mocactf crypto (english)
Post
Cancel

mocactf crypto (english)

mocactf

my first time to play CTF with camp…!

i guess i can’t never experience two times in my life, but i want to buy my camp goods…

hash oracle

chall

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

with open("flag.txt", "rb") as f:
    FLAG = f.read().strip()


def hash(s, i):
    msg = FLAG[:i] + s + FLAG[i:]
    return hashlib.sha256(msg).digest()


def main():
    print("Welcome to the hash challenge!")
    while True:
        print("Enter a string to hash:")
        s = bytes.fromhex(input())
        print("Enter an index to insert the string at:")
        i = int(input())
        h = hash(s, i)
        print("The hash is: " + h.hex())


if __name__ == "__main__":
    main()

solve

i use only this A + b + x + B == A + x + b + B

if x equal to b, hash(A + b + x + B ) == hash(A + x + b + B).so i can find the flag one one byte each.

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
#!/usr/bin/env python
from ptrlib import *
from tqdm import tqdm
io = process(["python3","chall.py"])
# io = remote("10.90.39.124 5555")
# io.debug = True


def sender(m, i):
    io.sendlineafter(b":\n", m.hex())
    io.sendlineafter(b":\n", i)
    return io.recvline().decode().split(": ")[1]


len_SALT = 50
pt = b""

for k in range(len(pt), len_SALT):
    for i in tqdm(range(0x20, 0x7f)):
        a = sender(bytes([i]), k + 1)
        b = sender(bytes([i]), k)
        if a[:-1] == b[:-1]:
            pt = pt + bytes([i])
            print(bytes([i]))
            break
print(pt)
b'pwnx{13ngth_3xt3n510n_4tt4ck_w1th_0r4cl3}'

ML

situlation

first, i have 3 files(ciphertext, test.py, model.onnx).

tst.py is only to “how to use model.onnx” so i omit. our goal is reverse and decrypt from cipher text.

my initial analysis is below.

  • encrypt for each 16bytes
  • definitive(not tweak)

then, i guess this onnx file has some encryption scheme. (because this challnege category is not rev and misc)

now i have two ideas, it is AES encryption and original encryption scheme.

analysis

so, i read this article TSG CTF 2020 onnxrev write-up (github.com) and parse to onnx.

![image-20240918194209927](assets/img/ctf/2024-09-18-moca/image-20240918194209927.pngimage-20240918194230902

i found this about 1000 lines code and these has similarly repeat part.

so, i guess to this encryption is BLOCK encryption.

image-20240918194334937

At this point, it was too huge to write code to parse each processing result like TSGCTF2024, so I had to work on other challenges and explore moca2024 venue.

BUT,

HINT

ok, we got a hint remember, this is crypto! We never design our own crypto, so i am convinced this encryption scheme is AES256.

So I decided to look for the part where the sbox is hard-coded to get an overview of the process, but after looking at all the constants, I ended up helplessly without anything that looked like an Sbox.

I had some free time on the plane on the way home, so I was doing some random searches and found How to add an output node to an ONNX model #Python3 - Qiita, which is a god function that adds a process just by writing a variable. I found a god function that adds a process to an ONNX model just by writing a variable. I’m so glad I found it!

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
import onnx
from util import *
from onnxruntime import InferenceSession
import numpy as np
from Crypto.Cipher import AES


def seach():
    model = onnx.load("../model/model.onnx")

    for node in model.graph.node:
        if node.op_type == "Identity" or node.op_type == "Constant":
            continue
        yield node.output[0]

for out in seach():
    input_path = '../model/model.onnx'
    output_path = 'model.onnx'
    input_names = ['input']
    output_names = [out]
    onnx.utils.extract_model(input_path, output_path, input_names, output_names)
    
    sess = InferenceSession("./model.onnx")
    inp = b"\x00"*16
    inps = [inp[i:i+16] for i in range(0, len(inp), 16)]

    inp = []
    for i in inps:
        inp += bin_list(i)

    x = np.concatenate(inp).reshape(-1, 16*8)

    outs = sess.run(None, {"input": x})
    print(out, outs)
    input()

I look at the nodes excluding constants and initialisation like this, suddenly the part appear not b‘\x00’*16.

image-20240918200117082

It turns out that this corresponds to Add Roundkey at the beginning of AES.

So for AES256, if we know roundkey[0] and roundkey[1], we also know the masterkey, so everything works.

If i continue to look at the process, i see the following, which shows the AES 1round. From here we can find the roundkey[1].

image-20240918200300680

1
2
roundkey0 = b'p{ylwt_dnht_xeom'
roundkey1 = b'ecdt_o__1und_loo'

Just be aware that this AES is transposed, so the character b ‘pwnx{they_told_me_1_could_not_do’ is the AES masterkey.

1
2
3
4
from Crypto.Cipher import AES
aes = AES.new(b"pwnx{they_told_me_1_could_not_do",AES.MODE_ECB)
print(aes.decrypt(open("ciphertext","rb").read()))
# b'here the last piece of the flag and the rest is in the key :) _aes_with_ml_so_1_did_it_anyway}\x02\x02'

Therefore, combining the information from the plaintext and key.

get a flag. pwnx{they_told_me_1_could_not_do_aes_with_ml_so_1_did_it_anyway}

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