NETCOMP4 CTF Qualification
Forensic - another cute little dump

First of all, I identified the forensic file, it appears that the file is a mini dump crash report. Using a template, I tried strings command and less to look at what strings are available in this file. And it turned out to be too many to check manually.
and then i filtered out the file into dump file

in this state i tried other grep command to find any hint or flag
grep -i netcomp dump_strings.txt
grep -i flag dump_strings.txt
grep -i ctf dump_strings.txt
grep -i birb dump_strings.txt
but sadly i didnt find anything useful, after research through the internet, i found that “/title” can be used to see a PDF metadata, without further ado i tried to grep the dump_strings.txt

and got the flag, FLAG:NETCOMP{a_cut3_littl3_b1rb_and_a_cut3_littl3_dump_00febbcd89881415}
Reverse Engineer - Linear

first of all i identified what kind of file is this, and i run ghidra for file analysis

Program Logic: The executable prompts the user with “Flag: ” and expects an input. It uses fgets to read up to 0x100 bytes from stdin. An if statement checks the input length using strlen; it must be exactly 0x20 (32 bytes). If the length is incorrect, it returns “Wrong”. Otherwise, it splits the 32 bytes into 4 distinct chunks
and then after crafting the script, this is the final script to extract the float values directly from the .rodata section, and calculate the correct characters
import struct, math
path = "/mnt/data/linear"
with open(path, "rb") as f:
f.seek(0x2000)
ro = f.read(0x100)
def f32(off):
return struct.unpack_from("<f", ro, off)[0]
def f32_arr(off, n=8):
return list(struct.unpack_from("<" + "f"*n, ro, off))
thr = f32(0x04)
A = f32_arr(0x40)
C = f32_arr(0x60)
B0 = f32_arr(0x80)
B1 = f32_arr(0xA0)
B2 = f32_arr(0xC0)
B3 = f32_arr(0xE0)
Bs = [B0, B1, B2, B3]
out = []
for chunk in range(4):
for j in range(8):
t = math.sqrt(-Bs[chunk][j])
x = (t - C[j]) / A[j]
out.append(int(round(x)))
flag = bytes(out).decode()
print(flag)

Forensic - Complexity
after dumped the APK content of the android package file, complexity.apk. i analyze its structure

Upon inspecting the assets directory, I found a large number of .dex files with the prefix “NETCOMP”. By researching the application’s code, I found a loop indicating that the application dynamically loads these files based on the MD5 hash of an index variable $i$: “NETCOMP_” + md5(String.valueOf(i)) + “.dex”


knowing that the .dex files need to be decoded based on their MD5 hashes, i used jadx to decompile the application and ran a one liner to parse the specific strings containing the flag fragments
jadx -r -d /dev/stdout NETCOMP_*.dex | grep 'append("' | sed 's/.*append("//;s/").*//' | tr -d '\n'
after running it, i got the flag
FLAG: NETCOMP{this_is_a_very_long_flag_to_be_honest_i_dont_even_know_what_i_wanna_put_after_this_but_as_long_as_the_flag_is_long_enough_its_fine_i_guess_lol_anyway_goodjob_on_solving_this_challenge_im_proud_of_you}
Forensic - long

firstly i checked the file using checksec, to identify the executable file, and after Reversing in Ghidra showed the program only outputs “Wrong!” or “Correct!”. The goal was to find the correct memory offsets dynamically
I loaded the binary into GDB. Disabling pagination, I set breakpoints at *main+0x32eaa and *main+0x32ed5. I manipulated the instruction pointer $rip to jump directly to the relevant checks. After providing dummy input (AAA), the second breakpoint was hit. By examining the stack memory (x/64cb $rbp-0x90), the flag was laid out clearly in memory.

FLAG: NETCOMP{T00_h4rd_for_LLM?_n0_Pr0bl3m_CuZ_Y0u_C4n_Ju5T_D3buG_M3}
Cryptography - Corner Adept
The challenge provided a chall.py script and a remote connection
Reviewing the script showed the logic for variable:
if b = 0
c = y*(p >> 202)*r, this dictates that 'c' always resides within the multiplication image y t = (p >> 202)
i used SageMath to calculate the elliptic curve order n. Once n was found, i craft a script utilizing pwntools and ecdsa to dump the points remotely

with this script i manage to get the flag
from pwn import remote
import re
from math import gcd
from ecdsa import ellipticcurve
HOST = "72.62.122.169"
PORT = 8302
p = 87975800266411715571738368366335380369666653297323309778888046169239182316403
a = 24706251795871763793703712664815408269526601526653607864558628275826577881713
b = 4
ORDER_N = 87975800266411715571738368366335380369976192431834769153921751523575374324650
ec = ellipticcurve.CurveFp(p, a, b)
def parse_point(line: bytes):
s = line.strip().decode(errors="ignore").strip().strip("()")
xs, ys = s.split(",")
return ellipticcurve.Point(ec, int(xs.strip()), int(ys.strip()))
def recv_bitlen_after_choice1(io):
for _ in range(100):
line = io.recvline(timeout=5)
if not line: continue
m = re.search(r"Printing\s+(\d+)\s+lines", line.decode(errors="ignore"))
if m: return int(m.group(1))
raise RuntimeError("Could not find 'Printing <n> lines...'")
def classify_bit(P, nH):
return 0 if (P * nH) == ellipticcurve.INFINITY else 1
def one_full_dump_bits(nH):
io = remote(HOST, PORT)
try:
io.recvuntil(b"What you want?")
io.sendline(b"1")
bitlen = recv_bitlen_after_choice1(io)
bits = []
for _ in range(bitlen):
line = io.recvline(timeout=5)
bits.append(classify_bit(parse_point(line), nH))
return bits
finally:
io.close()
def bits_to_inner_hex(bits):
flag_int = 0
for i, bbit in enumerate(bits):
flag_int |= (bbit << i)
blen = (flag_int.bit_length() + 7) // 8
return flag_int.to_bytes(blen, "big").lstrip(b"\x00")
def majority_vote(bit_runs):
bitlen = len(bit_runs[0])
return [1 if sum(run[i] for run in bit_runs) > len(bit_runs)//2 else 0 for i in range(bitlen)]
def main():
t = p >> 202
g = gcd(ORDER_N, t)
nH = ORDER_N // g
RUNS = 21
runs = []
while len(runs) < RUNS:
try:
runs.append(one_full_dump_bits(nH))
except Exception:
pass
voted = majority_vote(runs)
inner = bits_to_inner_hex(voted)
print("NETCOMP{" + inner.decode() + "}")
if __name__ == "__main__":
main()

That’s my writeup for this competition, i hope this is helpful. and sorry if the writeup is not that tidy and neat