up
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Analyzers.Native;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Reachability.Lifters;
|
||||
using Xunit;
|
||||
@@ -167,6 +168,62 @@ public class BinaryReachabilityLifterTests
|
||||
e.To == unknownNode.SymbolId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RichGraphIncludesPurlAndSymbolDigestForElfDependencies()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var binaryPath = System.IO.Path.Combine(temp.Path, "sample.elf");
|
||||
var bytes = CreateElf64WithDependencies(["libc.so.6"]);
|
||||
await System.IO.File.WriteAllBytesAsync(binaryPath, bytes);
|
||||
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = temp.Path,
|
||||
AnalysisId = "analysis-elf-deps"
|
||||
};
|
||||
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
var lifter = new BinaryReachabilityLifter();
|
||||
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var union = builder.ToUnionGraph(SymbolId.Lang.Binary);
|
||||
|
||||
var rich = RichGraphBuilder.FromUnion(union, "test-analyzer", "1.0.0");
|
||||
var edge = Assert.Single(rich.Edges);
|
||||
Assert.Equal(EdgeTypes.Import, edge.Kind);
|
||||
Assert.Equal("pkg:generic/libc@6", edge.Purl);
|
||||
Assert.NotNull(edge.SymbolDigest);
|
||||
Assert.StartsWith("sha256:", edge.SymbolDigest, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RichGraphIncludesPurlAndSymbolDigestForPeImports()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var binaryPath = System.IO.Path.Combine(temp.Path, "sample.exe");
|
||||
var bytes = CreatePe64WithImports(["KERNEL32.dll"]);
|
||||
await System.IO.File.WriteAllBytesAsync(binaryPath, bytes);
|
||||
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = temp.Path,
|
||||
AnalysisId = "analysis-pe-imports"
|
||||
};
|
||||
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
var lifter = new BinaryReachabilityLifter();
|
||||
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var union = builder.ToUnionGraph(SymbolId.Lang.Binary);
|
||||
|
||||
var rich = RichGraphBuilder.FromUnion(union, "test-analyzer", "1.0.0");
|
||||
var edge = Assert.Single(rich.Edges);
|
||||
Assert.Equal(EdgeTypes.Import, edge.Kind);
|
||||
Assert.Equal("pkg:generic/KERNEL32", edge.Purl);
|
||||
Assert.NotNull(edge.SymbolDigest);
|
||||
Assert.StartsWith("sha256:", edge.SymbolDigest, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static byte[] CreateMinimalElf()
|
||||
{
|
||||
var data = new byte[64];
|
||||
@@ -307,4 +364,205 @@ public class BinaryReachabilityLifterTests
|
||||
|
||||
private static void WriteU64LE(byte[] buffer, int offset, ulong value)
|
||||
=> BinaryPrimitives.WriteUInt64LittleEndian(buffer.AsSpan(offset, 8), value);
|
||||
|
||||
private static byte[] CreateElf64WithDependencies(IReadOnlyList<string> dependencies)
|
||||
{
|
||||
dependencies ??= [];
|
||||
|
||||
const string interpreter = "/lib64/ld-linux-x86-64.so.2";
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
|
||||
var stringTable = new StringBuilder();
|
||||
stringTable.Append('\0');
|
||||
var stringOffsets = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
|
||||
void AddString(string s)
|
||||
{
|
||||
if (stringOffsets.ContainsKey(s))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
stringOffsets[s] = stringTable.Length;
|
||||
stringTable.Append(s);
|
||||
stringTable.Append('\0');
|
||||
}
|
||||
|
||||
AddString(interpreter);
|
||||
foreach (var dep in dependencies)
|
||||
{
|
||||
AddString(dep);
|
||||
}
|
||||
|
||||
var stringTableBytes = Encoding.UTF8.GetBytes(stringTable.ToString());
|
||||
|
||||
const int elfHeaderSize = 64;
|
||||
const int phdrSize = 56;
|
||||
const int phdrCount = 3; // PT_INTERP, PT_LOAD, PT_DYNAMIC
|
||||
var phdrOffset = elfHeaderSize;
|
||||
var interpOffset = phdrOffset + (phdrSize * phdrCount);
|
||||
var interpSize = Encoding.UTF8.GetByteCount(interpreter) + 1;
|
||||
var dynamicOffset = interpOffset + interpSize;
|
||||
|
||||
var dynEntries = new List<(ulong Tag, ulong Value)>();
|
||||
foreach (var dep in dependencies)
|
||||
{
|
||||
dynEntries.Add((1, (ulong)stringOffsets[dep])); // DT_NEEDED
|
||||
}
|
||||
|
||||
dynEntries.Add((5, 0)); // DT_STRTAB (patched later)
|
||||
dynEntries.Add((10, (ulong)stringTableBytes.Length)); // DT_STRSZ
|
||||
dynEntries.Add((0, 0)); // DT_NULL
|
||||
|
||||
var dynamicSize = dynEntries.Count * 16;
|
||||
var stringTableOffset = dynamicOffset + dynamicSize;
|
||||
var totalSize = stringTableOffset + stringTableBytes.Length;
|
||||
|
||||
for (var i = 0; i < dynEntries.Count; i++)
|
||||
{
|
||||
if (dynEntries[i].Tag == 5)
|
||||
{
|
||||
dynEntries[i] = (5, (ulong)stringTableOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write(new byte[] { 0x7f, 0x45, 0x4c, 0x46 }); // Magic
|
||||
writer.Write((byte)2); // 64-bit
|
||||
writer.Write((byte)1); // Little endian
|
||||
writer.Write((byte)1); // ELF version
|
||||
writer.Write((byte)0); // OS ABI
|
||||
writer.Write(new byte[8]); // Padding
|
||||
writer.Write((ushort)2); // ET_EXEC
|
||||
writer.Write((ushort)0x3e); // x86_64
|
||||
writer.Write(1u); // Version
|
||||
writer.Write(0ul); // Entry point
|
||||
writer.Write((ulong)phdrOffset); // Program header offset
|
||||
writer.Write(0ul); // Section header offset
|
||||
writer.Write(0u); // Flags
|
||||
writer.Write((ushort)elfHeaderSize); // ELF header size
|
||||
writer.Write((ushort)phdrSize); // Program header entry size
|
||||
writer.Write((ushort)phdrCount); // Number of program headers
|
||||
writer.Write((ushort)0); // Section header entry size
|
||||
writer.Write((ushort)0); // Number of section headers
|
||||
writer.Write((ushort)0); // Section name string table index
|
||||
|
||||
// PT_INTERP
|
||||
writer.Write(3u);
|
||||
writer.Write(4u);
|
||||
writer.Write((ulong)interpOffset);
|
||||
writer.Write((ulong)interpOffset);
|
||||
writer.Write((ulong)interpOffset);
|
||||
writer.Write((ulong)interpSize);
|
||||
writer.Write((ulong)interpSize);
|
||||
writer.Write(1ul);
|
||||
|
||||
// PT_LOAD
|
||||
writer.Write(1u);
|
||||
writer.Write(5u);
|
||||
writer.Write(0ul);
|
||||
writer.Write(0ul);
|
||||
writer.Write(0ul);
|
||||
writer.Write((ulong)totalSize);
|
||||
writer.Write((ulong)totalSize);
|
||||
writer.Write(0x1000ul);
|
||||
|
||||
// PT_DYNAMIC
|
||||
writer.Write(2u);
|
||||
writer.Write(6u);
|
||||
writer.Write((ulong)dynamicOffset);
|
||||
writer.Write((ulong)dynamicOffset);
|
||||
writer.Write((ulong)dynamicOffset);
|
||||
writer.Write((ulong)dynamicSize);
|
||||
writer.Write((ulong)dynamicSize);
|
||||
writer.Write(8ul);
|
||||
|
||||
writer.Write(Encoding.UTF8.GetBytes(interpreter));
|
||||
writer.Write((byte)0);
|
||||
|
||||
foreach (var (tag, value) in dynEntries)
|
||||
{
|
||||
writer.Write(tag);
|
||||
writer.Write(value);
|
||||
}
|
||||
|
||||
writer.Write(stringTableBytes);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] CreatePe64WithImports(IReadOnlyList<string> imports)
|
||||
{
|
||||
imports ??= [];
|
||||
if (imports.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Must provide at least one import.", nameof(imports));
|
||||
}
|
||||
|
||||
const int peHeaderOffset = 0x80;
|
||||
const int optionalHeaderSize = 240;
|
||||
const uint sectionVirtualAddress = 0x1000;
|
||||
const uint sectionVirtualSize = 0x200;
|
||||
const uint sectionRawSize = 0x200;
|
||||
const uint sectionRawOffset = 0x200;
|
||||
|
||||
const uint importDirRva = sectionVirtualAddress;
|
||||
const uint importDirSize = 40; // 2 descriptors
|
||||
const uint nameRva = sectionVirtualAddress + 0x100;
|
||||
|
||||
var dllNameBytes = Encoding.ASCII.GetBytes(imports[0] + "\0");
|
||||
var totalSize = (int)(sectionRawOffset + sectionRawSize);
|
||||
if (sectionRawOffset + 0x100 + dllNameBytes.Length > sectionRawOffset + sectionRawSize)
|
||||
{
|
||||
totalSize = (int)(sectionRawOffset + 0x100 + dllNameBytes.Length);
|
||||
}
|
||||
|
||||
var buffer = new byte[totalSize];
|
||||
|
||||
buffer[0] = (byte)'M';
|
||||
buffer[1] = (byte)'Z';
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(0x3C, 4), peHeaderOffset);
|
||||
|
||||
WriteU32LE(buffer, peHeaderOffset, 0x00004550); // PE\0\0
|
||||
|
||||
var coff = peHeaderOffset + 4;
|
||||
WriteU16LE(buffer, coff + 0, 0x8664); // Machine
|
||||
WriteU16LE(buffer, coff + 2, 1); // NumberOfSections
|
||||
WriteU32LE(buffer, coff + 16, 0); // NumberOfSymbols
|
||||
WriteU16LE(buffer, coff + 16 + 4, (ushort)optionalHeaderSize); // SizeOfOptionalHeader
|
||||
WriteU16LE(buffer, coff + 16 + 6, 0x22); // Characteristics
|
||||
|
||||
var opt = peHeaderOffset + 24;
|
||||
WriteU16LE(buffer, opt + 0, 0x20b); // PE32+
|
||||
WriteU16LE(buffer, opt + 68, (ushort)PeSubsystem.WindowsConsole); // Subsystem
|
||||
WriteU32LE(buffer, opt + 108, 16); // NumberOfRvaAndSizes
|
||||
|
||||
var dataDir = opt + 112;
|
||||
// Import directory entry (#1)
|
||||
WriteU32LE(buffer, dataDir + 8, importDirRva);
|
||||
WriteU32LE(buffer, dataDir + 12, importDirSize);
|
||||
|
||||
var sectionHeader = opt + optionalHeaderSize;
|
||||
var sectionName = Encoding.ASCII.GetBytes(".rdata\0\0");
|
||||
sectionName.CopyTo(buffer, sectionHeader);
|
||||
WriteU32LE(buffer, sectionHeader + 8, sectionVirtualSize);
|
||||
WriteU32LE(buffer, sectionHeader + 12, sectionVirtualAddress);
|
||||
WriteU32LE(buffer, sectionHeader + 16, sectionRawSize);
|
||||
WriteU32LE(buffer, sectionHeader + 20, sectionRawOffset);
|
||||
|
||||
// Import descriptor #1 at RVA 0x1000 -> file offset 0x200.
|
||||
var importOffset = (int)sectionRawOffset;
|
||||
WriteU32LE(buffer, importOffset + 0, 0); // OriginalFirstThunk (skip function parsing)
|
||||
WriteU32LE(buffer, importOffset + 12, nameRva); // Name RVA
|
||||
|
||||
// Import descriptor #2 is the terminator (zeros), already zero-initialized.
|
||||
|
||||
// DLL name string
|
||||
var nameOffset = (int)(sectionRawOffset + (nameRva - sectionVirtualAddress));
|
||||
dllNameBytes.CopyTo(buffer, nameOffset);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user