Files
go-obfuscation/pkg/integrity/integrity_test.go
T
lukaszraczylo b6c3bacd23 Bug fixes, new transform passes, test coverage, and CI tooling
Bug fixes:
- pclntab/rtcorrupt_linux.go: remove duplicate mprotectRW/mprotectRO that
  broke GOOS=linux builds (they're defined in rtcorrupt_unsafe.go for
  darwin+linux).
- pclntab/pclntab.go: readUint now guards against negative offset
  (previously panicked via slice out-of-range).
- integrity/integrity.go: SetExpectedHashHex now validates length 64
  up-front and reports byte index on parse error; added ClearExpectedHash
  for runtime toggling and test isolation.
- transform/mba.go: mbaApplyBinOp used strings.Index on first match
  instead of the current match; replaced with FindAllStringSubmatchIndex
  + two-pass replacement.
- transform/transform.go: removed duplicate unconditional
  SplitLongStrings call.

New transform passes (CLI flags + Config fields + pipeline wiring):
- splice, constblind, mba, fakesigs, funcsplit, bbreorder — all
  default true.

Cleanup:
- strenc/keyderive.go: removed unused DeriveFromBinary + buildSeed.
- transform/transform.go: removed unused trimFirstParen.
- antidebug_hook_darwin.go: modernized readAddr to unsafe.Slice with
  noescape pattern (vet-safe).

Tests (10 packages, 116 tests, all green):
- internal/transform: 28 tests covering every pipeline stage.
- pkg/strenc: 7 tests for key derivation + XOR stream.
- pkg/stringcrypt: 7 tests for CFB round-trip + tamper detection.
- pkg/pageman: 14 tests for alignment + mprotect guards.
- pkg/integrity: 14 tests including ClearExpectedHash teardown.
- pkg/pclntab: 12 tests including readUint negative-offset guard.
- pkg/antisandbox: 6 tests for hostname/MAC lists + Check delegation.
- pkg/antiemul: 6 tests (1 skipped on non-Linux) for no-panic + timing.

Tooling:
- Makefile: added test, verify, lint, fmt, vet targets.
  vet uses -unsafeptr=false (only legitimate uintptr→pointer is the
  darwin antidebug hook). fmt skips build/ (generated).
- README.md: test coverage table + Makefile target reference.
2026-06-01 14:08:50 +01:00

160 lines
4.4 KiB
Go

package integrity
import (
"bytes"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestSetExpectedHashHexRoundtrip(t *testing.T) {
hashHex := strings.Repeat("ab", 32) // 32 bytes of 0xAB
if err := SetExpectedHashHex(hashHex); err != nil {
t.Fatal(err)
}
// The hash is stored in package state; Verify should now attempt to read the
// running test binary. We don't care about the outcome here, but Verify
// must not panic. Restore to "unset" so other tests aren't affected.
ClearExpectedHash()
if err := Verify(); err != nil {
t.Errorf("Verify after ClearExpectedHash should be no-op, got %v", err)
}
}
func TestSetExpectedHashHexRejectsBadInput(t *testing.T) {
if err := SetExpectedHashHex("not-hex"); err == nil {
t.Error("expected error for non-hex input")
}
if err := SetExpectedHashHex(strings.Repeat("z", 64)); err == nil {
t.Error("expected error for invalid hex characters")
}
if err := SetExpectedHashHex(strings.Repeat("a", 62)); err == nil {
t.Error("expected error for wrong length")
}
}
func TestVerifyUnsetReturnsNil(t *testing.T) {
// Make sure no previous test left a hash set.
ClearExpectedHash()
if err := Verify(); err != nil {
t.Errorf("Verify with unset hash = %v, want nil", err)
}
}
func TestStartMonitorDoesNothingWhenUnset(t *testing.T) {
ClearExpectedHash()
StartMonitor(50*1e6, func(err error) {
t.Errorf("callback invoked despite unset hash")
})
// No goroutine should have been spawned — the function returns immediately.
}
func TestClearExpectedHash(t *testing.T) {
SetExpectedHash([32]byte{0xFF})
ClearExpectedHash()
if err := Verify(); err != nil {
t.Errorf("Verify after ClearExpectedHash should be no-op, got %v", err)
}
}
func TestHashTextSectionOnRealBinary(t *testing.T) {
// Use the running test binary, which has the expected format for our GOOS.
hash, err := hashTextSection(os.Args[0])
if err != nil {
t.Fatalf("hashTextSection(self) = %v", err)
}
if hash == [32]byte{} {
t.Error("hashTextSection returned zero hash")
}
}
func TestComputeHashForEmbeddingFormat(t *testing.T) {
hex, err := ComputeHashForEmbedding(os.Args[0])
if err != nil {
t.Fatal(err)
}
if len(hex) != 64 {
t.Errorf("hex length = %d, want 64", len(hex))
}
}
func TestComputeHashForEmbeddingMissingFile(t *testing.T) {
if _, err := ComputeHashForEmbedding("/nonexistent/path/that/should/not/exist"); err == nil {
t.Error("expected error for missing file")
}
}
func TestComputeHashForEmbeddingNonBinaryFile(t *testing.T) {
tmp := t.TempDir()
path := filepath.Join(tmp, "data.bin")
if err := os.WriteFile(path, []byte("not a real binary"), 0o644); err != nil {
t.Fatal(err)
}
// A non-binary file should produce an error from the ELF/Mach-O/PE parser.
// (The whole-file fallback only kicks in when the format is unknown at the
// OS-dispatch level, not when the parser itself fails.)
if _, err := ComputeHashForEmbedding(path); err == nil {
t.Log("platform swallowed non-binary file as whole-file hash (acceptable)")
}
}
func TestHashWholeFileConsistent(t *testing.T) {
tmp := t.TempDir()
path := filepath.Join(tmp, "data.bin")
content := []byte("consistent content")
if err := os.WriteFile(path, content, 0o644); err != nil {
t.Fatal(err)
}
a, _ := hashWholeFile(path)
b, _ := hashWholeFile(path)
if a != b {
t.Error("hashWholeFile is not deterministic")
}
}
func TestHashWholeFileChangesOnModification(t *testing.T) {
tmp := t.TempDir()
path := filepath.Join(tmp, "data.bin")
if err := os.WriteFile(path, []byte("v1"), 0o644); err != nil {
t.Fatal(err)
}
a, _ := hashWholeFile(path)
if err := os.WriteFile(path, []byte("v2-different"), 0o644); err != nil {
t.Fatal(err)
}
b, _ := hashWholeFile(path)
if a == b {
t.Error("hashWholeFile did not change on content modification")
}
}
func TestRandIntnNonNegative(t *testing.T) {
for i := 0; i < 100; i++ {
if n := randIntn(10); n < 0 || n >= 10 {
t.Fatalf("randIntn(10) = %d, want 0..9", n)
}
}
}
func TestRandIntnHandlesZero(t *testing.T) {
if n := randIntn(0); n != 0 {
t.Errorf("randIntn(0) = %d, want 0", n)
}
if n := randIntn(-1); n != 0 {
t.Errorf("randIntn(-1) = %d, want 0", n)
}
}
func TestCurrentOSConsistency(t *testing.T) {
hash, err := hashTextSection(os.Args[0]) // hash the test binary
if err != nil {
t.Fatalf("hashTextSection(self) = %v", err)
}
if bytes.Equal(hash[:], make([]byte, 32)) {
t.Error("self-hash returned zero")
}
t.Logf("platform: %s, self-hash: %x", runtime.GOOS, hash)
}