<repo_name>q1f3/jeb-golang-analyzer<file_sep>/README.md
# JEB Decompiler's scripts to analyze Golang executables.

## Information

Main: Analyzer.py

The script was tested primarily on Go 1.13, most features should work on Go >= 1.5.
You might want to deactivate specific modules on older Go versions (see globals in Analyzer.py).

Support Modules:

- FunctionsFinder: recover routines entry points and rename them
- StringsBuilder: build proper strings
- TypesBuilder: recover types, and import them in JEB
- DuffDevicesFinder: rename routines for zeroing/copying memory
- PointerAnalyzer: improve pointer naming

## References

Blog: https://www.pnfsoftware.com/blog/analyzing-golang-executables/

How to run JEB scripts: https://www.pnfsoftware.com/jeb/manual/dev/introducing-jeb-extensions/#executing-scripts

## Version
  
Version 1.0 - Oct 2019 (<NAME> - PNF Software - <EMAIL>)
<file_sep>/FunctionsFinder.py
from com.pnfsoftware.jeb.core.units.code import EntryPointDescription
from com.pnfsoftware.jeb.core.units.code.asm.analyzer import INativeCodeAnalyzer

from Commons import PcLineTable, locatePclntab

"""
Recover function entry points from pclntab structure, and disassemble them.

If the binary is stripped, name the functions with the symbols provided in the structure.

Also, print hints when special routines are found (see ROUTINES_OF_INTEREST).

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""

# name -> point of interest
ROUTINES_OF_INTEREST = {'runtime.GOROOT': 'references Go root path of developer\'s machine (sys.DefaultGoroot)', \
                        'runtime.Version': 'references Go version (sys.TheVersion)', \
                        'runtime.schedinit': 'references Go version (sys.TheVersion)'}

class FunctionsFinder():

  def __init__(self, golangAnalyzer):
    self.golangAnalyzer = golangAnalyzer
    self.nativeCodeAnalyzer = self.golangAnalyzer.nativeCodeAnalyzer
    self.labelManager = self.golangAnalyzer.codeUnit.getCodeModel().getLabelManager()

  def run(self, pclntab, rename):
    global ROUTINES_OF_INTEREST

    for myFunc in pclntab.functionSymbolTable.values():
      self.nativeCodeAnalyzer.enqueuePointerForAnalysis(EntryPointDescription(myFunc.startPC), INativeCodeAnalyzer.PERMISSION_FORCEFUL)
      if rename:
        self.labelManager.setLabel(myFunc.startPC, myFunc.name, True, True, False)

    self.stats = '%d function entry points enqueued %s' % (len(pclntab.functionSymbolTable.values()), '(and renamed)' if rename else '')        
    print('> %s: %s' % (self.__class__.__name__, self.stats))
    print('> %s: running disassembler...' % self.__class__.__name__),
    self.nativeCodeAnalyzer.analyze()
    print('OK')

    # notifications for routines of interest
    for rtnName in ROUTINES_OF_INTEREST:
      rtnAddr = self.labelManager.resolveLabel(rtnName)
      if rtnAddr != None:
        print(' > point of interest: routine %s (0x%x): %s' % (rtnName, rtnAddr, ROUTINES_OF_INTEREST[rtnName]))

  def getStats(self):
    return self.stats
<file_sep>/DuffDevicesFinder.py
from com.pnfsoftware.jeb.core.units.codeobject import ProcessorType
from com.pnfsoftware.jeb.core.units.code.asm.items import INativeInstructionItem

"""
Identify routines for zeroing/copying memory, generated as Duff devices by Golang compiler.

Only the highest routine has a proper symbol; unnamed routines will be created for each 
other entry points. This script takes care of renaming those, with 'duff_zero/copy_N', where
N is the number of zeroed/copied bytes.

See SUPPORTED_ARCHITECTURE.

Reference: /src/runtime/mkduff.go

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""
DEBUG_MODE = False
DUFF_ZERO_NAME = 'duff_zero'
DUFF_COPY_NAME = 'duff_copy'

class InstructionSled():
  '''
    Serie of assembly instructions that can be repeated, ending with a specific instruction.
    (instructions are defined by their assembly mnemonics). Also, sleds can begin with prefixes.

    For example:
      - ['mov', 'lea', 'mov', 'lea', 'ret'] is a sled with pattern ['mov', 'lea'] 
        and ending instruction 'ret'
      - ['mov', 'mov', 'lea', 'mov', 'lea', 'mov', 'lea', 'ret'] is a sled with 
        pattern ['mov', 'lea'], prefix ['mov', 'mov', 'lea'], ending instruction 'ret'
  '''
  def __init__(self, name, pattern, endingMnemonic, sizeOfModifiedMemory):
    self.name = name
    self.pattern = pattern # list of mnemonics
    self.endingMnemonic = endingMnemonic
    self.sizeOfModifiedMemory = sizeOfModifiedMemory # modified by *each* pattern
    self.prefixPatterns = list() # list of (pattern, sizeOfModifiedMemory)

  def addPrefix(self, pattern, sizeOfModifiedMemory):
     self.prefixPatterns.append((pattern, sizeOfModifiedMemory))

  def matchesMemory(self, codeUnit, startAddr):
    '''
      Checks memory at the given address matches the sled.

      Return (boolean, total number of bytes zeroed/copied by sled)
    '''
    sledIndex = 0
    sledCounter = 0 # number of times sled is present
    curItem = codeUnit.getNativeItemAt(startAddr)
    prefixModifiedMemory = 0

    # check prefixes: jump over it if present
    if len(self.prefixPatterns) != 0:
      for (prefix, prefixSizeOfModifiedMemory) in self.prefixPatterns:

        curItemPrefix = curItem
        prefixIndex = 0
        while prefixIndex < len(prefix) and isinstance(curItemPrefix, INativeInstructionItem):
          curMnemonic = curItemPrefix.getInstruction().getMnemonic().lower()
          if curMnemonic != prefix[prefixIndex].lower():
            break
          prefixIndex += 1
          curItemPrefix = codeUnit.getNativeItemAt(curItemPrefix.getEnd())

        if prefixIndex == len(prefix):
          # prefix match
          prefixModifiedMemory = prefixSizeOfModifiedMemory
          curItem = curItemPrefix
          break

    # check pattern
    while isinstance(curItem, INativeInstructionItem):
      curMnemonic = curItem.getInstruction().getMnemonic().lower()
      if curMnemonic == self.endingMnemonic:
        return sledCounter != 0, sledCounter * self.sizeOfModifiedMemory + prefixModifiedMemory

      if curMnemonic != self.pattern[sledIndex].lower():
        return False, 0

      sledIndex = (sledIndex + 1) % len(self.pattern)

      if sledIndex == 0:
        sledCounter += 1
      curItem = codeUnit.getNativeItemAt(curItem.getEnd())

    return False, 0

class DuffDevicesFinder():
  SUPPORTED_ARCHITECTURE = [ProcessorType.X86, ProcessorType.X86_64, ProcessorType.ARM]

  def __init__(self, golangAnalyzer):
    self.nativeCodeAnalyzer = golangAnalyzer.nativeCodeAnalyzer
    self.codeContainerUnit = golangAnalyzer.codeContainerUnit
    self.codeUnit = golangAnalyzer.codeUnit
    self.labelManager = golangAnalyzer.codeUnit.getCodeModel().getLabelManager()
    self.identifiedRoutines = 0

  def run(self):
    global DEBUG_MODE, DUFF_ZERO_NAME, DUFF_COPY_NAME

    if self.codeUnit.getProcessor().getType() not in self.SUPPORTED_ARCHITECTURE:
      print('> WARNING: Duff device identifier not supported for this architecture (supported %s)' % self.SUPPORTED_ARCHITECTURE)
      return
      
    print('> %s: finding memory zero/copy routines...' % self.__class__.__name__),

    sleds = list()
    if self.codeUnit.getProcessor().getType() == ProcessorType.X86:
      sleds.append(InstructionSled(DUFF_ZERO_NAME, ['stosd'], 'ret', 4))
      sleds.append(InstructionSled(DUFF_COPY_NAME, ['mov','add','mov','add'], 'ret', 4))

    elif self.codeUnit.getProcessor().getType() == ProcessorType.X86_64:
      zero_sled = InstructionSled(DUFF_ZERO_NAME, ['movups','movups','movups','movups','lea'], 'ret', 64)
      # code can branch directly on one of the movups
      zero_sled.addPrefix(['movups','lea'], 16)
      zero_sled.addPrefix(['movups','movups','lea'], 32)
      zero_sled.addPrefix(['movups','movups','movups','lea'], 48)
      sleds.append(zero_sled)
      sleds.append(InstructionSled(DUFF_COPY_NAME, ['movups','add','movups','add'], 'ret', 16))

    elif self.codeUnit.getProcessor().getType() == ProcessorType.ARM:
      sleds.append(InstructionSled(DUFF_ZERO_NAME, ['str'], 'add', 4))
      sleds.append(InstructionSled(DUFF_COPY_NAME, ['ldr','str'], 'add', 4))

    for routine in self.codeUnit.getInternalMethods():
      routineCFG = routine.getData().getCFG()

      for sled in sleds:
        match, modifiedBytes = sled.matchesMemory(self.codeUnit, routine.getData().getMemoryAddress())
        if match:
          if DEBUG_MODE:
            print('> sled detector: %s routine matches sled %s (%d modified bytes)' % (routine.getAddress(), sled.name, modifiedBytes))
          self.labelManager.setLabel(routine.getData().getMemoryAddress(), '%s_%d' % (sled.name, modifiedBytes), True, True, False)
          self.identifiedRoutines+=1

    print('OK (%d routines identified)' % self.identifiedRoutines)



<file_sep>/StringsBuilder.py
from com.pnfsoftware.jeb.core.units import WellKnownUnitTypes
from com.pnfsoftware.jeb.core.units.codeobject import CodeObjectUnitUtil, ProcessorType
from com.pnfsoftware.jeb.core.units.code.asm.items import INativeDataItem, INativeStringItem, INativeInstructionItem
from com.pnfsoftware.jeb.core.units.code.asm.type import StringType
from com.pnfsoftware.jeb.core.units.code.asm.processor import IInstructionOperandGeneric, IInstructionOperandCMA

import string
import os

"""
Define strings referenced by Golang's StringHeader structures. 
(see https://golang.org/pkg/reflect/#StringHeader)

Strings are stored as a series of UTF8 bytes *without* null-terminator. 
Terminator's abscence makes usual strings recognition algorithms fail.

By default, three heuristics are used:

- search for specific assembly patterns building StringHeader structure 
(for x86/x64, ARM, MIPS, see searchAssemblyPatterns()

- search in memory for data blobs looking like a StringHeader structure
(see stringHeaderBruteForceSearch())

- linearly scan memory for series of UTF-8 characters
(two implementations: linearSweepItemBased() and linearSweep()) 

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""

# (de)activate heuristics
HEUR_PATTERN_MATCHING         = True
HEUR_STRING_HEADER_BRUTEFORCE = True
HEUR_LINEAR_SWEEP_DATA_ITEM   = True
HEUR_LINEAR_SWEEP             = True

DEBUG_MODE = False

class StringsBuilder():

  def __init__(self, golangAnalyzer):
    self.nativeCodeAnalyzer = golangAnalyzer.nativeCodeAnalyzer
    self.codeContainerUnit = golangAnalyzer.codeContainerUnit
    self.codeUnit = golangAnalyzer.codeUnit
    self.typeManager = golangAnalyzer.typeManager
    self.referenceManager = golangAnalyzer.codeUnit.getCodeModel().getReferenceManager()
    self.ptrSize = golangAnalyzer.pclntab.ptrSize
    self.possibleStrings = list() # contains tuples: [string start addr, string size]
    self.buildStringsCounter = 0 # for stats
    self.debugLogs = ''

  def run(self):
    global HEUR_PATTERN_MATCHING, HEUR_LINEAR_SWEEP_DATA_ITEM, HEUR_STRING_HEADER_BRUTEFORCE, HEUR_LINEAR_SWEEP, DEBUG_MODE
      
    print('> %s: building strings...' % self.__class__.__name__),

    if HEUR_PATTERN_MATCHING:
      self.searchAssemblyPatterns()
      self.createFoundStrings(heuristic='HEUR_PATTERN_MATCHING')

    if HEUR_STRING_HEADER_BRUTEFORCE:
      self.stringHeaderBruteForceSearch()
      self.createFoundStrings(heuristic='HEUR_STRING_HEADER_BRUTEFORCE')

    if HEUR_LINEAR_SWEEP_DATA_ITEM:
      self.linearSweepItemBased()
      self.createFoundStrings(heuristic='HEUR_LINEAR_SWEEP_DATA_ITEM')

    if HEUR_LINEAR_SWEEP:
      self.linearSweep()
      self.createFoundStrings(heuristic='HEUR_LINEAR_SWEEP')
    
    if DEBUG_MODE:
      self.printDebugLogs()

    print('OK (%s)' % self.getStats())

  def searchAssemblyPatterns(self):
    if self.codeUnit.getProcessor().getType() == ProcessorType.X86 \
      or self.codeUnit.getProcessor().getType() == ProcessorType.X86_64:
      self.searchX86Patterns()
    elif self.codeUnit.getProcessor().getType() == ProcessorType.ARM:
      self.searchARMPatterns()
    elif self.codeUnit.getProcessor().getType() == ProcessorType.MIPS:
      self.searchMIPSPatterns()
    else:
      print('> %s: pattern matching is not implemented for this architecture' % self.__class__.__name__)

  def createFoundStrings(self, heuristic='UNKNOWN'):
    global DEBUG_MODE
    for foundString in self.possibleStrings:
      stringAddr = foundString[0]
      stringSize = foundString[1]

      curItem = self.codeUnit.getNativeItemAt(stringAddr)
      if isinstance(curItem, INativeStringItem):
        continue

      if not self.checkPossibleString(stringAddr, stringSize):
        print('> %s: heur %s false positive at %x' % (self.__class__.__name__, heuristic, stringAddr))
        continue

      if not self.codeUnit.setStringAt(stringAddr, stringAddr + stringSize, StringType.UTF8_NONZERO, 1, -1):
        print('> %s: warning: failed to define string at %x' % (self.__class__.__name__, stringAddr))
      else:
        if DEBUG_MODE:
          self.debugLogs += '> heur %s: defined str %x (size:%d)\n' % (heuristic, stringAddr, stringSize)
        self.buildStringsCounter+=1

    self.possibleStrings = list()

  def printDebugLogs(self):
    filePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'str_debug_log.txt')
    f = open(filePath, 'w')
    f.write(self.debugLogs)
    f.close()

  def searchMIPSPatterns(self):
    '''
      LUI       REG, A
      ADDIU     REG, REG, B       // STRING_ADDRESS
      SW        REG, X($sp)
      LI        REG, STRING_SIZE
      SW        REG, Y($sp)
    '''
    for routine in self.codeUnit.getInternalMethods():
      routineCFG = routine.getData().getCFG()
      for bb in routineCFG.getBlocks():
        i = 0
        while i < bb.size() - 5: # at least four following instructions

          # important: get2 for addressable instructions (to handle PC-relative addressing)
          insn = bb.get2(i) 
          nextInsn = bb.get2(i+1)
          nextNextInsn = bb.get2(i+2)
          nextNextNextInsn = bb.get2(i+3)
          nextNextNextNextInsn = bb.get2(i+4)

          # check first three instructions: LUI REG, A / ADDIU REG, REG, B / SW REG, X($sp)
          if insn.getMnemonic().lower() == 'lui' \
           and insn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_IMM \
           and nextInsn.getMnemonic().lower() == 'addiu' \
           and nextInsn.getOperands()[0] == nextInsn.getOperands()[1] \
           and nextInsn.getOperands()[0] == insn.getOperands()[0] \
           and nextNextInsn.getMnemonic().lower() == 'sw' \
           and nextNextInsn.getOperands()[0] == insn.getOperands()[0]:
           a = (insn.getOperands()[1].getOperandValue() << 16) & 0xFFFFFFFF
           b = nextInsn.getOperands()[2].getOperandValue() & 0xFFFFFFFF
           stringAddr = (a + b) & 0xFFFFFFFF
          else:
            i += 1
            continue

          # check next two instructions: LI REG, STRING_SIZE / SW REG, Y($sp)
          if nextNextNextInsn.getMnemonic().lower() == 'li' \
           and nextNextNextInsn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_IMM \
           and nextNextNextNextInsn.getMnemonic().lower() == 'sw' \
           and nextNextNextNextInsn.getOperands()[0] == nextNextNextInsn.getOperands()[0]:
           stringSize = nextNextNextInsn.getOperands()[1].getOperandValue()
          else:
            i += 1
            continue

          if not self.checkPossibleString(stringAddr, stringSize):
            i += 1
            continue
          
          # everything is fine, register string
          self.possibleStrings.append([stringAddr, stringSize])
          i += 1

  def searchARMPatterns(self):
    '''
        LDR       REG, [PC, IMM]  // string address
        STR       REG, [SP, X]
        MOV       REG, STRING_SIZE
        STR       REG, [SP, Y]
    '''
    for routine in self.codeUnit.getInternalMethods():
      routineCFG = routine.getData().getCFG()
      for bb in routineCFG.getBlocks():
        i = 0
        while i < bb.size() - 4: # at least three following instructions

          # important: get2 for addressable instructions (to handle PC-relative addressing)
          insn = bb.get2(i) 
          nextInsn = bb.get2(i+1)
          nextNextInsn = bb.get2(i+2)
          nextNextNextInsn = bb.get2(i+3)

          # check first two instructions: LDR REG, [PC, IMM] / STR REG, [SP, X]
          if insn.getMnemonic().lower() == 'ldr' \
           and insn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_LIST \
           and 'pc' in insn.toString().lower() \
           and nextInsn.getMnemonic().lower() == 'str' \
           and nextInsn.getOperands()[0] == insn.getOperands()[0]:
           stringAddr = self.nativeCodeAnalyzer.memory.readInt(insn.getOperands()[1].getOperandValue(insn.getOffset()))
          else:
            i += 1
            continue
          
           # check last two instructions:  MOV REG, STRING_SIZE / STR REG, [SP, Y]
          if nextNextInsn.getMnemonic().lower() == 'mov' \
           and nextNextInsn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_IMM \
           and nextNextNextInsn.getMnemonic().lower() == 'str' \
           and nextNextNextInsn.getOperands()[0] == nextNextInsn.getOperands()[0]:
            stringSize = nextNextInsn.getOperands()[1].getOperandValue()
          else:
            i += 1
            continue

          if not self.checkPossibleString(stringAddr, stringSize):
            i += 1
            continue
          
          # everything is fine, register string
          self.possibleStrings.append([stringAddr, stringSize])
          i += 1

  def searchX86Patterns(self):
    '''
      Pattern 1 (Go >= 1.8, x86/x64):

        LEA REG, [STRING_ADDR]
        MOV [SP+X], REG
        MOV [SP+Y], STRING_SIZE

      Pattern 2 (Go == 1.7, x86): 

        MOV REG, [STRING_ADDR]
        MOV [SP+X], REG
        MOV [SP+Y], STRING_SIZE
    '''
    for routine in self.codeUnit.getInternalMethods():
      routineCFG = routine.getData().getCFG()
      for bb in routineCFG.getBlocks():
        i = 0
        while i < bb.size() - 3: # at least two following instructions

          # important: get2 for addressable instructions (to handle x64 RIP-relative addressing)
          insn = bb.get2(i) 
          nextInsn = bb.get2(i+1)
          nextNextInsn = bb.get2(i+2)

          # check first instruction: LEA REG, [STRING_ADDR]
          firstInsnOk = False
          if insn.getMnemonic().lower() == 'lea':
            if self.codeUnit.getProcessor().getType() == ProcessorType.X86:
              if insn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_CMA \
                              and insn.getOperands()[1].getMemoryBaseRegister() == -1 \
                              and insn.getOperands()[1].getMemoryIndexRegister() == -1 \
                              and insn.getOperands()[1].getMemoryDisplacement() != 0:
                stringAddr = insn.getOperands()[1].getMemoryDisplacement() & 0xFFFFFFFF
                firstInsnOk = True
            elif self.codeUnit.getProcessor().getType() == ProcessorType.X86_64:
              if insn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_CMA \
                and insn.getOperands()[1].getMemoryIndexRegister() == -1 \
                and insn.getOperands()[1].getMemoryDisplacement() != 0 \
                and 'rip+' in insn.toString().lower(): # RIP-based addressing on x64
                stringAddr = nextInsn.getOffset() + (insn.getOperands()[1].getMemoryDisplacement() & 0xFFFFFFFF)
                firstInsnOk = True
          # alternatively, check first instruction: MOV REG, STRING_ADDR
          elif insn.getMnemonic().lower() == 'mov' \
              and insn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_IMM:
              stringAddr = insn.getOperands()[1].getOperandValue() & 0xFFFFFFFF
              firstInsnOk = True

          if not firstInsnOk:
            i += 1
            continue

          reg = insn.getOperands()[0]

          # check second instruction: MOV [SP+X], REG (same REG)
          # check third instruction : MOV [SP+Y], STRING_SIZE
          if not(nextInsn.getMnemonic().lower() == 'mov' \
                and nextNextInsn.getMnemonic().lower() == 'mov' \
                and nextInsn.getOperands()[0].getOperandType() == IInstructionOperandGeneric.TYPE_CMA \
                and nextInsn.getOperands()[1] == reg \
                and nextNextInsn.getOperands()[0].getOperandType() == IInstructionOperandGeneric.TYPE_CMA \
                and nextNextInsn.getOperands()[1].getOperandType() == IInstructionOperandGeneric.TYPE_IMM):
            i += 1
            continue

          stringSize = nextNextInsn.getOperands()[1].getOperandValue()

          if not self.checkPossibleString(stringAddr, stringSize):
            i += 1
            continue
          
          # everything is fine, register string
          self.possibleStrings.append([stringAddr, stringSize])
          i += 1

  def checkPossibleString(self, stringAddr, stringSize, checkPrintable = False):
    '''
      Check given address and size can correspond to a string.
    '''
    if stringSize <= 0 or stringSize > 0x1000:
      return False

    # string address points to allocated memory
    startAddress = self.codeUnit.getVirtualImageBase() & 0xFFFFFFFF
    endAddress = (startAddress + self.codeUnit.getImageSize()) & 0xFFFFFFFF
    if stringAddr < startAddress or stringAddr >= endAddress:
      return False

    # string address do not point to an instruction
    alreadyDefinedItem = self.codeUnit.getCodeModel().getItemAt(stringAddr)
    if alreadyDefinedItem != None and isinstance(alreadyDefinedItem, INativeInstructionItem):
      return False

    # string characters are printable (i.e. ASCII)
    # (in general, Golang strings are not necessarily printable (UTF-8)
    # this serves to limit false positives)
    if checkPrintable:
      count = 0
      while count < stringSize:
        curByte = self.nativeCodeAnalyzer.getMemory().readByte(stringAddr + count) & 0xFF
        if chr(curByte) not in string.printable:
          return False
        count += 1

    # string is UTF-8 decodable
    count = 0
    buildString = ''
    while count < stringSize:
      curByte = self.nativeCodeAnalyzer.getMemory().readByte(stringAddr + count) & 0xFF
      buildString += chr(curByte)
      count += 1
    try:
        decoded = buildString.decode('utf-8')
    except UnicodeError:
        return False

    return True

  def getStringsMemoryRange(self):
    '''
      Return (start addr, end addr) where strings might be located

      Relies on default section names for data, if present; 
      otherwise, the whole binary.
    '''
    dataSections = list()
    if self.codeContainerUnit.getFormatType() == WellKnownUnitTypes.typeWinPe:
      dataSectionNames = ['.rdata', '.data']
      for dataSectionName in dataSectionNames:
        dataSection = CodeObjectUnitUtil.findSegmentByName(self.codeContainerUnit, dataSectionName)
        if dataSection != None:
          dataSections.append(dataSection)
    elif self.codeContainerUnit.getFormatType() == WellKnownUnitTypes.typeLinuxElf:
      dataSectionNames = ['.rodata', '.data']
      for dataSectionName in dataSectionNames:
        dataSection = CodeObjectUnitUtil.findSectionByName(self.codeContainerUnit, dataSectionName)
        if dataSection != None:
          dataSections.append(dataSection)

    if len(dataSections) != 0:
      rangeStartAddress = self.codeUnit.getVirtualImageBase() + min([dataSection.getOffsetInMemory() for dataSection in dataSections])
      rangeEndAddress = self.codeUnit.getVirtualImageBase() + max([dataSection.getOffsetInMemory() + dataSection.getSizeInMemory() for dataSection in dataSections])
    else:
      print('> %s: cannot find string section, searching on all binary...' % self.__class__.__name__)
      rangeStartAddress = self.codeUnit.getVirtualImageBase() & 0xFFFFFFFF
      rangeEndAddress = (rangeStartAddress + self.codeUnit.getImageSize()) & 0xFFFFFFFF

    return (rangeStartAddress, rangeEndAddress)

  def stringHeaderBruteForceSearch(self):
    '''
      Search for StringHeader structures stored in memory.

      Note that we restrain the search to *printable* StringHeader values.
    '''
    rangeStartAddress, rangeEndAddress = self.getStringsMemoryRange()
    for curAddr in range(rangeStartAddress, rangeEndAddress):
      possibleData = self.nativeCodeAnalyzer.getMemory().readInt(curAddr) & 0xFFFFFFFF
      possibleLen = self.nativeCodeAnalyzer.getMemory().readInt(curAddr + self.ptrSize) & 0xFFFFFFFF

      if possibleLen <= 4 or possibleLen >= 100:
        # try to avoid false positives
        continue

      if self.checkPossibleString(possibleData, possibleLen, checkPrintable = True):
        self.possibleStrings.append([possibleData, possibleLen])
        self.codeUnit.setDataTypeAt(curAddr, self.typeManager.getType('StringHeader'))
        self.referenceManager.recordReference(possibleData, curAddr)

  def linearSweepItemBased(self):
    '''
      Search for 1-byte data items (created by the LEA-like instruction)
      starting a series of *printable* UTF-8 characters. 

      We then create a new string, spanning until the next item.

      This serves to define strings whose instantiation was missed by previous heuristics.
    '''
    rangeStartAddress, rangeEndAddress = self.getStringsMemoryRange()
    itemsMap = self.codeUnit.getNativeItemsOver(rangeStartAddress, rangeEndAddress - rangeStartAddress)
    for entry in itemsMap.entrySet():
      itemAddr = entry.getKey()
      item = entry.getValue()

      if isinstance(item, INativeDataItem) and item.getType().getSize() == 1:
        firstByte = self.nativeCodeAnalyzer.getMemory().readByte(itemAddr) & 0xFF
        if firstByte != 0:
          curAddr = itemAddr+1

          # append next bytes -- stop on next item
          while (curAddr < rangeEndAddress) and (not self.codeUnit.getNativeItemAt(curAddr)):
            curByte = self.nativeCodeAnalyzer.getMemory().readByte(curAddr) & 0xFF
            curAddr+=1

          possibleStringSize = curAddr - itemAddr
          if possibleStringSize <= 2:
            # try to avoid false positives
            continue

          if self.checkPossibleString(itemAddr, possibleStringSize, checkPrintable = True):
            self.possibleStrings.append([itemAddr, possibleStringSize])

  def linearSweep(self):
    '''
      Search for series of *printable* UTF-8 chars contained in-between two string items.

      This serves to define unreferenced strings (but possibly with incorrect 
      length, due to the absence of terminators)
    '''
    rangeStartAddress, rangeEndAddress = self.getStringsMemoryRange()
    lastItemWasString = False
    stringStartAddr = 0
    for curAddr in range(rangeStartAddress, rangeEndAddress):
      curItem = self.codeUnit.getCodeModel().getItemOver(curAddr)
      if curItem:
        if isinstance(curItem, INativeStringItem):
          if stringStartAddr != 0 and lastItemWasString:
            possibleStringSize = curAddr - stringStartAddr
            if possibleStringSize > 2: # try to avoid false positives
              if self.checkPossibleString(stringStartAddr, curAddr - stringStartAddr, checkPrintable = True):
                self.possibleStrings.append([stringStartAddr, curAddr - stringStartAddr])
          lastItemWasString = True
        else:
          lastItemWasString = False
        stringStartAddr = 0
      else:
        if stringStartAddr == 0 and lastItemWasString:
          stringStartAddr = curAddr

  def getStats(self):
    return '%d built strings' % self.buildStringsCounter<file_sep>/Analyzer.py
from com.pnfsoftware.jeb.core.units import WellKnownUnitTypes, INativeCodeUnit
from com.pnfsoftware.jeb.core.units.codeobject import ProcessorType, CodeObjectUnitUtil
from java.lang import Runnable
from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext

import os

import JebUtils
from StringsBuilder import StringsBuilder
from FunctionsFinder import FunctionsFinder
from TypesBuilder import TypesBuilder
from DuffDevicesFinder import DuffDevicesFinder
from PointerAnalyzer import PointerAnalyzer
from Commons import StringHeader, SliceHeader, BitVector, ModuleData, PcLineTable, FunctionSymbolTableEntry, getPclntab, buildPclntab, getModuleDataList, buildModuleDataList

"""
JEB Decompiler's script to analyze Golang executables.
Reference: Blog: https://www.pnfsoftware.com/blog/analyzing-golang-executables/
Version 1.0 - Oct 2019 (<NAME> - PNF Software)

Modules:
  - FunctionsFinder: recover routines entry points and rename them
  - StringsBuilder: build proper strings
  - TypesBuilder: recover types, and import them in JEB
  - DuffDevicesFinder: rename routines for zeroing/copying memory
  - PointerAnalyzer: improve pointer naming

The script was tested primarily on Go 1.13, most features should work on Go >= 1.5.
You might want to deactivate specific modules on older Go versions (see globals below).
"""

# customize these values to (de)activate features
FIND_FUNCTIONS    = True
BUILD_STRINGS     = True
BUILD_TYPES       = True
FIND_DUFF_DEVICES = True
POINTER_ANALYZER  = True

# manually provide structures addresses if heuristic search fail
PCLNTAB_ADDR           = 0x0
FIRST_MODULE_DATA_ADDR = 0x0

class Analyzer(IScript):

  def run(self, clientContext):
    if not isinstance(clientContext, IGraphicalClientContext):
      print('This script must be run within a graphical client')
      return
    # run analyzer in separate thread
    clientContext.executeAsync('Golang analyzer...', GolangAnalyzerTask(clientContext))

class GolangAnalyzerTask(Runnable):
  SUPPORTED_FILE_FORMATS = [WellKnownUnitTypes.typeWinPe, WellKnownUnitTypes.typeLinuxElf]

  def __init__(self, clientContext):
    self.codeContainerUnit = None
    self.codeUnit = None
    self.nativeCodeAnalyzer = None
    self.typeManager = None
    self.pclntab = None
    self.moduleDataList = None
    self.moduleStats = dict() # module name -> stats
    self.getUnits(clientContext.getMainProject())

  def getUnits(self, mainProject):
    '''
      Retrieves the first supported executable file from JEB project
      and the corresponding child code unit, native code analyzer and type manager.
    '''
    for unit in mainProject.getLiveArtifact(0).getUnits(): # assume first artifact is the good one
      if unit.getFormatType() in self.SUPPORTED_FILE_FORMATS:
        self.codeContainerUnit = unit
        break
    if not self.codeContainerUnit:
      raise Exception('cannot find suitable code container unit (supported=%s)' % self.SUPPORTED_FILE_FORMATS)

    for unit in self.codeContainerUnit.getChildren():
      if isinstance(unit, INativeCodeUnit):
        self.codeUnit = unit
        break
    if not self.codeUnit:
      raise Exception('cannot find native code unit')

    self.nativeCodeAnalyzer = self.codeUnit.getCodeAnalyzer()
    self.typeManager = self.codeUnit.getTypeManager()

  def run(self):
    global FIND_FUNCTIONS, BUILD_STRINGS, BUILD_TYPES, FIND_DUFF_DEVICES, POINTER_ANALYZER

    print('>>> Golang Analyzer <<<')
    metadataFound = self.getMetadata()
    if not metadataFound:
      print('> error: cannot find metadata')
      return
    
    if FIND_FUNCTIONS:
      functionsFinder = FunctionsFinder(self)
      isStripped = False if CodeObjectUnitUtil.findSymbolByName(self.codeContainerUnit, 'runtime.pclntab') else True
      functionsFinder.run(self.pclntab, isStripped)
      self.moduleStats[functionsFinder.__class__.__name__] = functionsFinder.getStats()

    if BUILD_STRINGS:
      stringsBuilder = StringsBuilder(self)
      stringsBuilder.run()
      self.moduleStats[stringsBuilder.__class__.__name__] = stringsBuilder.getStats()

    if BUILD_TYPES:
      typesBuilder = TypesBuilder(self)
      typesBuilder.run()
      self.moduleStats[typesBuilder.__class__.__name__] = typesBuilder.getStats()

    if FIND_DUFF_DEVICES:
      duffDevicesFinder = DuffDevicesFinder(self)
      duffDevicesFinder.run()
    
    if POINTER_ANALYZER:
      ptrAnalyzer = PointerAnalyzer(self)
      ptrAnalyzer.run()

    self.printStats('log.txt')

  def getMetadata(self):
    '''
      Locate and parse needed metadata in memory, depending on the wanted features.

      Return True on success, False otherwise.
    '''
    global BUILD_TYPES

    # pclntab is needed for all modules,
    # because it provides pointer size
    pclntabParsing = self.parsePcntab()
    if pclntabParsing:
      print('> pclntab parsed (0x%X)' % self.pclntab.startAddr)
    else:
      print('> ERROR: cannot find pclntab -- suggestion: provide hardcoded address (Analyzer.PCLNTAB_ADDR)')
      return False

    self.importBasicTypes()

    # types-specific parsing
    if BUILD_TYPES:
      firstMdParsing = self.parseFirstModuleData()
      if firstMdParsing:
        print('> first module data parsed (0x%X)' % self.moduleDataList[0].startAddr)
      else:
        print('> ERROR: cannot find module data list -- suggestions: deactivate types building (Analyzer.BUILD_TYPES)' \
                      ' or provide hardcoded address (Analyzer.FIRST_MODULE_DATA_ADDR)')
        return False
    return True

  def parsePcntab(self):
    global PCLNTAB_ADDR

    if PCLNTAB_ADDR == 0x0:
      self.pclntab = getPclntab(self)
    else:
      print('> using provided address for pclntab (0x%X)' % PCLNTAB_ADDR)
      self.pclntab = buildPclntab(self, PCLNTAB_ADDR)
    if self.pclntab == None:
      return False
    return True

  def parseFirstModuleData(self):
    global FIRST_MODULE_DATA_ADDR
    if FIRST_MODULE_DATA_ADDR == 0x0:
      self.moduleDataList = getModuleDataList(self)
    else:
      print('> using provided address for first module data (0x%X)' % FIRST_MODULE_DATA_ADDR)
      self.moduleDataList = buildModuleDataList(self, FIRST_MODULE_DATA_ADDR)
    if self.moduleDataList == None or len(self.moduleDataList) == 0:
      return False
    return True

  def importBasicTypes(self):
    '''
      Built-in types that are needed by different modules.
    '''
    StringHeader.createJebStructure(self.typeManager, self.pclntab.ptrSize)
    SliceHeader.createJebStructure(self.typeManager, self.pclntab.ptrSize)
    BitVector.createJebStructure(self.typeManager, self.pclntab.ptrSize)
    ModuleData.createJebStructure(self.typeManager, self.pclntab.ptrSize)

  def printStats(self, file):
    STAT_SEPARATOR = '\n======================================\n'

    filePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), file)
    f = open(filePath, 'w')
  
    # generic stats
    statStr = '> General information:\n'
    generalInfo = False
    if self.pclntab != None:
      if len(self.pclntab.sourceFiles) != 0:
        statStr += '\n\t> source files (extracted from pclntab):\n'
        for sourceFile in sorted(self.pclntab.sourceFiles):
          statStr += '\t\t> %s\n' % sourceFile
        generalInfo = True
        statStr += STAT_SEPARATOR
    if not generalInfo:
      statStr = ''

    # module-specific stats
    for module in self.moduleStats:
      statStr += '> %s:\n%s' % (module, self.moduleStats[module])
      statStr += STAT_SEPARATOR

    f.write(statStr)
    f.close()
    print('> see logs (%s)' % filePath)




<file_sep>/JebUtils.py
"""
JEB utils

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""

def readNullTerminatedString(memory, curAddress):
  return readString(memory, curAddress, 0)

def readString(memory, curAddress, maxLen):
  '''
    Read UTF-8 string from memory, using the provided maximum length if non zero, or until next null-byte.
  '''
  DEFAULT_MAX_LENGTH = 0x1000

  curChar = ''
  result = list()
  readLength = 0
  if maxLen == 0:
    maxLen = DEFAULT_MAX_LENGTH

  while curChar != 0x00 and readLength < maxLen:
    curChar = memory.readByte(curAddress) & 0xFF
    result.append(chr(curChar))
    curAddress+=1
    readLength+=1

  if curChar == 0x00:
    result = result[:-1]
    
  return ''.join(result).decode('utf-8')

def searchMemoryFor4BConstant(memory, constant, startAddres, endAddress):
  '''
    Return the address of the first occurence of 4-byte constant in given 
    memory range (assuming it is aligned on 4), or 0 if it could not be found.
  '''
  curAddress = startAddres
  while curAddress < endAddress:
    if memory.readInt(curAddress) & 0xFFFFFFFF == constant:
      return curAddress
    curAddress+=4
  return 0
<file_sep>/PointerAnalyzer.py
from com.pnfsoftware.jeb.core.units.code.asm.items import INativeDataItem, INativeStringItem

from Commons import readPtr

"""
Search for data items that could be pointers to routines/data items,
and rename them with meaningful names, if possible.

This module should be executed after functions discovery.

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""
DEBUG_MODE = False

class PointerAnalyzer():

  def __init__(self, golangAnalyzer):
    self.nativeCodeAnalyzer = golangAnalyzer.nativeCodeAnalyzer
    self.codeContainerUnit = golangAnalyzer.codeContainerUnit
    self.codeUnit = golangAnalyzer.codeUnit
    self.typeManager = golangAnalyzer.typeManager
    self.referenceManager = golangAnalyzer.codeUnit.getCodeModel().getReferenceManager()
    self.ptrSize = golangAnalyzer.pclntab.ptrSize
    self.labelManager = golangAnalyzer.codeUnit.getCodeModel().getLabelManager()
    self.memoryStartAddress = self.codeUnit.getVirtualImageBase()
    self.memorySize = self.codeContainerUnit.getLoaderInformation().getImageSize()
    self.pointersFound = 0

  def run(self):
    itemsMap = self.codeUnit.getNativeItemsOver(self.memoryStartAddress, self.memorySize)
    for entry in itemsMap.entrySet():
      itemAddr = entry.getKey()
      item = entry.getValue()

      if isinstance(item, INativeDataItem) and self.hasDefaultName(item):
        possiblePtr, curAddr = readPtr(self.nativeCodeAnalyzer.getMemory(), self.ptrSize, itemAddr)
        if not self.isInMemoryRange(possiblePtr):
          continue

        if self.renamePtrToRoutines(item, itemAddr, possiblePtr):
          self.pointersFound += 1
          continue
        if self.renamePtrToNamedDataItems(item, itemAddr, possiblePtr):
          self.pointersFound += 1
          continue

    print('> %s: %d pointers renamed' % (self.__class__.__name__, self.pointersFound))

  def hasDefaultName(self, item):
    name = item.getName(True)
    return name.startswith('gvar_') or name.startswith('ptr_gvar_')

  def isInMemoryRange(self, ptr):
    return ptr >= self.memoryStartAddress and ptr < (self.memoryStartAddress + self.memorySize) 

  def renamePtrToRoutines(self, item, itemAddr, possiblePtr):
    try:
      pointedRoutine = self.codeUnit.getInternalMethod(possiblePtr, True)
      if pointedRoutine != None:
        self.renameAndTypePtr(item, itemAddr, pointedRoutine, possiblePtr)
        return True
    except Exception as err: 
      pass
    return False

  def renamePtrToNamedDataItems(self, item, itemAddr, possiblePtr):
    global DEBUG_MODE
    try:
      targetItem = self.codeUnit.getCodeModel().getItemAt(possiblePtr)
      if targetItem != None and isinstance(targetItem, INativeDataItem) and not self.hasDefaultName(targetItem):
        self.renameAndTypePtr(item, itemAddr, targetItem, possiblePtr)
        return True
    except Exception as err: 
      pass
    return False

  def renameAndTypePtr(self, ptrItem, ptrAddr, targetItem, targetAddr):
    global DEBUG_MODE
    if DEBUG_MODE:
      print('ptr to named item: %x to %s' % (ptrAddr, targetItem))
    self.labelManager.setLabel(ptrAddr, 'ptr_%s' % targetItem.getName(True), True, False, True)
    self.referenceManager.recordReference(targetAddr, ptrAddr)
    # type pointer only if default 1-byte type
    if ptrItem.getType().getSize() == 1:
      self.codeUnit.setDataTypeAt(ptrAddr, self.typeManager.getType('void*'))
<file_sep>/Commons.py
from com.pnfsoftware.jeb.core.units.codeobject import CodeObjectUnitUtil
from com.pnfsoftware.jeb.core.units import WellKnownUnitTypes
from com.pnfsoftware.jeb.core.units.code.asm.memory import MemoryException
from com.pnfsoftware.jeb.core.units.code.asm.items import INativeStringItem

import JebUtils

"""
Go-specific utils and structures.

(This file is part of JEB Decompiler's scripts used to analyze Golang executables.)
"""
def getModuleDataList(golangAnalyzer):
  '''
    Locate module data in memory and return *a list* of parsed ModuleData object, or None on failure.

    IMPORTANT: for now, the returned list contains only the *first* module data. 
    (see modulesinit() in src\\runtime\\symtab.go for cases where several modules might be present)
  '''
  firstModuleDataAddr = locateFirstModuleData(golangAnalyzer)
  if firstModuleDataAddr == -1:
    return None
  return buildModuleDataList(golangAnalyzer, firstModuleDataAddr)

def buildModuleDataList(golangAnalyzer, firstModuleDataAddr):
  # FIXME: iterate over all module data
  memory = golangAnalyzer.nativeCodeAnalyzer.getMemory()
  ptrSize = golangAnalyzer.pclntab.ptrSize
  firstMD = ModuleData(golangAnalyzer, firstModuleDataAddr)
  if firstMD:
    firstMD.parse()
    firstMD.applyJebStructure()
  return [firstMD]

def locateFirstModuleData(golangAnalyzer):
    '''
      Return address of first module data structure in memory, or -1 if it cannot be found.
    '''
    # non-stripped binary
    if CodeObjectUnitUtil.findSymbolByName(golangAnalyzer.codeContainerUnit, 'runtime.firstmoduledata') != None:
      firstModuleDataAddress = CodeObjectUnitUtil.findSymbolByName(golangAnalyzer.codeContainerUnit, 'runtime.firstmoduledata').getSymbolRelativeAddress() \
                          + golangAnalyzer.codeUnit.getVirtualImageBase()
    else:
      # brute-force search over the whole binary
      startAddress = golangAnalyzer.codeUnit.getVirtualImageBase()
      endAddress = startAddress + golangAnalyzer.codeUnit.getImageSize()
      firstModuleDataAddress = moduleDataBruteForceSearch(golangAnalyzer, startAddress, endAddress)
    return firstModuleDataAddress

def moduleDataBruteForceSearch(golangAnalyzer, startAddress, endAddress):
  '''
    Search for a 'moduledata-looking' structure over the given range.
  '''
  MAGIC = golangAnalyzer.pclntab.startAddr # module data starts with ptr to pclntab
  curStartAddress = startAddress
  while curStartAddress < endAddress:
    try:
      possibleModuleDataAddr = JebUtils.searchMemoryFor4BConstant(golangAnalyzer.nativeCodeAnalyzer.memory, MAGIC, curStartAddress, endAddress)
      if possibleModuleDataAddr == 0:
        return -1
      if looksLikeModuleData(golangAnalyzer, possibleModuleDataAddr):
        return possibleModuleDataAddr
      curStartAddress = possibleModuleDataAddr + 4
    except MemoryException:
      break
  return -1

def looksLikeModuleData(golangAnalyzer, possibleModuleDataAddr):
  try:
    # 1- attempt to parse
    possibleModuleData = ModuleData(golangAnalyzer, possibleModuleDataAddr)
    possibleModuleData.parse()
    # 2- check minpc value (see moduledataverify1() in src\\runtime\\symtab.go)
    firstFunctionTableEntry = FunctionSymbolTableEntry(golangAnalyzer.pclntab, possibleModuleData.ftab.data)
    firstFunctionTableEntry.parse()
    if possibleModuleData.minpc != firstFunctionTableEntry.startPC:
      return False
  except MemoryException:
    return False
  return True

def getPclntab(golangAnalyzer):
  '''
    Locate pclntab in memory and return parsed PcLineTable object, or None on failure.
  '''
  pclntabAddress = locatePclntab(golangAnalyzer)
  if pclntabAddress == -1:
    return None
  return buildPclntab(golangAnalyzer, pclntabAddress)

def buildPclntab(golangAnalyzer, pclntabAddress):
  memory = golangAnalyzer.nativeCodeAnalyzer.getMemory()
  pclntab = PcLineTable(golangAnalyzer, pclntabAddress)
  if pclntab:
    pclntab.parse()
    
    # structures needed for pclntab parsing
    PcLineTable.createJebStructure(golangAnalyzer.typeManager)
    FunctionSymbolTableEntry.createJebStructure(golangAnalyzer.typeManager, pclntab.ptrSize) 
    
    pclntab.applyJebStructure()
  return pclntab

def locatePclntab(golangAnalyzer):
    '''
      Return address of pclntab structure in memory, or -1 if it cannot be found.

      Reference: golang.org/s/go12symtab
    '''
    # non-stripped binary
    if CodeObjectUnitUtil.findSymbolByName(golangAnalyzer.codeContainerUnit, 'runtime.pclntab') != None:
      pclntabAddress = CodeObjectUnitUtil.findSymbolByName(golangAnalyzer.codeContainerUnit, 'runtime.pclntab').getSymbolRelativeAddress() \
                          + golangAnalyzer.codeUnit.getVirtualImageBase()
    
    else:
      # PE: brute force search in .rdata. or in all binary if section not present
      if golangAnalyzer.codeContainerUnit.getFormatType() == WellKnownUnitTypes.typeWinPe:
        rdataSection = CodeObjectUnitUtil.findSegmentByName(golangAnalyzer.codeContainerUnit, '.rdata') # PE sections are JEB segments
        if rdataSection:
          startAddress = golangAnalyzer.codeUnit.getVirtualImageBase() + rdataSection.getOffsetInMemory()
          endAddress = startAddress + rdataSection.getSizeInMemory()
        else:
          print('> cannot find .rdata section (packed?), brute force search in memory...')
          startAddress = golangAnalyzer.codeUnit.getVirtualImageBase()
          endAddress = startAddress + golangAnalyzer.codeUnit.getImageSize()
        
        pclntabAddress = pclntabBruteForceSearch(golangAnalyzer.nativeCodeAnalyzer.getMemory(), startAddress, endAddress)

      # ELF: .gopclntab section if present, otherwise brute force search in all binary
      elif golangAnalyzer.codeContainerUnit.getFormatType() == WellKnownUnitTypes.typeLinuxElf:
        gopclntabSection = CodeObjectUnitUtil.findSectionByName(golangAnalyzer.codeContainerUnit, '.gopclntab')
        if gopclntabSection:
          pclntabAddress = golangAnalyzer.codeUnit.getVirtualImageBase() + gopclntabSection.getOffsetInMemory()
        else:
          print('> cannot find .gopclntab section (stripped?), brute force search in memory...')
          startAddress = golangAnalyzer.codeUnit.getVirtualImageBase()
          endAddress = startAddress + golangAnalyzer.codeUnit.getImageSize()
          pclntabAddress = pclntabBruteForceSearch(golangAnalyzer.nativeCodeAnalyzer.getMemory(), startAddress, endAddress)

    return pclntabAddress

def pclntabBruteForceSearch(memory, startAddress, endAddress):
  '''
    Search for a 'pclntab-looking' structure over the given range.
  '''
  MAGIC = 0xFFFFFFFB
  curStartAddress = startAddress
  while curStartAddress < endAddress:
    try:
      possiblePclntabAddr = JebUtils.searchMemoryFor4BConstant(memory, MAGIC, curStartAddress, endAddress)
      if possiblePclntabAddr == 0:
        return -1
      if looksLikePclntab(memory, possiblePclntabAddr):
        return possiblePclntabAddr
      curStartAddress = possiblePclntabAddr + 4
    except MemoryException:
      break
  return -1

def looksLikePclntab(memory, possiblePclntabAddr):
  '''
      Reference for header check: go12Init() in src\\debug\\gosym\\pclntab.go
    '''
  pcQuantum = memory.readByte(possiblePclntabAddr + 6)
  ptrSize = memory.readByte(possiblePclntabAddr + 7)
  return  memory.readByte(possiblePclntabAddr + 4) & 0xFF == 0 \
      and memory.readByte(possiblePclntabAddr + 5) & 0xFF == 0 \
      and pcQuantum in [1, 2, 4] \
      and ptrSize in [4, 8]
      
class PcLineTable(): 
  '''
    a.k.a. pclntab

    Reference:golang.org/s/go12symtab
  '''
  JEB_NAME = 'PcLineTableHeader'
  HEADER_SIZE = 8

  @staticmethod
  def createJebStructure(typeManager):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(PcLineTable.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(PcLineTable.JEB_NAME)
      typeManager.addStructureField(jebType, 'magic', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, '_', primitiveTypeManager.getExactIntegerBySize(1, False))
      typeManager.addStructureField(jebType, '__', primitiveTypeManager.getExactIntegerBySize(1, False))
      typeManager.addStructureField(jebType, 'minLC', primitiveTypeManager.getExactIntegerBySize(1, False))
      typeManager.addStructureField(jebType, 'ptrSize', primitiveTypeManager.getExactIntegerBySize(1, False))
      return jebType

  def __init__(self, golangAnalyzer, startAddr):
    self.startAddr = startAddr & 0xFFFFFFFF
    self.memory = golangAnalyzer.nativeCodeAnalyzer.getMemory()
    self.typeManager = golangAnalyzer.typeManager
    self.magic = 0
    self.minLC = 0 # "instruction size quantum", i.e. minimum length of an instruction code
    self.ptrSize = 0 # size in bytes of pointers and the predeclared "int", "uint", and "uintptr" types
    self.functionSymbolTable = dict() # pc -> FunctionSymbolTableEntry
    self.endPC = 0
    self.sourceFileTableAddress = 0
    self.sourceFiles = list()
    self.codeUnit = golangAnalyzer.codeUnit

  def applyJebStructure(self):
    primitiveTypeManager =  self.typeManager.getPrimitives()
    self.codeUnit.setDataAt(self.startAddr, self.typeManager.getType(PcLineTable.JEB_NAME), 'pclntab')
    self.codeUnit.setDataAt(self.startAddr + PcLineTable.HEADER_SIZE, primitiveTypeManager.getExactIntegerBySize(self.ptrSize, False), 'pclntab.func_entries')
    curAddr = self.startAddr + PcLineTable.HEADER_SIZE + self.ptrSize
    for i in range(len(self.functionSymbolTable.values())):
      self.codeUnit.setDataAt(curAddr, primitiveTypeManager.getExactIntegerBySize(self.ptrSize, False), 'pc%d' % i)
      curAddr+=self.ptrSize
      self.codeUnit.setDataAt(curAddr, primitiveTypeManager.getExactIntegerBySize(self.ptrSize, False), 'offset_func%d' % i)
      curAddr+=self.ptrSize

    for funcEntry in self.functionSymbolTable.values():
      funcEntry.applyJebStructure()
  
  def parse(self):
    curAddress = self.parseHeader()
    curAddress = self.parseSymbolFunctionTable(curAddress)
    self.parseSourceFileTable(curAddress)
    
  def parseSourceFileTable(self, curAddress):
    '''
      Basic parsing to extract paths, see pclntab.go
    '''
    self.sourceFileTableAddress = self.startAddr + (self.memory.readInt(curAddress) & 0xFFFFFFFF)
    curAddress += 4
    if self.sourceFileTableAddress != 0:
      numberOfFiles = self.memory.readInt(self.sourceFileTableAddress) & 0xFFFFFFFF
      curAddress = self.sourceFileTableAddress + 4 # !!
      for i in range(numberOfFiles):
        ptrToFilePath = (self.memory.readInt(curAddress) & 0xFFFFFFFF) + self.startAddr
        filePathItem = self.codeUnit.getCodeModel().getItemAt(ptrToFilePath)
        if isinstance(filePathItem, INativeStringItem):
          self.sourceFiles.append(filePathItem.getValue())
        curAddress += 4

  def parseSymbolFunctionTable(self, curAddress):
    functionSTEntries, curAddress = self.readNextField(curAddress)
    for i in range(0, functionSTEntries):
      pc, curAddress = self.readNextField(curAddress)
      funcOffset, curAddress = self.readNextField(curAddress)
      funcEntry = FunctionSymbolTableEntry(self, funcOffset + self.startAddr)
      funcEntry.parse()
      self.functionSymbolTable[pc] = funcEntry
    self.endPC, curAddress = self.readNextField(curAddress)
    return curAddress

  def readNextField(self, curAddress):
    val = self.memory.readInt(curAddress) & 0xFFFFFFFF if self.ptrSize == 4 else self.memory.readLong(curAddress) & 0xFFFFFFFFFFFFFFFF
    curAddress = curAddress + 4 if self.ptrSize == 4 else curAddress + 8
    return val, curAddress

  def parseHeader(self):
    '''
      Reference for header check: go12Init() in src\\debug\\gosym\\pclntab.go
    '''
    curAddress = self.startAddr
    self.magic = self.memory.readInt(curAddress) & 0xFFFFFFFF
    if self.magic != 0xFFFFFFFB:
      raise Exception('not the expected pclntab magic')
    curAddress+=4

    if self.memory.readByte(curAddress) & 0xFF != 0:
      raise Exception('wrong pclntab header')
    curAddress+=1
    if self.memory.readByte(curAddress) & 0xFF != 0:
      raise Exception('wrong pclntab header')
    curAddress+=1

    self.minLC = self.memory.readByte(curAddress) & 0xFF
    if self.minLC != 1 and self.minLC != 2 and self.minLC != 4:
      raise Exception('wrong pclntab header (strange minLC value: %d)' % self.minLC)
    curAddress+=1

    self.ptrSize = self.memory.readByte(curAddress) & 0xFF
    if self.ptrSize != 4 and self.ptrSize != 8:
      raise Exception('wrong pclntab header (strange ptrSize value: %d)' % self.ptrSize)
    curAddress+=1    
    return curAddress

class FunctionSymbolTableEntry():
  '''
    Reference: golang.org/s/go12symtab

    struct Func
    {
        uintptr        entry;  // start pc
        int32 name;         // name (offset to C string)
        int32 args;         // size of arguments passed to function
        int32 frame;        // size of function frame, including saved caller PC
        int32        pcsp;                // pcsp table (offset to pcvalue table)
        int32        pcfile;          // pcfile table (offset to pcvalue table)
        int32        pcln;                  // pcln table (offset to pcvalue table)
        int32        nfuncdata;          // number of entries in funcdata list
        int32        npcdata;          // number of entries in pcdata list
    };
  '''
  JEB_NAME = 'FunctionSymbolTableEntry'

  @staticmethod
  def createJebStructure(typeManager, ptrSize):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(FunctionSymbolTableEntry.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(FunctionSymbolTableEntry.JEB_NAME)
      typeManager.addStructureField(jebType, 'entry', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'name', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'args', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'frame', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'pcsp', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'pcfile', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'pcln', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'nfuncdata', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'npcdata', primitiveTypeManager.getExactIntegerBySize(4, False))
      return jebType

  def __init__(self, pclntab, startAddr):
    self.pclntab = pclntab
    self.memory = pclntab.memory
    self.startAddr = startAddr
    self.startPC = 0 # a.k.a. entry
    self.name = ''
    self.codeUnit = pclntab.codeUnit
    self.typeManager = pclntab.typeManager

  def applyJebStructure(self):
    self.codeUnit.setDataAt(self.startAddr, self.typeManager.getType(FunctionSymbolTableEntry.JEB_NAME), self.getSimpleName())

  def parse(self):
    curAddress = self.startAddr
    self.startPC, curAddress = self.pclntab.readNextField(curAddress)

    nameAddress = ((self.memory.readInt(curAddress) & 0xFFFFFFFF) + self.pclntab.startAddr)
    self.name = JebUtils.readNullTerminatedString(self.memory, nameAddress)

    # TODO: all fields

  def getSimpleName(self):
    return 'funcSymbol:%s' % self.name 


class StringHeader():
  '''
    Reference: https://golang.org/pkg/reflect/#StringHeader

    type StringHeader struct {
        Data uintptr
        Len  int
    }
  '''
  JEB_NAME = 'StringHeader'

  @staticmethod
  def createJebStructure(typeManager, ptrSize):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(StringHeader.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(StringHeader.JEB_NAME)
      typeManager.addStructureField(jebType, 'Data', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'Len', primitiveTypeManager.getExactIntegerBySize(ptrSize, True))
      return jebType

  def __init__(self, startAddr, memory, ptrSize):
    self.memory = memory
    self.ptrSize   = ptrSize
    self.startAddr = startAddr
    self.data = 0
    self.len = 0
    self.mySize = 2 * ptrSize

  def parse(self):
    curAddr = self.startAddr
    self.data, curAddr = readPtr(self.memory, self.ptrSize, curAddr)
    self.len, curAddr = readPtr(self.memory, self.ptrSize, curAddr)

class BitVector():
  '''
    Reference: src\\reflect\\type.go

    type bitvector struct {
      n        int32 // # of bits
      bytedata *uint8
    }
  '''
  JEB_NAME = 'BitVector'

  @staticmethod
  def createJebStructure(typeManager, ptrSize):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(BitVector.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(BitVector.JEB_NAME)
      typeManager.addStructureField(jebType, 'n', primitiveTypeManager.getExactIntegerBySize(4, False))
      typeManager.addStructureField(jebType, 'bytedata', typeManager.getType('void*'))
      return jebType

  def __init__(self, startAddr, memory, ptrSize):
    self.memory = memory
    self.ptrSize   = ptrSize
    self.startAddr = startAddr
    self.n = 0
    self.mySize = 4 + ptrSize

  def parse(self):
    curAddr = self.startAddr
    self.n = self.memory.readInt(curAddr) & 0xFFFFFFFF
    curAddr+=4
    self.bytedata, curAddr = readPtr(self.memory, self.ptrSize, curAddr)

class SliceHeader():
  '''
    Reference: https://golang.org/pkg/reflect/#SliceHeader

    type SliceHeader struct {
      Data uintptr
      Len  int
      Cap  int
    }
  '''
  JEB_NAME = 'SliceHeader'

  @staticmethod
  def createJebStructure(typeManager, ptrSize):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(SliceHeader.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(SliceHeader.JEB_NAME)
      typeManager.addStructureField(jebType, 'Data', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'Len', primitiveTypeManager.getExactIntegerBySize(ptrSize, True))
      typeManager.addStructureField(jebType, 'Cap', primitiveTypeManager.getExactIntegerBySize(ptrSize, True))
      return jebType
     
  def __init__(self, startAddr, memory, ptrSize):
    self.memory = memory
    self.ptrSize   = ptrSize
    self.startAddr = startAddr
    self.data = 0
    self._len = 0
    self.cap  = 0
    self.mySize = 3 * ptrSize

  def parse(self):
    curAddr = self.startAddr
    self.data, curAddr = readPtr(self.memory, self.ptrSize, curAddr)
    self._len, curAddr = readPtr(self.memory, self.ptrSize, curAddr)
    self.cap, curAddr = readPtr(self.memory, self.ptrSize, curAddr)

  def __str__(self):
    return 'data:%x - len:%d - cap:%d' % (self.data, self._len, self.cap)

class ModuleData():
  '''
    Reference: src\\runtime\\symtab.go

    type moduledata struct {
      pclntable    []byte
      ftab         []functab
      filetab      []uint32
      findfunctab  uintptr
      minpc, maxpc uintptr

      text, etext           uintptr
      noptrdata, enoptrdata uintptr
      data, edata           uintptr
      bss, ebss             uintptr
      noptrbss, enoptrbss   uintptr
      end, gcdata, gcbss    uintptr
      types, etypes         uintptr

      textsectmap []textsect
      typelinks   []int32 // offsets from types
      itablinks   []*itab

      ptab []ptabEntry

      pluginpath string
      pkghashes  []modulehash

      modulename   string
      modulehashes []modulehash

      hasmain uint8 // 1 if module contains the main function, 0 otherwise

      gcdatamask, gcbssmask bitvector

      typemap map[typeOff]*_type // offset to *_rtype in previous module

      bad bool // module failed to load and should be ignored

      next *moduledata
    }
  '''
  JEB_NAME = 'ModuleData'

  @staticmethod
  def createJebStructure(typeManager, ptrSize):
    primitiveTypeManager =  typeManager.getPrimitives()
    if typeManager.getType(ModuleData.JEB_NAME):
      return None
    else:
      jebType = typeManager.createStructure(ModuleData.JEB_NAME)
      typeManager.addStructureField(jebType, 'pclntable', typeManager.getType(SliceHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'ftab', typeManager.getType(SliceHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'filetab', typeManager.getType(SliceHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'findfunctab', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'minpc', primitiveTypeManager.getExactIntegerBySize(ptrSize, False))
      typeManager.addStructureField(jebType, 'maxpc', primitiveTypeManager.getExactIntegerBySize(ptrSize, False))

      typeManager.addStructureField(jebType, 'text', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'etext', typeManager.getType('void*'))          
      typeManager.addStructureField(jebType, 'noptrdata', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'enoptrdata', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'data', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'edata', typeManager.getType('void*'))          
      typeManager.addStructureField(jebType, 'bss', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'ebss', typeManager.getType('void*'))            
      typeManager.addStructureField(jebType, 'noptrbss', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'enoptrbss', typeManager.getType('void*'))  
      typeManager.addStructureField(jebType, 'end', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'gcdata', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'gcbss', typeManager.getType('void*'))   
      typeManager.addStructureField(jebType, 'types', typeManager.getType('void*'))
      typeManager.addStructureField(jebType, 'etypes', typeManager.getType('void*'))

      typeManager.addStructureField(jebType, 'textsectmap', typeManager.getType(SliceHeader.JEB_NAME))    
      typeManager.addStructureField(jebType, 'typelinks', typeManager.getType(SliceHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'itablinks', typeManager.getType(SliceHeader.JEB_NAME))

      typeManager.addStructureField(jebType, 'ptab', typeManager.getType(SliceHeader.JEB_NAME))

      typeManager.addStructureField(jebType, 'pluginpath', typeManager.getType(StringHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'pkghashes', typeManager.getType(SliceHeader.JEB_NAME))

      typeManager.addStructureField(jebType, 'modulename', typeManager.getType(StringHeader.JEB_NAME))
      typeManager.addStructureField(jebType, 'modulehashes', typeManager.getType(SliceHeader.JEB_NAME))

      typeManager.addStructureField(jebType, 'hasmain', primitiveTypeManager.getExactIntegerBySize(1, False))

      typeManager.addStructureField(jebType, 'gcdatamask', typeManager.getType(BitVector.JEB_NAME))
      typeManager.addStructureField(jebType, 'gcbssmask', typeManager.getType(BitVector.JEB_NAME))

      typeManager.addStructureField(jebType, 'typemap', typeManager.getType('void*'))

      typeManager.addStructureField(jebType, 'bad', typeManager.getType('_Bool'))

      typeManager.addStructureField(jebType, 'next', typeManager.getType('void*'))

      return jebType

  def __init__(self, golangAnalyzer, startAddr):
    self.memory = golangAnalyzer.nativeCodeAnalyzer.getMemory()
    self.ptrSize = golangAnalyzer.pclntab.ptrSize
    self.startAddr = startAddr & 0xFFFFFFFF
    self.codeUnit = golangAnalyzer.codeUnit
    self.typeManager = golangAnalyzer.typeManager

  def applyJebStructure(self):
    self.codeUnit.setDataAt(self.startAddr, self.typeManager.getType(ModuleData.JEB_NAME), 'firstmoduledata')

  def parse(self):
      curAddress = self.startAddr

      self.pclntable = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.pclntable.parse()
      curAddress += self.pclntable.mySize

      self.ftab = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.ftab.parse()
      curAddress += self.ftab.mySize

      self.filetab = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.filetab.parse()
      curAddress += self.filetab.mySize

      self.findfunctab, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.minpc, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.maxpc, curAddress = readPtr(self.memory, self.ptrSize, curAddress)

      self.text, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.etext, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.noptrdata, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.enoptrdata, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.data, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.edata, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.bss, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.ebss, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.noptrbss, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.enoptrbss, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.end, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.gcdata, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.gcbss, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.types, curAddress = readPtr(self.memory, self.ptrSize, curAddress)
      self.etypes, curAddress = readPtr(self.memory, self.ptrSize, curAddress)

      self.textsectmap = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.textsectmap.parse()
      curAddress+=self.textsectmap.mySize

      self.typelinks = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.typelinks.parse()
      curAddress+=self.typelinks.mySize

      self.itablinks = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.itablinks.parse()
      curAddress+=self.itablinks.mySize

      self.ptab = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.ptab.parse()
      curAddress+=self.ptab.mySize

      self.pluginpath = StringHeader(curAddress, self.memory, self.ptrSize)
      self.pluginpath.parse()
      curAddress+=self.pluginpath.mySize

      self.pkghashes = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.pkghashes.parse()
      curAddress+=self.pkghashes.mySize

      self.modulename = StringHeader(curAddress, self.memory, self.ptrSize)
      self.modulename.parse()
      curAddress+=self.modulename.mySize

      self.modulehashes = SliceHeader(curAddress, self.memory, self.ptrSize)
      self.modulehashes.parse()
      curAddress+=self.modulehashes.mySize

      self.hasmain = self.memory.readByte(curAddress) & 0xFF
      curAddress+=1

      self.gcdatamask = BitVector(curAddress, self.memory, self.ptrSize)
      self.gcdatamask.parse()
      curAddress += self.gcdatamask.mySize

      self.gcbssmask = BitVector(curAddress, self.memory, self.ptrSize)
      self.gcbssmask.parse()
      curAddress += self.gcbssmask.mySize

      self.typemap, curAddress = readPtr(self.memory, self.ptrSize, curAddress)

      self.bad = self.memory.readByte(curAddress) & 0xFF
      curAddress+=1

      self.next, curAddress = readPtr(self.memory, self.ptrSize, curAddress)

def readPtr(memory, ptrSize, curAddress):
  val = memory.readInt(curAddress) & 0xFFFFFFFF if ptrSize == 4 else memory.readLong(curAddress) & 0xFFFFFFFFFFFFFFFF
  curAddress = curAddress + 4 if ptrSize == 4 else curAddress + 8
  return val, curAddress