Files

126 lines
7.3 KiB
C#

using System.Reflection.PortableExecutable;
using Iced.Intel;
using System.Buffers.Binary;
// Read-only on-disk signature audit. Does not open a process or modify the executable.
if(args.Length<2) throw new ArgumentException("NativeAudit executable signature|rva:HEX [instruction-count]");
using var input=File.OpenRead(args[0]);
using var pe=new PEReader(input);
var section=pe.PEHeaders.SectionHeaders.Single(x=>x.Name==".text");
var bytes=pe.GetSectionData(section.VirtualAddress).GetContent().ToArray();
if(args[1].StartsWith("xrefstr:") || args[1].StartsWith("xrefs:"))
{
int target;
if(args[1].StartsWith("xrefstr:"))
{
var image=File.ReadAllBytes(args[0]);
var offset=image.AsSpan().IndexOf(System.Text.Encoding.UTF8.GetBytes(args[1][8..]+"\0"));
if(offset<0) throw new InvalidDataException("String not found");
var dataSection=pe.PEHeaders.SectionHeaders.Single(s=>offset>=s.PointerToRawData && offset<s.PointerToRawData+s.SizeOfRawData);
target=offset-dataSection.PointerToRawData+dataSection.VirtualAddress;
}
else target=Convert.ToInt32(args[1][6..].Split('-')[0],16);
var endTarget=args[1].StartsWith("xrefs:") && args[1].Contains('-') ? Convert.ToInt32(args[1].Split('-')[1],16) : target;
var directory=pe.PEHeaders.PEHeader!.ExceptionTableDirectory;
var functions=pe.GetSectionData(directory.RelativeVirtualAddress).GetContent(0,directory.Size).ToArray();
Console.WriteLine($"XREF TARGET RVA=0x{target:X}");
bool IsStringPointer(ulong pointer)
{
if(pointer<pe.PEHeaders.PEHeader!.ImageBase) return false;
var rva=(long)(pointer-pe.PEHeaders.PEHeader.ImageBase);
var data=pe.PEHeaders.SectionHeaders.FirstOrDefault(s=>rva>=s.VirtualAddress && rva<s.VirtualAddress+s.SizeOfRawData);
if(data.SizeOfRawData==0) return false;
var content=pe.GetSectionData((int)rva).GetContent(0,Math.Min(128,data.VirtualAddress+data.SizeOfRawData-(int)rva)).ToArray();
var end=Array.IndexOf(content,(byte)0);
return end>=3 && content.Take(end).All(b=>b>=32 && b<127);
}
foreach(var dataSection in pe.PEHeaders.SectionHeaders.Where(s=>s.Name!=".text" && s.SizeOfRawData>0))
{
var raw=pe.GetSectionData(dataSection.VirtualAddress).GetContent().ToArray();
for(int at=0;at+8<=raw.Length;at+=8)
if(BinaryPrimitives.ReadUInt64LittleEndian(raw.AsSpan(at))==pe.PEHeaders.PEHeader!.ImageBase+(uint)target)
{
Console.WriteLine($"DATA REF RVA=0x{dataSection.VirtualAddress+at:X}");
var before=at;
while(before>=8 && at-before<8192 && IsStringPointer(BinaryPrimitives.ReadUInt64LittleEndian(raw.AsSpan(before-8)))) before-=8;
Console.WriteLine($"STRING TABLE candidate RVA=0x{dataSection.VirtualAddress+before:X}, preceding={(at-before)/8}");
}
}
for(int i=0;i+7<=bytes.Length;i++)
{
var callTarget=section.VirtualAddress+i+5+BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i+1));
var ripTarget=section.VirtualAddress+i+7+BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i+3));
bool call=(bytes[i]==0xE8 || bytes[i]==0xE9) && callTarget>=target && callTarget<=endTarget;
bool rip=(bytes[i] is 0x48 or 0x4C) && bytes[i+1] is 0x8D or 0x8B && (bytes[i+2]&0xC7)==5 && ripTarget>=target && ripTarget<=endTarget;
var rawTarget=BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i));
bool immediate=rawTarget>=target && rawTarget<=endTarget;
if(!call && !rip && !immediate) continue;
var rva=section.VirtualAddress+i;
bool described=false;
for(int at=0;at+12<=functions.Length;at+=12)
{
int begin=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at)),end=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+4));
if(rva>=begin && rva<end) { Console.WriteLine($"REF RVA=0x{rva:X}, FUNCTION=0x{begin:X}..0x{end:X}, {(call ? "CALL/JMP" : rip ? "RIP" : "IMM/CANDIDATE")} target=0x{(call ? callTarget : rip ? ripTarget : rawTarget):X}"); described=true; break; }
}
if(!described) Console.WriteLine($"REF RVA=0x{rva:X}, FUNCTION=leaf/unknown, {(call ? "CALL/JMP" : rip ? "RIP" : "IMM/CANDIDATE")} target=0x{(call ? callTarget : rip ? ripTarget : rawTarget):X}");
}
return;
}
var containing=args[1].StartsWith("func:",StringComparison.OrdinalIgnoreCase);
var direct=containing || args[1].StartsWith("rva:",StringComparison.OrdinalIgnoreCase);
var requested=direct ? Convert.ToInt32(args[1][(containing ? 5 : 4)..],16) : 0;
if(containing)
{
var directory=pe.PEHeaders.PEHeader!.ExceptionTableDirectory;
var functions=pe.GetSectionData(directory.RelativeVirtualAddress).GetContent(0,directory.Size).ToArray();
bool found=false;
for(int at=0;at+12<=functions.Length;at+=12)
{
int begin=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at)),end=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+4));
if(requested>=begin && requested<end)
{
Console.WriteLine($"CONTAINING RVA=0x{begin:X}..0x{end:X}");
var unwind=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+8));
for(int depth=0;depth<16;depth++)
{
var header=pe.GetSectionData(unwind).GetContent(0,4).ToArray();
if(((header[0]>>3)&4)==0) break;
var chainAt=(4+header[2]*2+3)&~3;
var chain=pe.GetSectionData(unwind+chainAt).GetContent(0,12).ToArray();
begin=BinaryPrimitives.ReadInt32LittleEndian(chain); end=BinaryPrimitives.ReadInt32LittleEndian(chain.AsSpan(4));
unwind=BinaryPrimitives.ReadInt32LittleEndian(chain.AsSpan(8));
Console.WriteLine($"CHAINED ROOT RVA=0x{begin:X}..0x{end:X}");
}
requested=begin;found=true;break;
}
}
if(!found) throw new InvalidDataException("No unwind entry for requested RVA");
}
var start=direct ? requested-section.VirtualAddress : 0;
if(start<0 || start>=bytes.Length) throw new ArgumentException("RVA outside .text");
var pattern=direct ? "" : args[1].Replace(" ","");
if(pattern.Length%2!=0) throw new ArgumentException("Odd signature length");
var values=Enumerable.Range(0,pattern.Length/2).Select(i=>pattern.Substring(i*2,2)).ToArray();
var count=args.Length>2 ? int.Parse(args[2]) : 100;
int matches=0;
for(int i=start;i<=(direct ? start : bytes.Length-values.Length);i++)
{
bool match=true;
for(int j=0;j<values.Length;j++)
if(values[j]!="??" && bytes[i+j]!=Convert.ToByte(values[j],16)) { match=false; break; }
if(!match) continue;
matches++;
var decoder=Decoder.Create(64,new ByteArrayCodeReader(bytes,i,Math.Min(8192,bytes.Length-i)));
decoder.IP=pe.PEHeaders.PEHeader!.ImageBase+(uint)section.VirtualAddress+(uint)i;
Console.WriteLine($"MATCH RVA=0x{section.VirtualAddress+i:X}");
Console.WriteLine("PREFIX "+Convert.ToHexString(bytes.AsSpan(i,Math.Min(64,bytes.Length-i))));
var formatter=new IntelFormatter(); var output=new StringOutput();
for(int n=0;n<count;n++)
{
decoder.Decode(out var instruction); formatter.Format(instruction,output);
Console.WriteLine($"{instruction.IP:X16} {output.ToStringAndReset()}");
if(instruction.FlowControl==FlowControl.Return) break;
}
}
Console.WriteLine($"Matches: {matches}");