Merge remote-tracking branch

'origin/GP-6291_dev747368_better_obfuscated_golang--SQUASHED'
(Closes #8804)
This commit is contained in:
Ryan Kurtz
2026-01-07 12:07:25 -05:00
6 changed files with 188 additions and 25 deletions

View File

@@ -43,6 +43,8 @@ import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.options.Options;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.*;
import ghidra.program.model.block.CodeBlock;
import ghidra.program.model.block.MultEntSubModel;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.lang.Language;
@@ -124,6 +126,10 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
this.program = program;
aam = AutoAnalysisManager.getAnalysisManager(program);
if (!analyzerOptions.fallbackGoVer.isEmpty()) {
GoBuildInfo.setFallbackVersion(program, analyzerOptions.fallbackGoVer);
}
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
if (goBinary == null) {
Msg.error(this, "Go symbol analyzer error: unable to get GoRttiMapper");
@@ -613,7 +619,10 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
}
String ccName = duffFunc.getCallingConventionName();
Namespace funcNS = duffFunc.getParentNamespace();
AddressSet funcBody = new AddressSet(funcData.getBody());
// need to use our own logic instead of relying on possibly obfuscated go metadata
AddressSet funcBody = getDuffBody(duffFunc, monitor);
String duffComment = program.getListing()
.getCodeUnitAt(duffFunc.getEntryPoint())
.getComment(CommentType.PLATE);
@@ -647,6 +656,19 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
return true;
}
private AddressSet getDuffBody(Function func, TaskMonitor monitor) {
MultEntSubModel bm = new MultEntSubModel(func.getProgram());
CodeBlock cb;
try {
cb = bm.getCodeBlockAt(func.getEntryPoint(), monitor);
return new AddressSet(cb);
}
catch (CancelledException e) {
// fail, fall thru, return 1 byte range
}
return new AddressSet(func.getBody());
}
}
//--------------------------------------------------------------------------------------------
@@ -1283,19 +1305,24 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
public boolean fixupGcWriteBarrierFlag = true;
static final String FALLBACK_GOVER_OPTIONNAME = "Fallback Go Version";
static final String FALLBACK_GOVER_DESC = """
Go version to use if the Go metadata has been obfuscated.
""";
public String fallbackGoVer;
void registerOptions(Options options, Program program) {
options.registerOption(GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_OPTIONNAME,
outputSourceInfo, null, GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_DESC);
options.registerOption(GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_OPTIONNAME,
fixupDuffFunctions, null, GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_DESC);
options.registerOption(GolangAnalyzerOptions.PROP_RTTI_OPTIONNAME, propagateRtti, null,
GolangAnalyzerOptions.PROP_RTTI_DESC);
options.registerOption(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_OPTIONNAME,
fixupGcWriteBarrierFunctions, null,
GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FUNCS_DESC);
options.registerOption(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME,
fixupGcWriteBarrierFlag, null,
GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_DESC);
options.registerOption(OUTPUT_SOURCE_INFO_OPTIONNAME, outputSourceInfo, null,
OUTPUT_SOURCE_INFO_DESC);
options.registerOption(FIXUP_DUFF_FUNCS_OPTIONNAME, fixupDuffFunctions, null,
FIXUP_DUFF_FUNCS_DESC);
options.registerOption(PROP_RTTI_OPTIONNAME, propagateRtti, null, PROP_RTTI_DESC);
options.registerOption(FIXUP_GCWRITEBARRIER_OPTIONNAME, fixupGcWriteBarrierFunctions,
null, FIXUP_GCWRITEBARRIER_FUNCS_DESC);
options.registerOption(FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME, fixupGcWriteBarrierFlag,
null, FIXUP_GCWRITEBARRIER_FLAG_DESC);
options.registerOption(FALLBACK_GOVER_OPTIONNAME, "", null, FALLBACK_GOVER_DESC);
}
void optionsChanged(Options options, Program program) {
@@ -1312,6 +1339,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
fixupGcWriteBarrierFlag =
options.getBoolean(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME,
fixupGcWriteBarrierFlag);
fallbackGoVer = options.getString(FALLBACK_GOVER_OPTIONNAME, "");
}
}
@@ -1326,4 +1354,5 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
Options options = program.getOptions(Program.PROGRAM_INFO);
return options.getBoolean(ANALYZED_FLAG_OPTION_NAME, false);
}
}

View File

@@ -46,6 +46,8 @@ public class GoBuildInfo implements ElfInfoItem {
public static final String ELF_SECTION_NAME = ".go.buildinfo";
public static final String MACHO_SECTION_NAME = "go_buildinfo";
public static final String FALLBACK_GOVER_OPTION = "Go version fallback";
// Defined in Go's src/debug/buildinfo/buildinfo.go
// NOTE: ISO_8859_1 charset is required to not mangle the \u00ff when converting to bytes
private static final byte[] GO_BUILDINF_MAGIC =
@@ -147,6 +149,21 @@ public class GoBuildInfo implements ElfInfoItem {
return readStringInfo(reader, inlineStr, program, pointerSize, struct);
}
public static void setFallbackVersion(Program program, String fallbackGoVerStr) {
program.getOptions(Program.PROGRAM_INFO).setString(FALLBACK_GOVER_OPTION, fallbackGoVerStr);
}
public static GoBuildInfo fromFallbackInfo(Program program) {
String fallbackGoVerStr =
program.getOptions(Program.PROGRAM_INFO).getString(FALLBACK_GOVER_OPTION, "");
if (fallbackGoVerStr.isEmpty() || GoVer.parse(fallbackGoVerStr).isInvalid()) {
return null;
}
return new GoBuildInfo(program.getDefaultPointerSize(),
program.getMemory().isBigEndian() ? Endian.BIG : Endian.LITTLE, fallbackGoVerStr,
"unknown path", null, List.of(), List.of(), null);
}
/**
* Probes the specified InputStream and returns true if it starts with a Go buildinfo magic
* signature.

View File

@@ -66,11 +66,17 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
private long deferreturn;
@FieldMapping
@MarkupReference("getPcfileRefAddress")
private long pcfile; // offset in moduledata.pctab where file info starts
@FieldMapping
@MarkupReference("getPclnRefAddress")
private long pcln; // offset in moduledata.pctab where line num info starts
@FieldMapping
@MarkupReference("getPcspRefAddress")
private long pcsp; // offset in moduledata.pctab, -1.15=int32, 1.16+=uint32
@FieldMapping
private int npcdata; // number of elements in varlen pcdata array
@@ -90,6 +96,7 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
//--------------------------------------------------------------------------------------
private Address funcAddress; // set when entryoff or entry are set
private boolean funcAddressOverride;
/**
* Sets the function's entry point via a relative offset value
@@ -118,6 +125,11 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
this.funcAddress = context.getDataTypeMapper().getCodeAddress(entry);
}
public void setFuncAddressOverride(Address addr) {
funcAddress = addr;
funcAddressOverride = true;
}
/**
* {@return the address of this function}
*/
@@ -175,6 +187,30 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
return getPcDataStart(npcdata + tableIndex);
}
public Address getPcfileRefAddress() {
GoModuledata moduledata = getModuledata();
GoSlice pctab = moduledata.getPctab();
return pctab != null
? programContext.getDataAddress(pctab.getElementOffset(1, pcfile))
: null;
}
public Address getPclnRefAddress() {
GoModuledata moduledata = getModuledata();
GoSlice pctab = moduledata.getPctab();
return pctab != null
? programContext.getDataAddress(pctab.getElementOffset(1, pcln))
: null;
}
public Address getPcspRefAddress() {
GoModuledata moduledata = getModuledata();
GoSlice pctab = moduledata.getPctab();
return pctab != null
? programContext.getDataAddress(pctab.getElementOffset(1, pcsp))
: null;
}
/**
* Returns a value from the specified pc->value lookup table, for a specific
* address (that should be within the function's footprint).
@@ -304,7 +340,7 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
* @return String description
*/
public String getDescription() {
return getName() + "@" + getFuncAddress();
return getName() + "@" + getFuncAddress() + (funcAddressOverride ? " (overridden)" : "");
}
/**
@@ -470,6 +506,21 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
session.labelAddress(deferreturnAddr, funcName.asString() + "_deferreturn",
funcName.packagePath());
}
try {
GoPcValueEvaluator pceval = new GoPcValueEvaluator(this, pcfile);
pceval.markup(session);
pceval = new GoPcValueEvaluator(this, pcln);
pceval.markup(session);
pceval = new GoPcValueEvaluator(this, pcsp);
pceval.markup(session);
}
catch (IOException e) {
// ignore
}
}
//-------------------------------------------------------------------------------------------

View File

@@ -16,6 +16,7 @@
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.Objects;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
@@ -86,9 +87,19 @@ public class GoFunctabEntry {
@Markup
public GoFuncData getFuncData() throws IOException {
GoModuledata moduledata = getModuledata();
return funcoff != 0 && moduledata != null
GoFuncData result = funcoff != 0 && moduledata != null
? moduledata.getFuncDataInstance(funcoff)
: null;
if (result != null && !Objects.equals(funcAddress, result.getFuncAddress())) {
// defeat obfuscated GoFuncData func address values with good addr from ftab entry
if (programContext.getProgram()
.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(funcAddress)) {
result.setFuncAddressOverride(funcAddress);
}
}
return result;
}
/**

View File

@@ -20,7 +20,13 @@ import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.LEB128Info;
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
import ghidra.app.util.bin.format.golang.structmapping.MarkupSession;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.LEB128;
import ghidra.program.model.data.UnsignedLeb128DataType;
import ghidra.program.model.listing.CommentType;
/**
* Evaluates a sequence of (value_delta,pc_delta) leb128 pairs to calculate a value for a certain
@@ -31,6 +37,7 @@ public class GoPcValueEvaluator {
private final long funcEntry;
private final BinaryReader reader;
private final long startPosition;
private final long pctabOffset;
private int value = -1;
private long pc;
@@ -49,6 +56,7 @@ public class GoPcValueEvaluator {
this.pcquantum = moduledata.getGoBinary().getMinLC();
this.reader = moduledata.getPcValueTable().getElementReader(1, (int) offset);
this.startPosition = reader.getPointerIndex();
this.pctabOffset = offset;
this.funcEntry = func.getFuncAddress().getOffset();
this.pc = funcEntry;
@@ -126,4 +134,55 @@ public class GoPcValueEvaluator {
return true;
}
public void markup(MarkupSession session) throws IOException {
Address startAddr = session.getMappingContext().getDataAddress(startPosition);
if (session.getMarkedupAddresses().contains(startAddr)) {
return;
}
session.labelAddress(startAddr, "pctab[0x%x]".formatted(pctabOffset));
int count = 0;
while (markupStep(session)) {
count++;
}
long size = reader.getPointerIndex() - startPosition;
String msg = "stepcount=%d,size=%d".formatted(count, size);
DWARFUtil.appendComment(session.getProgram(), startAddr, CommentType.PRE, "", msg, ",");
}
private boolean markupStep(MarkupSession session) throws IOException {
LEB128Info uvdeltaInfo = reader.readNext(LEB128Info::unsigned);
Address uvdeltaAddr = session.getMappingContext().getDataAddress(uvdeltaInfo.getOffset());
if (session.getMarkedupAddresses().contains(uvdeltaAddr)) {
return false;
}
session.markupAddress(uvdeltaAddr, UnsignedLeb128DataType.dataType,
uvdeltaInfo.getLength());
int uvdelta = uvdeltaInfo.asUInt32();
if (uvdelta == 0 && pc != funcEntry) {
// a delta of 0 is only valid on the first element
DWARFUtil.appendComment(session.getProgram(), uvdeltaAddr, CommentType.EOL, "", "end",
",");
return false;
}
int vdelta = -(uvdelta & 1) ^ (uvdelta >> 1);
value += vdelta;
String msg = "value+%d=0x%x".formatted(vdelta, value);
DWARFUtil.appendComment(session.getProgram(), uvdeltaAddr, CommentType.EOL, "", msg, ",");
LEB128Info pcdeltaInfo = reader.readNext(LEB128Info::unsigned);
Address pcdeltaAddr = session.getMappingContext().getDataAddress(pcdeltaInfo.getOffset());
session.markupAddress(pcdeltaAddr, UnsignedLeb128DataType.dataType,
pcdeltaInfo.getLength());
int pcdelta = pcdeltaInfo.asUInt32();
pc += pcdelta * pcquantum;
msg = "pc+0x%x=+0x%08x".formatted(pcdelta * pcquantum, pc - funcEntry);
DWARFUtil.appendComment(session.getProgram(), pcdeltaAddr, CommentType.EOL, "", msg, ",");
return true;
}
}

View File

@@ -82,8 +82,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex
private static final List<String> SECTION_PREFIXES =
List.of("." /* ELF */, "__" /* macho sections */);
private static final String FAILED_FLAG = "FAILED TO FIND GOLANG BINARY";
/**
* Returns a shared {@link GoRttiMapper} for the specified program, or null if the binary
* is not a supported Go binary.
@@ -102,10 +100,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex
*
*/
public static GoRttiMapper getSharedGoBinary(Program program, TaskMonitor monitor) {
if (TransientProgramProperties.hasProperty(program, FAILED_FLAG)) {
// don't try to do any work if we've failed earlier
return null;
}
GoRttiMapper goBinary = TransientProgramProperties.getProperty(program, GoRttiMapper.class,
TransientProgramProperties.SCOPE.ANALYSIS_SESSION, GoRttiMapper.class, () -> {
// cached instance not found, create new instance
@@ -128,10 +122,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex
logAnalyzerMsg(program, e.getMessage());
}
// this sets the failed flag
TransientProgramProperties.getProperty(program, FAILED_FLAG,
TransientProgramProperties.SCOPE.PROGRAM, Boolean.class, () -> true);
return null;
});
@@ -161,6 +151,12 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex
public static GoRttiMapper getGoBinary(Program program, TaskMonitor monitor)
throws BootstrapInfoException, IOException {
GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program);
if (buildInfo == null || buildInfo.getGoVer().isInvalid()) {
GoBuildInfo fallbackBI = GoBuildInfo.fromFallbackInfo(program);
if (fallbackBI != null) {
buildInfo = fallbackBI;
}
}
if (buildInfo == null) {
// probably not a Go binary
return null;