Skip to content

Commit 3ea42b8

Browse files
committed
Address challenge feedback from Discord
1 parent 5d6a6d7 commit 3ea42b8

14 files changed

Lines changed: 81 additions & 25 deletions

File tree

challenges/computing-101/DESCRIPTION.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ If you have questions, comments, feedback, and so on, join us on [the Discord ch
1515
**NOTE:**
1616
If you are looking for the old Assembly Crash Course, it has [been archived](https://pwn.college/archive/assembly-crash-course).
1717
Computing 101 is the current path through that material, with the concepts split across the modules below.
18+
19+
**NOTE:**
20+
Computing 101 is foundational welcome material, not a belt checkpoint.
21+
Later belt dojos assume these machine-code and assembly skills, so learners with prior experience can skip around, and this is the place to return when those assumptions feel shaky.

challenges/computing-101/building-a-web-server/common/run.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ def retry_session():
317317
return session
318318

319319

320+
def request_failure(method, exc):
321+
return f"{method}: Failed to connect ({type(exc).__name__}: {exc})"
322+
323+
320324
def random_data():
321325
return ''.join(random.choices(string.ascii_letters + string.digits,
322326
k=random.randrange(32, 256))).encode()
@@ -334,8 +338,8 @@ def validate_connect():
334338
session = retry_session()
335339
try:
336340
session.get("http://localhost", timeout=1)
337-
except requests.exceptions.ConnectionError:
338-
return "Connect: Failed to connect"
341+
except requests.exceptions.RequestException as e:
342+
return request_failure("Connect", e)
339343

340344

341345
def validate_get(data=None):
@@ -347,8 +351,8 @@ def validate_get(data=None):
347351
f.flush()
348352
try:
349353
response = session.get("http://localhost" + f.name, timeout=1)
350-
except requests.exceptions.ConnectionError:
351-
return "GET: Failed to connect"
354+
except requests.exceptions.RequestException as e:
355+
return request_failure("GET", e)
352356
if response.text.encode() != data:
353357
return "GET: File contents not correct"
354358

@@ -361,8 +365,8 @@ def validate_post(data=None):
361365
pass
362366
try:
363367
session.post("http://localhost" + f.name, data=data, timeout=1)
364-
except requests.exceptions.ConnectionError:
365-
return "POST: Failed to connect"
368+
except requests.exceptions.RequestException as e:
369+
return request_failure("POST", e)
366370
try:
367371
if open(f.name, "rb").read() != data:
368372
return "POST: File contents not correct"
@@ -461,19 +465,30 @@ def target():
461465
"-e", "inject=brk:signal=SIGKILL",
462466
results_dir=results_dir,
463467
timeout=timeout) as results:
464-
for operation in operations:
465-
print(f"Performing operation: {operation_names[operation]}")
468+
for operation_index, operation in enumerate(operations, 1):
469+
operation_name = operation_names[operation]
470+
operation_label = f"operation {operation_index}/{len(operations)} ({operation_name})"
471+
print(f"Performing {operation_label}")
466472
try:
467473
error = operation()
468474
if error:
469-
errors.append(error)
475+
errors.append(f"{operation_label}: {error}")
470476
except Exception as e:
471-
errors.append(f"Exception: {str(e)}")
477+
errors.append(f"{operation_label}: Exception ({type(e).__name__}: {e})")
472478
print()
473479

474480
strace_errors = validate_strace(level, results, requirements)
475481
errors.extend(strace_errors)
476482

483+
request_operations = [op for op in operations if op in (validate_get, validate_post)]
484+
connection_failures = [e for e in errors if "Failed to connect" in e]
485+
if level >= 9 and request_operations and len(connection_failures) >= len(request_operations):
486+
errors.append(
487+
"Every checked HTTP request failed to connect. "
488+
"For the concurrent levels, the parent should keep accepting after fork, "
489+
"while each child handles one accepted client and exits."
490+
)
491+
477492
print("===== Result =====")
478493
if not errors:
479494
print("[✓] Success")

challenges/computing-101/common/builder

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
os.environ["PATH"] = "/challenge/bin:/bin:/usr/bin:/usr/local/bin"
55

6+
import logging
67
import pwnlib.context
78
import pwnlib.asm
89
import pwnlib.elf
@@ -14,6 +15,7 @@ import magic
1415
import sys
1516

1617
pwnlib.context.context.arch = "amd64"
18+
logging.getLogger("pwnlib").setLevel(logging.ERROR)
1719
cs = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
1820

1921

challenges/computing-101/control-flow/switch/DESCRIPTION.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ In this way, the program implements conditional logic _without any conditional c
2727
This challenge (at `/challenge/reverse-me`) has 256 possible cases, with only one of them (corresponding to an alphanumeric character) being different than the others.
2828
Look at the jump table (you'll have to look at a lot of entries...), look at the program to understand how to influence the index, and get the flag!
2929

30+
`/challenge/reverse-me` is generated without source-level debug information, so gdb may say that it has no debugging symbols.
31+
That is expected: use gdb for disassembly and memory inspection, not for source-level debugging.
32+
As in the previous challenge, use gdb to understand the binary, then run `/challenge/reverse-me` directly with the recovered byte to get the flag.
33+
3034
----
3135
**NOTE:**
3236
Though you should look at the disassembly using `objdump -d -M intel /challenge/reverse-me`, objdump will try to interpret the jump table data as assembly instructions, which will result in garbage.
@@ -70,4 +74,3 @@ The output will be long, but it starts like this:
7074
The number after `x/` is how many entries gdb should print.
7175
Since the input byte chooses one of 256 entries, and each entry is one 8-byte code address, this lets you scan the table for the one address that differs.
7276
If gdb prints multiple entries on one line, the address on the left is the first entry on that line; the next entry is 8 bytes later.
73-
As in the previous challenge, use gdb to understand the binary, then run `/challenge/reverse-me` directly with the recovered byte to get the flag.

challenges/computing-101/numbers-as-strings/itoa-digit/challenge/.py/chal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def run_one(so_path, value, *, quiet):
1414
p = subprocess.run(
1515
["/challenge/harness", so_path, str(value)],
1616
stdout=subprocess.PIPE,
17-
stderr=(subprocess.DEVNULL if quiet else None),
17+
stderr=subprocess.PIPE,
1818
timeout=5,
1919
)
2020
except subprocess.TimeoutExpired:
@@ -23,7 +23,9 @@ def run_one(so_path, value, *, quiet):
2323
"A function has to reach a `ret`; an accidental loop with no way out spins forever."
2424
)
2525
if p.returncode != 0:
26-
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.")
26+
stderr = p.stderr.decode("utf-8", errors="replace").strip()
27+
details = f"\n\nHarness stderr:\n{stderr}" if stderr else ""
28+
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.{details}")
2729
if len(p.stdout) < 8:
2830
raise AssertionError("The harness never reported a result --- did your itoa_digit crash?")
2931
return int.from_bytes(p.stdout[-8:], "little")

challenges/computing-101/numbers-as-strings/itoa-minimal/challenge/.py/chal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def run_one(so_path, value, *, quiet):
1515
p = subprocess.run(
1616
["/challenge/harness", so_path, str(value)],
1717
stdout=subprocess.PIPE,
18-
stderr=(subprocess.DEVNULL if quiet else None),
18+
stderr=subprocess.PIPE,
1919
timeout=5,
2020
)
2121
except subprocess.TimeoutExpired:
@@ -24,7 +24,9 @@ def run_one(so_path, value, *, quiet):
2424
"A function has to reach a `ret`; an accidental loop with no way out spins forever."
2525
)
2626
if p.returncode != 0:
27-
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.")
27+
stderr = p.stderr.decode("utf-8", errors="replace").strip()
28+
details = f"\n\nHarness stderr:\n{stderr}" if stderr else ""
29+
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.{details}")
2830
return p.stdout
2931

3032

challenges/computing-101/numbers-as-strings/itoa-negative/challenge/.py/chal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def run_one(so_path, value, *, quiet):
1818
p = subprocess.run(
1919
["/challenge/harness", so_path, str(value)],
2020
stdout=subprocess.PIPE,
21-
stderr=(subprocess.DEVNULL if quiet else None),
21+
stderr=subprocess.PIPE,
2222
timeout=5,
2323
)
2424
except subprocess.TimeoutExpired:
@@ -27,7 +27,9 @@ def run_one(so_path, value, *, quiet):
2727
"If the divide loop doesn't shrink the value toward 0 each pass, it spins forever."
2828
)
2929
if p.returncode != 0:
30-
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.")
30+
stderr = p.stderr.decode("utf-8", errors="replace").strip()
31+
details = f"\n\nHarness stderr:\n{stderr}" if stderr else ""
32+
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.{details}")
3133
return p.stdout
3234

3335

challenges/computing-101/numbers-as-strings/itoa-two-digits/challenge/.py/chal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def run_one(so_path, value, *, quiet):
1515
p = subprocess.run(
1616
["/challenge/harness", so_path, str(value)],
1717
stdout=subprocess.PIPE,
18-
stderr=(subprocess.DEVNULL if quiet else None),
18+
stderr=subprocess.PIPE,
1919
timeout=5,
2020
)
2121
except subprocess.TimeoutExpired:
@@ -24,7 +24,9 @@ def run_one(so_path, value, *, quiet):
2424
"A function has to reach a `ret`; an accidental loop with no way out spins forever."
2525
)
2626
if p.returncode != 0:
27-
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.")
27+
stderr = p.stderr.decode("utf-8", errors="replace").strip()
28+
details = f"\n\nHarness stderr:\n{stderr}" if stderr else ""
29+
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.{details}")
2830
return p.stdout
2931

3032

challenges/computing-101/numbers-as-strings/itoa/challenge/.py/chal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def run_one(so_path, value, *, quiet):
1515
p = subprocess.run(
1616
["/challenge/harness", so_path, str(value)],
1717
stdout=subprocess.PIPE,
18-
stderr=(subprocess.DEVNULL if quiet else None),
18+
stderr=subprocess.PIPE,
1919
timeout=5,
2020
)
2121
except subprocess.TimeoutExpired:
@@ -24,7 +24,9 @@ def run_one(so_path, value, *, quiet):
2424
"If the divide loop doesn't shrink the value toward 0 each pass, it spins forever."
2525
)
2626
if p.returncode != 0:
27-
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.")
27+
stderr = p.stderr.decode("utf-8", errors="replace").strip()
28+
details = f"\n\nHarness stderr:\n{stderr}" if stderr else ""
29+
raise AssertionError(f"The harness exited abnormally (status {p.returncode}) on value {value}.{details}")
2830
return p.stdout
2931

3032

challenges/computing-101/the-stack-revisited/mem-envp/DESCRIPTION.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ Two new things to notice:
3939

4040
In this challenge, we will set the `FLAG` environment variable to the actual flag and run your program with no arguments and no other env vars.
4141
That means `[rsp+24]` will hold a pointer to the `FLAG=...` string, and you can get the flag by `write()`ing it out!
42+
43+
This is a whole-program level, so submit an executable, not a shared library.
44+
Assemble and link your program, then pass that executable to the checker:
45+
46+
```console
47+
hacker@dojo:~$ as -o envp.o envp.s
48+
hacker@dojo:~$ ld -o envp envp.o
49+
hacker@dojo:~$ /challenge/check envp
50+
```

0 commit comments

Comments
 (0)