Files
go-obfuscation/pkg/integrity/integrity.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

200 lines
3.7 KiB
Go

package integrity
import (
"crypto/rand"
"crypto/sha256"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"fmt"
"os"
"runtime"
"sync"
"time"
)
var (
expectedHash [32]byte
hashIsSet bool
mu sync.RWMutex
)
func SetExpectedHash(hash [32]byte) {
mu.Lock()
defer mu.Unlock()
expectedHash = hash
hashIsSet = true
}
// ClearExpectedHash disables integrity verification until the next
// SetExpectedHash call. Use this in test teardown or runtime toggles.
func ClearExpectedHash() {
mu.Lock()
defer mu.Unlock()
expectedHash = [32]byte{}
hashIsSet = false
}
func SetExpectedHashHex(hex string) error {
if len(hex) != 64 {
return fmt.Errorf("integrity: invalid hash hex length %d, want 64", len(hex))
}
var hash [32]byte
for i := 0; i < 32; i++ {
_, err := fmt.Sscanf(hex[i*2:i*2+2], "%x", &hash[i])
if err != nil {
return fmt.Errorf("integrity: invalid hash hex at byte %d: %w", i, err)
}
}
SetExpectedHash(hash)
return nil
}
func Verify() error {
mu.RLock()
set := hashIsSet
mu.RUnlock()
if !set {
return nil
}
selfPath, err := os.Executable()
if err != nil {
return fmt.Errorf("integrity: cannot determine executable path: %w", err)
}
hash, err := hashTextSection(selfPath)
if err != nil {
return fmt.Errorf("integrity: cannot hash text section: %w", err)
}
mu.RLock()
expected := expectedHash
mu.RUnlock()
if hash != expected {
return fmt.Errorf("integrity: binary has been tampered with")
}
return nil
}
func StartMonitor(interval time.Duration, onTamper func(error)) {
mu.RLock()
set := hashIsSet
mu.RUnlock()
if !set {
return
}
go func() {
jitter := time.Duration(randIntn(int(interval.Milliseconds()/2))) * time.Millisecond
time.Sleep(jitter)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
if err := Verify(); err != nil {
if onTamper != nil {
onTamper(err)
}
os.Exit(66)
}
}
}()
}
func hashTextSection(path string) ([32]byte, error) {
switch runtime.GOOS {
case "linux":
return hashTextELF(path)
case "darwin":
return hashTextMacho(path)
case "windows":
return hashTextPE(path)
default:
return hashWholeFile(path)
}
}
func hashTextELF(path string) ([32]byte, error) {
f, err := elf.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
section := f.Section(".text")
if section == nil {
return hashWholeFile(path)
}
data, err := section.Data()
if err != nil {
return [32]byte{}, err
}
return sha256.Sum256(data), nil
}
func hashTextMacho(path string) ([32]byte, error) {
f, err := macho.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
for _, section := range f.Sections {
if section.Name == "__text" {
data, err := section.Data()
if err != nil {
return [32]byte{}, err
}
return sha256.Sum256(data), nil
}
}
return hashWholeFile(path)
}
func hashTextPE(path string) ([32]byte, error) {
f, err := pe.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
for _, section := range f.Sections {
if section.Name == ".text" {
data, err := section.Data()
if err != nil {
return [32]byte{}, err
}
return sha256.Sum256(data), nil
}
}
return hashWholeFile(path)
}
func hashWholeFile(path string) ([32]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return [32]byte{}, err
}
return sha256.Sum256(data), nil
}
func ComputeHashForEmbedding(path string) (string, error) {
hash, err := hashTextSection(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", hash), nil
}
func randIntn(n int) int {
if n <= 0 {
return 0
}
var buf [4]byte
_, _ = rand.Read(buf[:])
return int(binary.BigEndian.Uint32(buf[:])) % n
}