REVERSE
R0ll
import struct
from unicorn import *
from unicorn.x86_const import *
import pefile
BINARY = 'R0ll.exe'
CRYPT_FUNC = {
'start': 0x10E0,
'end': 0x1540
}
FLAG = {
'prefix': b'FlagY{',
'suffix': b'}',
'range': b'0123456789abcdef'
}
FLAG_LEN = 39
KEY = b'fbec495785a8bcf346b'
KEY_LEN = len(KEY)
class DumbEmu(object):
def __init__(self, file):
self.executable = pefile.PE(file)
# PE image configuration
self.image = {
'base': self.executable.OPTIONAL_HEADER.ImageBase,
'size': ((self.executable.OPTIONAL_HEADER.SizeOfImage + 0x1000 - 1) // 0x1000) * 0x1000
}
# Memory regions configuration
self.memory = {
'pool': 0x10000000,
'size': 0x100000,
'original': 0x10000000
}
# Stack configuration
self.stack = {
'base': 0x20000000,
'size': 0x20000,
'top': 0x20000000 + 0x20000 - 8
}
# Unicorn engine setup
self.mu = Uc(UC_ARCH_X86, UC_MODE_64)
# Map PE image
self.mu.mem_map(self.image['base'], self.image['size'])
self.mu.mem_write(self.image['base'], self.executable.get_memory_mapped_image())
# Setup stack
self.mu.mem_map(self.stack['base'], self.stack['size'])
self.mu.reg_write(UC_X86_REG_RSP, self.stack['top'])
# Setup heap
self.mu.mem_map(self.memory['pool'], self.memory['size'])
# Hooks configuration
self.hooks = {
'mem': None,
'code': None
}
# Add memory hook
def hook_mem(uc, access, address, size, value, user_data):
if access == UC_MEM_WRITE_UNMAPPED:
uc.mem_map(address & ~0xfff, 0x1000)
return True
self.hooks['mem'] = self.mu.hook_add(UC_HOOK_MEM_WRITE_UNMAPPED, hook_mem)
def malloc(self, size):
size = (size + 15) & ~15
addr = self.memory['pool']
self.memory['pool'] += size
# Auto-expand heap if needed
if self.memory['pool'] > 0x10000000 + self.memory['size']:
needed = self.memory['pool'] - (0x10000000 + self.memory['size'])
self.mu.mem_map(0x10000000 + self.memory['size'], needed + 0x1000)
self.memory['size'] += needed + 0x1000
return addr
def reset(self):
# Reset memory configuration
self.memory.update({
'pool': self.memory['original'],
'size': 0x100000
})
# Reset stack pointer
self.mu.reg_write(UC_X86_REG_RSP, self.stack['top'])
# Clear general registers
registers = {
UC_X86_REG_RAX: 0,
UC_X86_REG_RBX: 0,
UC_X86_REG_RCX: 0,
UC_X86_REG_RDX: 0,
UC_X86_REG_RSI: 0,
UC_X86_REG_RDI: 0,
UC_X86_REG_R8: 0,
UC_X86_REG_R9: 0,
UC_X86_REG_R10: 0,
UC_X86_REG_R11: 0,
UC_X86_REG_R12: 0,
UC_X86_REG_R13: 0,
UC_X86_REG_R14: 0,
UC_X86_REG_R15: 0
}
for reg, value in registers.items():
self.mu.reg_write(reg, value)
def write(self, address, data):
self.mu.mem_write(address, data.ljust((len(data)+15)&~15, b'\x00'))
def args(self, rcx, rdx, r8, r9):
self.mu.reg_write(UC_X86_REG_RCX, rcx)
self.mu.reg_write(UC_X86_REG_RDX, rdx)
self.mu.reg_write(UC_X86_REG_R8, r8)
self.mu.reg_write(UC_X86_REG_R9, r9)
# Stack Ignored
def call(self, start, end):
ret_addr = self.image['base'] + 0xffffff
self.mu.mem_write(self.mu.reg_read(UC_X86_REG_RSP), struct.pack("<Q", ret_addr))
try:
self.mu.emu_start(self.image['base'] + start, self.image['base'] + end)
except:
pass
def reg(self, name):
return self.mu.reg_read(name)
if __name__ == "__main__":
emu = DumbEmu(BINARY)
buffers = {
'key': emu.malloc(KEY_LEN + 1),
'flag': emu.malloc(FLAG_LEN + 1)
}
# Permanent allocations
emu.write(buffers['key'], KEY)
while len(FLAG['prefix']) < FLAG_LEN - 1:
for c in FLAG['range']:
emu.reset()
# Rebuild test flag
flag = FLAG['prefix'] + bytes([c])
flag = flag.ljust(FLAG_LEN, b'X') + FLAG['suffix']
# Write the flag and call func
emu.write(buffers['flag'], flag)
emu.args(buffers['flag'], buffers['key'], 0, KEY_LEN)
emu.call(CRYPT_FUNC['start'], CRYPT_FUNC['end'])
if emu.reg(UC_X86_REG_R9) > len(FLAG['prefix']):
# The instruction at 0x140001514 (inc r9) is executed for each correct character
# to verify whether the 39-byte input flag is correct or is not correct.
FLAG['prefix'] += bytes([c])
print(f"[+] Current Flag : {FLAG['prefix'].decode()}")
if emu.reg(UC_X86_REG_RAX) == 1:
# On return if RAX == 1 (return value) is 1 that means (39-byte input) the input is correct
break
break
print(f"[+] Final Flag: {FLAG['prefix'].decode()}}}")
Last updated
Was this helpful?