I would assemble the code and then analyze it using emulation.
Example assembly taken from the link:
mov rax, QWORD PTR [rbp-16] ; Move i (=9) to RAX
movabs rdx, -3689348814741910323 ; Move some magic number to RDX (?)
mul rdx ; Multiply 9 by magic number
mov rax, rdx ; Take only the upper 64 bits of the result
shr rax, 2 ; Shift these bits 2 places to the right (?)
mov QWORD PTR [rbp-8], rax ; Magically, RAX contains 9/5=1 now,
; so we can assign it to j
Code to be emulated:
mov rax, 9 ;
movabs rdx, -3689348814741910323 ;
mul rdx ;
mov rax, rdx ;
shr rax, 2 ;
Output of emulation:
Initial state:
RAX = 0x0
RDX = 0x0
=================================================
>>> 0x1000000: mov rax, 9
RAX = 0x9
RDX = 0x0
=================================================
>>> 0x1000007: movabs rdx, 0xcccccccccccccccd
RAX = 0x9
RDX = 0xcccccccccccccccd
=================================================
>>> 0x1000011: mul rdx
RAX = 0x3333333333333335
RDX = 0x7
=================================================
>>> 0x1000014: mov rax, rdx
RAX = 0x7
RDX = 0x7
=================================================
>>> 0x1000017: shr rax, 2
RAX = 0x1
RDX = 0x7
>>>Emulation complete.
As we can see, RAX holds 1, which is the value computed for 9 / 5. Emulation allows us to easily view the results of every step of the computation in order to understand what is happening.
The program performing the emulation is included below. A colorized gist can be found here.
It consists of 3 major components:
- The assembly code to assemble and emulate
- Assembly via the Keystone engine, as alluded to by blabb in his comment above
- Emulation via Unicorn
A callback function registered with the emulation engine allows us to print information such as register values to STDOUT for each instruction in the instruction stream that is executed.
#!/usr/bin/python3
from keystone import *
from capstone import *
from unicorn import *
from unicorn.x86_const import *
# 9 divided by 5
ASM = "mov rax, 9; \
movabs rdx, -3689348814741910323; \
mul rdx; \
mov rax, rdx; \
shr rax, 2;"
# Use Keystone Engine to assemble
def assemble_snippet():
try:
ks = Ks(KS_ARCH_X86, KS_MODE_64) # initialize assembler object
encoding, count = ks.asm(ASM) # save results of assembly
except KsError as e:
print("ERROR: %s" %e)
return bytes(encoding) # return assembled object code
# callback for tracing instructions
# Use Capstone Engine to disassemble
def hook_code(uc, address, size, user_data):
# print contents of registers of interest
print("RAX = 0x%x" % uc.reg_read(UC_X86_REG_RAX))
print("RDX = 0x%x" % uc.reg_read(UC_X86_REG_RDX))
print("=================================================")
# print disassembly of intruction stream
instruction = uc.mem_read(address, size)
md = user_data
for i in md.disasm(instruction, address):
print(">>> 0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
# from https://github.com/unicorn-engine/unicorn/blob/8621bca53758532ad6a4ec5d17684fcdb9923cc6/bindings/python/sample_x86.py#L475
def emulate():
ADDRESS = 0x1000000 # memory address where emulation starts
CODE = assemble_snippet() # object code to emulate
mu = Uc(UC_ARCH_X86, UC_MODE_64) # Initialize emulator in X86-64bit mode
mu.mem_map(ADDRESS, 2 * 1024 * 1024) # map 2MB memory for this emulation
mu.mem_write(ADDRESS, CODE) # map machine code to be emulated to memory
mu.reg_write(UC_X86_REG_RSP, ADDRESS + 0x200000) # set up stack
md = Cs(CS_ARCH_X86, CS_MODE_64) # initialize disassembler
mu.hook_add(UC_HOOK_CODE, hook_code, md)
print("Initial state:")
try:
# emulate machine code in infinite time
mu.emu_start(ADDRESS, ADDRESS + len(CODE))
except UcError as e:
print("ERROR: %s" % e)
# final state
print("RAX = 0x%x" % mu.reg_read(UC_X86_REG_RAX))
print("RDX = 0x%x" % mu.reg_read(UC_X86_REG_RDX))
print("\n>>>Emulation complete.")
if __name__ == "__main__":
emulate()