use crate::ops::security_detection::detector_support::{magic, DetectionError};
pub const SPEC_TOML: &str = r#"schema_version = 1
id = "security_detection.detect_packed_binary"
archetype = "rule-bytes-to-bool"
display_name = "Detect Packed Binary"
summary = "Returns true when executable file magic combines with high byte entropy."
category = "C"
[intrinsic]
wgsl = "security_detection_detect_packed_binary"
[signature]
inputs = ["Bytes"]
output = "Bool"
laws = []
equivalence_classes = ["executable_high_entropy", "executable_low_entropy", "non_executable", "t47_cap"]
workgroup_size = [64, 1, 1]
tags = ["security-detection", "packed-binary", "malware", "t47"]
fixtures_dir = "fixtures/"
"#;
pub const REFERENCE_VECTORS_TOML: &str = r#"[[case]]
name = "positive_elf_entropy"
input_hex = "7f454c46000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
expected = true
[[case]]
name = "negative_text"
input = "plain text file with no executable magic"
expected = false
"#;
pub mod lowering {
#[must_use]
pub const fn source() -> &'static str {
r#"struct Params {
input_len: u32,
_pad0: u32,
_pad1: u32,
_pad2: u32,
}
@group(0) @binding(0) var<storage, read> input: array<u32>;
@group(0) @binding(1) var<storage, read_write> output: array<u32>;
@group(0) @binding(2) var<uniform> params: Params;
fn byte_at(index: u32) -> u32 {
if (index >= params.input_len) {
return 0u;
}
return input[index] & 255u;
}
fn starts2(a: u32, b: u32) -> bool {
return params.input_len >= 2u && byte_at(0u) == a && byte_at(1u) == b;
}
fn starts4(a: u32, b: u32, c: u32, d: u32) -> bool {
return params.input_len >= 4u && byte_at(0u) == a && byte_at(1u) == b &&
byte_at(2u) == c && byte_at(3u) == d;
}
fn executable_magic() -> bool {
let pe = starts2(77u, 90u);
let elf = starts4(127u, 69u, 76u, 70u);
let macho32 = starts4(254u, 237u, 250u, 206u);
let macho64 = starts4(254u, 237u, 250u, 207u);
let java_class = starts4(202u, 254u, 186u, 190u);
return pe || elf || macho32 || macho64 || java_class;
}
fn min_u32(a: u32, b: u32) -> u32 {
if (a < b) {
return a;
}
return b;
}
fn entropy_score(sample_len: u32) -> u32 {
var histogram: array<u32, 256>;
for (var i = 0u; i < 256u; i = i + 1u) {
histogram[i] = 0u;
}
for (var i = 0u; i < sample_len; i = i + 1u) {
let b = byte_at(i);
histogram[b] = histogram[b] + 1u;
}
var occupied = 0u;
var singleton = 0u;
for (var b = 0u; b < 256u; b = b + 1u) {
if (histogram[b] != 0u) {
occupied = occupied + 1u;
if (histogram[b] == 1u) {
singleton = singleton + 1u;
}
}
}
return occupied * 32u + singleton;
}
@compute @workgroup_size(64)
fn security_detection_detect_packed_binary() {
let sample_len = min_u32(params.input_len, 8192u);
if (!executable_magic() || sample_len < 256u) {
output[0] = 0u;
return;
}
output[0] = select(0u, 1u, entropy_score(sample_len) >= 7150u);
}
"#
}
}
pub fn detect_packed_binary(input: &[u8]) -> Result<bool, DetectionError> {
magic::detect_packed_binary(input)
}
pub mod implementation {
pub use super::detect_packed_binary;
pub mod kernel {
pub use super::super::detect_packed_binary;
}
pub mod lowering {
pub mod wgsl {
pub use super::super::super::lowering::source;
}
}
}