TJCTF 2023: shelly
sally sells seashells by the seashore sally sells seashells by the seashore sally sells seashells by the seashore sally sells seashells by the seashore sally sells seashells by the seashore
TL;DR
Classic ret2shellcode with bad bytes. Alternatively, with NX disabled, it is also possible to Bring Your Own ROP gadget to perform ret2libc attack.
Basic File Checks
At first glance, running the binary returns an unknown address that randomizes on each execution.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./shelly
0x7ffcdb8739e0
test
ok
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./shelly
0x7fffa170a2b0
test
okFurther enumeration shows that NX is disabled, meaning we can inject any arbitrary shellcode.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ checksec --file=shelly
[*] '/home/kali/Desktop/CTF/TJCTF2023/pwn: shelly/shelly'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segmentsStatic Code Analysis
Turns out, the address returned is the address of buffer local_108 allocated on runtime. This is an obvious ret2shellcode attack simply due to 3 conditions:
NX Disabled: Our input stored in the .data section is marked as an executable region. This allows us to jump to the custom shellcode stored on the stack or in a global variable.
Address of Buffer: The base address of our input/buffer is a known value, which allows us to overwrite the Instruction Pointer with it.
Buffer Overflow:
fgetstakes in 512 (0x200) characters from stdin, which overflows the buffer[256], meaning an attacker can control the execution flow of the program via the EIP.
The twists here is that, before executing the shellcode on the buffer, it will check for 2 specific bad bytes: \x0f & \x05 and exit immediately if they are present.
Finding Offset
Using de Brujin Sequence and pwndbg to locate the offset.
Generating Shellcode
Using msfvenom, we can specify these bad bytes to exclude them from our shellcode.
ret2shellcode
With everything set, overflow the buffer to divert execution flow so that it points to the start of our buffer and execute the injected shellcode.
Alternative: ret2libc
Another clever solution is to ROP around the limitations of bad bytes in our shellcode, which leads to a ret2libc attack. However, this attack would be harder without the pop rdi gadget.
Since NX is disabled, we can Bring Our Own Gadget to make up for the lack of gadgets.
Just like a classic ret2libc attack, we will exploit the buffer overflow vulnerability once to leak puts address, then ROP a second time to pop a shell. The first ROP chain looks something like this:
Inject
pop rdigadget to the start of buffer.Fill remaining space with junk, so that it fills up the entire 264 buffer.
Point EIP to the base address of the buffer to empty out the
rdiregister.Supply the argument for
puts.Invoke
putsfunction to leakputsaddress.Return to main for second ROP chain.
For the exploit to work remotely, we need to find out the LIBC version of the remote server.

Download and update the correct LIBC library, and perform a second ROP chain to pop a shell.
For some reason, explicit declaration of the binary architecture and context is required in this challenge for the solution to work properly. It came as a surprise because pwntools usually deals with it for us automatically.
Flag: tjctf{s4lly_s3lls_s34sh3lls_50973fce}
Last updated