1

It might be too silly of a question, but here you go.

Suppose you have some software that contains a tricky nested assembly sequence (like this one) you try to reverse engineer. brain*[pen&paper] totally rocks, but you would like to consult IDA (or anything similar), but you cannot directly drag&drop into IDA for (whatever reason, maybe a bug).

You have the assembly code for that assembly sequence, and as a work around, you can theoretically compile a program that would contain the following (in C-code),

int main(int argc, char *argv[])
{
  // your tricky assembly sequence here, then
  // perhaps puts(eax), puts(ebx) etc
  return(0)
}

Seems like a fair an easy task. But in practice, how could I compile such a quick and dirty program in practice? Preferably via gcc?

TAbdiukov
  • 197
  • 8
  • what do you have in hand 0x55 or push ebp fir first try capstone for second try key stone – blabb Jan 07 '20 at 15:41

1 Answers1

2

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:

  1. The assembly code to assemble and emulate
  2. Assembly via the Keystone engine, as alluded to by blabb in his comment above
  3. 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()
julian
  • 7,128
  • 3
  • 22
  • 55