Files
go-obfuscation/internal/transform/transform.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

1639 lines
41 KiB
Go

package transform
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
cryptoRand "crypto/rand"
"crypto/sha256"
"fmt"
"go/ast"
"go/parser"
"go/token"
"math/rand"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/lukaszraczylo/go-obfuscation/pkg/strenc"
"github.com/lukaszraczylo/go-obfuscation/pkg/stringcrypt"
"github.com/lukaszraczylo/go-obfuscation/pkg/vm"
)
type Config struct {
EncryptStrings bool
InjectOpaquePreds bool
InjectDeadCode bool
RandomizeOrder bool
IndirectDispatch bool
BogusControlFlow bool
FlattenControl bool
SpliceCode bool
BuildSeed int64
InjectBuildID bool
VirtualizeFunctions bool
AntiDisassembly bool
JunkStrings bool
ConstantBlind bool
ApplyMBA bool
FakeSignatures bool
FunctionSplit bool
ReorderBasicBlocks bool
}
func DefaultConfig() Config {
return Config{
EncryptStrings: true,
InjectOpaquePreds: true,
InjectDeadCode: true,
RandomizeOrder: true,
IndirectDispatch: true,
BogusControlFlow: true,
FlattenControl: true,
SpliceCode: true,
InjectBuildID: true,
VirtualizeFunctions: true,
AntiDisassembly: true,
JunkStrings: true,
ConstantBlind: true,
ApplyMBA: true,
FakeSignatures: true,
FunctionSplit: true,
ReorderBasicBlocks: true,
}
}
type Transformer struct {
config Config
rng *rand.Rand
}
func New(config Config) *Transformer {
return &Transformer{
config: config,
rng: rand.New(rand.NewSource(config.BuildSeed)),
}
}
type stringReplacement struct {
start int
end int
replExpr string
}
func (t *Transformer) TransformFile(srcPath, dstPath string) error {
src, err := os.ReadFile(srcPath)
if err != nil {
return fmt.Errorf("read %s: %w", srcPath, err)
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, srcPath, src, parser.ParseComments)
if err != nil {
return fmt.Errorf("parse %s: %w", srcPath, err)
}
result := string(src)
if t.config.EncryptStrings {
result, err = t.transformStrings(fset, f, result)
if err != nil {
return fmt.Errorf("encrypt strings %s: %w", srcPath, err)
}
result = convertConstToVar(result)
result = SplitLongStrings(result)
}
if t.config.InjectOpaquePreds {
result = t.injectOpaquePredicates(result)
}
if t.config.InjectDeadCode {
result = t.injectDeadCode(result)
}
if t.config.BogusControlFlow {
result = t.injectBogusControlFlow(result)
}
if t.config.IndirectDispatch {
result = t.indirectDispatch(result)
}
if t.config.SpliceCode {
result = t.spliceCode(result)
}
if t.config.FlattenControl {
result = t.flattenSimpleFunctions(result)
}
if t.config.FunctionSplit {
result = SplitFunctions(result)
}
if t.config.ReorderBasicBlocks {
result = ReorderBasicBlocks(result)
}
if t.config.ConstantBlind {
result = TransformConstants(result)
}
if t.config.ApplyMBA {
result = TransformMBA(result)
}
if t.config.FakeSignatures {
result = InjectFakeSignatures(result)
}
if t.config.InjectBuildID && strings.HasSuffix(srcPath, "main.go") {
result = t.injectBuildID(result)
}
if t.config.VirtualizeFunctions {
result = t.virtualizeFunctions(result)
}
if t.config.AntiDisassembly {
result = t.injectAntiDisassembly(result)
}
if t.config.JunkStrings {
result = t.injectJunkStrings(result)
}
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return fmt.Errorf("mkdir %s: %w", filepath.Dir(dstPath), err)
}
if err := os.WriteFile(dstPath, []byte(result), 0644); err != nil {
return fmt.Errorf("write %s: %w", dstPath, err)
}
return nil
}
func (t *Transformer) TransformDir(srcDir, dstDir string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("readdir %s: %w", srcDir, err)
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
if entry.IsDir() {
if err := t.TransformDir(srcPath, dstPath); err != nil {
return err
}
continue
}
if !strings.HasSuffix(entry.Name(), ".go") {
data, err := os.ReadFile(srcPath)
if err != nil {
return err
}
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
if err := os.WriteFile(dstPath, data, 0644); err != nil {
return err
}
continue
}
if strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
fmt.Printf(" transforming: %s\n", entry.Name())
if err := t.TransformFile(srcPath, dstPath); err != nil {
return fmt.Errorf("transform %s: %w", srcPath, err)
}
}
return nil
}
func (t *Transformer) transformStrings(fset *token.FileSet, f *ast.File, src string) (string, error) {
type repl struct {
start int
end int
expr string
}
importRanges := map[int]bool{}
ast.Inspect(f, func(n ast.Node) bool {
imp, ok := n.(*ast.ImportSpec)
if !ok {
return true
}
if imp.Path != nil {
start := fset.Position(imp.Path.Pos()).Offset
end := fset.Position(imp.Path.End()).Offset
for i := start; i < end; i++ {
importRanges[i] = true
}
}
return true
})
var replacements []repl
var plaintexts []string
ast.Inspect(f, func(n ast.Node) bool {
basicLit, ok := n.(*ast.BasicLit)
if !ok || basicLit.Kind != token.STRING {
return true
}
startOff := fset.Position(basicLit.Pos()).Offset
if importRanges[startOff] {
return true
}
raw := basicLit.Value
if len(raw) < 4 {
return true
}
var unquoted string
if raw[0] == '"' && raw[len(raw)-1] == '"' {
var err error
unquoted, err = unquoteGoString(raw)
if err != nil || len(unquoted) < 2 {
return true
}
} else {
return true
}
if shouldSkipString(unquoted) {
return true
}
idx := len(plaintexts)
plaintexts = append(plaintexts, unquoted)
pos := fset.Position(basicLit.Pos())
end := fset.Position(basicLit.End())
replacements = append(replacements, repl{
start: pos.Offset,
end: end.Offset,
expr: fmt.Sprintf("_decStr(%d)", idx),
})
return true
})
if len(replacements) == 0 {
return src, nil
}
for i := len(replacements) - 1; i >= 0; i-- {
r := replacements[i]
src = src[:r.start] + r.expr + src[r.end:]
}
var seed [32]byte
if _, err := cryptoRand.Read(seed[:]); err != nil {
return "", fmt.Errorf("strenc: generate seed: %w", err)
}
masterKey := strenc.DeriveMasterKey(seed)
var entries []strenc.Entry
const padBlock = 64
for _, pt := range plaintexts {
ptBytes := []byte(pt)
ptLen := len(ptBytes)
padded := make([]byte, 2+ptLen)
padded[0] = byte(ptLen & 0xFF)
padded[1] = byte((ptLen >> 8) & 0xFF)
copy(padded[2:], ptBytes)
if len(padded)%padBlock != 0 {
targetLen := ((len(padded) / padBlock) + 1) * padBlock
paddedBuf := make([]byte, targetLen)
copy(paddedBuf, padded)
cryptoRand.Read(paddedBuf[len(padded):])
padded = paddedBuf
}
var iv [16]byte
if _, err := cryptoRand.Read(iv[:]); err != nil {
return "", fmt.Errorf("strenc: generate iv: %w", err)
}
mac := hmac.New(sha256.New, masterKey[:])
mac.Write(iv[:])
var subkey [32]byte
copy(subkey[:], mac.Sum(nil))
entry := encryptEntryGCM(string(padded), subkey, iv)
entries = append(entries, entry)
}
var poolCode strings.Builder
poolCode.WriteString("\nvar _masterSeed = [32]byte{")
for i, b := range seed {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}\n")
poolCode.WriteString("var _encEntries = []strenc.Entry{\n")
for _, e := range entries {
poolCode.WriteString("\t{Ciphertext: []byte{")
for i, b := range e.Ciphertext {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}, IV: [16]byte{")
for i, b := range e.IV {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}},\n")
}
poolCode.WriteString("}\n")
poolCode.WriteString("var _encPool = strenc.NewPool(_masterSeed, _encEntries)\n")
poolCode.WriteString("//go:noinline\nfunc _decStr(i int) string { _b := []byte(_encPool.GetString(i)); if len(_b) < 2 { return \"\" }; _n := int(_b[0]) | int(_b[1])<<8; if _n > len(_b)-2 { return \"\" }; return string(_b[2:2+_n]) }\n")
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] + poolCode.String() + src[insertAt:]
}
src = t.ensureImport(src, "github.com/lukaszraczylo/go-obfuscation/pkg/strenc")
return src, nil
}
func encryptEntryGCM(plaintext string, key [32]byte, iv [16]byte) strenc.Entry {
block, err := aes.NewCipher(key[:])
if err != nil {
panic(fmt.Sprintf("transform: aes.NewCipher: %v", err))
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(fmt.Sprintf("transform: cipher.NewGCM: %v", err))
}
nonceSize := gcm.NonceSize()
nonce := make([]byte, nonceSize)
copy(nonce, iv[:nonceSize])
ct := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return strenc.Entry{Ciphertext: ct, IV: iv}
}
func writeHexLiteral(sb *strings.Builder, data []byte) {
sb.WriteString("[]byte{")
for i, b := range data {
if i > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(sb, "0x%02X", b)
}
sb.WriteString("}")
}
func findImportBlockEnd(src string) int {
idx := strings.Index(src, "import (")
if idx != -1 {
rest := src[idx+len("import ("):]
depth := 1
for i, c := range rest {
if c == '(' {
depth++
} else if c == ')' {
depth--
if depth == 0 {
return idx + len("import (") + i + 1
}
}
}
return -1
}
idx = strings.Index(src, "import \"")
if idx != -1 {
lineEnd := strings.Index(src[idx:], "\n")
if lineEnd != -1 {
return idx + lineEnd + 1
}
return len(src)
}
return -1
}
func shouldSkipString(s string) bool {
skipPrefixes := []string{
"go:", "//", "+build", "package", "go:", "module",
"//go:", "// +build",
}
for _, p := range skipPrefixes {
if strings.HasPrefix(s, p) {
return true
}
}
if len(s) == 0 {
return true
}
if s[0] == '/' || s[0] == '+' {
return true
}
return false
}
func unquoteGoString(s string) (string, error) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return "", fmt.Errorf("not a quoted string")
}
s = s[1 : len(s)-1]
var result strings.Builder
for i := 0; i < len(s); i++ {
if s[i] == '\\' && i+1 < len(s) {
switch s[i+1] {
case 'n':
result.WriteByte('\n')
i++
case 't':
result.WriteByte('\t')
i++
case 'r':
result.WriteByte('\r')
i++
case '\\':
result.WriteByte('\\')
i++
case '"':
result.WriteByte('"')
i++
default:
result.WriteByte(s[i])
}
} else {
result.WriteByte(s[i])
}
}
return result.String(), nil
}
func (t *Transformer) injectOpaquePredicates(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
funcStmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
funcStmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
funcStmtCount++
if funcStmtCount >= 3 && t.rng.Float64() < 0.4 {
result = append(result, "\t"+"if func() bool { return "+t.generateTrueExpression()+" }() { _ = struct{}{} }")
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) injectDeadCode(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
funcStmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
funcStmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
funcStmtCount++
if funcStmtCount >= 3 && t.rng.Float64() < 0.3 {
result = append(result, "\t"+"if func() bool { return "+t.generateFalseExpression()+" }() { println(\"unreachable\") }")
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func isValidIdent(s string) bool {
if len(s) == 0 {
return false
}
for i, c := range s {
if c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
continue
}
if i > 0 && c >= '0' && c <= '9' {
continue
}
return false
}
return true
}
func getIndent(line string) string {
indent := ""
for _, c := range line {
if c == ' ' || c == '\t' {
indent += string(c)
} else {
break
}
}
return indent
}
func convertConstToVar(src string) string {
lines := strings.Split(src, "\n")
inConstBlock := false
hasEncrypted := false
constStartLines := []int{}
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "const (" {
inConstBlock = true
hasEncrypted = false
constStartLines = append(constStartLines, i)
continue
}
if inConstBlock && trimmed == ")" {
if hasEncrypted {
lines[constStartLines[len(constStartLines)-1]] = strings.Replace(
lines[constStartLines[len(constStartLines)-1]], "const (", "var (", 1,
)
}
inConstBlock = false
continue
}
if inConstBlock && (strings.Contains(line, "base64.StdEncoding.DecodeString") || strings.Contains(line, "_decStr(")) {
hasEncrypted = true
}
}
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "const ") && (strings.Contains(line, "base64.StdEncoding.DecodeString") || strings.Contains(line, "_decStr(")) {
lines[i] = strings.Replace(line, "const ", "var ", 1)
}
}
return strings.Join(lines, "\n")
}
func replaceInLines(lines []string, old, new string) []string {
var result []string
for _, l := range lines {
result = append(result, strings.Replace(l, old, new, 1))
}
return result
}
func (t *Transformer) RandomizeOrder(f *ast.File) {
if len(f.Decls) <= 1 {
return
}
t.rng.Shuffle(len(f.Decls), func(i, j int) {
f.Decls[i], f.Decls[j] = f.Decls[j], f.Decls[i]
})
}
func (t *Transformer) injectBogusControlFlow(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
stmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
stmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
stmtCount++
if stmtCount >= 2 && t.rng.Float64() < 0.25 {
result = append(result, t.generateBogusBranch())
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) generateBogusBranch() string {
n := t.rng.Intn(4)
switch n {
case 0:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\tif func() bool { %s := int64(1); for i := 0; i < 10; i++ { %s = %s*7%%49999999 }; return %s == 1 }() { _ = struct{}{} }",
v, v, v, v,
)
case 1:
v := stringcrypt.RandomIdent("_bc", 5)
a := t.rng.Intn(100) + 1
return fmt.Sprintf(
"\tif func() bool { %s := %d*%d; return %s%%2 == 0 && %s > 0 }() { _ = struct{}{} }",
v, a, a, v, v,
)
case 2:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\tif func() bool { %s := [4]byte{0x72, 0x6F, 0x6F, 0x74}; return len(%s) == 4 }() { _ = struct{}{} }",
v, v,
)
default:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\t{ %s := func() int { n := 0; for i := 1; i <= 100; i++ { n += i }; return n }(); _ = %s }",
v, v,
)
}
}
func (t *Transformer) flattenSimpleFunctions(src string) string {
lines := strings.Split(src, "\n")
var result []string
i := 0
for i < len(lines) {
trimmed := strings.TrimSpace(lines[i])
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") && !strings.Contains(trimmed, "main") && !strings.Contains(trimmed, "init()") {
funcLines := []string{lines[i]}
i++
depth := 1
for i < len(lines) && depth > 0 {
for _, c := range lines[i] {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
funcLines = append(funcLines, lines[i])
i++
}
if len(funcLines) > 5 && t.rng.Float64() < 0.4 {
flattened := flattenFunctionBody(funcLines)
result = append(result, flattened...)
} else {
result = append(result, funcLines...)
}
continue
}
result = append(result, lines[i])
i++
}
return strings.Join(result, "\n")
}
func flattenFunctionBody(funcLines []string) []string {
funcSig := funcLines[0]
bodyLines := funcLines[1 : len(funcLines)-1]
closingBrace := funcLines[len(funcLines)-1]
if len(bodyLines) < 3 {
return funcLines
}
var statements [][]string
var current []string
depth := 0
for _, l := range bodyLines {
trimmed := strings.TrimSpace(l)
if trimmed == "" {
continue
}
for _, c := range l {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
current = append(current, l)
if depth <= 0 && trimmed != "{" {
statements = append(statements, current)
current = nil
depth = 0
}
}
if len(current) > 0 {
statements = append(statements, current)
}
if len(statements) < 3 {
return funcLines
}
for _, stmt := range statements {
for _, line := range stmt {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, ":=") {
parts := strings.SplitN(trimmed, ":=", 2)
varName := strings.TrimSpace(parts[0])
if isValidIdent(varName) && !strings.Contains(varName, ",") {
rhs := strings.TrimSpace(parts[1])
if inferTypeFromRHS(rhs) == "" {
return funcLines
}
}
}
}
}
var result []string
result = append(result, funcSig)
var hoistedVars []string
declaredVars := map[string]bool{}
for idx, stmt := range statements {
for _, line := range stmt {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, ":=") {
parts := strings.SplitN(trimmed, ":=", 2)
varName := strings.TrimSpace(parts[0])
if !isValidIdent(varName) && !strings.Contains(varName, ",") {
continue
}
rhs := strings.TrimSpace(parts[1])
if !strings.Contains(varName, ",") {
typ := inferTypeFromRHS(rhs)
if typ == "" {
continue
}
if !declaredVars[varName] {
hoistedVars = append(hoistedVars, "\tvar "+varName+" "+typ)
declaredVars[varName] = true
}
statements[idx] = replaceInLines(statements[idx], varName+" :=", varName+" =")
} else {
names := strings.Split(varName, ",")
allValid := true
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !isValidIdent(n) {
allValid = false
break
}
}
if !allValid {
continue
}
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !declaredVars[n] {
hoistedVars = append(hoistedVars, "\tvar "+n+" int")
declaredVars[n] = true
}
}
statements[idx] = replaceInLines(statements[idx], varName+" :=", varName+" =")
}
}
}
}
for _, v := range hoistedVars {
result = append(result, v)
}
stateVar := stringcrypt.RandomIdent("_st", 4)
result = append(result, "\t"+stateVar+" := 0")
result = append(result, "\tfor {")
result = append(result, "\t\tswitch "+stateVar+" {")
numStates := len(statements)
for idx, stmt := range statements {
result = append(result, fmt.Sprintf("\t\tcase %d:", idx))
for _, line := range stmt {
result = append(result, "\t\t"+strings.TrimPrefix(line, "\t"))
}
if idx < numStates-1 {
result = append(result, fmt.Sprintf("\t\t\t%s = %d", stateVar, idx+1))
} else {
result = append(result, "\t\t\treturn")
}
}
result = append(result, "\t\tdefault:")
result = append(result, "\t\t\treturn")
result = append(result, "\t\t}")
result = append(result, "\t}")
result = append(result, closingBrace)
return result
}
func (t *Transformer) generateTrueExpression() string {
a := t.rng.Intn(1000) + 1
switch t.rng.Intn(6) {
case 0:
return fmt.Sprintf("%d%%%d == 0", a*7, 7)
case 1:
return fmt.Sprintf("(%d^%d) == 0", a, a)
case 2:
return fmt.Sprintf("^(%d|%d) == ^%d", a, a, a)
case 3:
return fmt.Sprintf("(%d & %d) == %d", a, a, a)
case 4:
return fmt.Sprintf("len([%d]byte{}) == 0", t.rng.Intn(8)+1)
default:
b := t.rng.Intn(100) + 1
return fmt.Sprintf("(%d+%d) > %d", b, a, b+a-1)
}
}
func (t *Transformer) generateFalseExpression() string {
a := t.rng.Intn(0xFFFF) + 1
switch t.rng.Intn(6) {
case 0:
return fmt.Sprintf("(%d - %d) < 0", a, a)
case 1:
return fmt.Sprintf("func() bool { return 1 < 0 }()")
case 2:
return fmt.Sprintf("func() bool { var _x = %d; return _x > _x }()", a)
case 3:
return fmt.Sprintf("func() bool { var _x int = %d; _x -= %d; return _x > 0 }()", a, a)
case 4:
return fmt.Sprintf("len([%d]byte{}) < 0", t.rng.Intn(8)+1)
default:
return fmt.Sprintf("func() bool { return (%d^%d) > 0 && (%d^%d) < 0 }()", a, a, a, a)
}
}
func (t *Transformer) injectBuildID(src string) string {
buildID := fmt.Sprintf("BUILD_%d_%08X", t.config.BuildSeed, t.rng.Int31())
decl := fmt.Sprintf("\nvar _buildID = %q // polymorphic build marker\nvar _ = _buildID\n", buildID)
insertAfter := findImportBlockEnd(src)
if insertAfter == -1 {
pkgIdx := strings.Index(src, "package ")
if pkgIdx == -1 {
return src
}
lineEnd := strings.Index(src[pkgIdx:], "\n")
if lineEnd == -1 {
return src + decl
}
insertAfter = pkgIdx + lineEnd
}
return src[:insertAfter+1] + decl + src[insertAfter+1:]
}
func (t *Transformer) indirectDispatch(src string) string {
lines := strings.Split(src, "\n")
var result []string
var wrappers []string
dispatchIdx := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "func ") || !strings.HasSuffix(trimmed, "{") {
result = append(result, line)
continue
}
parenIdx := strings.Index(trimmed, "(")
if parenIdx == -1 {
result = append(result, line)
continue
}
funcName := strings.TrimSpace(trimmed[5:parenIdx])
if funcName == "main" || funcName == "init" || funcName == "" || strings.ToUpper(funcName[:1]) == funcName[:1] || strings.HasPrefix(funcName, "_") {
result = append(result, line)
continue
}
obfSuffix := fmt.Sprintf("_obf%d", t.rng.Intn(9000)+1000)
renameFrom := "func " + funcName + "("
renameTo := "func " + funcName + obfSuffix + "("
renamedLine := strings.Replace(line, renameFrom, renameTo, 1)
result = append(result, renamedLine)
restOfSig := trimmed[parenIdx:]
sigClean := strings.TrimSuffix(restOfSig, " {")
depth := 0
paramsEnd := -1
for i, c := range sigClean {
if c == '(' {
depth++
} else if c == ')' {
depth--
if depth == 0 {
paramsEnd = i
break
}
}
}
if paramsEnd == -1 {
continue
}
params := sigClean[:paramsEnd+1]
returnType := strings.TrimSpace(sigClean[paramsEnd+1:])
hasReturn := returnType != ""
argsStart := strings.Index(params, "(") + 1
argsEnd := strings.LastIndex(params, ")")
argStr := params[argsStart:argsEnd]
argNames := extractArgNames(argStr)
callArgs := strings.Join(argNames, ", ")
ptrVar := fmt.Sprintf("_fptr%d", dispatchIdx)
dispatchIdx++
if hasReturn {
ptrDecl := fmt.Sprintf("var %s = %s", ptrVar, funcName+obfSuffix)
wrappers = append(wrappers, ptrDecl)
wrapperFunc := fmt.Sprintf("//go:noinline\nfunc %s%s %s { return %s(%s) }",
funcName, params, returnType, ptrVar, callArgs)
wrappers = append(wrappers, wrapperFunc)
} else {
ptrDecl := fmt.Sprintf("var %s = %s", ptrVar, funcName+obfSuffix)
wrappers = append(wrappers, ptrDecl)
wrapperFunc := fmt.Sprintf("//go:noinline\nfunc %s%s { %s(%s) }",
funcName, params, ptrVar, callArgs)
wrappers = append(wrappers, wrapperFunc)
}
}
if len(wrappers) > 0 {
insertAt := -1
inImportBlock := false
for i, line := range result {
t := strings.TrimSpace(line)
if strings.HasPrefix(t, "import") && strings.Contains(t, "(") {
inImportBlock = true
continue
}
if inImportBlock && t == ")" {
insertAt = i + 1
break
}
}
if insertAt == -1 {
for i, line := range result {
if strings.HasPrefix(strings.TrimSpace(line), "package ") {
insertAt = i + 1
break
}
}
}
if insertAt >= 0 {
for insertAt < len(result) && strings.TrimSpace(result[insertAt]) == "" {
insertAt++
}
newResult := make([]string, 0, len(result)+len(wrappers)+1)
newResult = append(newResult, result[:insertAt]...)
newResult = append(newResult, "")
newResult = append(newResult, wrappers...)
newResult = append(newResult, result[insertAt:]...)
result = newResult
}
}
return strings.Join(result, "\n")
}
func extractArgNames(argStr string) []string {
if strings.TrimSpace(argStr) == "" {
return nil
}
var names []string
for _, arg := range strings.Split(argStr, ",") {
arg = strings.TrimSpace(arg)
parts := strings.Fields(arg)
if len(parts) >= 2 {
names = append(names, parts[0])
} else if len(parts) == 1 {
names = append(names, parts[0])
}
}
return names
}
func (t *Transformer) spliceCode(src string) string {
lines := strings.Split(src, "\n")
var result []string
i := 0
for i < len(lines) {
trimmed := strings.TrimSpace(lines[i])
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") &&
!strings.Contains(trimmed, "main") && !strings.Contains(trimmed, "init") &&
!strings.Contains(trimmed, "func()") {
funcSig := lines[i]
i++
depth := 1
var bodyLines []string
for i < len(lines) && depth > 0 {
for _, c := range lines[i] {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
if depth > 0 {
bodyLines = append(bodyLines, lines[i])
}
i++
}
simpleStmts := 0
hasControlFlow := false
for _, bl := range bodyLines {
bt := strings.TrimSpace(bl)
if bt == "" || bt == "{" || bt == "}" {
continue
}
if strings.HasPrefix(bt, "if ") || strings.HasPrefix(bt, "for ") ||
strings.HasPrefix(bt, "switch ") || strings.HasPrefix(bt, "select ") ||
strings.HasPrefix(bt, "case ") || strings.HasPrefix(bt, "default:") {
hasControlFlow = true
break
}
if strings.HasPrefix(bt, "return ") || strings.HasPrefix(bt, "return") {
break
}
simpleStmts++
}
if simpleStmts >= 5 && !hasControlFlow && t.rng.Float64() < 0.5 {
spliced := t.buildSplicedFunction(funcSig, bodyLines)
result = append(result, spliced...)
} else {
result = append(result, funcSig)
result = append(result, bodyLines...)
result = append(result, "}")
}
continue
}
result = append(result, lines[i])
i++
}
return strings.Join(result, "\n")
}
func (t *Transformer) buildSplicedFunction(funcSig string, bodyLines []string) []string {
var result []string
result = append(result, funcSig)
var stmts []string
var hoistedVars []string
declaredVars := map[string]bool{}
for _, line := range bodyLines {
bt := strings.TrimSpace(line)
if bt == "" || bt == "{" || bt == "}" {
continue
}
if strings.Contains(bt, ":=") {
parts := strings.SplitN(bt, ":=", 2)
varName := strings.TrimSpace(parts[0])
if !isValidIdent(varName) && !strings.Contains(varName, ",") {
stmts = append(stmts, bt)
continue
}
rhs := strings.TrimSpace(parts[1])
if !strings.Contains(varName, ",") {
typ := inferTypeFromRHS(rhs)
if typ == "" {
stmts = append(stmts, bt)
continue
}
if !declaredVars[varName] {
hoistedVars = append(hoistedVars, "\tvar "+varName+" "+typ)
declaredVars[varName] = true
}
bt = varName + " = " + rhs
} else {
names := strings.Split(varName, ",")
allValid := true
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !isValidIdent(n) {
allValid = false
break
}
}
if !allValid {
stmts = append(stmts, bt)
continue
}
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !declaredVars[n] {
hoistedVars = append(hoistedVars, "\tvar "+n+" int")
declaredVars[n] = true
}
}
bt = varName + " = " + rhs
}
}
stmts = append(stmts, bt)
}
for _, v := range hoistedVars {
result = append(result, v)
}
stateVar := stringcrypt.RandomIdent("_sp", 4)
result = append(result, "\t"+stateVar+" := 0")
returnVar := ""
lastStmt := strings.TrimSpace(stmts[len(stmts)-1])
if strings.HasPrefix(lastStmt, "return ") {
returnExpr := strings.TrimPrefix(lastStmt, "return ")
returnVar = stringcrypt.RandomIdent("_ret", 4)
result = append(result, "\tvar "+returnVar+" "+inferTypeFromRHS(returnExpr))
stmts[len(stmts)-1] = returnVar + " = " + returnExpr
}
result = append(result, "\tfor {")
result = append(result, "\t\tswitch "+stateVar+" {")
numFragments := 2
if len(stmts) > 7 {
numFragments = 3
}
fragmentSize := len(stmts) / numFragments
fragIdx := 0
for f := 0; f < numFragments; f++ {
result = append(result, fmt.Sprintf("\t\tcase %d:", f))
end := (f + 1) * fragmentSize
if f == numFragments-1 {
end = len(stmts)
}
for j := f * fragmentSize; j < end; j++ {
result = append(result, "\t\t\t"+stmts[j])
}
if f < numFragments-1 {
result = append(result, fmt.Sprintf("\t\t\t%s = %d", stateVar, f+1))
} else {
if returnVar != "" {
result = append(result, "\t\t\treturn "+returnVar)
} else {
result = append(result, "\t\t\treturn")
}
}
fragIdx++
}
result = append(result, "\t\tdefault:")
if returnVar != "" {
result = append(result, "\t\t\treturn "+returnVar)
} else {
result = append(result, "\t\t\treturn")
}
result = append(result, "\t\t}")
result = append(result, "\t}")
result = append(result, "}")
return result
}
func inferTypeFromRHS(rhs string) string {
rhs = strings.TrimSpace(rhs)
if strings.HasPrefix(rhs, "\"") || strings.HasPrefix(rhs, "`") {
return "string"
}
if strings.HasPrefix(rhs, "fmt.Sprintf") || strings.HasPrefix(rhs, "func() string") {
return "string"
}
if strings.HasPrefix(rhs, "[]byte") || strings.HasPrefix(rhs, "make([]byte") {
return "[]byte"
}
if rhs == "true" || rhs == "false" {
return "bool"
}
if strings.Contains(rhs, "sha256.Sum256") {
return "[32]byte"
}
return ""
}
var vmLenPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*>\s*(\d+)\s*\n\s*\}`)
var vmLenEqPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*==\s*(\d+)\s*\n\s*\}`)
var vmLenLtPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*<\s*(\d+)\s*\n\s*\}`)
var vmLenGePattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*>=\s*(\d+)\s*\n\s*\}`)
var vmArith2Pattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+),\s*(\w+)\s+(?:int|int64)\)\s+(?:int|int64)\s*\{\s*\n\s*return\s+(\w+)\s*([+\-*/%^&|])\s*(\w+)\s*\n\s*\}`)
var vmArith1Pattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+(?:int|int64)\)\s+(?:int|int64)\s*\{\s*\n\s*return\s+(\w+)\s*([+\-*/%^&|])\s*(\d+)\s*\n\s*\}`)
func (t *Transformer) virtualizeFunctions(src string) string {
type replacement struct {
start int
end int
text string
}
var repls []replacement
for _, loc := range vmLenPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("gt", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenEqPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenEqPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("eq", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenLtPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenLtPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("lt", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenGePattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenGePattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("ge", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmArith2Pattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmArith2Pattern.FindStringSubmatch(full)
if len(sub) >= 7 {
funcName, p1, p2, left, op, right := sub[1], sub[2], sub[3], sub[4], sub[5], sub[6]
if left == p1 && right == p2 {
bc := compileArith2Bytecode(op, 0, 1)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc2(funcName, p1, p2, bc)})
} else if left == p2 && right == p1 {
bc := compileArith2Bytecode(op, 1, 0)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc2(funcName, p1, p2, bc)})
}
}
}
for _, loc := range vmArith1Pattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmArith1Pattern.FindStringSubmatch(full)
if len(sub) >= 6 {
funcName, param, left, op, nStr := sub[1], sub[2], sub[3], sub[4], sub[5]
if left == param {
n, _ := strconv.ParseInt(nStr, 10, 64)
bc := compileArith1Bytecode(op, n)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc1(funcName, param, bc)})
}
}
}
if len(repls) == 0 {
return src
}
for i := len(repls) - 1; i >= 0; i-- {
r := repls[i]
src = src[:r.start] + r.text + src[r.end:]
}
if !strings.Contains(src, "\"github.com/lukaszraczylo/go-obfuscation/pkg/vm\"") {
src = t.ensureImport(src, "github.com/lukaszraczylo/go-obfuscation/pkg/vm")
}
return src
}
func compileLenCmpBytecode(op string, n int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpCall, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpPush, n)...)
switch op {
case "gt":
bc = append(bc, vm.EncodeInstr(vm.OpCmpGt, 0)...)
case "eq":
bc = append(bc, vm.EncodeInstr(vm.OpCmpEq, 0)...)
case "lt":
bc = append(bc, vm.EncodeInstr(vm.OpCmpLt, 0)...)
case "ge":
bc = append(bc, vm.EncodeInstr(vm.OpCmpLt, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpCmpEq, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func compileArith2Bytecode(op string, leftID, rightID int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, leftID)...) // get left param
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, rightID)...) // get right param
switch op {
case "+":
bc = append(bc, vm.EncodeInstr(vm.OpAdd, 0)...)
case "-":
bc = append(bc, vm.EncodeInstr(vm.OpSub, 0)...)
case "*":
bc = append(bc, vm.EncodeInstr(vm.OpMul, 0)...)
case "/":
bc = append(bc, vm.EncodeInstr(vm.OpDiv, 0)...)
case "%":
bc = append(bc, vm.EncodeInstr(vm.OpMod, 0)...)
case "^":
bc = append(bc, vm.EncodeInstr(vm.OpXor, 0)...)
case "&":
bc = append(bc, vm.EncodeInstr(vm.OpAnd, 0)...)
case "|":
bc = append(bc, vm.EncodeInstr(vm.OpOr, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func compileArith1Bytecode(op string, n int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, 0)...) // get param
bc = append(bc, vm.EncodeInstr(vm.OpPush, n)...) // constant
switch op {
case "+":
bc = append(bc, vm.EncodeInstr(vm.OpAdd, 0)...)
case "-":
bc = append(bc, vm.EncodeInstr(vm.OpSub, 0)...)
case "*":
bc = append(bc, vm.EncodeInstr(vm.OpMul, 0)...)
case "/":
bc = append(bc, vm.EncodeInstr(vm.OpDiv, 0)...)
case "%":
bc = append(bc, vm.EncodeInstr(vm.OpMod, 0)...)
case "^":
bc = append(bc, vm.EncodeInstr(vm.OpXor, 0)...)
case "&":
bc = append(bc, vm.EncodeInstr(vm.OpAnd, 0)...)
case "|":
bc = append(bc, vm.EncodeInstr(vm.OpOr, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func buildVMBoolFunc(funcName, paramName string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s string) bool {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return int64(len(%s)) },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run() != 0
}`, funcName, paramName, paramName, strings.Join(byteLits, ", "))
}
func buildVMIntFunc2(funcName, p1, p2 string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s, %s int64) int64 {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return %s },
1: func(_ []int64) int64 { return %s },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run()
}`, funcName, p1, p2, p1, p2, strings.Join(byteLits, ", "))
}
func buildVMIntFunc1(funcName, param string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s int64) int64 {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return %s },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run()
}`, funcName, param, param, strings.Join(byteLits, ", "))
}
func (t *Transformer) injectAntiDisassembly(src string) string {
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
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
if t.rng.Float64() < 0.15 {
junkVar := stringcrypt.RandomIdent("_ad", 4)
junkSize := t.rng.Intn(32) + 8
result = append(result, fmt.Sprintf("\tvar %s [%d]byte; _ = %s", junkVar, junkSize, junkVar))
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) injectJunkStrings(src string) string {
junkStrings := []string{
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789abcdef0123456789abcdef",
"/usr/lib/libSystem.B.dylib",
"/proc/self/maps",
"\\x00\\x00\\x00\\x00",
"HKEY_LOCAL_MACHINE\\SOFTWARE",
"C:\\Windows\\System32\\ntdll.dll",
"SELECT * FROM users WHERE id=?",
"Content-Type: application/json",
"Mozilla/5.0 (Windows NT 10.0)",
"-----BEGIN RSA PRIVATE KEY-----",
"192.168.1.1:8080",
"admin@localhost",
"Bearer eyJhbGciOiJIUzI1NiJ9",
"application/x-www-form-urlencoded",
}
numJunk := t.rng.Intn(6) + 4
var junkBlock strings.Builder
junkBlock.WriteString("\nvar _junkData = [...]string{\n")
for i := 0; i < numJunk; i++ {
idx := t.rng.Intn(len(junkStrings))
junkBlock.WriteString(fmt.Sprintf("\t%q,\n", junkStrings[idx]))
}
junkBlock.WriteString("}\n")
junkBlock.WriteString("var _ = _junkData\n")
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] + junkBlock.String() + src[insertAt:]
}
return src
}
func (t *Transformer) ensureImport(src string, importPath string) string {
importBlock := `import ("` + importPath + `")`
if idx := strings.Index(src, "import ("); idx != -1 {
insertAt := idx + len("import (")
return src[:insertAt] + "\n\t\"" + importPath + "\"" + src[insertAt:]
}
if idx := strings.Index(src, "import \""); idx != -1 {
return src[:idx] + importBlock + "\n" + src[idx:]
}
pkgEnd := strings.Index(src, "\n\n")
if pkgEnd == -1 {
pkgEnd = strings.Index(src, "\n")
}
if pkgEnd != -1 {
return src[:pkgEnd+1] + "\n" + importBlock + "\n" + src[pkgEnd+1:]
}
return src
}