mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-13 05:39:02 +00:00
182 lines
4.1 KiB
Go
182 lines
4.1 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
|
|
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 <= 2 || val < 10 {
|
|
return match
|
|
}
|
|
|
|
key := rng.Uint64() | 1
|
|
blinded := key ^ val
|
|
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 uint64) uint64 { 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
|
|
}
|