From 6203484fcdb41fc61c0c92c2a82c8a73835f1161 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Wed, 29 Apr 2026 02:45:39 -0400 Subject: [PATCH] Fixes several bugs in encoding and obfuscation and adds a new payload execution stress test. - fix(encode): Remove redundant modulo operations from ADD/SUB ciphers - fix(obfuscate): Remove "JMP 2" from the list of safe garbage instructions - fix(obfuscate): Use correct slice length when selecting random garbage instruction - fix(sgn): Correct register exclusion logic in GetSafeRandomRegister - test: Add integration stress test for payload execution on Windows --- pkg/encode.go | 4 +-- pkg/instructions.go | 1 - pkg/obfuscate.go | 2 +- pkg/sgn.go | 9 +++-- test/integration_test.go | 72 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 test/integration_test.go diff --git a/pkg/encode.go b/pkg/encode.go index cea3825..c77d219 100644 --- a/pkg/encode.go +++ b/pkg/encode.go @@ -154,9 +154,9 @@ func (encoder *Encoder) SchemaCipher(data []byte, index int, schema SCHEMA) []by case "XOR": binary.BigEndian.PutUint32(data[index:index+4], (binary.BigEndian.Uint32(data[index:index+4]) ^ binary.LittleEndian.Uint32(cursor.Key))) case "ADD": - binary.LittleEndian.PutUint32(data[index:index+4], (binary.LittleEndian.Uint32(data[index:index+4])-binary.BigEndian.Uint32(cursor.Key))%0xFFFFFFFF) + binary.LittleEndian.PutUint32(data[index:index+4], binary.LittleEndian.Uint32(data[index:index+4])-binary.BigEndian.Uint32(cursor.Key)) case "SUB": - binary.LittleEndian.PutUint32(data[index:index+4], (binary.LittleEndian.Uint32(data[index:index+4])+binary.BigEndian.Uint32(cursor.Key))%0xFFFFFFFF) + binary.LittleEndian.PutUint32(data[index:index+4], binary.LittleEndian.Uint32(data[index:index+4])+binary.BigEndian.Uint32(cursor.Key)) case "ROL": binary.LittleEndian.PutUint32(data[index:index+4], bits.RotateLeft32(binary.LittleEndian.Uint32(data[index:index+4]), -int(binary.BigEndian.Uint32(cursor.Key)))) case "ROR": diff --git a/pkg/instructions.go b/pkg/instructions.go index 9429ced..4bc2dec 100644 --- a/pkg/instructions.go +++ b/pkg/instructions.go @@ -47,7 +47,6 @@ var SafeGarbageInstructions = []string{ "FNOP", "FXAM", "FTST", - "JMP 2", "ROL {R},0", "ROR {R},0", "SHL {R},0", diff --git a/pkg/obfuscate.go b/pkg/obfuscate.go index 8227252..1889e32 100644 --- a/pkg/obfuscate.go +++ b/pkg/obfuscate.go @@ -71,7 +71,7 @@ func GetRandomSafeAssembly() string { newSafeGarbageInstructions = append(newSafeGarbageInstructions, jmp+" {L};{G};{L}:") //newSafeGarbageInstructions = append(newSafeGarbageInstructions, jmp+" 2") } - return newSafeGarbageInstructions[rand.Intn(len(SafeGarbageInstructions))] + return newSafeGarbageInstructions[rand.Intn(len(newSafeGarbageInstructions))] } // GetRandomUnsafeAssembly return a safe garbage instruction assembly diff --git a/pkg/sgn.go b/pkg/sgn.go index 8549d8d..f8f34d8 100644 --- a/pkg/sgn.go +++ b/pkg/sgn.go @@ -162,11 +162,16 @@ func (encoder Encoder) GetBasePointer() string { func (encoder Encoder) GetSafeRandomRegister(size int, excludes ...string) (string, error) { regs := []REG{} for _, r := range REGS[encoder.architecture] { + safe := true for _, x := range excludes { - if r.Extended != x && r.Full != x && r.High != x && r.Low != x { - regs = append(regs, r) + if r.Extended == x || r.Full == x || r.High == x || r.Low == x { + safe = false + break } } + if safe { + regs = append(regs, r) + } } r := regs[rand.Intn(len(regs))] diff --git a/test/integration_test.go b/test/integration_test.go new file mode 100644 index 0000000..03379ed --- /dev/null +++ b/test/integration_test.go @@ -0,0 +1,72 @@ +//go:build windows +// +build windows + +package test + +import ( + "runtime" + "syscall" + "testing" + "unsafe" + + "github.com/EgeBalci/sgn/pkg" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + virtualAlloc = kernel32.NewProc("VirtualAlloc") + virtualFree = kernel32.NewProc("VirtualFree") + + // NOP NOP NOP RET works on both x86 and x64 + dummyPayload = []byte{0x90, 0x90, 0x90, 0xC3} +) + +func TestIntegrationExecution(t *testing.T) { + var arch int + if runtime.GOARCH == "386" { + arch = 32 + } else if runtime.GOARCH == "amd64" { + arch = 64 + } else { + t.Skipf("Unsupported architecture: %s", runtime.GOARCH) + } + + t.Logf("[*] Starting SGN execution stress test (%d-bit)...\n", arch) + + iterations := 1000 + + for i := 0; i < iterations; i++ { + enc, err := sgn.NewEncoder(arch) + if err != nil { + t.Fatalf("Failed to init encoder: %v", err) + } + + enc.ObfuscationLimit = 50 + + encoded, err := enc.Encode(dummyPayload) + if err != nil { + t.Fatalf("Failed to encode: %v", err) + } + + addr, _, errInfo := virtualAlloc.Call( + 0, + uintptr(len(encoded)), + 0x1000|0x2000, // MEM_COMMIT | MEM_RESERVE + 0x40, // PAGE_EXECUTE_READWRITE + ) + if addr == 0 { + t.Fatalf("VirtualAlloc failed: %v", errInfo) + } + + ptr := (*[1 << 30]byte)(unsafe.Pointer(addr)) + copy(ptr[:], encoded) + + // Execute payload + syscall.Syscall(addr, 0, 0, 0, 0) + + // Free memory + virtualFree.Call(addr, 0, 0x8000) + } + + t.Logf("[+] Stress test complete: %d executions with no crashes!", iterations) +}