Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -348,6 +348,346 @@ internal static class BinaryCommandHandlers
|
||||
return ExitCodes.VerificationFailed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle 'stella binary inspect' command (SCANINT-14).
|
||||
/// </summary>
|
||||
public static async Task<int> HandleInspectAsync(
|
||||
IServiceProvider services,
|
||||
string filePath,
|
||||
string format,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("binary-inspect");
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] File not found: {filePath}");
|
||||
return ExitCodes.FileNotFound;
|
||||
}
|
||||
|
||||
await AnsiConsole.Status()
|
||||
.StartAsync("Analyzing binary...", async ctx =>
|
||||
{
|
||||
await Task.Delay(100, cancellationToken);
|
||||
});
|
||||
|
||||
// Compute file hashes and extract identity
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var sha256 = System.Security.Cryptography.SHA256.HashData(stream);
|
||||
stream.Position = 0;
|
||||
|
||||
// Read ELF/PE/Mach-O header to determine format and architecture
|
||||
var header = new byte[64];
|
||||
await stream.ReadExactlyAsync(header, cancellationToken);
|
||||
|
||||
var binaryFormat = DetectFormat(header);
|
||||
var architecture = DetectArchitecture(header, binaryFormat);
|
||||
var buildId = ExtractBuildId(filePath); // Placeholder
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
|
||||
var result = new
|
||||
{
|
||||
Path = filePath,
|
||||
Size = fileInfo.Length,
|
||||
Format = binaryFormat,
|
||||
Architecture = architecture,
|
||||
BuildId = buildId ?? "(not found)",
|
||||
Sha256 = Convert.ToHexStringLower(sha256),
|
||||
BinaryKey = buildId ?? Convert.ToHexStringLower(sha256[..16])
|
||||
};
|
||||
|
||||
if (format == "json")
|
||||
{
|
||||
var json = JsonSerializer.Serialize(result, JsonOptions);
|
||||
AnsiConsole.WriteLine(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[bold]Binary:[/] {result.Path}");
|
||||
AnsiConsole.MarkupLine($"Size: {result.Size:N0} bytes");
|
||||
AnsiConsole.MarkupLine($"Format: [cyan]{result.Format}[/]");
|
||||
AnsiConsole.MarkupLine($"Architecture: [cyan]{result.Architecture}[/]");
|
||||
AnsiConsole.MarkupLine($"Build-ID: [cyan]{result.BuildId}[/]");
|
||||
AnsiConsole.MarkupLine($"SHA256: [dim]{result.Sha256}[/]");
|
||||
AnsiConsole.MarkupLine($"Binary Key: [green]{result.BinaryKey}[/]");
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogInformation("Inspected binary: {Path}", filePath);
|
||||
}
|
||||
|
||||
return ExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
logger.LogError(ex, "Failed to inspect binary {Path}", filePath);
|
||||
return ExitCodes.GeneralError;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle 'stella binary lookup' command (SCANINT-15).
|
||||
/// </summary>
|
||||
public static async Task<int> HandleLookupAsync(
|
||||
IServiceProvider services,
|
||||
string buildId,
|
||||
string? distro,
|
||||
string? release,
|
||||
string format,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("binary-lookup");
|
||||
|
||||
try
|
||||
{
|
||||
await AnsiConsole.Status()
|
||||
.StartAsync("Looking up vulnerabilities...", async ctx =>
|
||||
{
|
||||
// TODO: Call BinaryIndex API
|
||||
await Task.Delay(100, cancellationToken);
|
||||
});
|
||||
|
||||
// Mock results for now - in production, call IBinaryVulnerabilityService
|
||||
var mockResults = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
CveId = "CVE-2024-1234",
|
||||
Purl = "pkg:deb/debian/openssl@1.1.1n-0+deb11u3",
|
||||
Method = "buildid_catalog",
|
||||
Confidence = 0.95,
|
||||
FixStatus = distro != null ? "fixed" : "unknown",
|
||||
FixedVersion = distro != null ? "1.1.1n-0+deb11u4" : null
|
||||
}
|
||||
};
|
||||
|
||||
if (format == "json")
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
BuildId = buildId,
|
||||
Distro = distro,
|
||||
Release = release,
|
||||
Matches = mockResults
|
||||
}, JsonOptions);
|
||||
AnsiConsole.WriteLine(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[bold]Build-ID:[/] {buildId}");
|
||||
if (distro != null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"Distro: {distro}/{release ?? "any"}");
|
||||
}
|
||||
AnsiConsole.MarkupLine("");
|
||||
|
||||
if (mockResults.Length == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[green]No vulnerabilities found[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
var table = new Table();
|
||||
table.AddColumn("CVE");
|
||||
table.AddColumn("Package");
|
||||
table.AddColumn("Method");
|
||||
table.AddColumn("Confidence");
|
||||
table.AddColumn("Fix Status");
|
||||
|
||||
foreach (var match in mockResults)
|
||||
{
|
||||
var statusMarkup = match.FixStatus switch
|
||||
{
|
||||
"fixed" => $"[green]Fixed ({match.FixedVersion})[/]",
|
||||
"vulnerable" => "[red]Vulnerable[/]",
|
||||
_ => "[yellow]Unknown[/]"
|
||||
};
|
||||
|
||||
table.AddRow(
|
||||
match.CveId,
|
||||
match.Purl,
|
||||
match.Method,
|
||||
$"{match.Confidence:P0}",
|
||||
statusMarkup);
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogInformation("Looked up Build-ID: {BuildId}", buildId);
|
||||
}
|
||||
|
||||
return ExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
logger.LogError(ex, "Failed to lookup Build-ID {BuildId}", buildId);
|
||||
return ExitCodes.GeneralError;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle 'stella binary fingerprint' command (SCANINT-16).
|
||||
/// </summary>
|
||||
public static async Task<int> HandleFingerprintAsync(
|
||||
IServiceProvider services,
|
||||
string filePath,
|
||||
string algorithm,
|
||||
string? function,
|
||||
string format,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("binary-fingerprint");
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] File not found: {filePath}");
|
||||
return ExitCodes.FileNotFound;
|
||||
}
|
||||
|
||||
await AnsiConsole.Status()
|
||||
.StartAsync($"Generating {algorithm} fingerprint...", async ctx =>
|
||||
{
|
||||
// TODO: Call actual fingerprinting service
|
||||
await Task.Delay(200, cancellationToken);
|
||||
});
|
||||
|
||||
// Mock fingerprint generation
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var fileHash = System.Security.Cryptography.SHA256.HashData(stream);
|
||||
|
||||
// Simulate fingerprint based on algorithm
|
||||
var fingerprintId = algorithm switch
|
||||
{
|
||||
"basic-block" => $"bb:{Convert.ToHexStringLower(fileHash[..16])}",
|
||||
"cfg" => $"cfg:{Convert.ToHexStringLower(fileHash[..16])}",
|
||||
"string-refs" => $"str:{Convert.ToHexStringLower(fileHash[..16])}",
|
||||
_ => $"comb:{Convert.ToHexStringLower(fileHash[..16])}"
|
||||
};
|
||||
|
||||
var result = new
|
||||
{
|
||||
File = filePath,
|
||||
Algorithm = algorithm,
|
||||
Function = function,
|
||||
FingerprintId = fingerprintId,
|
||||
FingerprintHash = Convert.ToHexStringLower(fileHash),
|
||||
GeneratedAt = DateTimeOffset.UtcNow.ToString("O")
|
||||
};
|
||||
|
||||
if (format == "json")
|
||||
{
|
||||
var json = JsonSerializer.Serialize(result, JsonOptions);
|
||||
AnsiConsole.WriteLine(json);
|
||||
}
|
||||
else if (format == "hex")
|
||||
{
|
||||
AnsiConsole.WriteLine(result.FingerprintHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[bold]Fingerprint:[/] {result.FingerprintId}");
|
||||
AnsiConsole.MarkupLine($"Algorithm: [cyan]{result.Algorithm}[/]");
|
||||
if (function != null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"Function: [cyan]{function}[/]");
|
||||
}
|
||||
AnsiConsole.MarkupLine($"Hash: [dim]{result.FingerprintHash}[/]");
|
||||
AnsiConsole.MarkupLine($"Generated: {result.GeneratedAt}");
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Generated fingerprint for {Path} using {Algorithm}",
|
||||
filePath,
|
||||
algorithm);
|
||||
}
|
||||
|
||||
return ExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
logger.LogError(ex, "Failed to fingerprint {Path}", filePath);
|
||||
return ExitCodes.GeneralError;
|
||||
}
|
||||
}
|
||||
|
||||
private static string DetectFormat(byte[] header)
|
||||
{
|
||||
// ELF magic: 0x7f 'E' 'L' 'F'
|
||||
if (header[0] == 0x7f && header[1] == 'E' && header[2] == 'L' && header[3] == 'F')
|
||||
return "ELF";
|
||||
|
||||
// PE magic: 'M' 'Z'
|
||||
if (header[0] == 'M' && header[1] == 'Z')
|
||||
return "PE";
|
||||
|
||||
// Mach-O magic
|
||||
if ((header[0] == 0xfe && header[1] == 0xed && header[2] == 0xfa && header[3] == 0xce) ||
|
||||
(header[0] == 0xfe && header[1] == 0xed && header[2] == 0xfa && header[3] == 0xcf) ||
|
||||
(header[0] == 0xcf && header[1] == 0xfa && header[2] == 0xed && header[3] == 0xfe) ||
|
||||
(header[0] == 0xce && header[1] == 0xfa && header[2] == 0xed && header[3] == 0xfe))
|
||||
return "Mach-O";
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private static string DetectArchitecture(byte[] header, string format)
|
||||
{
|
||||
if (format == "ELF" && header.Length >= 19)
|
||||
{
|
||||
return header[18] switch
|
||||
{
|
||||
0x03 => "x86",
|
||||
0x3e => "x86_64",
|
||||
0xb7 => "aarch64",
|
||||
0x28 => "arm",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
|
||||
if (format == "PE")
|
||||
{
|
||||
return "x86/x86_64"; // Would need to parse PE header properly
|
||||
}
|
||||
|
||||
if (format == "Mach-O")
|
||||
{
|
||||
// Check for 64-bit magic
|
||||
if (header[3] == 0xcf || header[0] == 0xcf)
|
||||
return "x86_64/aarch64";
|
||||
return "x86/arm";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static string? ExtractBuildId(string filePath)
|
||||
{
|
||||
// In production, this would parse ELF .note.gnu.build-id section
|
||||
// For now, return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ExitCodes
|
||||
|
||||
Reference in New Issue
Block a user