mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-01-09 14:08:03 -05:00
Merge remote-tracking branch
'origin/GP-6291_dev747368_better_obfuscated_golang--SQUASHED' (Closes #8804)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user