Skip to content

Instantly share code, notes, and snippets.

@cetfor
Last active June 4, 2022 17:54
Show Gist options
  • Save cetfor/18cae544249e4cf78f66930175072876 to your computer and use it in GitHub Desktop.
Save cetfor/18cae544249e4cf78f66930175072876 to your computer and use it in GitHub Desktop.
PCode Emulation with Python
from ghidra.app.emulator import EmulatorHelper
from ghidra.program.model.symbol import SymbolUtilities
# Tested with Ghidra v9.1 and v9.1.1, future releases are likely to simplify
# and/or expand the EmulatorHelper class in the API.
# == Helper functions ======================================================
def getAddress(offset):
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset)
def getSymbolAddress(symbolName):
symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName, None)
if (symbol != None):
return symbol.getAddress()
else:
raise("Failed to locate label: {}".format(symbolName))
def getProgramRegisterList(currentProgram):
pc = currentProgram.getProgramContext()
return pc.registers
# == Main function =========================================================
def main():
CONTROLLED_RETURN_OFFSET = 0
# Identify function to be emulated
mainFunctionEntry = getSymbolAddress("main")
# Establish emulation helper, please check out the API docs
# for `EmulatorHelper` - there's a lot of helpful things
# to help make architecture agnostic emulator tools.
emuHelper = EmulatorHelper(currentProgram)
# Set controlled return location so we can identify return from emulated function
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET)
# Set initial RIP
mainFunctionEntryLong = int("0x{}".format(mainFunctionEntry), 16)
emuHelper.writeRegister(emuHelper.getPCRegister(), mainFunctionEntryLong)
# For x86_64 `registers` contains 872 registers! You probably don't
# want to print all of these. Just be aware, and print what you need.
# To see all supported registers. just print `registers`.
# We won't use this, it's just here to show you how to query
# valid registers for your target architecture.
registers = getProgramRegisterList(currentProgram)
# Here's a list of all the registers we want printed after each
# instruction. Modify this as you see fit, based on your architecture.
reg_filter = [
"RIP", "RAX", "RBX", "RCX", "RDX", "RSI", "RDI",
"RSP", "RBP", "rflags"
]
# Setup your desired starting state. By default, all registers
# and memory will be 0. This may or may not be acceptable for
# you. So please be aware.
emuHelper.writeRegister("RAX", 0x20)
emuHelper.writeRegister("RSP", 0x000000002FFF0000)
emuHelper.writeRegister("RBP", 0x000000002FFF0000)
# There are a couple of ways to write memory, use `writeMemoryValue` if you want
# to set a small typed value (e.g. uint64). Use `writeMemory` if you're mapping in
# a lot of memory (e.g. from a debugger memory dump). Note that each of these
# methods write with different endianess, see the example output.
emuHelper.writeMemoryValue(getAddress(0x000000000008C000), 4, 0x99AABBCC) # writes big endian
emuHelper.writeMemory(getAddress(0x00000000000CF000), b'\x99\xAA\xBB\xCC') # writes little endian
# You can verify writes worked, or just read memory at select points
# during emulation. Here's a couple of examples:
mem1 = emuHelper.readMemory(getAddress(0x000000000008C000), 4)
mem2 = emuHelper.readMemory(getAddress(0x00000000000CF000), 4)
print("Memory at 0x000000000008C000: {}".format(mem1))
print("Memory at 0x00000000000CF000: {}".format(mem2))
print("Emulation starting at 0x{}".format(mainFunctionEntry))
while monitor.isCancelled() is False:
# Check the current address in the program counter, if it's
# zero (our `CONTROLLED_RETURN_OFFSET` value) stop emulation.
# Set this to whatever end target you want.
executionAddress = emuHelper.getExecutionAddress()
if (executionAddress == controlledReturnAddr):
print("Emulation complete.")
return
# Print current instruction and the registers we care about
print("Address: 0x{} ({})".format(executionAddress, getInstructionAt(executionAddress)))
for reg in reg_filter:
reg_value = emuHelper.readRegister(reg)
print(" {} = {:#018x}".format(reg, reg_value))
# single step emulation
success = emuHelper.step(monitor)
if (success == False):
lastError = emuHelper.getLastError()
printerr("Emulation Error: '{}'".format(lastError))
return
# Cleanup resources and release hold on currentProgram
emuHelper.dispose()
# == Invoke main ===========================================================
main()
Memory at 0x000000000008C000: array('b', [-52, -69, -86, -103])
Memory at 0x00000000000CF000: array('b', [-103, -86, -69, -52])
Emulation starting at 0x00100690
Address: 0x00100690 (PUSH RBP)
RIP = 0x0000000000100690
RAX = 0x0000000000000020
RBX = 0x0000000000000000
RCX = 0x0000000000000000
RDX = 0x0000000000000000
RSI = 0x0000000000000000
RDI = 0x0000000000000000
RSP = 0x000000002fff0000
RBP = 0x000000002fff0000
rflags = 0x0000000000000000
Address: 0x00100691 (MOV RBP,RSP)
RIP = 0x0000000000100691
RAX = 0x0000000000000020
RBX = 0x0000000000000000
RCX = 0x0000000000000000
RDX = 0x0000000000000000
RSI = 0x0000000000000000
RDI = 0x0000000000000000
RSP = 0x000000002ffefff8
RBP = 0x000000002fff0000
rflags = 0x0000000000000000
Address: 0x00100694 (SUB RSP,0x30)
RIP = 0x0000000000100694
RAX = 0x0000000000000020
RBX = 0x0000000000000000
RCX = 0x0000000000000000
RDX = 0x0000000000000000
RSI = 0x0000000000000000
RDI = 0x0000000000000000
RSP = 0x000000002ffefff8
RBP = 0x000000002ffefff8
rflags = 0x0000000000000000
Address: 0x00100698 (MOV dword ptr [RBP + -0x24],EDI)
RIP = 0x0000000000100698
RAX = 0x0000000000000020
RBX = 0x0000000000000000
RCX = 0x0000000000000000
RDX = 0x0000000000000000
RSI = 0x0000000000000000
RDI = 0x0000000000000000
RSP = 0x000000002ffeffc8
RBP = 0x000000002ffefff8
rflags = 0x0000000000000000
... snip ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment