reverse engineering: for fun's sake

Exposing Ghidra to other processes

July 19, 2019

Foreword

Bit of a nothing post this time, just a quick look at the way I’m planning to add remote access to Ghidra.

I’ve gone for simple here, I would have liked for it to be simpler but Jython has some limitations.

There will be an accompanying PyGDB implementation coming in the future too.

Aim

Really I just want to show that the Ghidra Python (Jython) is basically usable like CPython. There are some limitation, and quirks, but it’s good enough.

The approach

In a Ghidra script:

  • Open a socket
  • Bind/listen/accept a connection
  • For the connection:

    • Receive the length of the upcoming JSON blob
    • Receive the JSON blob
    • Call the function named in the JSON with arguments
    • Send back a result as a JSON block (with length at the start)

I would have just like to use json.load but Jython sockets don’t support fileno.

The code


Server code:

import socket
import json
import os
import struct

CmdStruct = struct.Struct('!I')

class CmdServer(object):
    '''Command server
    
    Receives commands and actions them, send back the result.'''
    
    PORT = 12345
    
    def __init__(self, port=CmdServer.Port):
        '''Constructor'''
        
        self.port = port
        self.ssock = None
        
        self.cmds = {}
        
    def setup_socket(self):
        '''Setups up socket
        
        You know, socket bind etc'''
        
        self.ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.ssock.bind(('127.0.0.1', self.port))
        self.ssock.listen(1)
    
    def close(self):
        '''Finished things up'''
        
        self.ssock.close()
    
    def add_command(self, name, fn):
        '''Adds a command
        
        Args:
            name (str): name of the function
            fn (callable): function to call
        '''
        
        if name in self.cmds:
            raise RuntimeError('Command name "%s" already used' % name)
        
        self.cmds[name] = fn
        
    def serve(self):
        '''Starts the actual serving'''
        
        if self.ssock is None:
            raise RuntimeError('Socket not setup')
       
        while True: 
            csock, addr = self.ssock.accept()
            
            while True:
                ssize = csock.recv(CmdStruct.size)
                if not len(ssize):
                    break
                size = CmdStruct.unpack(ssize)[0]
                print("Reveived packet, size: %d" % size)
                cmd = json.loads(csock.recv(size))
                name = cmd['name']
                _kwargs = cmd['kwargs']
                ret = {}
                
                print('Received command: "%s"[%s], args: %s' % (name,
                                                                name in self.cmds,
                                                                _kwargs)) 
                if name not in self.cmds:
                    raise RuntimeError('Non-existent command "%s"' % name)
               
                try:
                    ret['result'] = self.cmds[name](**_kwargs)
                except Exception as e:
                    ret['error'] = str(e)
                
                jret = json.dumps(ret)
                
                csock.sendall(CmdStruct.pack(len(jret)) + jret)

The Ghidra script:

#Provides remote access into Ghidra
#@author djfarrell@re-ffs.com
#@category Remote
#@keybinding 
#@menupath 
#@toolbar 

from PyGDBStub.server import CmdServer
from ghidra.program.model.address import Address


def test(a=None, b=None, c=None):
    ret = {}
    ret['entry_point'] = getFunctionContaining(currentAddress).getEntryPoint()
            .getOffset()
    
    return ret

server = CmdServer()

server.add_command('test', test)

server.setup_socket()

try:
    server.serve()
except Exception as e:
    print(e)
finally:
    server.close()

And something to test with:

[200~#!/usr/bin/env python3

import server

import socket
import json


def do_cmd(name, **kwargs):
    '''Does a PyGDBStub cmd'''
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('127.0.0.1', 55555))
 
    o = {
        'name': name,
        'kwargs': kwargs,
    }
    
    j = json.dumps(o).encode()
    
    c = server.CmdStruct.pack(len(j)) + j
    
    sock.sendall(c)
    
    s = sock.recv(server.CmdStruct.size)
    size = server.CmdStruct.unpack(s)[0]
    
    r = sock.recv(size)
    
    print(json.loads(r))
    
    
do_cmd('test', a=1, b=2, c={'blah': 'bleh'})

Up next

Next will be using the above in PyGDB to track the memory copies.


Dan Farrell

Written by Dan Farrell who lives and works in Seattle tinkering away on firmware. To subscribe send an email to subscribe@re-ffs.com.