mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-14 05:48:22 +00:00
.
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
// Long string splitting transformation.
|
||||
//
|
||||
// Splits string literals longer than a threshold into concatenated chunks
|
||||
// of randomized length (5-9 characters). This ensures that even if string
|
||||
// encryption is bypassed, long sensitive values like GPG keys, API keys,
|
||||
// and connection strings cannot be found via `strings` on the binary.
|
||||
package transform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
splitMinChunk = 5
|
||||
splitMaxChunk = 9
|
||||
splitMinLen = 20
|
||||
)
|
||||
|
||||
// SplitLongStrings breaks string literals longer than splitMinLen into
|
||||
// concatenated chunks of 5-9 characters each.
|
||||
func SplitLongStrings(src string) string {
|
||||
rng := rand.New(rand.NewSource(rand.Int63()))
|
||||
return splitStringLiterals(src, rng)
|
||||
}
|
||||
|
||||
func splitStringLiterals(src string, rng *rand.Rand) string {
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(src) * 2)
|
||||
|
||||
i := 0
|
||||
inImport := false
|
||||
parenImport := false
|
||||
|
||||
for i < len(src) {
|
||||
// Track import block context
|
||||
rest := src[i:]
|
||||
if !inImport && strings.HasPrefix(rest, "import") {
|
||||
afterImport := strings.TrimSpace(rest[6:])
|
||||
if strings.HasPrefix(afterImport, "(") {
|
||||
inImport = true
|
||||
parenImport = true
|
||||
} else if strings.HasPrefix(afterImport, "\"") {
|
||||
inImport = true
|
||||
parenImport = false
|
||||
}
|
||||
}
|
||||
|
||||
if inImport {
|
||||
if parenImport && src[i] == ')' {
|
||||
inImport = false
|
||||
parenImport = false
|
||||
} else if !parenImport && src[i] == '\n' {
|
||||
inImport = false
|
||||
}
|
||||
buf.WriteByte(src[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if src[i] == '"' {
|
||||
end := findStringEnd(src, i)
|
||||
if end == -1 {
|
||||
buf.WriteByte(src[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
literal := src[i : end+1]
|
||||
inner := literal[1 : len(literal)-1]
|
||||
|
||||
if len(inner) >= splitMinLen && !containsEscapes(inner) {
|
||||
split := splitString(inner, rng)
|
||||
buf.WriteString(split)
|
||||
} else {
|
||||
buf.WriteString(literal)
|
||||
}
|
||||
i = end + 1
|
||||
} else if src[i] == '`' {
|
||||
end := findBacktickEnd(src, i)
|
||||
if end == -1 {
|
||||
buf.WriteByte(src[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
literal := src[i : end+1]
|
||||
inner := literal[1 : len(literal)-1]
|
||||
|
||||
if len(inner) >= splitMinLen {
|
||||
split := splitRawString(inner, rng)
|
||||
buf.WriteString(split)
|
||||
} else {
|
||||
buf.WriteString(literal)
|
||||
}
|
||||
i = end + 1
|
||||
} else {
|
||||
buf.WriteByte(src[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func findStringEnd(src string, start int) int {
|
||||
i := start + 1
|
||||
for i < len(src) {
|
||||
if src[i] == '\\' {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if src[i] == '"' {
|
||||
return i
|
||||
}
|
||||
i++
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func findBacktickEnd(src string, start int) int {
|
||||
for i := start + 1; i < len(src); i++ {
|
||||
if src[i] == '`' {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func containsEscapes(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\\' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func splitString(s string, rng *rand.Rand) string {
|
||||
chunks := splitIntoChunks(s, rng)
|
||||
if len(chunks) <= 1 {
|
||||
return `"` + s + `"`
|
||||
}
|
||||
|
||||
var parts []string
|
||||
for _, c := range chunks {
|
||||
parts = append(parts, `"`+escapeString(c)+`"`)
|
||||
}
|
||||
return "(" + strings.Join(parts, " + ") + ")"
|
||||
}
|
||||
|
||||
func splitRawString(s string, rng *rand.Rand) string {
|
||||
chunks := splitIntoChunks(s, rng)
|
||||
if len(chunks) <= 1 {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
|
||||
var parts []string
|
||||
for _, c := range chunks {
|
||||
parts = append(parts, `"`+escapeString(c)+`"`)
|
||||
}
|
||||
return "(" + strings.Join(parts, " + ") + ")"
|
||||
}
|
||||
|
||||
func splitIntoChunks(s string, rng *rand.Rand) []string {
|
||||
runes := []rune(s)
|
||||
if len(runes) < splitMinLen {
|
||||
return []string{s}
|
||||
}
|
||||
|
||||
var chunks []string
|
||||
pos := 0
|
||||
for pos < len(runes) {
|
||||
remaining := len(runes) - pos
|
||||
if remaining <= splitMaxChunk {
|
||||
chunks = append(chunks, string(runes[pos:]))
|
||||
break
|
||||
}
|
||||
|
||||
chunkLen := splitMinChunk + rng.Intn(splitMaxChunk-splitMinChunk+1)
|
||||
|
||||
if remaining-chunkLen < splitMinChunk && remaining-chunkLen > 0 {
|
||||
chunkLen = remaining / 2
|
||||
if chunkLen < splitMinChunk {
|
||||
chunkLen = splitMinChunk
|
||||
}
|
||||
}
|
||||
|
||||
chunks = append(chunks, string(runes[pos:pos+chunkLen]))
|
||||
pos += chunkLen
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
func escapeString(s string) string {
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(s))
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\\', '"':
|
||||
buf.WriteRune('\\')
|
||||
buf.WriteRune(r)
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
case '\r':
|
||||
buf.WriteString(`\r`)
|
||||
default:
|
||||
if r < 32 {
|
||||
buf.WriteString(fmt.Sprintf(`\x%02x`, r))
|
||||
} else {
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
Reference in New Issue
Block a user