Polyshell Programming -- Midnightsun 2019 Quals CTF
Points: 482
Solves: 22
Description:
You might be cool, but are you 5 popped shells cool?
flag format: midnight{FLAG}
Given:
settings Service: nc polyshell-01.play.midnightsunctf.se 30000
Solution
(by dorin)
❯ nc polyshell-01.play.midnightsunctf.se 30000
Welcome to the polyglot challenge!
Your task is to create a shellcode that can run on the following architectures:
x86
x86-64
ARM
ARM64
MIPS-LE
The shellcode must run within 1 second(s) and may run for at most 100000 cycles.
The code must perform a syscall on each platform according to the following parameters:
Syscall number: 237
Argument 1: 38085
Argument 2: A pointer to the string "great"
You submit your code as a hex encoded string of max 4096 characters (2048 bytes)
Generating the shellcode for each architecture is easy enough using pwntools and its shellcraft module:
arch_asm = shellcraft.arch.pushstr(string) + shellcraft.arch.linux.syscall(syscall, num, sp)
Now all we need is a polyglot shellcode that branches each architecture’s execution to its specific payload.
Polyglot shellcode
The first step I took was doing it for the two architectures that I’m more familiar with (x86 and x86_64).
This Stack Overflow question pointed me in the right direction for that. 0x40
is inc eax
for x86_32 but for x86_64 it’s just a useless REX
prefix that is ignored. Therefore,
_x86:
xor eax, eax
db 0x40
nop
jz _x86_64
will have a different outcome on x86 and x86_64.
After having done this, my teammate pointed me to this github repo: xarch_shellcode.
This program allows to build portable, architecture independant shellcode from C code. It currently supports the following architectures: x86, x86_64, arm, arm_64.
Hey! Thats 80% of our work done for us, lets see the shellcode that combines all 4 architectures.
; ARCH DISASSEMBLY - DESCRIPTION
; arm andlo r0, r0, #0xeb000 - harmless AND
; arm64 orr w11, w23, #7 - harmless inclusive OR (immediate)
; x86 jmp $+0xa / junk - jump to x86/x86_64 polyglot above
; x86_64 jmp $+0xa / junk - jump to x86/x86_64 polyglot above
db 0xeb, (_x86 - $ - 2), 0x00, 0x32
; arm b _arm ($+0x10) - branch to arm shellcode
; arm64 ands x1, x0, x0 - harmless AND (immediate)
db ((_arm - $ - 8) / 4) % 0x100, ((_arm - $ - 8) / 4) / 0x100, 0x00, 0xea
; arm64 b _arm64 ($+0x14) - branch to arm64 shellcode
db ((_arm64 - $) / 4) % 0x100, ((_arm64 - $) / 4) / 0x100, 0x00, 0x14
Now I just have to add MIPS to this!
This is what I came up with in the end:
template.asm
; 2016 - ixty
; 2019 - STT
; multi-arch template
; works on:
; x86
; x86_64
; arm
; arm_64
; mips
; compile with nasm
bits 32
_start:
; ======================================================================= ;
; init, polyglot shellcode for arm, arm64, x86, x86_64, mips
; branches out to specific arch dependent payloads
; ======================================================================= ;
; ARCH DISASSEMBLY - DESCRIPTION
; mips beq zero, a0, 0x14 - branch if 0==0, hardcoded offset because i'm lazy
; arm andne r0, r4, r4 - harmless
; arm64 adr x4, 0x8000 - apparently fine?
; x86 add al, 0x0 - simple adds
; add al, 0x10
; x86_64 add al, 0x0 - simple adds
; add al, 0x10
db 0x04, 0x00, 0x04, 0x10
; arm andlo r0, r0, #0xeb000
; arm64 orr w11, w23, #7
; x86 jmp $+0xa / junk
; x86_64 jmp $+0xa / junk
db 0xeb, (_x86 - $ - 2), 0x32, 0x32
; arm b _arm ($+0x10)
; arm64 ands x1, x0, x0
db ((_arm - $ - 8) / 4), 0x00, 0x00, 0xea
; arm64 b _arm64 ($+0x14)
db ((_arm64 - $) / 4), 0x00, 0x00, 0x14
; ======================================================================= ;
; MIPS PAYLOAD
; ======================================================================= ;
times (4 - (($ - _start) % 4)) nop ; must be 4b aligned
_mips:
db {}
; ======================================================================= ;
; x86 only, detect 32/64 bits
; ======================================================================= ;
_x86:
; x86 xor eax, eax;
; x86_64 xor eax, eax;
xor eax, eax
; x86 inc eax
; x86_64 REX + nop
db 0x40
nop
jz _x86_64
; ======================================================================= ;
; OTHER PAYLOADs
; ======================================================================= ;
_x86_32:
db {}
_x86_64:
db {}
times (4 - (($ - _start) % 4)) nop ; must be 4b aligned
_arm:
db {}
times (4 - (($ - _start) % 4)) nop ; must be 4b aligned
_arm64:
db {}
Finally, I can run my script:
from pwn import *
import subprocess
r = remote("polyshell-01.play.midnightsunctf.se", 30000)
r.recvuntil("Syscall number: ")
syscall = r.recvuntil("\n")
r.recvuntil("Argument 1: ")
num = r.recvuntil("\n")
r.recvuntil("Argument 2: ")
string = r.recvuntil("\n")
syscall = int(syscall.decode("utf-8"))
num = int(num.decode("utf-8"))
string = string.decode("utf-8").split()[-1].strip('"')
context.bits = 32
context.arch = 'amd64'
i386_asm = shellcraft.i386.pushstr(string) + shellcraft.i386.linux.syscall(syscall, num, 'esp')
i386_bytes = asm(i386_asm)
context.bits = 64
context.arch = 'amd64'
amd64_asm = shellcraft.amd64.pushstr(string) + shellcraft.amd64.linux.syscall(syscall, num, 'rsp')
amd64_bytes = asm(amd64_asm)
context.arch = 'arm'
arm_asm = shellcraft.arm.pushstr(string) + shellcraft.arm.linux.syscall(syscall, num, 'sp')
arm_bytes = asm(arm_asm)
context.arch = 'aarch64'
arm64_asm = shellcraft.aarch64.pushstr(string) + shellcraft.aarch64.linux.syscall(syscall, num, 'sp')
arm64_bytes = asm(arm64_asm)
context.arch = 'mips'
mips_asm = shellcraft.mips.pushstr(string) + shellcraft.mips.linux.syscall(syscall, num, '$sp')
mips_bytes = asm(mips_asm)
with open("template.asm") as f:
asm_template = f.read()
asm_final = asm_template.format(", ".join(map(hex, mips_bytes)),
", ".join(map(hex, i386_bytes)),
", ".join(map(hex, amd64_bytes)),
", ".join(map(hex, arm_bytes)),
", ".join(map(hex, arm64_bytes)))
with open("gen.asm", "w") as f:
f.write(asm_final)
subprocess.call(["nasm", "gen.asm"])
with open("gen", "rb") as f:
payload = f.read()
print(syscall, num, string)
r.recvuntil("Your shellcode:")
r.sendline(enhex(payload))
r.interactive()
To get the flag:
[+] Opening connection to polyshell-01.play.midnightsunctf.se on port 30000: Done
105 44748 guess
[*] Switching to interactive mode
Results:
x86: Success
x86-64: Success
ARM: Success
ARM64: Success
MIPS: Success
Congratulations! Here is your flag: midnight{Its_shellz_all_the_w4y_d0wn}
[*] Got EOF while reading in interactive