This commit is contained in:
2026-05-31 11:23:07 +01:00
parent 1d51b29b92
commit 5ea458fb06
18 changed files with 1367 additions and 220 deletions
+6 -150
View File
@@ -1,20 +1,12 @@
package antidebug
import (
"fmt"
"os"
"runtime"
"time"
)
func Check() error {
if runtime.GOOS == "linux" {
if err := checkPtrace(); err != nil {
return err
}
if err := disableCoreDump(); err != nil {
return err
}
if err := checkPtrace(); err != nil {
return err
}
if err := disableCoreDump(); err != nil {
return err
}
if err := checkTiming(); err != nil {
@@ -35,139 +27,3 @@ func Check() error {
return nil
}
func checkPtrace() error {
self, err := os.ReadFile("/proc/self/status")
if err != nil {
return nil
}
data := string(self)
for i := 0; i < len(data)-11; i++ {
if data[i:i+11] == "TracerPid:\t" {
j := i + 11
for j < len(data) && data[j] >= '0' && data[j] <= '9' {
j++
}
pid := data[i+11 : j]
if pid != "0" {
return fmt.Errorf("antidebug: debugger detected (tracer pid=%s)", pid)
}
break
}
}
return nil
}
func disableCoreDump() error {
f, err := os.OpenFile("/proc/self/coredump_filter", os.O_WRONLY, 0)
if err != nil {
return nil
}
defer f.Close()
_, _ = f.WriteString("0")
return nil
}
func checkTiming() error {
start := time.Now()
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
elapsed := time.Since(start)
if elapsed > 200*time.Millisecond {
return fmt.Errorf("antidebug: timing anomaly detected (%v for 1M iterations)", elapsed)
}
_ = sum
return nil
}
func checkEnv() error {
suspiciousVars := []string{
"GODEBUG",
"DELVE_LISTENER",
"DEBUGINFOD_URL",
"RR_LOG_FILE",
"_JAVA_OPTIONS",
"LD_PRELOAD",
"DYLD_INSERT_LIBRARIES",
}
for _, v := range suspiciousVars {
if os.Getenv(v) != "" {
return fmt.Errorf("antidebug: suspicious env var %s=%s", v, os.Getenv(v))
}
}
return nil
}
func checkParentProcess() error {
if runtime.GOOS != "linux" {
return nil
}
ppid := os.Getppid()
if ppid <= 1 {
return nil
}
commPath := fmt.Sprintf("/proc/%d/comm", ppid)
comm, err := os.ReadFile(commPath)
if err != nil {
return nil
}
parent := string(comm)
debuggers := []string{
"gdb", "lldb", "strace", "ltrace", "ida", "ida64",
"radare2", "r2", "ghidra", "x64dbg", "ollydbg",
"delve", "debugserver",
}
for _, d := range debuggers {
if len(parent) >= len(d) && parent[:len(d)] == d {
return fmt.Errorf("antidebug: parent process is debugger: %s", parent)
}
}
return nil
}
func AntiAttach() {
if runtime.GOOS != "linux" {
return
}
go func() {
for {
time.Sleep(500 * time.Millisecond)
if err := checkPtrace(); err != nil {
os.Exit(66)
}
}
}()
}
func AntiDump() {
if runtime.GOOS != "linux" {
return
}
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
_ = disableCoreDump()
}
}()
}
func CheckHooks() error {
if err := checkFunctionPrologues(); err != nil {
return err
}
if err := scanForBreakpoints(); err != nil {
return err
}
return nil
}
+147
View File
@@ -0,0 +1,147 @@
//go:build darwin
package antidebug
import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
)
const (
ctlKern = 1
kernProc = 14
kernProcPID = 1
pTraced = 0x00000800
ptDenyAttach = 31
)
func checkPtrace() error {
pid := os.Getpid()
var mib [4]int32
mib[0] = ctlKern
mib[1] = kernProc
mib[2] = kernProcPID
mib[3] = int32(pid)
bufSize := uintptr(64)
buf := make([]byte, bufSize)
_, _, errno := syscall.Syscall6(
uintptr(0x2000000+202),
uintptr(unsafe.Pointer(&mib[0])),
4,
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&bufSize)),
0,
0,
)
if errno != 0 {
return nil
}
if bufSize < 36 {
return nil
}
pFlag := uint32(buf[32]) | uint32(buf[33])<<8 | uint32(buf[34])<<16 | uint32(buf[35])<<24
if pFlag&pTraced != 0 {
return fmt.Errorf("antidebug: debugger detected via sysctl P_TRACED (pid=%d)", pid)
}
return nil
}
func denyPtraceAttach() {
pid := os.Getpid()
syscall.RawSyscall(
uintptr(syscall.SYS_PTRACE),
uintptr(ptDenyAttach),
uintptr(pid),
0,
)
}
func disableCoreDump() error {
return nil
}
func checkTiming() error {
start := time.Now()
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
elapsed := time.Since(start)
if elapsed > 200*time.Millisecond {
return fmt.Errorf("antidebug: timing anomaly detected (%v for 1M iterations)", elapsed)
}
_ = sum
return nil
}
func checkEnv() error {
suspiciousVars := []string{
"DYLD_INSERT_LIBRARIES",
"DYLD_FRAMEWORK_PATH",
"DYLD_LIBRARY_PATH",
"DYLD_FALLBACK_FRAMEWORK_PATH",
"DYLD_FALLBACK_LIBRARY_PATH",
"DYLD_ROOT_PATH",
"GODEBUG",
"DELVE_LISTENER",
"DEBUGINFOD_URL",
"RR_LOG_FILE",
"_JAVA_OPTIONS",
}
for _, v := range suspiciousVars {
if os.Getenv(v) != "" {
return fmt.Errorf("antidebug: suspicious env var %s=%s", v, os.Getenv(v))
}
}
return nil
}
func checkParentProcess() error {
return nil
}
func AntiAttach() {
go func() {
denyPtraceAttach()
for {
time.Sleep(500 * time.Millisecond)
if err := checkPtrace(); err != nil {
os.Exit(66)
}
}
}()
}
func AntiDump() {
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
_ = disableCoreDump()
}
}()
}
func CheckHooks() error {
if err := checkFunctionPrologues(); err != nil {
return err
}
if err := scanForBreakpoints(); err != nil {
return err
}
return nil
}
+106
View File
@@ -2,10 +2,116 @@
package antidebug
import (
"fmt"
"os"
"reflect"
"runtime"
"unsafe"
)
func checkFunctionPrologues() error {
type target struct {
name string
addr uintptr
}
targets := []target{
{"os.Exit", reflect.ValueOf(os.Exit).Pointer()},
{"runtime.Goexit", reflect.ValueOf(runtime.Goexit).Pointer()},
}
for _, t := range targets {
if t.addr == 0 {
continue
}
prologue := readAddr(t.addr, 8)
if len(prologue) < 2 {
continue
}
if prologue[0] == 0xE9 {
return fmt.Errorf("antidebug: JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xEB {
return fmt.Errorf("antidebug: short JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xFF && prologue[1] == 0x25 {
return fmt.Errorf("antidebug: indirect JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xCC && prologue[1] == 0xCC {
return fmt.Errorf("antidebug: INT3 breakpoint at %s (0x%x)", t.name, t.addr)
}
if runtime.GOARCH == "arm64" {
instr := uint32(prologue[0]) | uint32(prologue[1])<<8 | uint32(prologue[2])<<16 | uint32(prologue[3])<<24
if instr&0xFC000000 == 0x14000000 {
return fmt.Errorf("antidebug: ARM64 B hook detected at %s (0x%x)", t.name, t.addr)
}
if instr&0xFFE0001F == 0xD61F0000 {
return fmt.Errorf("antidebug: ARM64 BR hook detected at %s (0x%x)", t.name, t.addr)
}
}
}
return nil
}
func scanForBreakpoints() error {
type target struct {
name string
addr uintptr
}
targets := []target{
{"os.Exit", reflect.ValueOf(os.Exit).Pointer()},
{"runtime.Goexit", reflect.ValueOf(runtime.Goexit).Pointer()},
{"fmt.Printf", reflect.ValueOf(fmt.Printf).Pointer()},
{"os.ReadFile", reflect.ValueOf(os.ReadFile).Pointer()},
}
for _, t := range targets {
if t.addr == 0 {
continue
}
region := readAddr(t.addr, 64)
if len(region) < 8 {
continue
}
maxConsecutive := 0
consecutive := 0
for _, b := range region {
if b == 0xCC {
consecutive++
if consecutive > maxConsecutive {
maxConsecutive = consecutive
}
} else {
consecutive = 0
}
}
if maxConsecutive >= 4 {
return fmt.Errorf("antidebug: breakpoint cluster (%d consecutive INT3) near %s at 0x%x", maxConsecutive, t.name, t.addr)
}
}
return nil
}
func readAddr(addr uintptr, size int) []byte {
if size <= 0 || size > 4096 {
return nil
}
buf := make([]byte, size)
var src []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&src))
sh.Data = addr
sh.Len = size
sh.Cap = size
copy(buf, src)
return buf
}
+116
View File
@@ -0,0 +1,116 @@
//go:build windows
package antidebug
import (
"fmt"
"os"
"reflect"
"runtime"
"syscall"
"unsafe"
)
var procReadProcessMemory = kernel32.NewProc("ReadProcessMemory")
func checkFunctionPrologues() error {
type target struct {
name string
addr uintptr
}
targets := []target{
{"os.Exit", reflect.ValueOf(os.Exit).Pointer()},
{"runtime.Goexit", reflect.ValueOf(runtime.Goexit).Pointer()},
}
currentProc, _ := syscall.GetCurrentProcess()
for _, t := range targets {
if t.addr == 0 {
continue
}
prologue := make([]byte, 8)
var bytesRead uintptr
ret, _, _ := procReadProcessMemory.Call(
uintptr(currentProc),
t.addr,
uintptr(unsafe.Pointer(&prologue[0])),
uintptr(len(prologue)),
uintptr(unsafe.Pointer(&bytesRead)),
)
if ret == 0 || bytesRead < 2 {
continue
}
if prologue[0] == 0xE9 {
return fmt.Errorf("antidebug: JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xEB {
return fmt.Errorf("antidebug: short JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xFF && prologue[1] == 0x25 {
return fmt.Errorf("antidebug: indirect JMP hook detected at %s (0x%x)", t.name, t.addr)
}
if prologue[0] == 0xCC && prologue[1] == 0xCC {
return fmt.Errorf("antidebug: INT3 breakpoint at %s (0x%x)", t.name, t.addr)
}
}
return nil
}
func scanForBreakpoints() error {
type target struct {
name string
addr uintptr
}
targets := []target{
{"os.Exit", reflect.ValueOf(os.Exit).Pointer()},
{"runtime.Goexit", reflect.ValueOf(runtime.Goexit).Pointer()},
{"fmt.Printf", reflect.ValueOf(fmt.Printf).Pointer()},
{"os.ReadFile", reflect.ValueOf(os.ReadFile).Pointer()},
}
currentProc, _ := syscall.GetCurrentProcess()
for _, t := range targets {
if t.addr == 0 {
continue
}
region := make([]byte, 64)
var bytesRead uintptr
ret, _, _ := procReadProcessMemory.Call(
uintptr(currentProc),
t.addr,
uintptr(unsafe.Pointer(&region[0])),
uintptr(len(region)),
uintptr(unsafe.Pointer(&bytesRead)),
)
if ret == 0 || bytesRead < 8 {
continue
}
maxConsecutive := 0
consecutive := 0
for _, b := range region[:bytesRead] {
if b == 0xCC {
consecutive++
if consecutive > maxConsecutive {
maxConsecutive = consecutive
}
} else {
consecutive = 0
}
}
if maxConsecutive >= 4 {
return fmt.Errorf("antidebug: breakpoint cluster (%d consecutive INT3) near %s at 0x%x", maxConsecutive, t.name, t.addr)
}
}
return nil
}
+135
View File
@@ -0,0 +1,135 @@
//go:build linux
package antidebug
import (
"fmt"
"os"
"runtime"
"time"
)
func checkPtrace() error {
self, err := os.ReadFile("/proc/self/status")
if err != nil {
return nil
}
data := string(self)
for i := 0; i < len(data)-11; i++ {
if data[i:i+11] == "TracerPid:\t" {
j := i + 11
for j < len(data) && data[j] >= '0' && data[j] <= '9' {
j++
}
pid := data[i+11 : j]
if pid != "0" {
return fmt.Errorf("antidebug: debugger detected (tracer pid=%s)", pid)
}
break
}
}
return nil
}
func disableCoreDump() error {
f, err := os.OpenFile("/proc/self/coredump_filter", os.O_WRONLY, 0)
if err != nil {
return nil
}
defer f.Close()
_, _ = f.WriteString("0")
return nil
}
func checkTiming() error {
start := time.Now()
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
elapsed := time.Since(start)
if elapsed > 200*time.Millisecond {
return fmt.Errorf("antidebug: timing anomaly detected (%v for 1M iterations)", elapsed)
}
_ = sum
return nil
}
func checkEnv() error {
suspiciousVars := []string{
"GODEBUG",
"DELVE_LISTENER",
"DEBUGINFOD_URL",
"RR_LOG_FILE",
"_JAVA_OPTIONS",
"LD_PRELOAD",
}
for _, v := range suspiciousVars {
if os.Getenv(v) != "" {
return fmt.Errorf("antidebug: suspicious env var %s=%s", v, os.Getenv(v))
}
}
return nil
}
func checkParentProcess() error {
ppid := os.Getppid()
if ppid <= 1 {
return nil
}
commPath := fmt.Sprintf("/proc/%d/comm", ppid)
comm, err := os.ReadFile(commPath)
if err != nil {
return nil
}
parent := string(comm)
debuggers := []string{
"gdb", "lldb", "strace", "ltrace", "ida", "ida64",
"radare2", "r2", "ghidra", "x64dbg", "ollydbg",
"delve", "debugserver",
}
for _, d := range debuggers {
if len(parent) >= len(d) && parent[:len(d)] == d {
return fmt.Errorf("antidebug: parent process is debugger: %s", parent)
}
}
return nil
}
func AntiAttach() {
go func() {
for {
time.Sleep(500 * time.Millisecond)
if err := checkPtrace(); err != nil {
os.Exit(66)
}
}
}()
}
func AntiDump() {
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
_ = disableCoreDump()
}
}()
}
func CheckHooks() error {
if err := checkFunctionPrologues(); err != nil {
return err
}
if err := scanForBreakpoints(); err != nil {
return err
}
return nil
}
+266
View File
@@ -0,0 +1,266 @@
//go:build windows
package antidebug
import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
procIsDebuggerPresent = kernel32.NewProc("IsDebuggerPresent")
procCheckRemoteDebugger = kernel32.NewProc("CheckRemoteDebuggerPresent")
procNtQueryInformationProc = ntdll.NewProc("NtQueryInformationProcess")
procNtSetInformationThread = ntdll.NewProc("NtSetInformationThread")
procOutputDebugStringW = kernel32.NewProc("OutputDebugStringW")
procSetUnhandledException = kernel32.NewProc("SetUnhandledExceptionFilter")
)
const (
processDebugPort = 7
processDebugObjectHandle = 0x1E
processDebugFlags = 0x1F
threadHideFromDebugger = 0x11
)
func checkPtrace() error {
ret, _, _ := procIsDebuggerPresent.Call()
if ret != 0 {
return fmt.Errorf("antidebug: IsDebuggerPresent returned true")
}
var isRemoteDebugged int32
currentProc, _ := syscall.GetCurrentProcess()
ret, _, _ = procCheckRemoteDebugger.Call(
uintptr(currentProc),
uintptr(unsafe.Pointer(&isRemoteDebugged)),
)
if ret != 0 && isRemoteDebugged != 0 {
return fmt.Errorf("antidebug: CheckRemoteDebuggerPresent detected debugger")
}
var port uint32
var returnLength uint32
ret, _, _ = procNtQueryInformationProc.Call(
uintptr(currentProc),
processDebugPort,
uintptr(unsafe.Pointer(&port)),
unsafe.Sizeof(port),
uintptr(unsafe.Pointer(&returnLength)),
)
if ret == 0 && port != 0 {
return fmt.Errorf("antidebug: NtQueryInformationProcess(ProcessDebugPort) detected debugger (port=%d)", port)
}
var debugHandle uintptr
ret, _, _ = procNtQueryInformationProc.Call(
uintptr(currentProc),
processDebugObjectHandle,
uintptr(unsafe.Pointer(&debugHandle)),
unsafe.Sizeof(debugHandle),
uintptr(unsafe.Pointer(&returnLength)),
)
if ret == 0 {
return fmt.Errorf("antidebug: NtQueryInformationProcess(ProcessDebugObjectHandle) detected debugger")
}
var debugFlags uint32
ret, _, _ = procNtQueryInformationProc.Call(
uintptr(currentProc),
processDebugFlags,
uintptr(unsafe.Pointer(&debugFlags)),
unsafe.Sizeof(debugFlags),
uintptr(unsafe.Pointer(&returnLength)),
)
if ret == 0 && debugFlags == 0 {
return fmt.Errorf("antidebug: NtQueryInformationProcess(ProcessDebugFlags) detected debugger")
}
return nil
}
func checkPEB() error {
var pebBase uintptr
var returnLength uint32
currentProc, _ := syscall.GetCurrentProcess()
type processBasicInfo struct {
Reserved1 uintptr
PebBaseAddress uintptr
Reserved2 [2]uintptr
UniqueProcessID uintptr
Reserved3 uintptr
}
var info processBasicInfo
ret, _, _ := procNtQueryInformationProc.Call(
uintptr(currentProc),
0,
uintptr(unsafe.Pointer(&info)),
unsafe.Sizeof(info),
uintptr(unsafe.Pointer(&returnLength)),
)
if ret != 0 {
return nil
}
pebBase = info.PebBaseAddress
if pebBase == 0 {
return nil
}
beingDebugged := *(*byte)(unsafe.Pointer(pebBase + 2))
if beingDebugged != 0 {
return fmt.Errorf("antidebug: PEB.BeingDebugged is set")
}
ntGlobalFlag := *(*uint32)(unsafe.Pointer(pebBase + 0xBC))
const FLG_HEAP_ENABLE_TAIL_CHECK = 0x10
const FLG_HEAP_ENABLE_FREE_CHECK = 0x20
const FLG_HEAP_VALIDATE_PARAMETERS = 0x40
debugFlags := FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS
if ntGlobalFlag&uint32(debugFlags) != 0 {
return fmt.Errorf("antidebug: PEB.NtGlobalFlag has debug flags (0x%x)", ntGlobalFlag)
}
return nil
}
func checkHardwareBreakpoints() error {
type context struct {
ContextFlags uint32
Dr0 uintptr
Dr1 uintptr
Dr2 uintptr
Dr3 uintptr
Dr6 uintptr
Dr7 uintptr
FloatSave [112]byte
SegGs uint32
SegFs uint32
SegEs uint32
SegDs uint32
Edi uintptr
Esi uintptr
Ebx uintptr
Edx uintptr
Ecx uintptr
Eax uintptr
Ebp uintptr
Eip uintptr
SegCs uint32
EFlags uint32
Esp uintptr
SegSs uint32
ExtendedRegisters [512]byte
}
threadHandle, _ := syscall.GetCurrentThread()
var ctx context
ctx.ContextFlags = 0x00100010
procGetThreadContext := kernel32.NewProc("GetThreadContext")
ret, _, _ := procGetThreadContext.Call(
uintptr(threadHandle),
uintptr(unsafe.Pointer(&ctx)),
)
if ret == 0 {
return nil
}
if ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0 {
return fmt.Errorf("antidebug: hardware breakpoints detected (dr0=0x%x dr1=0x%x dr2=0x%x dr3=0x%x)",
ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3)
}
return nil
}
func hideFromDebugger() {
threadHandle, _ := syscall.GetCurrentThread()
procNtSetInformationThread.Call(
uintptr(threadHandle),
threadHideFromDebugger,
0,
0,
)
}
func disableCoreDump() error {
return nil
}
func checkTiming() error {
start := time.Now()
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
elapsed := time.Since(start)
if elapsed > 200*time.Millisecond {
return fmt.Errorf("antidebug: timing anomaly detected (%v for 1M iterations)", elapsed)
}
_ = sum
return nil
}
func checkEnv() error {
suspiciousVars := []string{
"GODEBUG",
"DELVE_LISTENER",
"DEBUGINFOD_URL",
"RR_LOG_FILE",
"_JAVA_OPTIONS",
}
for _, v := range suspiciousVars {
if os.Getenv(v) != "" {
return fmt.Errorf("antidebug: suspicious env var %s=%s", v, os.Getenv(v))
}
}
return nil
}
func checkParentProcess() error {
return nil
}
func AntiAttach() {
go func() {
hideFromDebugger()
for {
time.Sleep(500 * time.Millisecond)
if err := checkPtrace(); err != nil {
os.Exit(66)
}
}
}()
}
func AntiDump() {
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
_ = disableCoreDump()
}
}()
}
func CheckHooks() error {
if err := checkFunctionPrologues(); err != nil {
return err
}
if err := scanForBreakpoints(); err != nil {
return err
}
return nil
}
+22
View File
@@ -5,6 +5,7 @@ import (
"crypto/sha256"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"fmt"
"os"
@@ -97,6 +98,8 @@ func hashTextSection(path string) ([32]byte, error) {
return hashTextELF(path)
case "darwin":
return hashTextMacho(path)
case "windows":
return hashTextPE(path)
default:
return hashWholeFile(path)
}
@@ -139,6 +142,25 @@ func hashTextMacho(path string) ([32]byte, error) {
return hashWholeFile(path)
}
func hashTextPE(path string) ([32]byte, error) {
f, err := pe.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
for _, section := range f.Sections {
if section.Name == ".text" {
data, err := section.Data()
if err != nil {
return [32]byte{}, err
}
return sha256.Sum256(data), nil
}
}
return hashWholeFile(path)
}
func hashWholeFile(path string) ([32]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
+98 -16
View File
@@ -1,39 +1,121 @@
//go:build darwin
package syscallobf
import "syscall"
import (
"crypto/rand"
"syscall"
"unsafe"
)
var (
obfPtraceTrap ObfuscatedCall
obfGetpidTrap ObfuscatedCall
obfKillTrap ObfuscatedCall
obfOpenTrap ObfuscatedCall
obfCloseTrap ObfuscatedCall
obfReadTrap ObfuscatedCall
obfLseekTrap ObfuscatedCall
obfMmapTrap ObfuscatedCall
obfMprotectTrap ObfuscatedCall
)
func init() {
var key [4]byte
if _, err := rand.Read(key[:]); err != nil {
panic("syscallobf: failed to generate random key: " + err.Error())
}
obfPtraceTrap = encode(0x200001A, key)
obfGetpidTrap = encode(0x2000027, key)
obfKillTrap = encode(0x2000025, key)
obfOpenTrap = encode(0x2000005, key)
obfCloseTrap = encode(0x2000006, key)
obfReadTrap = encode(0x2000003, key)
obfLseekTrap = encode(0x20000C7, key)
obfMmapTrap = encode(0x20000C5, key)
obfMprotectTrap = encode(0x200004A, key)
}
func ObfPtrace(request int, pid uintptr, addr uintptr, data uintptr) (uintptr, uintptr, syscall.Errno) {
return 0, 0, syscall.ENOSYS
trap := obfPtraceTrap.Num()
r1, r2, errno := syscall.RawSyscall6(trap, uintptr(request), pid, addr, data, 0, 0)
return r1, r2, errno
}
func ObfGetpid() int {
return 0
trap := obfGetpidTrap.Num()
r1, _, _ := syscall.RawSyscall(trap, 0, 0, 0)
return int(r1)
}
func ObfKill(pid int, sig int) error {
return syscall.ENOSYS
trap := obfKillTrap.Num()
_, _, errno := syscall.RawSyscall(trap, uintptr(pid), uintptr(sig), 0)
if errno != 0 {
return errno
}
return nil
}
func ObfOpen(path string, flags int, mode int) (int, error) {
return -1, syscall.ENOSYS
trap := obfOpenTrap.Num()
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return -1, err
}
r1, _, errno := syscall.RawSyscall(trap, uintptr(unsafe.Pointer(pathBytes)), uintptr(flags), uintptr(mode))
if errno != 0 {
return int(r1), errno
}
return int(r1), nil
}
func ObfClose(fd int) error {
return syscall.ENOSYS
trap := obfCloseTrap.Num()
_, _, errno := syscall.RawSyscall(trap, uintptr(fd), 0, 0)
if errno != 0 {
return errno
}
return nil
}
func ObfRead(fd int, buf []byte) (int, error) {
return 0, syscall.ENOSYS
}
func ObfMmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset uintptr) (uintptr, error) {
return 0, syscall.ENOSYS
}
func ObfMprotect(addr uintptr, length uintptr, prot int) error {
return syscall.ENOSYS
trap := obfReadTrap.Num()
var p *byte
if len(buf) > 0 {
p = &buf[0]
}
r1, _, errno := syscall.RawSyscall(trap, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(len(buf)))
if errno != 0 {
return int(r1), errno
}
return int(r1), nil
}
func ObfLseek(fd int, offset int64, whence int) (int64, error) {
return 0, syscall.ENOSYS
trap := obfLseekTrap.Num()
r1, _, errno := syscall.RawSyscall(trap, uintptr(fd), uintptr(offset), uintptr(whence))
if errno != 0 {
return int64(r1), errno
}
return int64(r1), nil
}
func ObfMmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset uintptr) (uintptr, error) {
trap := obfMmapTrap.Num()
r1, _, errno := syscall.Syscall6(trap, addr, length, uintptr(prot), uintptr(flags), uintptr(fd), offset)
if errno != 0 {
return 0, errno
}
return r1, nil
}
func ObfMprotect(addr uintptr, length uintptr, prot int) error {
trap := obfMprotectTrap.Num()
_, _, errno := syscall.RawSyscall(trap, addr, length, uintptr(prot))
if errno != 0 {
return errno
}
return nil
}
+195
View File
@@ -0,0 +1,195 @@
//go:build windows
package syscallobf
import (
"crypto/rand"
"sync"
"syscall"
"unsafe"
)
var (
modntdll = syscall.NewLazyDLL("ntdll.dll")
procNtQueryInformationProcess = modntdll.NewProc("NtQueryInformationProcess")
procNtSetInformationThread = modntdll.NewProc("NtSetInformationThread")
procNtClose = modntdll.NewProc("NtClose")
procNtProtectVirtualMemory = modntdll.NewProc("NtProtectVirtualMemory")
procNtReadVirtualMemory = modntdll.NewProc("NtReadVirtualMemory")
procNtOpenProcess = modntdll.NewProc("NtOpenProcess")
obfPtraceTrap ObfuscatedCall
obfGetpidTrap ObfuscatedCall
obfKillTrap ObfuscatedCall
obfOpenTrap ObfuscatedCall
obfCloseTrap ObfuscatedCall
obfReadTrap ObfuscatedCall
obfLseekTrap ObfuscatedCall
obfMmapTrap ObfuscatedCall
obfMprotectTrap ObfuscatedCall
)
func init() {
var key [4]byte
if _, err := rand.Read(key[:]); err != nil {
panic("syscallobf: failed to generate random key: " + err.Error())
}
obfCloseTrap = encode(0, key)
obfReadTrap = encode(0, key)
obfOpenTrap = encode(0, key)
obfLseekTrap = encode(0, key)
obfMmapTrap = encode(0, key)
obfMprotectTrap = encode(0, key)
obfPtraceTrap = encode(0, key)
obfGetpidTrap = encode(0, key)
obfKillTrap = encode(0, key)
}
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetCurrentPID = kernel32.NewProc("GetCurrentProcessId")
procOpenProcess = kernel32.NewProc("OpenProcess")
procCloseHandle = kernel32.NewProc("CloseHandle")
procReadProcessMem = kernel32.NewProc("ReadProcessMemory")
)
func ObfGetpid() int {
r1, _, _ := procGetCurrentPID.Call()
return int(r1)
}
func ObfOpen(path string, flags int, mode int) (int, error) {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return -1, err
}
access := uint32(syscall.GENERIC_READ)
if flags&syscall.O_WRONLY != 0 {
access = syscall.GENERIC_WRITE
}
if flags&syscall.O_RDWR != 0 {
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
}
creation := uint32(syscall.OPEN_EXISTING)
if flags&syscall.O_CREAT != 0 {
creation = syscall.CREATE_ALWAYS
}
h, _, err := syscall.CreateFile(p, access, syscall.FILE_SHARE_READ, nil, creation, syscall.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil && err.(syscall.Errno) != 0 {
return -1, err
}
return int(h), nil
}
func ObfClose(fd int) error {
r1, _, err := procCloseHandle.Call(uintptr(fd))
if r1 == 0 {
return err
}
return nil
}
func ObfRead(fd int, buf []byte) (int, error) {
var bytesRead uint32
var p *byte
if len(buf) > 0 {
p = &buf[0]
}
r1, _, err := syscall.ReadFile(
syscall.Handle(fd),
buf,
&bytesRead,
nil,
)
if r1 == 0 {
return int(bytesRead), err
}
return int(bytesRead), nil
}
func ObfLseek(fd int, offset int64, whence int) (int64, error) {
var newOff int64
high := int32(offset >> 32)
r1, _, err := syscall.Seek(syscall.Handle(fd), int32(offset), high)
if r1 == 0xffffffff && err != nil {
return 0, err
}
_ = newOff
return int64(r1), nil
}
func ObfMmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset uintptr) (uintptr, error) {
flProtect := uint32(syscall.PAGE_READWRITE)
if prot&syscall.PROT_EXEC != 0 {
flProtect = syscall.PAGE_EXECUTE_READWRITE
}
h, _, err := syscall.CreateFileMapping(
syscall.Handle(fd),
nil,
flProtect,
uint32(length>>32),
uint32(length),
nil,
)
if h == 0 {
return 0, err
}
return uintptr(h), nil
}
func ObfMprotect(addr uintptr, length uintptr, prot int) error {
var oldProtect uint32
flProtect := uint32(syscall.PAGE_READWRITE)
if prot&syscall.PROT_EXEC != 0 {
flProtect = syscall.PAGE_EXECUTE_READWRITE
}
r1, _, err := procNtProtectVirtualMemory.Call(
uintptr(0xffffffffffffffff),
uintptr(unsafe.Pointer(&addr)),
uintptr(unsafe.Pointer(&length)),
uintptr(flProtect),
uintptr(unsafe.Pointer(&oldProtect)),
)
if r1 != 0 {
return err
}
return nil
}
type windowsObfCall struct {
once sync.Once
procAddr uintptr
proc *syscall.LazyProc
}
func (w *windowsObfCall) Call(args ...uintptr) (uintptr, uintptr, error) {
w.once.Do(func() {
if w.proc != nil {
w.procAddr = w.proc.Addr()
}
})
return syscall.Syscall(w.procAddr, uintptr(len(args)), args[0], args[1], args[2])
}
func NtQueryInformationProcess(handle uintptr, infoclass uint32, info uintptr, infolen uint32, retlen *uint32) uintptr {
r1, _, _ := procNtQueryInformationProcess.Call(
handle,
uintptr(infoclass),
info,
uintptr(infolen),
uintptr(unsafe.Pointer(retlen)),
)
return r1
}
func NtSetInformationThread(handle uintptr, infoclass uint32, info uintptr, infolen uint32) uintptr {
r1, _, _ := procNtSetInformationThread.Call(
handle,
uintptr(infoclass),
info,
uintptr(infolen),
)
return r1
}
+32
View File
@@ -1,7 +1,9 @@
package vm
import (
"crypto/rand"
"fmt"
"math/big"
)
type Instruction struct {
@@ -15,7 +17,15 @@ type LabelledInstr struct {
Target string
}
type CompileOpts struct {
NoJunk bool
}
func Compile(program []LabelledInstr) []byte {
return CompileWith(program, CompileOpts{})
}
func CompileWith(program []LabelledInstr, opts CompileOpts) []byte {
labelAddrs := make(map[string]int)
type fixup struct {
@@ -26,11 +36,18 @@ func Compile(program []LabelledInstr) []byte {
var fixups []fixup
var bytecode []byte
junkOpcodes := []Opcode{opJunkA, opJunkB, opJunkC, opJunkD}
for _, entry := range program {
if entry.Label != "" {
labelAddrs[entry.Label] = len(bytecode)
}
if !opts.NoJunk && shouldInsertJunk() {
junkOp := junkOpcodes[randIntn(len(junkOpcodes))]
bytecode = append(bytecode, encodeOp(junkOp))
}
instr := entry.Instr
isJump := instr.Op == OpJmp || instr.Op == OpJz || instr.Op == OpJnz
@@ -64,3 +81,18 @@ func Compile(program []LabelledInstr) []byte {
return bytecode
}
func shouldInsertJunk() bool {
return randIntn(3) == 0
}
func randIntn(n int) int {
if n <= 0 {
return 0
}
b, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
if err != nil {
return 0
}
return int(b.Int64())
}
+27 -5
View File
@@ -430,7 +430,7 @@ func TestStepwiseExecution(t *testing.T) {
{Instr: Instruction{Op: OpHalt}},
}
code := Compile(program)
code := CompileWith(program, CompileOpts{NoJunk: true})
vm := NewVM(code, nil)
steps := 0
@@ -461,8 +461,13 @@ func TestXorObfuscationDeterminism(t *testing.T) {
{Instr: Instruction{Op: OpHalt}},
}
code1 := Compile(program)
code2 := Compile(program)
opts := CompileOpts{NoJunk: true}
code1 := CompileWith(program, opts)
code2 := CompileWith(program, opts)
if len(code1) != len(code2) {
t.Fatalf("bytecode length differs: %d vs %d", len(code1), len(code2))
}
for i := range code1 {
if code1[i] != code2[i] {
@@ -487,9 +492,8 @@ func TestBytecodeIsXORed(t *testing.T) {
{Instr: Instruction{Op: OpHalt}},
}
code := Compile(program)
code := CompileWith(program, CompileOpts{NoJunk: true})
// First byte should be OpPush ^ dispatchKey, not OpPush itself
rawPush := byte(OpPush) ^ dispatchKey
if code[0] != rawPush {
t.Errorf("expected bytecode[0]=0x%02X (XOR'd PUSH), got 0x%02X", rawPush, code[0])
@@ -501,6 +505,24 @@ func TestBytecodeIsXORed(t *testing.T) {
}
}
func TestJunkOpcodesCorrectness(t *testing.T) {
program := []LabelledInstr{
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
{Instr: Instruction{Op: OpAdd}},
{Instr: Instruction{Op: OpHalt}},
}
for i := 0; i < 50; i++ {
code := Compile(program)
vm := NewVM(code, nil)
result := vm.Run()
if result != 30 {
t.Fatalf("iteration %d: expected 30, got %d (bytecode len %d)", i, result, len(code))
}
}
}
func TestOrOp(t *testing.T) {
// 0x0F OR 0xF0 = 0xFF
program := []LabelledInstr{