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
}