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