80 lines
2.8 KiB
Python
80 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare PE section metadata and aligned byte blocks for local NGX DLLs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
from inspect_ngx_dll import PEImage
|
|
|
|
|
|
def version_of(image: PEImage) -> str:
|
|
for resource in image.resources():
|
|
info = resource.get("version_info", {}).get("strings", {})
|
|
if "FileVersion" in info:
|
|
return info["FileVersion"]
|
|
return "unknown"
|
|
|
|
|
|
def compare_sections(a: PEImage, b: PEImage, block_size: int) -> None:
|
|
by_name_a = {section["name"]: section for section in a.sections}
|
|
by_name_b = {section["name"]: section for section in b.sections}
|
|
common = [name for name in by_name_a if name in by_name_b]
|
|
|
|
hash_a = hashlib.sha256(a.data).hexdigest()
|
|
hash_b = hashlib.sha256(b.data).hexdigest()
|
|
print(f"A: {a.path.name} version={version_of(a)} size={len(a.data)} sha256={hash_a}")
|
|
print(f"B: {b.path.name} version={version_of(b)} size={len(b.data)} sha256={hash_b}")
|
|
print(f"Aligned comparison uses {block_size}-byte blocks at matching section offsets.")
|
|
|
|
for name in common:
|
|
sa = by_name_a[name]
|
|
sb = by_name_b[name]
|
|
start_a, start_b = sa["raw_offset"], sb["raw_offset"]
|
|
size_a, size_b = sa["raw_size"], sb["raw_size"]
|
|
overlap = min(size_a, size_b)
|
|
prefix = 0
|
|
ba = a.data[start_a : start_a + overlap]
|
|
bb = b.data[start_b : start_b + overlap]
|
|
for x, y in zip(ba, bb):
|
|
if x != y:
|
|
break
|
|
prefix += 1
|
|
|
|
blocks = overlap // block_size
|
|
equal_blocks = 0
|
|
for index in range(blocks):
|
|
lo = index * block_size
|
|
hi = lo + block_size
|
|
if ba[lo:hi] == bb[lo:hi]:
|
|
equal_blocks += 1
|
|
|
|
aligned_percent = 100.0 * equal_blocks / blocks if blocks else 0.0
|
|
print(
|
|
f"{name:8} A={size_a:>10} B={size_b:>10} "
|
|
f"aligned-identical={equal_blocks}/{blocks} ({aligned_percent:.2f}%) "
|
|
f"common-prefix={prefix} bytes entropy={sa['entropy']:.3f}/{sb['entropy']:.3f} "
|
|
f"section-sha256={sa['sha256'][:12]}/{sb['sha256'][:12]}"
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("dll_a", type=Path)
|
|
parser.add_argument("dll_b", type=Path)
|
|
parser.add_argument("--block-size", type=int, default=4096)
|
|
args = parser.parse_args()
|
|
if args.block_size < 64 or args.block_size & (args.block_size - 1):
|
|
parser.error("--block-size must be a power of two and at least 64")
|
|
|
|
image_a = PEImage(args.dll_a, string_limit=0)
|
|
image_b = PEImage(args.dll_b, string_limit=0)
|
|
compare_sections(image_a, image_b, args.block_size)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|