Skip to content

Commit

Permalink
Preparing for version 1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
dipusone committed Jan 4, 2023
1 parent 0418078 commit 62a4239
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 27 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ _BinaryNinja plugin to parse GoLang binaries and restore some information, like

This plugin will parse a go binary and restore some information like:
- Function names by parsing the `.gopclntab` section in the binary. If there is no section named .gopclntab it will try to search for it.
- Recover type information by parsing specific callsbuthe gopclntab and restore the function names extracting the information from the `.gopclntab` section in the binary.
- Comment the function with the filename from which the function comes
- Print the list of files in the binary
- Recover type information and names by parsing specific callsites gopclntab.

The plugin works for all GoLang version from 12 to 119.

Expand Down
14 changes: 12 additions & 2 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
from binaryninja import PluginCommand
from .golang_parser import rename_functions, create_types, parse_go_file
from .golang_parser import rename_functions, create_types, parse_go_file, print_files, comment_functions

PluginCommand.register(
"golang\\auto-rename functions (gopclntab)",
"Automatically rename go functions based on information from gopclntab",
rename_functions)

PluginCommand.register(
"golang\\Comment functions with filename (gopclntab)",
"Comment the functions adding the filename where the function was defined",
comment_functions)

PluginCommand.register(
"golang\\Apply types",
"Automatically apply type information",
create_types)

PluginCommand.register(
"golang\\Print file list",
"Print on the console the list of files in the GoLang binary",
print_files)

PluginCommand.register(
"golang\\Parse GoLang executable",
"Automatically apply all the transformation in the right order",
parse_go_file)
parse_go_file)
2 changes: 0 additions & 2 deletions binaryninja_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,5 @@
int64_t gcData;
int32_t nameoff;
int32_t typeoff;
int64_t name;
int64_t mhdr;
};
""")
135 changes: 124 additions & 11 deletions golang_parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import binaryninja as bn
import struct

from binaryninja import Symbol, SymbolType

from .binaryninja_types import *
from .types import *


NAME = 'Golang Loader Helper'
GoFixLogger = bn.Logger(0, NAME)

Expand All @@ -28,10 +30,12 @@ def sanitize_gotype_name(name):


class GoHelper(bn.plugin.BackgroundTaskThread):
def __init__(self, bv: bn.BinaryView):
super().__init__(NAME, True)
def __init__(self, bv: bn.BinaryView, name: str = None):
name = f"{NAME} ({name})" if name else NAME
super().__init__(name, True)
self.bv = bv
self.br = bn.binaryview.BinaryReader(bv)
# Consider caching the table as class variable
self.gopclntab = None

def init_gopclntab(self):
Expand Down Expand Up @@ -110,9 +114,16 @@ def init_gopclntab(self):
self.gopclntab.nfunctab = self.gopclntab.uintptr(8)
self.gopclntab.funcdata = self.gopclntab.raw
self.gopclntab.funcnametab = self.gopclntab.raw
self.gopclntab.pctab = self.gopclntab.raw
self.gopclntab.functab = self.gopclntab.data_after_offset(8 + self.gopclntab.ptrsize)
self.gopclntab.functabsize = (self.gopclntab.nfunctab * 2 + 1) * functabFieldSize
fileoff = struct.unpack("I",
self.gopclntab.functab[self.gopclntab.functabsize:self.gopclntab.functabsize + 4])[0]
self.gopclntab.functab = self.gopclntab.functab[:self.gopclntab.functabsize]
self.gopclntab.filetab = self.gopclntab.data_after_offset(fileoff)
self.gopclntab.nfiletab = struct.unpack("I", self.gopclntab.filetab[:4])[0]
self.gopclntab.filetab = self.gopclntab.filetab[:(self.gopclntab.nfiletab + 1) * 4]

else:
raise ValueError("Invalid go version")

Expand Down Expand Up @@ -220,11 +231,76 @@ def rename_functions(self):
log_info(f"Created {created} functions")
log_info(f"Renamed {renamed - created} functions")
log_info(f"Total {renamed} functions")
self.bv.update_analysis_and_wait()

def run(self):
return self.rename_functions()


class PrintFiles(GoHelper):

def print_files(self):
try:
self.init_gopclntab()
except ValueError:
log_error("Golang version not supported")
return

for fidx in range(self.gopclntab.nfiletab):
file_name = self.gopclntab.fileName(fidx)
log_info(file_name.decode('utf-8'))

def run(self):
return self.print_files()


class FunctionCommenter(GoHelper):

OVERRIDE_COMMENT = True
COMMENT_KEY = "File:"

def comment_functions(self):
try:
self.init_gopclntab()
except ValueError:
log_error("Golang version not supported")
return

log_info("Commenting functions based on .gopclntab section")
log_info(f"gopclntab contains {self.gopclntab.nfunctab} functions")

commented = 0

for fidx in range(self.gopclntab.nfunctab):
if self.gopclntab.version == GoVersion.ver12:
function = self.gopclntab.go12FuncInfo(fidx)
else:
function = self.gopclntab.funcInfo(fidx)
function_addr = function.entry

func = self.bv.get_function_at(function_addr)
# Parse only already existing functions
if not func:
continue

filename = self.gopclntab.pc2filename(function)
if not filename:
continue

if not self.OVERRIDE_COMMENT and func.comment:
log_debug("Already commented, skipping")
continue

comment = f"{self.COMMENT_KEY} {filename.decode('utf-8')}"
func.comment = comment
commented += 1

log_info(f"Commented {commented} functions")

def run(self):
return self.comment_functions()


class TypeParser(GoHelper):
TYPES = [
GO_KIND,
Expand All @@ -245,6 +321,8 @@ def create_types(self):
go_version = self.quick_go_version()
log_debug(f"Go Version is {go_version}")

already_parsed = set()

for segment_name in ('.rodata', '__rodata'):
rodata = self.get_section_by_name(segment_name)
if rodata:
Expand Down Expand Up @@ -280,8 +358,12 @@ def create_types(self):
ptr_var.type = bn.Type.pointer(self.bv.arch, golang_type)
log_debug(f"Parsing xrefs to {function.name}")
for caller_site in function.caller_sites:

mlil = caller_site.mlil
try:
mlil = caller_site.mlil
except:
log_debug("Unable to get the mlil for instruction")
continue

if not mlil or mlil.operation != bn.MediumLevelILOperation.MLIL_CALL:
log_debug(f"Callsite at 0x{mlil.address:x} is not a call, skipping")
continue
Expand All @@ -292,6 +374,8 @@ def create_types(self):
# funny enough `not <void var>` will return `True`
if go_data_type is None:
continue
if param in already_parsed:
log_debug(f"Skipping already parsed at 0x{param:x}")

go_data_type.type = golang_type
# TODO figure out why sometime the type info are not there
Expand All @@ -313,13 +397,19 @@ def create_types(self):
log_debug(f"Found name at 0x{gotype.resolved_name_addr:x} with value {name}")
sanitazed_name = sanitize_gotype_name(name)
go_data_type.name = f"{sanitazed_name}_type"
# add cross-reference for convenience
# add cross-reference for convenience (both directions)
self.bv.add_user_data_ref(
gotype.address_off('nameOff'),
gotype.resolved_name_addr)

self.bv.add_user_data_ref(
gotype.resolved_name_addr,
gotype.address_off('nameOff')
)

name_datavar = self.bv.get_data_var_at(gotype.resolved_name_addr)
name_datavar.name = f"{go_data_type.name}_name"
already_parsed.add(param)
created += 1

log_info(f"Created {created} types")
Expand All @@ -328,6 +418,22 @@ def run(self):
return self.create_types()


class RunAll(bn.plugin.BackgroundTaskThread):
def __init__(self, bv):
super().__init__(NAME, True)
self.bv = bv
self.analysis = []
self.analysis.append(FunctionRenamer(bv))
self.analysis.append(FunctionCommenter(bv))
self.analysis.append(TypeParser(bv))

def run(self):
for analysis in self.analysis:
analysis.start()
analysis.join()
log_info(f"Terminated all analysis")


def rename_functions(bv):
helper = FunctionRenamer(bv)
return helper.start()
Expand All @@ -338,9 +444,16 @@ def create_types(bv):
return helper.start()


def parse_go_file(bv):
fr = FunctionRenamer(bv)
fr.start()
fr.join()
tp = TypeParser(bv)
return tp.start()
def print_files(bv):
helper = PrintFiles(bv)
return helper.start()


def comment_functions(bv):
helper = FunctionCommenter(bv)
return helper.start()


def parse_go_file(bv: bn.BinaryView):
ra = RunAll(bv)
return ra.start()
4 changes: 2 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"python3"
],
"description": "BinaryNinja plugin to parse GoLang binaries and restore some information, like function names and type information",
"longdescription": "This plugin will parse a go binary and restore information like the functions names, by parsing the .gopclntab, and extract typing information from the executable itself.",
"longdescription": "This plugin will parse a go binary and restore information like the functions/file names, by parsing the .gopclntab, and extract typing information from the executable itself.",
"license": {
"name": "MIT",
"text": "Copyright 2023 Jacopo Ferrigno\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
Expand All @@ -24,6 +24,6 @@
"Windows": "no special instructions, package manager is recommended",
"Linux": "no special instructions, package manager is recommended"
},
"version": "1.1",
"version": "1.2",
"minimumbinaryninjaversion": 3946
}
Loading

0 comments on commit 62a4239

Please sign in to comment.