Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,53 @@
using StellaOps.Scanner.CallGraph.Binary;
using StellaOps.Scanner.CallGraph.Binary.Disassembly;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class BinaryDisassemblyTests
{
[Fact]
public void X86Disassembler_Extracts_Call_And_Jmp()
{
var disassembler = new X86Disassembler();
var code = new byte[]
{
0xE8, 0x05, 0x00, 0x00, 0x00, // call +5
0xE9, 0x02, 0x00, 0x00, 0x00, // jmp +2
0x90, 0x90
};
var calls = disassembler.ExtractDirectCalls(code, 0x1000, 64);
Assert.Equal(2, calls.Length);
Assert.Equal(0x1000UL, calls[0].InstructionAddress);
Assert.Equal(0x100AUL, calls[0].TargetAddress);
Assert.Equal(0x1005UL, calls[1].InstructionAddress);
Assert.Equal(0x100CUL, calls[1].TargetAddress);
}
[Fact]
public void DirectCallExtractor_Maps_Targets_To_Symbols()
{
var extractor = new DirectCallExtractor();
var textSection = new BinaryTextSection(
Bytes: new byte[] { 0xE8, 0x00, 0x00, 0x00, 0x00 },
VirtualAddress: 0x1000,
Bitness: 64,
Architecture: BinaryArchitecture.X64,
SectionName: ".text");
var symbols = new List<BinarySymbol>
{
new() { Name = "main", Address = 0x1000, Size = 10, IsGlobal = true, IsExported = true },
new() { Name = "helper", Address = 0x1005, Size = 10, IsGlobal = true, IsExported = true }
};
var edges = extractor.Extract(textSection, symbols, "app.bin");
Assert.Single(edges);
Assert.Equal("native:app.bin/main", edges[0].SourceId);
Assert.Equal("native:app.bin/helper", edges[0].TargetId);
Assert.Equal("0x1000", edges[0].CallSite);
}
}

View File

@@ -0,0 +1,267 @@
using System.Buffers.Binary;
using System.Text;
using StellaOps.Scanner.CallGraph.Binary;
using StellaOps.Scanner.CallGraph.Binary.Disassembly;
using StellaOps.Scanner.CallGraph.Binary.Analysis;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class BinaryTextSectionReaderTests
{
[Fact]
public async Task ReadsElfTextSection()
{
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
var rodataBytes = Encoding.ASCII.GetBytes("libfoo.so\0");
var data = BuildElf64Fixture(textBytes, rodataBytes);
var path = WriteTempFile(data);
try
{
var section = await BinaryTextSectionReader.TryReadAsync(path, BinaryFormat.Elf, CancellationToken.None);
Assert.NotNull(section);
Assert.Equal(".text", section!.SectionName);
Assert.Equal(BinaryArchitecture.X64, section.Architecture);
Assert.Equal(0x1000UL, section.VirtualAddress);
Assert.Equal(textBytes, section.Bytes);
}
finally
{
File.Delete(path);
}
}
[Fact]
public async Task ReadsPeTextSection()
{
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
var data = BuildPe64Fixture(textBytes);
var path = WriteTempFile(data);
try
{
var section = await BinaryTextSectionReader.TryReadAsync(path, BinaryFormat.Pe, CancellationToken.None);
Assert.NotNull(section);
Assert.Equal(".text", section!.SectionName);
Assert.Equal(BinaryArchitecture.X64, section.Architecture);
Assert.Equal(0x1000UL, section.VirtualAddress);
Assert.Equal(textBytes, section.Bytes);
}
finally
{
File.Delete(path);
}
}
[Fact]
public async Task ReadsMachOTextSection()
{
var textBytes = new byte[] { 0x1F, 0x20, 0x03, 0xD5 };
var data = BuildMachO64Fixture(textBytes);
var path = WriteTempFile(data);
try
{
var section = await BinaryTextSectionReader.TryReadAsync(path, BinaryFormat.MachO, CancellationToken.None);
Assert.NotNull(section);
Assert.Equal("__text", section!.SectionName);
Assert.Equal(BinaryArchitecture.Arm64, section.Architecture);
Assert.Equal(0x1000UL, section.VirtualAddress);
Assert.Equal(textBytes, section.Bytes);
}
finally
{
File.Delete(path);
}
}
[Fact]
public async Task StringScannerExtractsLibraryCandidates()
{
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
var rodataBytes = Encoding.ASCII.GetBytes("libfoo.so\0libbar.so.1\0");
var data = BuildElf64Fixture(textBytes, rodataBytes);
var path = WriteTempFile(data);
try
{
var scanner = new BinaryStringLiteralScanner();
var candidates = await scanner.ExtractLibraryCandidatesAsync(path, BinaryFormat.Elf, CancellationToken.None);
Assert.Contains("libfoo.so", candidates);
Assert.Contains("libbar.so.1", candidates);
}
finally
{
File.Delete(path);
}
}
private static string WriteTempFile(byte[] data)
{
var path = Path.GetTempFileName();
File.WriteAllBytes(path, data);
return path;
}
private static byte[] BuildElf64Fixture(byte[] textBytes, byte[] rodataBytes)
{
const int textOffset = 0x100;
var rodataOffset = textOffset + textBytes.Length;
var shstrOffset = rodataOffset + rodataBytes.Length;
const int sectionHeaderOffset = 0x200;
const ushort sectionHeaderSize = 64;
const ushort sectionCount = 4;
var shstrtab = Encoding.ASCII.GetBytes("\0.text\0.rodata\0.shstrtab\0");
var fileSize = sectionHeaderOffset + sectionHeaderSize * sectionCount;
if (fileSize < shstrOffset + shstrtab.Length)
{
fileSize = shstrOffset + shstrtab.Length;
}
var data = new byte[fileSize];
data[0] = 0x7F;
data[1] = (byte)'E';
data[2] = (byte)'L';
data[3] = (byte)'F';
data[4] = 2; // 64-bit
data[5] = 1; // little endian
WriteUInt16(data, 16, 2); // e_type
WriteUInt16(data, 18, 62); // e_machine x86_64
WriteInt64(data, 40, sectionHeaderOffset); // e_shoff
WriteUInt16(data, 58, sectionHeaderSize); // e_shentsize
WriteUInt16(data, 60, sectionCount); // e_shnum
WriteUInt16(data, 62, 3); // e_shstrndx
Array.Copy(textBytes, 0, data, textOffset, textBytes.Length);
Array.Copy(rodataBytes, 0, data, rodataOffset, rodataBytes.Length);
Array.Copy(shstrtab, 0, data, shstrOffset, shstrtab.Length);
WriteElfSectionHeader(data, sectionHeaderOffset + sectionHeaderSize * 1, 1, textOffset, textBytes.Length, 0x1000);
WriteElfSectionHeader(data, sectionHeaderOffset + sectionHeaderSize * 2, 7, rodataOffset, rodataBytes.Length, 0x2000);
WriteElfSectionHeader(data, sectionHeaderOffset + sectionHeaderSize * 3, 15, shstrOffset, shstrtab.Length, 0);
return data;
}
private static void WriteElfSectionHeader(
byte[] data,
int offset,
uint nameOffset,
int sectionOffset,
int sectionSize,
ulong address)
{
WriteUInt32(data, offset + 0, nameOffset);
WriteUInt32(data, offset + 4, 1); // SHT_PROGBITS
WriteUInt64(data, offset + 8, 0); // flags
WriteUInt64(data, offset + 16, address);
WriteUInt64(data, offset + 24, (ulong)sectionOffset);
WriteUInt64(data, offset + 32, (ulong)sectionSize);
}
private static byte[] BuildPe64Fixture(byte[] textBytes)
{
const int peOffset = 0x80;
const int optionalHeaderSize = 0xF0;
const int sectionHeaderStart = peOffset + 4 + 20 + optionalHeaderSize;
const int textOffset = 0x200;
var fileSize = textOffset + textBytes.Length;
if (fileSize < sectionHeaderStart + 40)
{
fileSize = sectionHeaderStart + 40;
}
var data = new byte[fileSize];
WriteInt32(data, 0x3C, peOffset);
WriteUInt32(data, peOffset, 0x00004550); // PE\0\0
WriteUInt16(data, peOffset + 4, 0x8664); // machine
WriteUInt16(data, peOffset + 6, 1); // sections
WriteUInt16(data, peOffset + 20, optionalHeaderSize);
WriteUInt16(data, peOffset + 24, 0x20b); // optional header magic
WriteAscii(data, sectionHeaderStart + 0, ".text", 8);
WriteUInt32(data, sectionHeaderStart + 8, (uint)textBytes.Length);
WriteUInt32(data, sectionHeaderStart + 12, 0x1000);
WriteUInt32(data, sectionHeaderStart + 16, (uint)textBytes.Length);
WriteUInt32(data, sectionHeaderStart + 20, textOffset);
Array.Copy(textBytes, 0, data, textOffset, textBytes.Length);
return data;
}
private static byte[] BuildMachO64Fixture(byte[] textBytes)
{
const uint magic = 0xFEEDFACF;
const int headerSize = 32;
const int cmdSize = 152;
const int commandStart = headerSize;
const int textOffset = 0x200;
var fileSize = textOffset + textBytes.Length;
if (fileSize < commandStart + cmdSize)
{
fileSize = commandStart + cmdSize;
}
var data = new byte[fileSize];
WriteUInt32(data, 0, magic);
WriteInt32(data, 4, unchecked((int)0x0100000C)); // CPU_TYPE_ARM64
WriteInt32(data, 8, 0);
WriteUInt32(data, 12, 2); // filetype
WriteUInt32(data, 16, 1); // ncmds
WriteUInt32(data, 20, cmdSize);
WriteUInt32(data, 24, 0); // flags
WriteUInt32(data, 28, 0); // reserved
WriteUInt32(data, commandStart + 0, 0x19); // LC_SEGMENT_64
WriteUInt32(data, commandStart + 4, cmdSize);
WriteAscii(data, commandStart + 8, "__TEXT", 16);
WriteUInt64(data, commandStart + 24, 0x1000);
WriteUInt64(data, commandStart + 32, 0x1000);
WriteUInt64(data, commandStart + 40, textOffset);
WriteUInt64(data, commandStart + 48, textBytes.Length);
WriteInt32(data, commandStart + 56, 7);
WriteInt32(data, commandStart + 60, 5);
WriteUInt32(data, commandStart + 64, 1); // nsects
WriteUInt32(data, commandStart + 68, 0); // flags
var sectionStart = commandStart + 72;
WriteAscii(data, sectionStart + 0, "__text", 16);
WriteAscii(data, sectionStart + 16, "__TEXT", 16);
WriteUInt64(data, sectionStart + 32, 0x1000);
WriteUInt64(data, sectionStart + 40, textBytes.Length);
WriteUInt32(data, sectionStart + 48, textOffset);
Array.Copy(textBytes, 0, data, textOffset, textBytes.Length);
return data;
}
private static void WriteAscii(byte[] buffer, int offset, string value, int length)
{
var bytes = Encoding.ASCII.GetBytes(value);
Array.Copy(bytes, 0, buffer, offset, Math.Min(length, bytes.Length));
}
private static void WriteUInt16(byte[] buffer, int offset, ushort value)
=> BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(offset, 2), value);
private static void WriteUInt32(byte[] buffer, int offset, uint value)
=> BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(offset, 4), value);
private static void WriteInt32(byte[] buffer, int offset, int value)
=> BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(offset, 4), value);
private static void WriteUInt64(byte[] buffer, int offset, ulong value)
=> BinaryPrimitives.WriteUInt64LittleEndian(buffer.AsSpan(offset, 8), value);
private static void WriteInt64(byte[] buffer, int offset, long value)
=> BinaryPrimitives.WriteInt64LittleEndian(buffer.AsSpan(offset, 8), value);
}