Skip to content

Commit 62a4239

Browse files
committed
Preparing for version 1.2
1 parent 0418078 commit 62a4239

File tree

6 files changed

+294
-27
lines changed

6 files changed

+294
-27
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ _BinaryNinja plugin to parse GoLang binaries and restore some information, like
77

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

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

__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
from binaryninja import PluginCommand
2-
from .golang_parser import rename_functions, create_types, parse_go_file
2+
from .golang_parser import rename_functions, create_types, parse_go_file, print_files, comment_functions
33

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

9+
PluginCommand.register(
10+
"golang\\Comment functions with filename (gopclntab)",
11+
"Comment the functions adding the filename where the function was defined",
12+
comment_functions)
13+
914
PluginCommand.register(
1015
"golang\\Apply types",
1116
"Automatically apply type information",
1217
create_types)
1318

19+
PluginCommand.register(
20+
"golang\\Print file list",
21+
"Print on the console the list of files in the GoLang binary",
22+
print_files)
23+
1424
PluginCommand.register(
1525
"golang\\Parse GoLang executable",
1626
"Automatically apply all the transformation in the right order",
17-
parse_go_file)
27+
parse_go_file)

binaryninja_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,5 @@
4949
int64_t gcData;
5050
int32_t nameoff;
5151
int32_t typeoff;
52-
int64_t name;
53-
int64_t mhdr;
5452
};
5553
""")

golang_parser.py

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import binaryninja as bn
2+
import struct
23

34
from binaryninja import Symbol, SymbolType
45

56
from .binaryninja_types import *
67
from .types import *
78

9+
810
NAME = 'Golang Loader Helper'
911
GoFixLogger = bn.Logger(0, NAME)
1012

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

2931

3032
class GoHelper(bn.plugin.BackgroundTaskThread):
31-
def __init__(self, bv: bn.BinaryView):
32-
super().__init__(NAME, True)
33+
def __init__(self, bv: bn.BinaryView, name: str = None):
34+
name = f"{NAME} ({name})" if name else NAME
35+
super().__init__(name, True)
3336
self.bv = bv
3437
self.br = bn.binaryview.BinaryReader(bv)
38+
# Consider caching the table as class variable
3539
self.gopclntab = None
3640

3741
def init_gopclntab(self):
@@ -110,9 +114,16 @@ def init_gopclntab(self):
110114
self.gopclntab.nfunctab = self.gopclntab.uintptr(8)
111115
self.gopclntab.funcdata = self.gopclntab.raw
112116
self.gopclntab.funcnametab = self.gopclntab.raw
117+
self.gopclntab.pctab = self.gopclntab.raw
113118
self.gopclntab.functab = self.gopclntab.data_after_offset(8 + self.gopclntab.ptrsize)
114119
self.gopclntab.functabsize = (self.gopclntab.nfunctab * 2 + 1) * functabFieldSize
120+
fileoff = struct.unpack("I",
121+
self.gopclntab.functab[self.gopclntab.functabsize:self.gopclntab.functabsize + 4])[0]
115122
self.gopclntab.functab = self.gopclntab.functab[:self.gopclntab.functabsize]
123+
self.gopclntab.filetab = self.gopclntab.data_after_offset(fileoff)
124+
self.gopclntab.nfiletab = struct.unpack("I", self.gopclntab.filetab[:4])[0]
125+
self.gopclntab.filetab = self.gopclntab.filetab[:(self.gopclntab.nfiletab + 1) * 4]
126+
116127
else:
117128
raise ValueError("Invalid go version")
118129

@@ -220,11 +231,76 @@ def rename_functions(self):
220231
log_info(f"Created {created} functions")
221232
log_info(f"Renamed {renamed - created} functions")
222233
log_info(f"Total {renamed} functions")
234+
self.bv.update_analysis_and_wait()
223235

224236
def run(self):
225237
return self.rename_functions()
226238

227239

240+
class PrintFiles(GoHelper):
241+
242+
def print_files(self):
243+
try:
244+
self.init_gopclntab()
245+
except ValueError:
246+
log_error("Golang version not supported")
247+
return
248+
249+
for fidx in range(self.gopclntab.nfiletab):
250+
file_name = self.gopclntab.fileName(fidx)
251+
log_info(file_name.decode('utf-8'))
252+
253+
def run(self):
254+
return self.print_files()
255+
256+
257+
class FunctionCommenter(GoHelper):
258+
259+
OVERRIDE_COMMENT = True
260+
COMMENT_KEY = "File:"
261+
262+
def comment_functions(self):
263+
try:
264+
self.init_gopclntab()
265+
except ValueError:
266+
log_error("Golang version not supported")
267+
return
268+
269+
log_info("Commenting functions based on .gopclntab section")
270+
log_info(f"gopclntab contains {self.gopclntab.nfunctab} functions")
271+
272+
commented = 0
273+
274+
for fidx in range(self.gopclntab.nfunctab):
275+
if self.gopclntab.version == GoVersion.ver12:
276+
function = self.gopclntab.go12FuncInfo(fidx)
277+
else:
278+
function = self.gopclntab.funcInfo(fidx)
279+
function_addr = function.entry
280+
281+
func = self.bv.get_function_at(function_addr)
282+
# Parse only already existing functions
283+
if not func:
284+
continue
285+
286+
filename = self.gopclntab.pc2filename(function)
287+
if not filename:
288+
continue
289+
290+
if not self.OVERRIDE_COMMENT and func.comment:
291+
log_debug("Already commented, skipping")
292+
continue
293+
294+
comment = f"{self.COMMENT_KEY} {filename.decode('utf-8')}"
295+
func.comment = comment
296+
commented += 1
297+
298+
log_info(f"Commented {commented} functions")
299+
300+
def run(self):
301+
return self.comment_functions()
302+
303+
228304
class TypeParser(GoHelper):
229305
TYPES = [
230306
GO_KIND,
@@ -245,6 +321,8 @@ def create_types(self):
245321
go_version = self.quick_go_version()
246322
log_debug(f"Go Version is {go_version}")
247323

324+
already_parsed = set()
325+
248326
for segment_name in ('.rodata', '__rodata'):
249327
rodata = self.get_section_by_name(segment_name)
250328
if rodata:
@@ -280,8 +358,12 @@ def create_types(self):
280358
ptr_var.type = bn.Type.pointer(self.bv.arch, golang_type)
281359
log_debug(f"Parsing xrefs to {function.name}")
282360
for caller_site in function.caller_sites:
283-
284-
mlil = caller_site.mlil
361+
try:
362+
mlil = caller_site.mlil
363+
except:
364+
log_debug("Unable to get the mlil for instruction")
365+
continue
366+
285367
if not mlil or mlil.operation != bn.MediumLevelILOperation.MLIL_CALL:
286368
log_debug(f"Callsite at 0x{mlil.address:x} is not a call, skipping")
287369
continue
@@ -292,6 +374,8 @@ def create_types(self):
292374
# funny enough `not <void var>` will return `True`
293375
if go_data_type is None:
294376
continue
377+
if param in already_parsed:
378+
log_debug(f"Skipping already parsed at 0x{param:x}")
295379

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

405+
self.bv.add_user_data_ref(
406+
gotype.resolved_name_addr,
407+
gotype.address_off('nameOff')
408+
)
409+
321410
name_datavar = self.bv.get_data_var_at(gotype.resolved_name_addr)
322411
name_datavar.name = f"{go_data_type.name}_name"
412+
already_parsed.add(param)
323413
created += 1
324414

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

330420

421+
class RunAll(bn.plugin.BackgroundTaskThread):
422+
def __init__(self, bv):
423+
super().__init__(NAME, True)
424+
self.bv = bv
425+
self.analysis = []
426+
self.analysis.append(FunctionRenamer(bv))
427+
self.analysis.append(FunctionCommenter(bv))
428+
self.analysis.append(TypeParser(bv))
429+
430+
def run(self):
431+
for analysis in self.analysis:
432+
analysis.start()
433+
analysis.join()
434+
log_info(f"Terminated all analysis")
435+
436+
331437
def rename_functions(bv):
332438
helper = FunctionRenamer(bv)
333439
return helper.start()
@@ -338,9 +444,16 @@ def create_types(bv):
338444
return helper.start()
339445

340446

341-
def parse_go_file(bv):
342-
fr = FunctionRenamer(bv)
343-
fr.start()
344-
fr.join()
345-
tp = TypeParser(bv)
346-
return tp.start()
447+
def print_files(bv):
448+
helper = PrintFiles(bv)
449+
return helper.start()
450+
451+
452+
def comment_functions(bv):
453+
helper = FunctionCommenter(bv)
454+
return helper.start()
455+
456+
457+
def parse_go_file(bv: bn.BinaryView):
458+
ra = RunAll(bv)
459+
return ra.start()

plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"python3"
1010
],
1111
"description": "BinaryNinja plugin to parse GoLang binaries and restore some information, like function names and type information",
12-
"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.",
12+
"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.",
1313
"license": {
1414
"name": "MIT",
1515
"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."
@@ -24,6 +24,6 @@
2424
"Windows": "no special instructions, package manager is recommended",
2525
"Linux": "no special instructions, package manager is recommended"
2626
},
27-
"version": "1.1",
27+
"version": "1.2",
2828
"minimumbinaryninjaversion": 3946
2929
}

0 commit comments

Comments
 (0)