mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-13 05:39:02 +00:00
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.
200 lines
3.7 KiB
Go
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
|
|
}
|