mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-13 05:39:02 +00:00
Anti-RE features (10 new packages/files):
- pkg/antidebug/breakpoint_{linux,darwin,other}.go: HW breakpoint
detection via ptrace GETREGS (DR0-DR3 on linux/amd64).
- pkg/antidebug/singlestep.go: SIGTRAP-count-based single-step
detection.
- pkg/antidbi/frida_deep.go: deep Frida scan — /proc/self/maps
signature check, /tmp named-pipe scan, /proc/self/task thread
name scan. Wired into antidbi.Check().
- pkg/antisandbox/ldpreload_{linux,other}.go: LD_PRELOAD and
/etc/ld.so.preload detection on linux; env-unset helper.
- pkg/antire/response.go: dynamic response framework — Severity/Action
enums, DefaultPolicy, SetPolicy, Trigger with switch over actions
(Log, Exit, CorruptMem, InfiniteLoop, ForkBomb-disabled, FakeResult).
- pkg/integrity/monitor.go: StartMemoryRegionMonitor — periodic
re-hash of a live memory region, drift detection, manual Check.
(Distinct from existing StartMonitor which re-hashes the file.)
- pkg/antivm/timer_resolution.go: time.Now() granularity check —
real CPUs have < 1us resolution, VMs often 1ms+. Exposed as
CheckTimerResolution().
- pkg/smc/smc.go: self-modifying code MVP — XOR encrypt/decrypt
via pageman.SecureBuffer. (Full atomic decrypt+exec is future work.)
- cmd/packer/main.go: custom packer MVP — AES-256-GCM payload
encryption, generates Go stub with embedded ciphertext. (Native
stub is future work.)
- Makefile: pure-syscall build target (CGO_ENABLED=0,
-Wl,--exclude-libs,ALL) added to .PHONY and help.
Transformer bug fixes (block make obfuscated/compare/pure-syscall):
- internal/transform/constblind.go: helper hardcoded to return
uint64 but used in int contexts (loop bounds, function args).
Changed to int. Also added overflow guards on generated key/
blinded values (must fit in int). Added missing upper-bound
check in cbBlindDecimal.
- internal/transform/fakesigs.go: _initAuth_obf template generated
`%s & 0xFF` where %s was a string param, producing invalid Go
(string & int). Changed to `%s[0] & 0xFF` (byte index).
Naming collision fix:
- pkg/integrity/monitor.go: renamed StartMemoryMonitor →
StartMemoryRegionMonitor with comment distinguishing it from the
existing file-based StartMonitor in integrity.go.
Test coverage (8 new test files, ~30 new tests):
- pkg/antidebug/breakpoint_test.go: no-panic, concurrent.
- pkg/antidebug/singlestep_test.go: install/uninstall, no-false-positive.
- pkg/antidbi/frida_deep_test.go: all three scans return cleanly.
- pkg/antisandbox/ldpreload_test.go: no-panic on linux, clean state.
- pkg/antire/response_test.go: Severity.String, SetPolicy, Trigger.
- pkg/integrity/monitor_test.go: invalid params, start/stop/check,
drift detection, -race clean.
- pkg/antivm/timer_resolution_test.go: no-panic, returns bool, repeatable.
- pkg/smc/smc_test.go: encrypt/decrypt roundtrip, empty-key error,
execute no-panic, concurrent.
- cmd/packer/packer_test.go: pack/unpack roundtrip, invalid key, missing flags.
Verification:
- go test -count=1 ./... — all 17 packages pass.
- make compare — full pipeline succeeds (normal + obfuscated + integrity).
- make pure-syscall — succeeds, produces minimal-libc binary.
- Obfuscated binary runs correctly, exits 0, all 6 demo secrets hidden.
- Symbol count: 2864 → 58. String count: 15287 → 4346.
191 lines
4.3 KiB
Go
191 lines
4.3 KiB
Go
// Constant blinding transformation.
|
|
//
|
|
// Replaces integer literal constants with runtime XOR expressions that
|
|
// evaluate to the original value, defeating pattern-matching in disassemblers
|
|
// and static analysis tools. Each constant C is replaced with (K ^ (K ^ C))
|
|
// where K is a per-constant random key.
|
|
package transform
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/lukaszraczylo/go-obfuscation/pkg/stringcrypt"
|
|
)
|
|
|
|
var (
|
|
cbHexRe = regexp.MustCompile(`0x[0-9a-fA-F]+`)
|
|
cbStringRe = regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"`)
|
|
cbBacktickRe = regexp.MustCompile("`[^`]*`")
|
|
|
|
cbSkipContexts = []string{
|
|
"case ", "case\t",
|
|
"[",
|
|
"len(", "cap(",
|
|
"make(",
|
|
"iota",
|
|
}
|
|
)
|
|
|
|
func TransformConstants(src string) string {
|
|
rng := rand.New(rand.NewSource(rand.Int63()))
|
|
blindHelperName := stringcrypt.RandomIdent("_blind", 4)
|
|
|
|
lines := strings.Split(src, "\n")
|
|
var result []string
|
|
inFuncBody := false
|
|
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
|
|
inFuncBody = true
|
|
}
|
|
if inFuncBody && trimmed == "}" {
|
|
inFuncBody = false
|
|
}
|
|
|
|
if !inFuncBody {
|
|
result = append(result, line)
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(trimmed, "//") || trimmed == "" || trimmed == "{" || trimmed == "}" {
|
|
result = append(result, line)
|
|
continue
|
|
}
|
|
|
|
if shouldCBLineSkip(trimmed) {
|
|
result = append(result, line)
|
|
continue
|
|
}
|
|
|
|
blinded := cbBlindLine(line, rng, blindHelperName)
|
|
result = append(result, blinded)
|
|
}
|
|
|
|
resultStr := strings.Join(result, "\n")
|
|
resultStr = injectBlindHelper(resultStr, blindHelperName)
|
|
return resultStr
|
|
}
|
|
|
|
func shouldCBLineSkip(trimmed string) bool {
|
|
if strings.HasPrefix(trimmed, "case ") || strings.HasPrefix(trimmed, "case\t") {
|
|
return true
|
|
}
|
|
if strings.Contains(trimmed, "[") && strings.Contains(trimmed, "]") {
|
|
return true
|
|
}
|
|
for _, ctx := range cbSkipContexts {
|
|
if strings.Contains(trimmed, ctx) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func cbBlindLine(line string, rng *rand.Rand, helperName string) string {
|
|
line = cbHexRe.ReplaceAllStringFunc(line, func(match string) string {
|
|
return cbBlindHex(match, rng, helperName)
|
|
})
|
|
|
|
line = blindDecimalsInLine(line, rng, helperName)
|
|
return line
|
|
}
|
|
|
|
func blindDecimalsInLine(line string, rng *rand.Rand, helperName string) string {
|
|
var buf strings.Builder
|
|
i := 0
|
|
for i < len(line) {
|
|
if unicode.IsDigit(rune(line[i])) {
|
|
start := i
|
|
for i < len(line) && unicode.IsDigit(rune(line[i])) {
|
|
i++
|
|
}
|
|
numStr := line[start:i]
|
|
|
|
if start > 0 && (unicode.IsLetter(rune(line[start-1])) || line[start-1] == '_') {
|
|
buf.WriteString(numStr)
|
|
continue
|
|
}
|
|
if i < len(line) && (unicode.IsLetter(rune(line[i])) || line[i] == '_' || line[i] == 'x' || line[i] == 'X') {
|
|
buf.WriteString(numStr)
|
|
continue
|
|
}
|
|
|
|
replaced := cbBlindDecimal(numStr, rng, helperName)
|
|
buf.WriteString(replaced)
|
|
} else {
|
|
buf.WriteByte(line[i])
|
|
i++
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func cbBlindHex(match string, rng *rand.Rand, helperName string) string {
|
|
val, err := strconv.ParseUint(match, 0, 64)
|
|
if err != nil {
|
|
return match
|
|
}
|
|
if val == 0 || val == 1 {
|
|
return match
|
|
}
|
|
if val > 0x7FFFFFFFFFFFFFFF {
|
|
return match
|
|
}
|
|
|
|
key := rng.Uint64() | 1
|
|
blinded := key ^ val
|
|
if key > 0x7FFFFFFFFFFFFFFF || blinded > 0x7FFFFFFFFFFFFFFF {
|
|
return match
|
|
}
|
|
return fmt.Sprintf("%s(0x%X, 0x%X)", helperName, key, blinded)
|
|
}
|
|
|
|
func cbBlindDecimal(match string, rng *rand.Rand, helperName string) string {
|
|
val, err := strconv.ParseUint(match, 10, 64)
|
|
if err != nil {
|
|
return match
|
|
}
|
|
if val < 10 {
|
|
return match
|
|
}
|
|
if val > 0x7FFFFFFFFFFFFFFF {
|
|
return match
|
|
}
|
|
|
|
key := rng.Uint64() | 1
|
|
blinded := key ^ val
|
|
if key > 0x7FFFFFFFFFFFFFFF || blinded > 0x7FFFFFFFFFFFFFFF {
|
|
return match
|
|
}
|
|
return fmt.Sprintf("%s(0x%X, 0x%X)", helperName, key, blinded)
|
|
}
|
|
|
|
func injectBlindHelper(src string, helperName string) string {
|
|
helper := fmt.Sprintf(`
|
|
//go:noinline
|
|
func %s(a, b int) int { return a ^ b }
|
|
`, helperName)
|
|
|
|
insertAt := findImportBlockEnd(src)
|
|
if insertAt == -1 {
|
|
pkgIdx := strings.Index(src, "package ")
|
|
if pkgIdx != -1 {
|
|
lineEnd := strings.Index(src[pkgIdx:], "\n")
|
|
if lineEnd != -1 {
|
|
insertAt = pkgIdx + lineEnd + 1
|
|
}
|
|
}
|
|
}
|
|
if insertAt != -1 && insertAt < len(src) {
|
|
src = src[:insertAt] + helper + src[insertAt:]
|
|
}
|
|
return src
|
|
}
|