mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
360 lines
11 KiB
Go
360 lines
11 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// ObfuscationConfig controls how the obfuscation behaves
|
|
type ObfuscationConfig struct {
|
|
// MinSplits is the minimum number of parts to split strings into
|
|
MinSplits int
|
|
// MaxSplits is the maximum number of parts to split strings into
|
|
MaxSplits int
|
|
// UseNumberSuffix determines if variable names should have number suffixes
|
|
UseNumberSuffix bool
|
|
// MinNumberSuffix is the minimum value for number suffixes
|
|
MinNumberSuffix int
|
|
// MaxNumberSuffix is the maximum value for number suffixes
|
|
MaxNumberSuffix int
|
|
// UseXOR determines if strings should be XOR encrypted
|
|
UseXOR bool
|
|
// MinXORKey is the minimum XOR key value (1-255)
|
|
MinXORKey int
|
|
// MaxXORKey is the maximum XOR key value (1-255)
|
|
MaxXORKey int
|
|
}
|
|
|
|
// DefaultObfuscationConfig returns sensible defaults for obfuscation
|
|
func DefaultObfuscationConfig() ObfuscationConfig {
|
|
return ObfuscationConfig{
|
|
MinSplits: 2,
|
|
MaxSplits: 4,
|
|
UseNumberSuffix: true,
|
|
MinNumberSuffix: 0,
|
|
MaxNumberSuffix: 9,
|
|
UseXOR: true,
|
|
MinXORKey: 1,
|
|
MaxXORKey: 255,
|
|
}
|
|
}
|
|
|
|
// ObfuscateHTML obfuscates HTML content using compression, base64 encoding,
|
|
// and random variable names to make it difficult to fingerprint
|
|
func ObfuscateHTML(html string, config ObfuscationConfig, htmlTemplate string, funcMap template.FuncMap) (string, error) {
|
|
// generate random variable names
|
|
varNames := generateRandomVariableNames(15, config)
|
|
xorFuncName := varNames[9]
|
|
windowVar := varNames[10]
|
|
// xor function variables use indices 11-14 to avoid conflicts with windowVar
|
|
xorVars := []string{varNames[11], varNames[12], varNames[13], varNames[14]}
|
|
|
|
// randomly select method to access window object
|
|
windowAccessor := getRandomWindowAccessor()
|
|
// compress the HTML to reduce size and add another layer
|
|
compressed, err := compressGzip([]byte(html))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to compress html: %w", err)
|
|
}
|
|
|
|
// encode to base64
|
|
encoded := base64.StdEncoding.EncodeToString(compressed)
|
|
|
|
// split the base64 payload
|
|
encodedSplit := splitStringRandom(encoded, config, xorFuncName)
|
|
|
|
// split critical strings t
|
|
atobSplit := splitStringRandom("atob", config, xorFuncName)
|
|
uint8ArraySplit := splitStringRandom("Uint8Array", config, xorFuncName)
|
|
fromSplit := splitStringRandom("from", config, xorFuncName)
|
|
charCodeAtSplit := splitStringRandom("charCodeAt", config, xorFuncName)
|
|
responseSplit := splitStringRandom("Response", config, xorFuncName)
|
|
bufferSplit := splitStringRandom("buffer", config, xorFuncName)
|
|
bodySplit := splitStringRandom("body", config, xorFuncName)
|
|
pipeThroughSplit := splitStringRandom("pipeThrough", config, xorFuncName)
|
|
decompressionStreamSplit := splitStringRandom("DecompressionStream", config, xorFuncName)
|
|
gzipSplit := splitStringRandom("gzip", config, xorFuncName)
|
|
textSplit := splitStringRandom("text", config, xorFuncName)
|
|
thenSplit := splitStringRandom("then", config, xorFuncName)
|
|
documentSplit := splitStringRandom("document", config, xorFuncName)
|
|
openSplit := splitStringRandom("open", config, xorFuncName)
|
|
writeSplit := splitStringRandom("write", config, xorFuncName)
|
|
closeSplit := splitStringRandom("close", config, xorFuncName)
|
|
|
|
// create xor helper function if needed
|
|
xorFunc := ""
|
|
if config.UseXOR {
|
|
// obfuscate the xor function internals (xorVars already generated above to avoid conflicts)
|
|
// create a minimal config without xor to avoid recursion
|
|
noXorConfig := config
|
|
noXorConfig.UseXOR = false
|
|
fromCharCodeSplit := splitStringRandom("fromCharCode", noXorConfig, "")
|
|
parseIntSplit := splitStringRandom("parseInt", noXorConfig, "")
|
|
substrSplit := splitStringRandom("substr", noXorConfig, "")
|
|
lengthSplit := splitStringRandom("length", noXorConfig, "")
|
|
|
|
xorFunc = fmt.Sprintf(`function %s(%s,%s){var %s='';for(var %s=0;%s<%s[%s];%s+=2)%s+=String[%s](%s[%s](%s[%s](%s,2),16)^%s);return %s;}`,
|
|
xorFuncName, xorVars[0], xorVars[1], xorVars[2], xorVars[3],
|
|
xorVars[3], xorVars[0], lengthSplit, xorVars[3], xorVars[2],
|
|
fromCharCodeSplit, windowVar, parseIntSplit, xorVars[0], substrSplit, xorVars[3],
|
|
xorVars[1], xorVars[2])
|
|
}
|
|
|
|
// create the deobfuscation script with heavily obfuscated strings (minified)
|
|
deobfScript := fmt.Sprintf(`%svar %s=%s;var %s=%s;var %s=%s[%s](%s);var %s=%s[%s][%s](%s,function(%s){return %s[%s](0);});var %s=new %s[%s](%s[%s])[%s][%s](new %s[%s](%s));var %s=new %s[%s](%s);%s[%s]()[%s](function(%s){%s[%s][%s]();%s[%s][%s](%s);%s[%s][%s]();});`,
|
|
xorFunc,
|
|
windowVar, windowAccessor,
|
|
varNames[0], encodedSplit,
|
|
varNames[1], windowVar, atobSplit, varNames[0],
|
|
varNames[2], windowVar, uint8ArraySplit, fromSplit, varNames[1], varNames[8], varNames[8], charCodeAtSplit,
|
|
varNames[3], windowVar, responseSplit, varNames[2], bufferSplit, bodySplit, pipeThroughSplit, windowVar, decompressionStreamSplit, gzipSplit,
|
|
varNames[4], windowVar, responseSplit, varNames[3],
|
|
varNames[4], textSplit, thenSplit, varNames[5],
|
|
windowVar, documentSplit, openSplit,
|
|
windowVar, documentSplit, writeSplit, varNames[5],
|
|
windowVar, documentSplit, closeSplit)
|
|
|
|
// render the html template with the deobfuscation script
|
|
tmpl, err := template.New("obfuscation").Funcs(funcMap).Parse(htmlTemplate)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse obfuscation template: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
data := map[string]interface{}{
|
|
"Script": deobfScript,
|
|
}
|
|
err = tmpl.Execute(&buf, data)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to execute obfuscation template: %w", err)
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// getRandomWindowAccessor returns a random way to access the window object
|
|
func getRandomWindowAccessor() string {
|
|
accessors := []string{
|
|
"self",
|
|
"this",
|
|
"globalThis",
|
|
"Function('return this')()",
|
|
"(function(){return this})()",
|
|
"(0,eval)('this')",
|
|
}
|
|
|
|
// randomly select one
|
|
idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(accessors))))
|
|
return accessors[idx.Int64()]
|
|
}
|
|
|
|
// xorString encrypts a string with XOR using the given key and returns hex encoded string
|
|
func xorString(s string, key byte) string {
|
|
var result strings.Builder
|
|
for i := 0; i < len(s); i++ {
|
|
result.WriteString(fmt.Sprintf("%02x", s[i]^key))
|
|
}
|
|
return result.String()
|
|
}
|
|
|
|
// splitStringRandom splits a string into random parts and returns a concatenation expression
|
|
func splitStringRandom(s string, config ObfuscationConfig, xorFuncName string) string {
|
|
if len(s) <= 1 {
|
|
return fmt.Sprintf(`"%s"`, s)
|
|
}
|
|
|
|
// determine number of splits based on config
|
|
minParts := config.MinSplits
|
|
maxParts := config.MaxSplits
|
|
if minParts < 1 {
|
|
minParts = 1
|
|
}
|
|
if maxParts < minParts {
|
|
maxParts = minParts
|
|
}
|
|
|
|
rangeSize := maxParts - minParts + 1
|
|
numParts, _ := rand.Int(rand.Reader, big.NewInt(int64(rangeSize)))
|
|
parts := int(numParts.Int64()) + minParts
|
|
|
|
if parts > len(s) {
|
|
parts = len(s)
|
|
}
|
|
|
|
// generate random split positions
|
|
positions := make([]int, 0, parts-1)
|
|
for i := 0; i < parts-1; i++ {
|
|
maxPos := int64(len(s) - 1)
|
|
if maxPos < 1 {
|
|
break
|
|
}
|
|
pos, _ := rand.Int(rand.Reader, big.NewInt(maxPos))
|
|
positions = append(positions, int(pos.Int64())+1)
|
|
}
|
|
|
|
// sort positions to split correctly
|
|
// bubble sort since we have few elements
|
|
for i := 0; i < len(positions); i++ {
|
|
for j := i + 1; j < len(positions); j++ {
|
|
if positions[i] > positions[j] {
|
|
positions[i], positions[j] = positions[j], positions[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove duplicates and ensure boundaries
|
|
uniquePositions := make([]int, 0)
|
|
lastPos := 0
|
|
for _, pos := range positions {
|
|
if pos > lastPos && pos < len(s) {
|
|
uniquePositions = append(uniquePositions, pos)
|
|
lastPos = pos
|
|
}
|
|
}
|
|
|
|
// build the split string parts with optional XOR encryption
|
|
var result strings.Builder
|
|
start := 0
|
|
for i, pos := range uniquePositions {
|
|
if i > 0 {
|
|
result.WriteString(" + ")
|
|
}
|
|
part := s[start:pos]
|
|
if config.UseXOR {
|
|
// generate random XOR key within configured range
|
|
keyRange := config.MaxXORKey - config.MinXORKey + 1
|
|
if keyRange < 1 {
|
|
keyRange = 1
|
|
}
|
|
xorKey, _ := rand.Int(rand.Reader, big.NewInt(int64(keyRange)))
|
|
key := byte(int(xorKey.Int64()) + config.MinXORKey)
|
|
|
|
// xor encrypt the part
|
|
encrypted := xorString(part, key)
|
|
result.WriteString(fmt.Sprintf(`%s("%s",%d)`, xorFuncName, encrypted, key))
|
|
} else {
|
|
result.WriteString(fmt.Sprintf(`"%s"`, part))
|
|
}
|
|
start = pos
|
|
}
|
|
|
|
// add the last part
|
|
if len(uniquePositions) > 0 {
|
|
result.WriteString(" + ")
|
|
}
|
|
lastPart := s[start:]
|
|
if config.UseXOR {
|
|
// generate random XOR key for last part
|
|
keyRange := config.MaxXORKey - config.MinXORKey + 1
|
|
if keyRange < 1 {
|
|
keyRange = 1
|
|
}
|
|
xorKey, _ := rand.Int(rand.Reader, big.NewInt(int64(keyRange)))
|
|
key := byte(int(xorKey.Int64()) + config.MinXORKey)
|
|
|
|
// xor encrypt the last part
|
|
encrypted := xorString(lastPart, key)
|
|
result.WriteString(fmt.Sprintf(`%s("%s",%d)`, xorFuncName, encrypted, key))
|
|
} else {
|
|
result.WriteString(fmt.Sprintf(`"%s"`, lastPart))
|
|
}
|
|
|
|
return result.String()
|
|
}
|
|
|
|
// compressGzip compresses data using gzip
|
|
func compressGzip(data []byte) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
writer := gzip.NewWriter(&buf)
|
|
_, err := writer.Write(data)
|
|
if err != nil {
|
|
writer.Close()
|
|
return nil, err
|
|
}
|
|
err = writer.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// generateRandomVariableNames generates random variable names to prevent fingerprinting
|
|
func generateRandomVariableNames(count int, config ObfuscationConfig) []string {
|
|
// common but non-suspicious variable name prefixes
|
|
prefixes := []string{
|
|
"a", "b", "c", "d", "v", "x", "i", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "u", "w", "y", "z",
|
|
}
|
|
|
|
names := make([]string, count)
|
|
used := make(map[string]bool)
|
|
|
|
for i := 0; i < count; i++ {
|
|
for {
|
|
// select random prefix
|
|
prefixIdx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(prefixes))))
|
|
prefix := prefixes[prefixIdx.Int64()]
|
|
|
|
var name string
|
|
if config.UseNumberSuffix {
|
|
// generate random suffix within configured range
|
|
suffixRange := config.MaxNumberSuffix - config.MinNumberSuffix + 1
|
|
if suffixRange < 1 {
|
|
suffixRange = 1
|
|
}
|
|
suffix, _ := rand.Int(rand.Reader, big.NewInt(int64(suffixRange)))
|
|
name = fmt.Sprintf("%s%d", prefix, int(suffix.Int64())+config.MinNumberSuffix)
|
|
} else {
|
|
name = prefix
|
|
}
|
|
|
|
// ensure uniqueness
|
|
if !used[name] && !isReservedWord(name) {
|
|
names[i] = name
|
|
used[name] = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
// isReservedWord checks if a string is a JavaScript reserved word
|
|
func isReservedWord(word string) bool {
|
|
reserved := []string{
|
|
"break", "case", "catch", "class", "const", "continue", "debugger",
|
|
"default", "delete", "do", "else", "export", "extends", "finally",
|
|
"for", "function", "if", "import", "in", "instanceof", "let", "new",
|
|
"return", "super", "switch", "this", "throw", "try", "typeof", "var",
|
|
"void", "while", "with", "yield", "enum", "await", "implements",
|
|
"interface", "package", "private", "protected", "public", "static",
|
|
}
|
|
|
|
wordLower := strings.ToLower(word)
|
|
for _, r := range reserved {
|
|
if r == wordLower {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/*
|
|
// decompressGzip decompresses gzip data (for testing purposes)
|
|
func decompressGzip(data []byte) ([]byte, error) {
|
|
reader, err := gzip.NewReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer reader.Close()
|
|
|
|
return io.ReadAll(reader)
|
|
}
|
|
*/
|