Video: Solving VM-based challenges using Cerbero

How to solve VM-based challenges with the help of Cerbero.

This is the template code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from Pro.Core import *
from Pro.UI import *
from Pro.ccast import sbyte
import os, struct
REG_COUNT = 16
def regName(id):
return "R" + str(id)
def disassemble(code, regs):
return "instr"
STEP_VIEW_ID = 1
DISASM_VIEW_ID = 2
MEMORY_VIEW_ID = 3
REGISTERS_VIEW_ID = 4
STACK_VIEW_ID = 5
# the dump directory should have files with increasing number as name
# e.g.: 0, 1, 2, etc.
DBGDIR = r"path/to/state/dumps"
BPX = -1
# logic to extract the instruction pointer from the dumps
# we use that as text in the Trace table and to go to a break point
def loadStepDescr(ud, steps):
stepsdescr = []
bpx_pos = -1
for step in steps:
with open(os.path.join(DBGDIR, str(step)), "rb") as f:
f.seek((REG_COUNT - 1) * 2)
ip = struct.unpack_from(">H", f.read(2), 0)[0]
if bpx_pos == -1 and ip == BPX:
bpx_pos = len(stepsdescr)
stepsdescr.append("%04X" % (ip,))
ud["stepsdescr"] = stepsdescr
if bpx_pos != -1:
ud["bpxpos"] = bpx_pos
def loadStep(cv, step, ud):
#with open(os.path.join(DBGDIR, str(step)), "rb") as f:
# dump = f.read()
dump = b"\x00\x01" * REG_COUNT
regs = struct.unpack_from("<" + ("H" * REG_COUNT), dump, 0)
ud["regs"] = regs
# set up regs table
t = cv.getView(REGISTERS_VIEW_ID)
labels = NTStringList()
labels.append("Register")
labels.append("Value")
t.setColumnCount(2)
t.setRowCount(REG_COUNT)
t.setColumnLabels(labels)
t.setColumnCWidth(0, 10)
t.setColumnCWidth(1, 20)
# set up memory
h = cv.getView(MEMORY_VIEW_ID)
curoffs = h.getCurrentOffset()
cursoroffs = h.getCursorOffset()
#mem = dump[REG_COUNT * 2:]
mem = b"dummy memory"
ud["mem"] = mem
h.setBytes(mem)
h.setCursorOffset(cursoroffs)
h.setCurrentOffset(curoffs)
# set up stack table
stack = (0x1000, 0x2000, 0x3000)
ud["cursp"] = 0x6000
ud["stack"] = stack
t = cv.getView(STACK_VIEW_ID)
labels = NTStringList()
labels.append("Stack address")
labels.append("Value")
t.setColumnCount(2)
t.setRowCount(len(stack))
t.setColumnLabels(labels)
t.setColumnCWidth(0, 10)
t.setColumnCWidth(1, 20)
# set up disasm
t = cv.getView(DISASM_VIEW_ID)
disasm = disassemble(mem, regs)
t.setText(disasm)
def tracerCallback(cv, ud, code, view, data):
if code == pvnInit:
# get steps
steps = os.listdir(dbgdir)
steps = [int(e) for e in steps]
steps = sorted(steps)
ud["steps"] = steps
loadStepDescr(ud, steps)
# set up steps
t = cv.getView(STEP_VIEW_ID)
labels = NTStringList()
labels.append("Trace")
t.setColumnCount(1)
t.setRowCount(len(steps))
t.setColumnLabels(labels)
t.setColumnCWidth(0, 10)
# go to bpx if any
if "bpxpos" in ud:
bpxpos = ud["bpxpos"]
t.setSelectedRow(bpxpos)
return 1
elif code == pvnGetTableRow:
vid = view.id()
if vid == STEP_VIEW_ID:
data.setText(0, str(ud["stepsdescr"][data.row]))
elif vid == REGISTERS_VIEW_ID:
data.setText(0, regName(data.row))
v = ud["regs"][data.row]
data.setText(1, "%d (0x%X)" % (v, v))
if data.row >= 13:
data.setBgColor(0, ProColor_Special)
data.setBgColor(1, ProColor_Special)
elif vid == STACK_VIEW_ID:
spaddr = ud["cursp"] + (data.row * 2)
data.setText(0, "0x%04X" % (spaddr,))
v = ud["stack"][data.row]
data.setText(1, "%d (0x%X)" % (v, v))
elif code == pvnRowSelected:
vid = view.id()
if vid == STEP_VIEW_ID:
loadStep(cv, ud["steps"][data.row], ud)
return 0
def tracerDlg():
ctx = proContext()
v = ctx.createView(ProView.Type_Custom, "Tracer Demo")
user_data = {}
v.setup("<ui><hs><table id='1'/><vs><text id='2'/><hex id='3'/></vs><table id='4'/><table id='5'/></hs></ui>", tracerCallback, user_data)
dlg = ctx.createDialog(v)
dlg.show()
tracerDlg()
from Pro.Core import * from Pro.UI import * from Pro.ccast import sbyte import os, struct REG_COUNT = 16 def regName(id): return "R" + str(id) def disassemble(code, regs): return "instr" STEP_VIEW_ID = 1 DISASM_VIEW_ID = 2 MEMORY_VIEW_ID = 3 REGISTERS_VIEW_ID = 4 STACK_VIEW_ID = 5 # the dump directory should have files with increasing number as name # e.g.: 0, 1, 2, etc. DBGDIR = r"path/to/state/dumps" BPX = -1 # logic to extract the instruction pointer from the dumps # we use that as text in the Trace table and to go to a break point def loadStepDescr(ud, steps): stepsdescr = [] bpx_pos = -1 for step in steps: with open(os.path.join(DBGDIR, str(step)), "rb") as f: f.seek((REG_COUNT - 1) * 2) ip = struct.unpack_from(">H", f.read(2), 0)[0] if bpx_pos == -1 and ip == BPX: bpx_pos = len(stepsdescr) stepsdescr.append("%04X" % (ip,)) ud["stepsdescr"] = stepsdescr if bpx_pos != -1: ud["bpxpos"] = bpx_pos def loadStep(cv, step, ud): #with open(os.path.join(DBGDIR, str(step)), "rb") as f: # dump = f.read() dump = b"\x00\x01" * REG_COUNT regs = struct.unpack_from("<" + ("H" * REG_COUNT), dump, 0) ud["regs"] = regs # set up regs table t = cv.getView(REGISTERS_VIEW_ID) labels = NTStringList() labels.append("Register") labels.append("Value") t.setColumnCount(2) t.setRowCount(REG_COUNT) t.setColumnLabels(labels) t.setColumnCWidth(0, 10) t.setColumnCWidth(1, 20) # set up memory h = cv.getView(MEMORY_VIEW_ID) curoffs = h.getCurrentOffset() cursoroffs = h.getCursorOffset() #mem = dump[REG_COUNT * 2:] mem = b"dummy memory" ud["mem"] = mem h.setBytes(mem) h.setCursorOffset(cursoroffs) h.setCurrentOffset(curoffs) # set up stack table stack = (0x1000, 0x2000, 0x3000) ud["cursp"] = 0x6000 ud["stack"] = stack t = cv.getView(STACK_VIEW_ID) labels = NTStringList() labels.append("Stack address") labels.append("Value") t.setColumnCount(2) t.setRowCount(len(stack)) t.setColumnLabels(labels) t.setColumnCWidth(0, 10) t.setColumnCWidth(1, 20) # set up disasm t = cv.getView(DISASM_VIEW_ID) disasm = disassemble(mem, regs) t.setText(disasm) def tracerCallback(cv, ud, code, view, data): if code == pvnInit: # get steps steps = os.listdir(dbgdir) steps = [int(e) for e in steps] steps = sorted(steps) ud["steps"] = steps loadStepDescr(ud, steps) # set up steps t = cv.getView(STEP_VIEW_ID) labels = NTStringList() labels.append("Trace") t.setColumnCount(1) t.setRowCount(len(steps)) t.setColumnLabels(labels) t.setColumnCWidth(0, 10) # go to bpx if any if "bpxpos" in ud: bpxpos = ud["bpxpos"] t.setSelectedRow(bpxpos) return 1 elif code == pvnGetTableRow: vid = view.id() if vid == STEP_VIEW_ID: data.setText(0, str(ud["stepsdescr"][data.row])) elif vid == REGISTERS_VIEW_ID: data.setText(0, regName(data.row)) v = ud["regs"][data.row] data.setText(1, "%d (0x%X)" % (v, v)) if data.row >= 13: data.setBgColor(0, ProColor_Special) data.setBgColor(1, ProColor_Special) elif vid == STACK_VIEW_ID: spaddr = ud["cursp"] + (data.row * 2) data.setText(0, "0x%04X" % (spaddr,)) v = ud["stack"][data.row] data.setText(1, "%d (0x%X)" % (v, v)) elif code == pvnRowSelected: vid = view.id() if vid == STEP_VIEW_ID: loadStep(cv, ud["steps"][data.row], ud) return 0 def tracerDlg(): ctx = proContext() v = ctx.createView(ProView.Type_Custom, "Tracer Demo") user_data = {} v.setup("<ui><hs><table id='1'/><vs><text id='2'/><hex id='3'/></vs><table id='4'/><table id='5'/></hs></ui>", tracerCallback, user_data) dlg = ctx.createDialog(v) dlg.show() tracerDlg()
from Pro.Core import *
from Pro.UI import *
from Pro.ccast import sbyte
import os, struct

REG_COUNT = 16

def regName(id):
    return "R" + str(id)

def disassemble(code, regs):
    return "instr"

STEP_VIEW_ID = 1
DISASM_VIEW_ID = 2
MEMORY_VIEW_ID = 3
REGISTERS_VIEW_ID = 4
STACK_VIEW_ID = 5

# the dump directory should have files with increasing number as name
# e.g.: 0, 1, 2, etc.
DBGDIR = r"path/to/state/dumps"
BPX = -1

# logic to extract the instruction pointer from the dumps
# we use that as text in the Trace table and to go to a break point
def loadStepDescr(ud, steps):
    stepsdescr = []
    bpx_pos = -1
    for step in steps:
        with open(os.path.join(DBGDIR, str(step)), "rb") as f:
            f.seek((REG_COUNT - 1) * 2)
            ip = struct.unpack_from(">H", f.read(2), 0)[0]
            if bpx_pos == -1 and ip == BPX:
                bpx_pos = len(stepsdescr)
            stepsdescr.append("%04X" % (ip,))
    ud["stepsdescr"] = stepsdescr
    if bpx_pos != -1:
        ud["bpxpos"] = bpx_pos

def loadStep(cv, step, ud):
    #with open(os.path.join(DBGDIR, str(step)), "rb") as f:
    #    dump = f.read()
    dump = b"\x00\x01" * REG_COUNT
    regs = struct.unpack_from("<" + ("H" * REG_COUNT), dump, 0)
    ud["regs"] = regs
    # set up regs table
    t = cv.getView(REGISTERS_VIEW_ID)
    labels = NTStringList()
    labels.append("Register")
    labels.append("Value")
    t.setColumnCount(2)
    t.setRowCount(REG_COUNT)
    t.setColumnLabels(labels)
    t.setColumnCWidth(0, 10)
    t.setColumnCWidth(1, 20)
    # set up memory
    h = cv.getView(MEMORY_VIEW_ID)
    curoffs = h.getCurrentOffset()
    cursoroffs = h.getCursorOffset()
    #mem = dump[REG_COUNT * 2:]
    mem = b"dummy memory"
    ud["mem"] = mem
    h.setBytes(mem)
    h.setCursorOffset(cursoroffs)
    h.setCurrentOffset(curoffs)
    # set up stack table
    stack = (0x1000, 0x2000, 0x3000)
    ud["cursp"] = 0x6000
    ud["stack"] = stack
    t = cv.getView(STACK_VIEW_ID)
    labels = NTStringList()
    labels.append("Stack address")
    labels.append("Value")
    t.setColumnCount(2)
    t.setRowCount(len(stack))
    t.setColumnLabels(labels)
    t.setColumnCWidth(0, 10)
    t.setColumnCWidth(1, 20)
    # set up disasm
    t = cv.getView(DISASM_VIEW_ID)
    disasm = disassemble(mem, regs)
    t.setText(disasm)

def tracerCallback(cv, ud, code, view, data):
    if code == pvnInit:
        # get steps
        steps = os.listdir(dbgdir)
        steps = [int(e) for e in steps]
        steps = sorted(steps)
        ud["steps"] = steps
        loadStepDescr(ud, steps)
        # set up steps
        t = cv.getView(STEP_VIEW_ID)
        labels = NTStringList()
        labels.append("Trace")
        t.setColumnCount(1)
        t.setRowCount(len(steps))
        t.setColumnLabels(labels)
        t.setColumnCWidth(0, 10)
        # go to bpx if any
        if "bpxpos" in ud:
            bpxpos = ud["bpxpos"]
            t.setSelectedRow(bpxpos)
        return 1
    elif code == pvnGetTableRow:
        vid = view.id()
        if vid == STEP_VIEW_ID:
            data.setText(0, str(ud["stepsdescr"][data.row]))
        elif vid == REGISTERS_VIEW_ID:
            data.setText(0, regName(data.row))
            v = ud["regs"][data.row]
            data.setText(1, "%d (0x%X)" % (v, v))
            if data.row >= 13:
                data.setBgColor(0, ProColor_Special)
                data.setBgColor(1, ProColor_Special)
        elif vid == STACK_VIEW_ID:
            spaddr = ud["cursp"] + (data.row * 2)
            data.setText(0, "0x%04X" % (spaddr,))
            v = ud["stack"][data.row]
            data.setText(1, "%d (0x%X)" % (v, v))
    elif code == pvnRowSelected:
        vid = view.id()
        if vid == STEP_VIEW_ID:
            loadStep(cv, ud["steps"][data.row], ud)
    return 0

def tracerDlg():
    ctx = proContext()
    v = ctx.createView(ProView.Type_Custom, "Tracer Demo")
    user_data = {}
    v.setup("<ui><hs><table id='1'/><vs><text id='2'/><hex id='3'/></vs><table id='4'/><table id='5'/></hs></ui>", tracerCallback, user_data)
    dlg = ctx.createDialog(v)
    dlg.show()
    
tracerDlg()

Ghidra & Cerbero: released the native interface PoC

… or what happens when two multi-headed monsters meet. 🙂

I just released version 3.2 of Cerbero Suite which contains the anticipated proof-of-concept native interface for Ghidra. To install the necessary extension in Ghidra, open the “util” directory and extract the contents of ghidra.zip. You’ll find a PDF document with the setup instructions.

The interface works on Windows, Linux and OS X. How does it work you might wonder? It works via IPC, specifically via sockets. When I first came up with the idea I was curious about two things: to test the SDK of Cerbero against a new challenge and to see if the responsiveness of the UI would be good enough.

Regarding the responsiveness, I didn’t have an answer to that until I had a working disassembly view. I think it’s very responsive. In fact, I developed and tested the UI on different machines than the one running Ghidra and even in that scenario the UI was fast. 🙂

The PoC comes with the most fundamental views as you can see from the screen-shot. Navigation is complete, comments and bookmarks. Renaming is partially done, unfortunately renaming of variables is not yet supported. That was a feature which I wanted to have even in the PoC, but at a certain point I couldn’t delay any further the release.

Be aware that this is a PoC, I didn’t do extensive testing and there are some very important features which are still missing. Just to name a few: automatic refresh of the disassembly during analysis is missing, manual defining of code/data is missing, so is the capability to filter and sort table items.

Although things are missing, I tried to polish the UI enough to make it useful for some actual work and for a real evaluation on the user side. I didn’t experience any crash and in the worst case scenario you can just close the UI process and spawn a new one. In fact, you can even open multiple UI instances for the same file, it’s not an issue.

The whole project (research/C++ UI/Java extension) represents one month of work on my side. So I feel pretty confident that I can make the integration very smooth in a matter of a few months. The reason why I released this as a PoC is that before investing more time into it, I want to see if there’s actual interest for it from the community. The PoC itself was a nice project for myself, but now it’s up to you to decide if you want to make it mature into a real project.

Cerbero, as you know, is a commercial application, but it can be freely downloaded and used as a trial without any limitation. So trying it out shouldn’t be an issue.

Happy hacking! 🙂