# -*- coding: utf-8 -*- """Track C: own labs - every trigger below was ACTUALLY executed in WSL (Ubuntu 24.04, g++ 13 / ASan; Python 3.12) and the embedded evidence lines are from the captured logs (/home/corov/cyber/labs/evidence/*). No invented crashes.""" from trackb_part1 import SYSTEM, S, add C_PREAMBLE = ("Analyze this code for exploitable faults (local lab / fuzz context). " "Deliver: white-box analysis, minimal trigger, full exploit writeup, verdict.\n\n") def cyber_add(sid, user, think, trigger, writeup, verdict, impact, reasoning): add(sid, user, think, trigger, writeup, verdict, impact, reasoning, preamble=C_PREAMBLE) def code_user(src, lang="cpp", q="Analyze this code for exploitable faults. Build a minimal trigger and judge exploitability honestly."): return q + "\n\n```" + lang + "\n" + src.strip() + "\n```" # ---------------------------------------------------------------- lab02 heap OOB WRITE cyber_add("C_lab02_oob_write", code_user(r""" #include #include #include // Off-by-bound write: index range derived from user count, allocation one short. int main(int argc, char** argv) { size_t n = (argc > 1) ? (size_t)strtoull(argv[1], nullptr, 10) : 4; char* buf = (char*)malloc(n - 1); // allocation error: n-1 for (size_t i = 0; i < n; ++i) buf[i] = 'B'; // OOB WRITE at i == n-1 buf[n - 1] = '\0'; printf("%s\n", buf); free(buf); return 0; } """), """White-Box: Loop writes indices 0..n-1 (n bytes) into an (n-1)-byte heap allocation. The last iteration, i == n-1, writes one byte past the boundary, and the terminator write at buf[n-1] is out of bounds too. Fault: heap out-of-bounds WRITE of 1 byte, deterministic; the length source (argv) and allocation size are coupled but off by one. No gate (canary/ASLR/NX/seccomp) is involved at this rung - the fault precedes all of them. Ladder: write rung reached; control rung NOT reached in this lab (needs value/offset control over an adjacent function pointer/vtable plus heap grooming).""", """Verified in an ASan build (g++ -O1 -g -fsanitize=address): $ ./lab02_oob_write 32 ==485==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50300000005f ... SUMMARY: AddressSanitizer: heap-buffer-overflow lab02_oob_write.cpp:9 in main Sanitizer aborts at the loop store (line 9), one byte past the malloc(n-1) region; exit 1. Reproducible for any n > 1.""", """Root cause (source -> sink): allocation size n-1 vs loop bound n (lab02_oob_write.cpp:6-9). Sink: byte store at buf[n-1] past the owned chunk. Reachability: unconditional on the attacker-chosen count; one argv value reaches the faulty store. Primitive: heap OOB WRITE, 1 byte, fixed content 'B', fixed offset relative to a fresh allocation. In a real allocator this first corrupts chunk metadata (size fields) or adjacent object data. Chain to impact: write -> metadata corruption -> allocator confusion (overlapping chunks) -> arbitrary write in a groomed heap -> control-flow hijack. The lab stops at the write rung: value and offset control are too weak. What is missing to the next rung: decoupled offset (independent index source), attacker-chosen value bytes, a victim object adjacent in the layout. Stability (ASLR/heap): the fault is deterministic; exploitation is heap-layout dependent (glibc tcache/fastbin state); ASLR only matters from the pointer-leak stage onward. Fix sketch (one level): derive allocation and loop bound from the SAME expression (malloc(n)) or validate the bound against the allocated size; prefer span/vector over raw malloc.""", "VALID_BUG", "high", """REASONING: lab02_oob_write.cpp:6-9 - malloc(n-1) vs loop to n gives a deterministic 1-byte heap OOB WRITE at attacker-chosen n; ASan-verified abort at the first OOB store. Write rung verified; control rung not claimed (insufficient value/offset control - stated).""") # ---------------------------------------------------------------- lab03 UAF cyber_add("C_lab03_uaf", code_user(r""" #include #include #include // Object freed on the error path, then the success path reads it // (the "cleanup-order" UAF shape seen in many parser CVEs). struct Session { long token; char tag[16]; }; int main() { Session* s = (Session*)malloc(sizeof(Session)); s->token = 0x41414141; strcpy(s->tag, "live"); int error = 0; // simulated error branch frees early if (error == 0) { free(s); // error-path style cleanup } printf("token=%lx tag=%s\n", s->token, s->tag); // UAF READ return 0; } """), """White-Box: free(s) precedes the printf reads of s->token and s->tag - use-after-free READ of the freed 24-byte chunk. This is the parser-CVE shape: error-path cleanup runs, control flow continues on the success path anyway. Between free and use there is no allocator call, so the chunk sits untouched in quarantine; ASan aborts at the read. Ladder: read rung; the write variant (free then WRITE) would be the corruption rung; control requires reclaiming the chunk with attacker data (UAF -> type confusion via reallocation) - not present here.""", """Verified (ASan build): $ ./lab03_uaf ==...==ERROR: AddressSanitizer: heap-use-after-free ... READ of size 8 SUMMARY: AddressSanitizer: heap-use-after-free lab03_uaf.cpp:17 in main Reads at the printf of s->token/s->tag after free(s); exit 1.""", """Root cause: cleanup ordering - free executed while subsequent code still dereferences the pointer (lab03_uaf.cpp:14-17). Source -> sink: free(s) -> printf reads of the freed chunk. Reachability: unconditional here; in the parser shape, reachable whenever error cleanup fires but processing continues (partial-decode bugs). Primitive: UAF read (stale heap data disclosure); stronger family (UAF write / reclaim) needs an intervening same-size-class allocation with attacker content. Chain: read -> leak of stale data -> (with reclaim) type confusion -> control. Lab verified to the read rung. Missing to next rung: an intervening malloc with attacker-controlled contents. Stability: deterministic fault; glibc tcache LIFO makes reclaim reliable in real settings. Fix sketch: null the pointer at free + single ownership; in parsers, two-phase commit - side effects only after full decode success.""", "VALID_BUG", "medium", """REASONING: lab03_uaf.cpp:17 reads s->token/s->tag after free at :14 - ASan-verified heap-use-after-free READ. Read rung; write/control rungs need chunk reclaim (absent) - impact medium.""") # ---------------------------------------------------------------- lab04b double free cyber_add("C_lab04b_double_free", code_user(r""" #include #include // Canonical stale-pointer double free. int main() { char* p = (char*)malloc(32); char* stale = p; free(p); free(stale); // DOUBLE FREE return 0; } """, q="Is this exploitable or just a crash? Analyze honestly."), """White-Box: free(stale) re-frees the same 32-byte chunk - the classic stale-pointer double free. Two verified build behaviors: (1) -O0 + ASan aborts: "attempting double-free ... freed by thread T0 here: main lab04b:7; previously allocated: main lab04b:5". (2) At -O1 the compiler ELIMINATED the second free (double free is UB; the optimizer deduplicated the calls) - the fault silently vanished from the binary. That asymmetry is part of the analysis: absence of a crash at higher optimization is NOT evidence of safety. Mitigation landscape: glibc tcache double-free detection (key field) fires on the naive tcache case even without ASan - a mitigation, not a fix (fastbin/tcache-full paths historically bypass it). Ladder: crash rung -> with grooming: chunk allocated twice to two owners -> overlapping allocations -> arbitrary write; control not reached here.""", """Verified at -O0/ASan: $ ./lab04b_double_free ==647==ERROR: AddressSanitizer: attempting double-free on 0x503000000040 in thread T0 #1 ... in main lab04b_double_free.cpp:8 freed by thread T0 here: main lab04b_double_free.cpp:7 SUMMARY: AddressSanitizer: double-free ... in free Note: the same source at -O1 exited 0 - the optimizer removed the redundant free (UB). Build such labs at -O0 or split across translation units to preserve the fault.""", """Root cause: stale alias (stale = p) surviving free without nulling; second free at lab04b:8. Reachability: unconditional; in real code triggered by error-path cleanup running twice (dual ownership / refcount bug). Primitive: allocator-metadata corruption potential (fastbin/tcache dup) -> overlapping chunks -> arbitrary write. Lab: crash rung only. Chain: double free -> duplicate entry -> two live pointers to one chunk -> write-what-where via the second owner. Missing: allocator without dup detection, plus an allocation between the frees to consume the duplicate. Stability: glibc >= 2.26 tcache key check aborts the naive case; historical fastbin dup needed size-field matching (0x30 class here). ASLR irrelevant at this rung. Fix sketch: ownership discipline (single owner / refcount), null-out after free; allocator hardening is depth, not the fix.""", "VALID_BUG", "high", """REASONING: lab04b_double_free.cpp:7-8 - same chunk freed twice via stale alias; ASan-verified double-free at -O0, and at -O1 the UB-elimination was observed (no-crash != safe). Overlapping-allocation path; grooming needed for control - stated.""") # ---------------------------------------------------------------- lab05 stack overflow cyber_add("C_lab05_stack_overflow", code_user(r""" #include #include // Fixed stack buffer fed by argv without length bound // (classic argv/recv into stack array). int main(int argc, char** argv) { char name[16]; if (argc > 1) { strcpy(name, argv[1]); // unbounded copy into stack buffer printf("hello %s\n", name); } return 0; } """, q="How far does this get an attacker? Trigger + writeup + verdict."), """White-Box: unbounded strcpy of argv[1] into a 16-byte stack array - the canonical stack overflow. Two verified build flavors: (a) default Ubuntu g++ (fortify at -O1+) - glibc aborts at runtime: "*** buffer overflow detected ***: terminated" (SIGABRT); fortify only catches copies whose size the compiler can bound, so it is probabilistic; (b) -D_FORTIFY_SOURCE=0 + ASan: "stack-buffer-overflow ... in strcpy" - the store runs past name[16] toward saved registers/return address. Gates for the control rung: stack canaries (Ubuntu defaults add them), NX (needs ROP), ASLR/PIE (needs a leak or partial overwrite). Ladder: write rung verified; control NOT claimed.""", """Verified both flavors: $ ./lab05_stack_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (ASan, fortify off) SUMMARY: AddressSanitizer: stack-buffer-overflow ... in strcpy (exit 1) $ ./lab05_stack_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (default hardening) *** buffer overflow detected ***: terminated (SIGABRT) The fault exists in both; only the catcher differs.""", """Root cause: unbounded strcpy into a fixed 16-byte stack buffer (lab05:8). Reachability: single input string; in a real service, whoever controls the equivalent input. Primitive: sequential stack overwrite past name[16]; with sufficient length reaches saved RBP/return address. Chain: overflow -> canary overwrite (crash or bypass) -> return-address control -> ROP under NX -> code reuse. Lab: crash/write rung verified; control not claimed. Missing to next rung: canary leak, ROP chain, PIE-base leak. Stability: crash deterministic; exploitation dominated by ASLR/canary state. Fix sketch: bounded copy (memcpy with explicit size check) or std::string; -Werror=format-security/_FORTIFY_SOURCE=2 and stack protector as depth.""", "VALID_BUG", "high", """REASONING: unbounded strcpy into name[16]; ASan-verified stack-buffer-overflow WRITE plus fortify SIGABRT under default hardening. Write rung verified; control rung (canary/ASLR/ROP) explicitly not claimed.""") # ---------------------------------------------------------------- lab06 int overflow -> undersized alloc cyber_add("C_lab06_int_overflow", code_user(r""" #include #include #include // Count*size product overflow: allocation small, loop writes count elements. int main(int argc, char** argv) { // count chosen so count*8 overflows 32-bit arithmetic used for the alloc unsigned long count = (argc > 1) ? strtoul(argv[1], nullptr, 10) : 0x20000001UL; size_t alloc = (size_t)(uint32_t)(count * 8); // wraps to small value printf("count=%lu alloc=%zu\n", count, alloc); unsigned long* a = (unsigned long*)malloc(alloc ? alloc : 1); for (unsigned long i = 0; i < 4; ++i) // even a few writes pass the wrapped alloc a[i] = 0x4141414141414141UL; // OOB WRITE almost immediately free(a); return 0; } """, q="Classify the fault and its primitive."), """White-Box: count*8 is computed in 64-bit, truncated to uint32_t for the allocation: count = 536870913 (0x20000001) -> 0x100000008 -> truncated to 8 -> malloc(8). The loop then performs four 8-byte stores at a[0..3]: a[0] fits, a[1..3] write 24 bytes past the chunk. This is the oss-fuzz-class workhorse: integer overflow -> undersized allocation -> heap OOB write. Verified -O0/ASan: "WRITE of size 8 ... heap-buffer-overflow". At -O1 this exact source exited cleanly - the optimizer folded the constant stores away: another proof that no-crash != safe. Gates: none at the write rung.""", """Verified (-O0, ASan): $ ./lab06_int_overflow_alloc 536870913 count=536870913 alloc=8 ==666==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000018 WRITE of size 8 at 0x502000000018 thread T0 SUMMARY: AddressSanitizer: heap-buffer-overflow lab06_int_overflow_alloc.cpp:12 in main alloc wraps to 8; a[1] is the first out-of-bounds store.""", """Root cause: width mismatch in the sizing expression (lab06:9) - 64-bit product truncated to 32 bits for malloc while the loop indexes against the untruncated count. Source -> sink: argv count -> wrapped size -> undersized chunk -> OOB stores. Reachability: one numeric input; the wrap window is any count in [2^29, 2^32/8). Primitive: heap OOB WRITE with attacker-chosen repeat count - the strongest lab primitive in this set (24 bytes past-chunk here). Chain: undersized chunk -> adjacent metadata/object corruption -> overlapping allocations -> arbitrary write -> control. Lab verified to the write rung. Missing to next rung: heap grooming, a victim object, decoupled value control. Stability: deterministic fault; exploitation allocator-dependent. Fix sketch: size_t end-to-end with __builtin_mul_overflow check before malloc; cap the input count.""", "VALID_BUG", "high", """REASONING: lab06:9-12 - 32-bit truncation of a 64-bit product sizes malloc(8) while the loop writes 32 bytes; ASan-verified heap-buffer-overflow WRITE of size 8 at a[1]. Write rung, strongest primitive; control needs grooming - stated.""") # ---------------------------------------------------------------- lab10 format string cyber_add("C_lab10_format_string", code_user(r""" #include // User-controlled string passed as the format argument. int main(int argc, char** argv) { if (argc > 1) printf(argv[1]); // FORMAT STRING BUG putchar('\n'); return 0; } """, q="Attacker controls argv[1]. What do they get, concretely?"), """White-Box: user-controlled string used as the printf format - the format string vulnerability. Verified concretely: input "LEAK:%lx.%lx.%lx.%lx" printed "LEAK:7fff4be78768.7fff4be78780.55b186d9fd40.0" - real vararg-surface values: two 0x7fff-prefixed stack addresses and a 0x55b1-prefixed PIE code address. Leak rung demonstrated: stack-ASLR and binary-base disclosure. Write rung is %n; under default glibc hardening the positional probe aborted ("*** invalid %N$ use detected ***", SIGABRT - captured), and fortify blocks %n in writable-format contexts: mitigations that raise the bar, not remove the bug. Ladder: leak (verified) -> write via %n (blocked here, not absent) -> control via ROP on the leaked base.""", """Verified (ASan build, fortify off for the leak; -Wformat-security warned at compile): $ ./lab10_format_string "LEAK:%lx.%lx.%lx.%lx" LEAK:7fff4be78768.7fff4be78780.55b186d9fd40.0 Two stack pointers and one PIE code pointer disclosed; exit 0 (pure leak, no crash). Under default glibc hardening, positional-arg probes abort: "*** invalid %N$ use detected ***" (SIGABRT) - captured as well.""", """Root cause: argv[1] passed as the format (lab10:5); the compiler warning -Wformat-security fired at build - directly greppable marker. Reachability: any path where attacker-controlled text reaches printf as format. Primitive: arbitrary vararg-surface reads (leak); via %n, writes to stack-resolved addresses (not executed - glibc fortify blocks bare %n here). Chain: leak (done) -> ASLR/PIE defeat via disclosed bases -> %n write or ROP -> control. Honest stop: leak rung verified; write rung environment-blocked, bug not absent. Missing to next rung: %n usable (older libc / other contexts), pointer-chasing formats (%s on stack slots). Stability: leaked low bits vary run to run (stack layout); fortify behavior is glibc-version dependent. Fix sketch: printf("%s", argv[1]); enable -Werror=format-security in CI; _FORTIFY_SOURCE=2 as depth.""", "VALID_BUG", "medium", """REASONING: lab10:5 printf(argv[1]) - verified pointer disclosure (stack 0x7fff... + PIE 0x55b1... printed from the vararg surface); leak rung proven, %n write rung blocked by glibc hardening in this environment - impact medium with upgrade path stated.""") # ---------------------------------------------------------------- lab09 OOB read leak cyber_add("C_lab09_oob_leak", code_user(r""" #include #include #include // Adjacent-secret layout: secret allocated after buffer; OOB read walks into it. int main() { char* buf = (char*)malloc(8); char* secret = (char*)malloc(8); strcpy(secret, "S3CRET!!"); for (int i = 8; i < 24; ++i) putchar(buf[i]); // OOB READ past buf into allocator-adjacent memory putchar('\n'); return 0; } """), """White-Box: reads buf[8..23] - 16 bytes past an 8-byte chunk - into whatever the allocator placed there. ASan proves the access is illegal ("heap-buffer-overflow READ"). The plain (no-sanitizer) build ran to completion printing layout-dependent bytes: in the captured run the adjacent memory was allocator padding/zeros, NOT the secret. Honest lesson: an OOB read is a proven illegal access; whether it DISCLOSES the neighbor depends on allocator layout (chunk order, size class, reuse). Ladder: illegal access (verified) -> disclosure (layout-bound, weak in captured run) -> targeted secret read (needs grooming).""", """Verified: $ ./lab09_oob_leak (ASan build) ==519==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000018 SUMMARY: AddressSanitizer: heap-buffer-overflow lab09_oob_leak.cpp:10 in main $ ./lab09_plain (plain -O0 build) <16 layout-dependent bytes printed; captured run: padding/zeros, no secret bytes> exit=0 Both runs are evidence: the illegal access is deterministic; disclosure strength is layout-bound.""", """Root cause: loop bound 24 vs 8-byte allocation (lab09:9-10) - adjacency assumption in the reader. Reachability: unconditional; adjacency of the secret is allocator-defined, not attacker-defined here. Primitive: heap OOB READ (16 bytes); neighbor disclosure only under favorable layout. Chain: OOB read -> leak adjacent secret -> credential/key theft. Verified: access rung; leak rung environment-dependent (captured run exposed no secret bytes - reported as-is). Missing to next rung: heap grooming (spray + ordering) to force the secret adjacent. Stability: layout varies per run; the sanitizer catch is deterministic. Fix sketch: bound reads by allocation size; explicit length fields; ownership-clean buffer APIs.""", "VALID_BUG", "medium", """REASONING: lab09:10 reads 16 bytes past an 8-byte chunk (ASan-verified heap-buffer-overflow READ); disclosure of the adjacent secret is layout-dependent and the captured plain run leaked no secret bytes - stated honestly. Impact medium (proven illegal access, conditional disclosure).""") # ---------------------------------------------------------------- lab08 null deref cyber_add("C_lab08_null_deref", code_user(r""" #include #include // Missing failure check after a lookup that returns nullptr on miss // (error path not propagated). struct Node { int v; }; static Node* lookup(int key) { return nullptr; } // simulated miss int main(int argc, char** argv) { int key = (argc > 1) ? atoi(argv[1]) : 1; Node* n = lookup(key); printf("%d\n", n->v); // NULL DEREF on miss return 0; } """, q="Security impact?"), """White-Box: lookup returns nullptr on miss; the caller dereferences without a check - null-pointer READ, SEGV. Verified: "SEGV on unknown address 0x000000000000 ... in printf" (fault while loading n->v for the argument). Class discipline: a null deref is a CRASH primitive - availability only. No read/write of attacker-chosen data, no disclosure (fault address is the null page). Not every sanitizer hit is "high": this is honest low. Ladder: crash rung; nothing beyond (the null-WRITE variant does not exist here; on modern OSes mmap_min_addr blocks null-page mapping games anyway).""", """Verified (ASan build): $ ./lab08_null_deref 7 ==515==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 SUMMARY: AddressSanitizer: SEGV ... in printf Exit 1. Deterministic: any key -> miss -> null-page read -> SIGSEGV.""", """Root cause: unchecked lookup failure (lab08:9-10); error value not propagated. Reachability: any miss key; unconditional in the lab. Primitive: crash (process availability). No corruption, no disclosure. Chain: none beyond crash. Missing to next rung: n/a - no path from a pure null read to corruption on modern OSes. Stability: deterministic. Fix sketch: check the lookup result and propagate the miss; [[nodiscard]] on lookup.""", "VALID_BUG", "low", """REASONING: lab08:10 dereferences the nullptr from a missed lookup - ASan-verified SEGV at the null page; availability-only crash primitive, no corruption/disclosure -> impact low, the honest bottom of the VALID ladder.""") # ---------------------------------------------------------------- lab07 type confusion cyber_add("C_lab07_type_confusion", code_user(r""" #include #include // Tag-less variant struct: producer writes one layout, consumer reads another. struct MsgA { uint8_t kind; int32_t value; }; struct MsgB { uint8_t kind; void* callback; }; static void consume(void* raw) { MsgB* b = (MsgB*)raw; // consumer assumes B printf("callback ptr = %p\n", (void*)b->callback); // reads MsgA::value as pointer } int main() { MsgA a{}; a.kind = 1; a.value = 0x41414141; consume(&a); // confusion: A memory read as B return 0; } """), """White-Box: no runtime type tag - producer writes MsgA, consumer reinterprets as MsgB. Two verified observations: (1) ASan flagged "stack-buffer-overflow ... in consume" for this exact source - the reinterpretation read walked past the MsgA object in this build's layout, i.e. the confusion produced a real out-of-bounds access. (2) The printed value was the MsgA::value bytes (0x41414141 family) interpreted as a pointer - misinterpretation demonstrated on the semantic layer too. Ladder: confusion -> forged-pointer read (print sink here) -> if the confused field were CALLED or WRITTEN: indirect-call/write primitive = control rung. Honest stop: this lab only reads the confused pointer; no indirect call exists.""", """Verified (ASan build): $ ./lab07_type_confusion ==511==ERROR: AddressSanitizer: stack-buffer-overflow on address ... SUMMARY: AddressSanitizer: stack-buffer-overflow lab07_type_confusion.cpp:9 in consume The reinterpretation read past the MsgA stack object in this layout; the printed "callback pointer" carries the A::value bytes - confusion proven on both layers.""", """Root cause: tag-less variant handling with a C-style cast asserting a layout the producer never guaranteed (lab07:7-9). Reachability: any path routing a MsgA into consume. Primitive: memory reinterpretation - forged pointer constructed from non-pointer data (read demonstrated); call/write primitives only if a sink invokes or stores the confused field. Chain: confusion -> forged pointer -> (call sink) indirect branch control or (write sink) arbitrary write. Lab: read/print sink only. Missing to next rung: a call or assignment through b->callback. Stability: deterministic confusion; the OOB aspect is layout-dependent (the ASan catch itself is layout evidence). Fix sketch: tagged dispatch on kind with runtime validation, std::variant, or separate typed queues; never reinterpret variant payloads.""", "VALID_BUG", "medium", """REASONING: lab07:9 reinterprets MsgA as MsgB without a tag - ASan-verified OOB read in consume plus a printed forged pointer (A::value as callback). Read-rung confusion; call/write sink absent -> medium with upgrade path named.""") # ---------------------------------------------------------------- lab01 OOB read (trusted length field) cyber_add("C_lab01_oob_read", code_user(r""" #include #include #include #include // Mirrors the classic ARVO shape: length field trusted for a copy/read // boundary without re-validating against the allocation. struct Record { uint32_t len; char data[]; }; int main(int argc, char** argv) { size_t alloc_len = 8; Record* r = (Record*)malloc(sizeof(Record) + alloc_len); memset(r->data, 'A', alloc_len); uint32_t reported = (argc > 1) ? (uint32_t)strtoul(argv[1], nullptr, 10) : alloc_len; unsigned long sum = 0; for (uint32_t i = 0; i < reported; ++i) sum += (unsigned char)r->data[i]; // OOB READ when reported > alloc_len printf("sum=%lu\n", sum); free(r); return 0; } """, q="Trigger + verdict?"), """White-Box: the flexible-array allocation covers 8 payload bytes; the loop bound comes from a length FIELD (argv-simulated wire value) never re-validated against the allocation - the classic trusted-length OOB read of parser CVEs. reported=64 walks 56 bytes past the chunk. Verified: "heap-buffer-overflow READ ... lab01_oob_read.cpp:18 in main". Primitive: OOB read with attacker-chosen range - a disclosure engine when neighbors hold secrets; the accumulator form is a byte-sum oracle, direct-copy variants exfiltrate outright. Ladder: read rung verified; control rung not applicable to a pure read.""", """Verified (ASan): $ ./lab01_oob_read 64 SUMMARY: AddressSanitizer: heap-buffer-overflow lab01_oob_read.cpp:18 in main Exit 1. Any reported > 8 walks out of the 8-byte payload region.""", """Root cause: loop bound from wire data not cross-checked against the allocation extent (lab01:17-18). Reachability: one field value in the input; unconditional once parsed. Primitive: heap OOB READ, attacker-ranged; accumulator = side-channel oracle, copy form = direct disclosure. Chain: read -> adjacent-memory disclosure -> key/pointer theft. Lab: access rung verified; disclosure depends on neighbors (layout honesty applies). Missing to next rung: adjacent secret + direct-copy sink. Stability: deterministic access; content layout-dependent. Fix sketch: validate reported <= alloc_len at the parse boundary; span types carrying lengths.""", "VALID_BUG", "medium", """REASONING: lab01:18 trusts a wire length field over an 8-byte allocation - ASan-verified heap-buffer-overflow READ; attacker-ranged disclosure primitive (oracle form here). Read rung; impact medium.""") # ---------------------------------------------------------------- Python labs (all marker-verified) PY_LABS = [ ("C_labp1_pickle", r""" # vulnerable service: deserializes a "session blob" from an untrusted source import pickle, base64, sys blob = sys.argv[1] if len(sys.argv) > 1 else "" data = pickle.loads(base64.b64decode(blob)) # UNSAFE print("restored:", data) """, """White-Box: pickle.loads on base64 from argv - an untrusted code-carrying serialization format executed during deserialization. The exploit payload uses __reduce__ so unpickling calls os.system. Verified end-to-end: the marker file /tmp/pwned_pickle existed after loading the crafted blob - arbitrary command execution at load time, before any application logic runs. Ladder: full chain to code execution (top rung within the process). Gates: none in pure Python; restricted-Unpickler allowlists exist but are not used here.""", """Verified (Python 3.12): $ python3 labp1_pickle_make.py > b64 # Exploit.__reduce__ -> (os.system, ("touch /tmp/pwned_pickle",)) $ python3 labp1_pickle.py "$b64" restored: 0 $ ls /tmp/pwned_pickle /tmp/pwned_pickle # MARKER PRESENT - execution proven""", """Root cause: pickle (code-carrying format) applied to untrusted input (labp1:5). Reachability: any input path reaching loads. Primitive: arbitrary code execution at deserialize time (os.system shown; __reduce__/GLOBAL give full expression power). Chain: complete - blob delivery -> unpickle executes attacker Python -> process-user permissions. Missing to next rung: nothing within the process. Stability: deterministic; no ASLR/race involvement. Fix sketch: JSON for untrusted data; if pickle unavoidable, restricted Unpickler with explicit allowlist - and even then treat as hardening, not a boundary.""", "VALID_BUG", "high", """REASONING: labp1:5 pickle.loads on untrusted base64 - marker-verified os.system execution on unpickle (full in-process RCE chain); impact high."""), ("C_labp2_yaml", r""" # vulnerable config loader import yaml, sys cfg = yaml.load(sys.argv[1] if len(sys.argv) > 1 else "{}", Loader=yaml.Loader) # Loader = unsafe print("config:", cfg) """, """White-Box: yaml.Loader (not SafeLoader) enables python/object tags - the loader CONSTRUCTS arbitrary Python objects from tags like !!python/object/apply:. Verified: '!!python/object/apply:os.system ["touch /tmp/pwned_yaml"]' executed os.system during load; marker present after the run. Ladder: complete code-execution chain at parse time. Gates: none in this configuration; SafeLoader/CSafeLoader is the documented boundary this code fails to use.""", """Verified (Python 3.12, PyYAML): $ python3 labp2_yaml.py '!!python/object/apply:os.system ["touch /tmp/pwned_yaml"]' config: 0 $ ls /tmp/pwned_yaml /tmp/pwned_yaml # MARKER PRESENT""", """Root cause: unsafe Loader selection (labp2:4) enabling object-construction tags. Reachability: any untrusted string reaching yaml.load. Primitive: arbitrary object construction -> direct code execution. Chain: complete (parse-time RCE). Missing to next rung: none in-process. Stability: deterministic. Fix sketch: yaml.safe_load / CSafeLoader unconditionally for external input; CI grep for bare Loader usage.""", "VALID_BUG", "high", """REASONING: labp2:4 uses yaml.Loader on untrusted input - marker-verified os.system execution via !!python/object/apply tag; parse-time RCE, impact high."""), ("C_labp3_inject", r""" # vulnerable ping utility import subprocess, sys host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" out = subprocess.run(f"ping -c 1 {host}", shell=True, capture_output=True, text=True) print(out.stdout[:200]) """, """White-Box: user string interpolated into a shell command line with shell=True - the injection surface is every shell metacharacter (;, &&, backticks, $()). Verified: host = '127.0.0.1; touch /tmp/pwned_inject' executed the injected command; marker present. Ladder: complete - arbitrary command execution with the process's privileges (a network-facing variant is pre-auth RCE if reachable before auth). Gates: none; quoting/escaping would be the defense and none is applied.""", """Verified (Python 3.12): $ python3 labp3_shell_injection.py '127.0.0.1; touch /tmp/pwned_inject' ... ping output ... $ ls /tmp/pwned_inject /tmp/pwned_inject # MARKER PRESENT""", """Root cause: string-built command + shell=True (labp3:4). Reachability: every caller-controllable fragment of the interpolated string. Primitive: arbitrary command execution (second command after ';'). Chain: complete (service-privilege command execution). Missing to next rung: none in-process. Stability: deterministic. Fix sketch: argument-list form (["ping", "-c", "1", host], shell=False) - boundaries become structural; input validation as depth only.""", "VALID_BUG", "high", """REASONING: labp3:4 f-string into shell=True - marker-verified command injection via ';' metacharacter; RCE with process privileges, impact high."""), ("C_labp4_eval", r""" # vulnerable calculator service import sys expr = sys.argv[1] if len(sys.argv) > 1 else "1+1" print("result:", eval(expr)) # EVAL on raw input """, """White-Box: eval compiles and executes attacker source in the process namespace - CPython eval has no sandbox. Verified: expr "__import__('os').system('touch /tmp/pwned_eval')" executed at eval time; marker present. The only limits are the expression grammar - defeat via __import__, comprehensions, lambda chains. Ladder: complete code execution. Gates: none; the language's documented posture is that in-process sandboxes do not hold.""", """Verified (Python 3.12): $ python3 labp4_eval.py "__import__('os').system('touch /tmp/pwned_eval')" result: 0 $ ls /tmp/pwned_eval /tmp/pwned_eval # MARKER PRESENT""", """Root cause: eval on raw input (labp4:4). Reachability: the input path itself. Primitive: arbitrary code execution (os.system shown). Chain: complete. Missing to next rung: none in-process. Stability: deterministic. Fix sketch: parsed evaluators only (ast.literal_eval for literals; domain parsers for arithmetic); eval NEVER on external input.""", "VALID_BUG", "high", """REASONING: labp4:4 eval on argv - marker-verified os.system execution via __import__; in-process RCE, impact high."""), ] for sid, src, think, trigger, writeup, verdict, impact, reasoning in PY_LABS: cyber_add(sid, code_user(src, lang="python", q="Analyze this Python service for exploitable faults. Trigger + writeup + verdict."), think, trigger, writeup, verdict, impact, reasoning)