This commit is contained in:
2026-05-31 19:48:56 +01:00
parent 7f32417736
commit 68f8904282
39 changed files with 4519 additions and 5 deletions
+26
View File
@@ -0,0 +1,26 @@
package antidbi
func Check() bool {
if checkMemoryMaps() {
return true
}
if checkUnixSockets() {
return true
}
if checkFridaPort() {
return true
}
if checkThreadNames() {
return true
}
if checkLoadedImages() {
return true
}
if checkMachPorts() {
return true
}
if checkEnvironment() {
return true
}
return false
}
+166
View File
@@ -0,0 +1,166 @@
//go:build darwin
package antidbi
import (
"net"
"os"
"strings"
"syscall"
"time"
"unsafe"
)
var dylibSignatures = []string{
"frida-agent",
"frida-gadget",
"frida-tracer",
}
var dylibSearchPaths = []string{
"/usr/local/lib/",
"/usr/lib/",
"/Library/Frameworks/",
}
var machPortSignatures = []string{
"re.frida.server",
"re.frida.agent",
"linjector",
}
var darwinEnvSignatures = []string{
"frida",
"frida-agent",
"frida-gadget",
}
func checkMemoryMaps() bool {
return false
}
func checkUnixSockets() bool {
return false
}
func checkFridaPort() bool {
conn, err := net.DialTimeout("tcp", "127.0.0.1:27042", 200*time.Millisecond)
if err != nil {
return false
}
conn.Close()
return true
}
func checkThreadNames() bool {
return false
}
func checkLoadedImages() bool {
dyld := os.Getenv("DYLD_INSERT_LIBRARIES")
if dyld != "" {
lower := strings.ToLower(dyld)
for _, sig := range dylibSignatures {
if strings.Contains(lower, sig) {
return true
}
}
}
for _, dir := range dylibSearchPaths {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, entry := range entries {
name := strings.ToLower(entry.Name())
for _, sig := range dylibSignatures {
if strings.Contains(name, sig) {
return true
}
}
}
}
return false
}
func checkMachPorts() bool {
taskSelf := machTaskSelf()
if taskSelf == 0 {
return false
}
var bootstrapPort uint32
ret, _, _ := syscall.Syscall(
uintptr(0x1000000+45),
uintptr(taskSelf),
uintptr(unsafe.Pointer(&bootstrapPort)),
0,
)
if ret != 0 || bootstrapPort == 0 {
return false
}
for _, portName := range machPortSignatures {
cname := append([]byte(portName), 0)
var port uint32
ret, _, _ := syscall.Syscall(
uintptr(0x2000000+410),
uintptr(unsafe.Pointer(&cname[0])),
uintptr(unsafe.Pointer(&port)),
0,
)
if ret == 0 && port != 0 {
return true
}
}
return false
}
func checkEnvironment() bool {
envVars := []string{
"DYLD_INSERT_LIBRARIES",
"DYLD_FRAMEWORK_PATH",
"DYLD_LIBRARY_PATH",
"DYLD_FALLBACK_FRAMEWORK_PATH",
"DYLD_FALLBACK_LIBRARY_PATH",
}
for _, v := range envVars {
val := os.Getenv(v)
if val == "" {
continue
}
lower := strings.ToLower(val)
for _, sig := range darwinEnvSignatures {
if strings.Contains(lower, sig) {
return true
}
}
}
return false
}
func machTaskSelf() uint32 {
ret, _, _ := syscall.Syscall(
uintptr(0x1000000+28),
0,
0,
0,
)
return uint32(ret)
}
func AntiAttach() {
go func() {
for {
time.Sleep(500 * time.Millisecond)
if checkLoadedImages() || checkMachPorts() || checkEnvironment() || checkFridaPort() {
syscall.Exit(66)
}
}
}()
}
+168
View File
@@ -0,0 +1,168 @@
//go:build linux
package antidbi
import (
"net"
"os"
"strings"
"syscall"
"time"
"unsafe"
)
var dbiSignatures = []string{
"frida-agent",
"frida-gadget",
"dynamorio",
"libdynamorio",
"libpin",
}
var socketSignatures = []string{
"frida",
"linjector",
}
var threadSignatures = []string{
"gmain",
"frida",
}
var linuxEnvSignatures = []string{
"frida",
"dynamorio",
"pin",
}
func readFileSyscall(path string) ([]byte, error) {
pathBytes := append([]byte(path), 0)
fd, _, errno := syscall.RawSyscall(
uintptr(syscall.SYS_OPEN),
uintptr(unsafe.Pointer(&pathBytes[0])),
uintptr(syscall.O_RDONLY),
0,
)
if fd == ^uintptr(0) || fd >= 0x7fffffffffff {
return nil, errno
}
defer syscall.Close(int(fd))
buf := make([]byte, 64*1024)
n, _, errno := syscall.RawSyscall(
uintptr(syscall.SYS_READ),
fd,
uintptr(unsafe.Pointer(&buf[0])),
uintptr(len(buf)),
)
if n == ^uintptr(0) || n >= 0x7fffffffffff {
return nil, errno
}
return buf[:n], nil
}
func checkMemoryMaps() bool {
data, err := readFileSyscall("/proc/self/maps")
if err != nil {
return false
}
content := string(data)
for _, sig := range dbiSignatures {
if strings.Contains(content, sig) {
return true
}
}
return false
}
func checkUnixSockets() bool {
data, err := readFileSyscall("/proc/net/unix")
if err != nil {
return false
}
content := string(data)
for _, sig := range socketSignatures {
if strings.Contains(content, sig) {
return true
}
}
return false
}
func checkFridaPort() bool {
conn, err := net.DialTimeout("tcp", "127.0.0.1:27042", 200*time.Millisecond)
if err != nil {
return false
}
conn.Close()
return true
}
func checkThreadNames() bool {
data, err := readFileSyscall("/proc/self/task")
if err != nil {
return false
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || line == "." || line == ".." {
continue
}
commPath := "/proc/self/task/" + line + "/comm"
comm, err := readFileSyscall(commPath)
if err != nil {
continue
}
name := strings.TrimSpace(string(comm))
for _, sig := range threadSignatures {
if strings.Contains(name, sig) {
return true
}
}
}
return false
}
func checkLoadedImages() bool {
return false
}
func checkMachPorts() bool {
return false
}
func checkEnvironment() bool {
suspiciousVars := []string{
"LD_PRELOAD",
"FRIDA_DNS_SERVER",
"_JAVA_OPTIONS",
}
for _, v := range suspiciousVars {
val := strings.ToLower(os.Getenv(v))
if val == "" {
continue
}
for _, sig := range linuxEnvSignatures {
if strings.Contains(val, sig) {
return true
}
}
}
return false
}
func AntiAttach() {
go func() {
for {
time.Sleep(500 * time.Millisecond)
if checkMemoryMaps() || checkUnixSockets() || checkFridaPort() || checkThreadNames() {
syscall.Exit(66)
}
}
}()
}
+65
View File
@@ -0,0 +1,65 @@
package antidebug
import (
"syscall"
"unsafe"
)
const (
offsetofDR0 = 6 * 8 // offsetof(struct user, u_debugreg[0])
offsetofDR7 = 13 * 8 // offsetof(struct user, u_debugreg[7])
)
func checkHardwareBreakpoints() bool {
pid, _, _ := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if pid == 0 {
// Child: attach to parent, read debug registers, report via exit code
syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_ATTACH), uintptr(syscall.Getppid()), 0)
var status syscall.WaitStatus
syscall.RawSyscall(syscall.SYS_WAIT4, uintptr(syscall.Getppid()), uintptr(unsafe.Pointer(&status)), 0, 0)
detected := false
for i := 0; i < 4; i++ {
drVal, _, _ := syscall.RawSyscall6(
syscall.SYS_PTRACE,
uintptr(syscall.PTRACE_PEEKUSER),
uintptr(syscall.Getppid()),
uintptr(offsetofDR0+i*8),
0, 0, 0,
)
if drVal != 0 {
detected = true
break
}
}
if !detected {
dr7, _, _ := syscall.RawSyscall6(
syscall.SYS_PTRACE,
uintptr(syscall.PTRACE_PEEKUSER),
uintptr(syscall.Getppid()),
uintptr(offsetofDR7),
0, 0, 0,
)
// Bits 0,2,4,6 enable DR0-DR3; bits 1,3,5,7 are condition bits
// If any enable bit is set, a hardware breakpoint is active
if dr7&0x55 != 0 {
detected = true
}
}
syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_DETACH), uintptr(syscall.Getppid()), 0)
exitCode := 0
if detected {
exitCode = 1
}
syscall.RawSyscall(syscall.SYS_EXIT, uintptr(exitCode), 0, 0)
}
// Parent: wait for child result
var status syscall.WaitStatus
syscall.RawSyscall(syscall.SYS_WAIT4, pid, uintptr(unsafe.Pointer(&status)), 0, 0)
return status.ExitStatus() == 1
}
+53
View File
@@ -0,0 +1,53 @@
//go:build windows
package antidebug
import (
"syscall"
"unsafe"
)
const _CONTEXT_DEBUG_REGISTERS = 0x00100010
type contextDebugRegs struct {
Dr0 uint64
Dr1 uint64
Dr2 uint64
Dr3 uint64
Dr6 uint64
Dr7 uint64
}
func checkHardwareBreakpoints() bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getCurrentThread := kernel32.NewProc("GetCurrentThread")
getThreadContext := kernel32.NewProc("GetThreadContext")
thread, _, _ := getCurrentThread.Call()
ctx := struct {
contextFlags uint32
_ [512]byte
debugRegs contextDebugRegs
}{}
ctx.contextFlags = _CONTEXT_DEBUG_REGISTERS
ret, _, _ := getThreadContext.Call(
thread,
uintptr(unsafe.Pointer(&ctx)),
)
if ret == 0 {
return false
}
if ctx.debugRegs.Dr0 != 0 || ctx.debugRegs.Dr1 != 0 ||
ctx.debugRegs.Dr2 != 0 || ctx.debugRegs.Dr3 != 0 {
return true
}
if ctx.debugRegs.Dr7&0x55 != 0 {
return true
}
return false
}
+70
View File
@@ -0,0 +1,70 @@
package antidebug
import (
"bufio"
"fmt"
"os"
"runtime"
"strings"
)
var suspiciousPatterns = []string{
"frida", "dynamorio", "pin", "hook", "inject",
"intercept", "instrument", "ltrace", "strace",
}
func checkReturnAddresses() bool {
var pcs [16]uintptr
n := runtime.Callers(2, pcs[:])
if n == 0 {
return false
}
for i := 0; i < n; i++ {
fn := runtime.FuncForPC(pcs[i])
if fn == nil {
return true
}
name := strings.ToLower(fn.Name())
for _, pat := range suspiciousPatterns {
if strings.Contains(name, pat) {
return true
}
}
}
if !checkPCInBinary(pcs[0]) {
return true
}
return false
}
func checkPCInBinary(pc uintptr) bool {
f, err := os.Open("/proc/self/maps")
if err != nil {
return false
}
defer f.Close()
selfPath, _ := os.Executable()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, "r-xp") && !strings.Contains(line, "r--p") {
continue
}
if selfPath != "" && !strings.Contains(line, selfPath) {
continue
}
var start, end uintptr
if _, err := fmt.Sscanf(line, "%x-%x", &start, &end); err != nil {
continue
}
if pc >= start && pc < end {
return true
}
}
return false
}
+35
View File
@@ -0,0 +1,35 @@
package antidebug
import (
"os"
"os/signal"
"syscall"
"time"
)
var signalTrapDetected bool
func checkSignalTrap() bool {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTRAP)
signalTrapDetected = false
go func() {
select {
case <-ch:
signalTrapDetected = true
case <-time.After(200 * time.Millisecond):
}
}()
time.Sleep(10 * time.Millisecond)
syscall.Kill(syscall.Getpid(), syscall.SIGTRAP)
time.Sleep(250 * time.Millisecond)
signal.Stop(ch)
// If the handler received the signal, no debugger intercepted it.
// If signalTrapDetected is false, a debugger caught the SIGTRAP.
return !signalTrapDetected
}
+82
View File
@@ -0,0 +1,82 @@
package antiemul
import (
"os"
"runtime"
"strconv"
"strings"
"time"
)
var platformCheck func() bool
func Check() bool {
if checkTiming() {
return true
}
if checkMemory() {
return true
}
if checkUptime() {
return true
}
if checkCoreCount() {
return true
}
if platformCheck != nil && platformCheck() {
return true
}
return false
}
func checkTiming() bool {
start := time.Now()
for i := 0; i < 1000000; i++ {
_ = i * i
}
return time.Since(start) > 500*time.Millisecond
}
func checkMemory() bool {
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
return false
}
content := string(data)
idx := strings.Index(content, "MemTotal:")
if idx == -1 {
return false
}
rest := content[idx+9:]
rest = strings.TrimSpace(rest)
fields := strings.Fields(rest)
if len(fields) == 0 {
return false
}
kb, err := strconv.ParseInt(fields[0], 10, 64)
if err != nil {
return false
}
return kb < 2000000
}
func checkUptime() bool {
data, err := os.ReadFile("/proc/uptime")
if err != nil {
return false
}
content := strings.TrimSpace(string(data))
fields := strings.Fields(content)
if len(fields) == 0 {
return false
}
seconds, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return false
}
return seconds < 60.0
}
func checkCoreCount() bool {
return runtime.NumCPU() < 2
}
+9
View File
@@ -0,0 +1,9 @@
//go:build darwin
package antiemul
func init() {
platformCheck = func() bool {
return checkTiming()
}
}
+53
View File
@@ -0,0 +1,53 @@
//go:build linux
package antiemul
import (
"os"
"strings"
)
func init() {
platformCheck = func() bool {
if checkCPUInfo() {
return true
}
if checkDMI() {
return true
}
return false
}
}
func checkCPUInfo() bool {
data, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
content := strings.ToUpper(string(data))
signatures := []string{"QEMU", "VIRTUAL", "KVM"}
for _, sig := range signatures {
if strings.Contains(content, sig) {
return true
}
}
return false
}
func checkDMI() bool {
data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_name")
if err != nil {
return false
}
product := strings.ToUpper(strings.TrimSpace(string(data)))
vmStrings := []string{
"QEMU", "VIRTUAL", "KVM", "VMWARE", "VBOX",
"VIRTUALBOX", "XEN", "HYPER-V", "PARALLELS",
}
for _, vm := range vmStrings {
if strings.Contains(product, vm) {
return true
}
}
return false
}
+44
View File
@@ -0,0 +1,44 @@
//go:build windows
package antiemul
import (
"syscall"
"unsafe"
)
type memoryStatusEx struct {
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
ullAvailPhys uint64
ullTotalPageFile uint64
ullAvailPageFile uint64
ullTotalVirtual uint64
ullAvailVirtual uint64
ullAvailExtendedVirtual uint64
}
func init() {
platformCheck = func() bool {
if checkTiming() {
return true
}
if checkWindowsMemory() {
return true
}
return false
}
}
func checkWindowsMemory() bool {
mod := syscall.NewLazyDLL("kernel32.dll")
proc := mod.NewProc("GlobalMemoryStatusEx")
var memStatus memoryStatusEx
memStatus.dwLength = uint32(unsafe.Sizeof(memStatus))
r1, _, _ := proc.Call(uintptr(unsafe.Pointer(&memStatus)))
if r1 == 0 {
return false
}
return memStatus.ullTotalPhys < 2*1024*1024*1024
}
+5
View File
@@ -0,0 +1,5 @@
package antisandbox
func Check() bool {
return CheckNetwork()
}
+78
View File
@@ -0,0 +1,78 @@
package antisandbox
import (
"net"
"os"
"strings"
)
var suspiciousHostnames = []string{
"sandbox", "malware", "cuckoo", "analysis", "sandox", "test",
}
var vmMACPrefixes = []string{
"00:0c:29", "08:00:27", "52:54:00", "00:1c:42",
}
func CheckNetwork() bool {
if checkInterfaces() {
return true
}
if checkMACPrefixes() {
return true
}
if checkHostname() {
return true
}
if checkARP() {
return true
}
return false
}
func checkInterfaces() bool {
ifaces, err := net.Interfaces()
if err != nil {
return false
}
return len(ifaces) <= 1
}
func checkMACPrefixes() bool {
ifaces, err := net.Interfaces()
if err != nil {
return false
}
for _, iface := range ifaces {
mac := strings.ToLower(iface.HardwareAddr.String())
for _, prefix := range vmMACPrefixes {
if strings.HasPrefix(mac, prefix) {
return true
}
}
}
return false
}
func checkHostname() bool {
hostname, err := os.Hostname()
if err != nil {
return false
}
lower := strings.ToLower(hostname)
for _, suspicious := range suspiciousHostnames {
if strings.Contains(lower, suspicious) {
return true
}
}
return false
}
func checkARP() bool {
data, err := os.ReadFile("/proc/net/arp")
if err != nil {
return false
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
return len(lines) <= 1
}
+35
View File
@@ -0,0 +1,35 @@
package antivm
func Check() bool {
if checkDMI() {
return true
}
if checkMAC() {
return true
}
if checkCPUID() {
return true
}
if checkDisk() {
return true
}
if checkPCI() {
return true
}
if checkTiming() {
return true
}
if checkSystemProfiler() {
return true
}
if checkIOKit() {
return true
}
if checkRegistry() {
return true
}
if checkWMI() {
return true
}
return false
}
+98
View File
@@ -0,0 +1,98 @@
//go:build darwin
package antivm
import (
"net"
"os/exec"
"strings"
)
var vmIdentifiers = []string{
"VMware",
"VirtualBox",
"QEMU",
"KVM",
"Xen",
"Microsoft Corporation",
"Parallels",
}
var vmMACPrefixes = []string{
"00:0c:29",
"08:00:27",
"52:54:00",
"00:1c:42",
}
func containsAny(s string, needles []string) bool {
for _, n := range needles {
if strings.Contains(s, n) {
return true
}
}
return false
}
func checkMAC() bool {
ifaces, err := net.Interfaces()
if err != nil {
return false
}
for _, iface := range ifaces {
mac := strings.ToLower(iface.HardwareAddr.String())
if mac == "" {
continue
}
for _, prefix := range vmMACPrefixes {
if strings.HasPrefix(mac, prefix) {
return true
}
}
}
return false
}
func checkSystemProfiler() bool {
out, err := exec.Command("system_profiler", "SPHardwareDataType").Output()
if err != nil {
return false
}
return containsAny(string(out), vmIdentifiers)
}
func checkIOKit() bool {
out, err := exec.Command("ioreg", "-l").Output()
if err != nil {
return false
}
return containsAny(string(out), vmIdentifiers)
}
func checkDMI() bool {
return false
}
func checkCPUID() bool {
return false
}
func checkDisk() bool {
return false
}
func checkPCI() bool {
return false
}
func checkTiming() bool {
return false
}
func checkRegistry() bool {
return false
}
func checkWMI() bool {
return false
}
+161
View File
@@ -0,0 +1,161 @@
//go:build linux
package antivm
import (
"net"
"os"
"strings"
"time"
)
var vmIdentifiers = []string{
"VMware",
"VirtualBox",
"QEMU",
"KVM",
"Xen",
"Microsoft Corporation",
"Parallels",
}
var vmMACPrefixes = []string{
"00:0c:29",
"08:00:27",
"52:54:00",
"00:1c:42",
}
var vmPCIVendors = []string{
"15ad",
"80ee",
"1234",
}
func readSysfsTrimmed(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}
func containsAny(s string, needles []string) bool {
for _, n := range needles {
if strings.Contains(s, n) {
return true
}
}
return false
}
func checkDMI() bool {
paths := []string{
"/sys/class/dmi/id/sys_vendor",
"/sys/class/dmi/id/product_name",
}
for _, p := range paths {
val := readSysfsTrimmed(p)
if containsAny(val, vmIdentifiers) {
return true
}
}
return false
}
func checkMAC() bool {
ifaces, err := net.Interfaces()
if err != nil {
return false
}
for _, iface := range ifaces {
mac := strings.ToLower(iface.HardwareAddr.String())
if mac == "" {
continue
}
for _, prefix := range vmMACPrefixes {
if strings.HasPrefix(mac, prefix) {
return true
}
}
}
return false
}
func checkCPUID() bool {
data, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
content := string(data)
if strings.Contains(content, "hypervisor") {
return true
}
if strings.Contains(content, "QEMU Virtual CPU") {
return true
}
return false
}
func checkDisk() bool {
entries, err := os.ReadDir("/sys/block")
if err != nil {
return false
}
for _, entry := range entries {
modelPath := "/sys/block/" + entry.Name() + "/device/model"
model := readSysfsTrimmed(modelPath)
if containsAny(model, vmIdentifiers) {
return true
}
}
return false
}
func checkPCI() bool {
entries, err := os.ReadDir("/sys/bus/pci/devices")
if err != nil {
return false
}
for _, entry := range entries {
vendorPath := "/sys/bus/pci/devices/" + entry.Name() + "/vendor"
vendor := readSysfsTrimmed(vendorPath)
vendorID := strings.TrimPrefix(vendor, "0x")
for _, v := range vmPCIVendors {
if vendorID == v {
return true
}
}
}
return false
}
func checkTiming() bool {
start := time.Now()
sum := 0
for i := 0; i < 100000; i++ {
sum += i
}
elapsed := time.Since(start)
_ = sum
return elapsed > 10*time.Millisecond
}
func checkSystemProfiler() bool {
return false
}
func checkIOKit() bool {
return false
}
func checkRegistry() bool {
return false
}
func checkWMI() bool {
return false
}
+130
View File
@@ -0,0 +1,130 @@
//go:build windows
package antivm
import (
"net"
"os/exec"
"strings"
"syscall"
"unsafe"
)
var vmIdentifiers = []string{
"VMware",
"VirtualBox",
"QEMU",
"KVM",
"Xen",
"Microsoft Corporation",
"Parallels",
"VBOX",
"Virtual HD",
}
var vmMACPrefixes = []string{
"00:0c:29",
"08:00:27",
"52:54:00",
"00:1c:42",
}
func containsAny(s string, needles []string) bool {
for _, n := range needles {
if strings.Contains(s, n) {
return true
}
}
return false
}
func checkMAC() bool {
ifaces, err := net.Interfaces()
if err != nil {
return false
}
for _, iface := range ifaces {
mac := strings.ToLower(iface.HardwareAddr.String())
if mac == "" {
continue
}
for _, prefix := range vmMACPrefixes {
if strings.HasPrefix(mac, prefix) {
return true
}
}
}
return false
}
func checkRegistry() bool {
var h syscall.Handle
keyPath, _ := syscall.UTF16PtrFromString(`SYSTEM\CurrentControlSet\Services\Disk\Enum`)
err := syscall.RegOpenKeyEx(
syscall.HKEY_LOCAL_MACHINE,
keyPath,
0,
syscall.KEY_READ,
&h,
)
if err != nil {
return false
}
defer syscall.RegCloseKey(h)
var buf [512]uint16
var bufLen uint32 = uint32(len(buf) * 2)
var valType uint32
name, _ := syscall.UTF16PtrFromString("0")
err = syscall.RegQueryValueEx(
h,
name,
nil,
&valType,
(*byte)(unsafe.Pointer(&buf[0])),
&bufLen,
)
if err != nil {
return false
}
val := syscall.UTF16ToString(buf[:])
return containsAny(val, vmIdentifiers)
}
func checkWMI() bool {
out, err := exec.Command("wmic", "computersystem", "get", "model").Output()
if err != nil {
return false
}
return containsAny(string(out), vmIdentifiers)
}
func checkDMI() bool {
return false
}
func checkCPUID() bool {
return false
}
func checkDisk() bool {
return false
}
func checkPCI() bool {
return false
}
func checkTiming() bool {
return false
}
func checkSystemProfiler() bool {
return false
}
func checkIOKit() bool {
return false
}
+277
View File
@@ -0,0 +1,277 @@
package pclntab
import (
"crypto/rand"
"encoding/binary"
"fmt"
)
var pclntabMagics = [][]byte{
{0xF1, 0xFF, 0xFF, 0xFA}, // Go 1.2 - 1.15
{0xF1, 0xFF, 0xFF, 0xF0}, // Go 1.16 - 1.17
{0xF1, 0xFF, 0xFF, 0xF1}, // Go 1.18+
}
type CorruptOptions struct {
EncryptNames bool
ScrambleMagic bool
InjectFake bool
Key byte
}
func CorruptPclntab(binaryPath string, opts CorruptOptions) ([]byte, error) {
if opts.Key == 0 {
var k [1]byte
if _, err := rand.Read(k[:]); err != nil {
return nil, fmt.Errorf("pclntab: failed to generate key: %w", err)
}
if k[0] == 0 {
k[0] = 0x42
}
opts.Key = k[0]
}
data, err := readPclntabSection(binaryPath)
if err != nil {
return nil, err
}
if data == nil {
return nil, fmt.Errorf("pclntab: .gopclntab section not found in %s", binaryPath)
}
corrupted := make([]byte, len(data))
copy(corrupted, data)
tab := &pclntabParser{data: corrupted}
if opts.ScrambleMagic {
scrambleMagic(corrupted)
}
if opts.EncryptNames {
if err := encryptNames(tab, opts.Key); err != nil {
return nil, fmt.Errorf("pclntab: failed to encrypt names: %w", err)
}
}
if opts.InjectFake {
corrupted = injectFakeEntries(corrupted)
}
return corrupted, nil
}
type pclntabParser struct {
data []byte
magic []byte
quantum uint8
ptrSize uint8
nfiles uint32
nameOff uint32
strtabOff uint32
valid bool
}
func (p *pclntabParser) parse() error {
if len(p.data) < 16 {
return fmt.Errorf("pclntab: data too small (%d bytes)", len(p.data))
}
p.magic = p.data[:4]
found := false
for _, m := range pclntabMagics {
if bytesEqual(p.magic, m) {
found = true
break
}
}
if !found {
return fmt.Errorf("pclntab: unrecognized magic: %x", p.magic)
}
p.ptrSize = p.data[6]
p.quantum = p.data[7]
if p.ptrSize == 0 || (p.ptrSize != 4 && p.ptrSize != 8) {
p.ptrSize = 8
}
if p.quantum == 0 {
p.quantum = 1
}
if bytesEqual(p.magic, pclntabMagics[2]) {
if len(p.data) >= 20 {
p.nfiles = binary.LittleEndian.Uint32(p.data[16:20])
}
}
p.valid = true
return nil
}
func scrambleMagic(data []byte) {
if len(data) < 4 {
return
}
var rnd [4]byte
_, _ = rand.Read(rnd[:])
for i := 0; i < 4; i++ {
data[i] = rnd[i]
}
}
func encryptNames(tab *pclntabParser, key byte) error {
if err := tab.parse(); err != nil {
return err
}
nameStart, nameEnd := findNameRegion(tab)
if nameStart == 0 || nameEnd == 0 || nameStart >= nameEnd {
return fmt.Errorf("pclntab: could not locate name region")
}
if nameEnd > uint32(len(tab.data)) {
nameEnd = uint32(len(tab.data))
}
for i := nameStart; i < nameEnd; i++ {
if tab.data[i] != 0 {
tab.data[i] ^= key
}
}
return nil
}
func findNameRegion(tab *pclntabParser) (uint32, uint32) {
data := tab.data
ptrSize := int(tab.ptrSize)
if bytesEqual(tab.magic, pclntabMagics[2]) {
if len(data) < 16+4+4*int(ptrSize) {
return 0, 0
}
offset := 16 + 4
_ = readUint(data, offset, ptrSize) // nfunc
offset += ptrSize
offset += ptrSize // nfiles (Go 1.18 already read above)
_ = readUint(data, offset, ptrSize) // textStart
offset += ptrSize
funcnametab := readUint(data, offset, ptrSize)
offset += ptrSize
_ = readUint(data, offset, ptrSize) // cutab
offset += ptrSize
_ = readUint(data, offset, ptrSize) // filetab
offset += ptrSize
_ = readUint(data, offset, ptrSize) // pctab
offset += ptrSize
pclntab := readUint(data, offset, ptrSize)
_ = pclntab
nameStart := uint32(funcnametab)
nameEnd := nameStart
for i := nameStart; i < uint32(len(data)); i++ {
if data[i] == 0 {
if i+1 < uint32(len(data)) && data[i+1] == 0 {
nameEnd = i
break
}
}
}
if nameEnd == nameStart {
nameEnd = uint32(len(data))
}
return nameStart, nameEnd
}
return findNameRegionLegacy(tab)
}
func findNameRegionLegacy(tab *pclntabParser) (uint32, uint32) {
data := tab.data
offset := 8 + 4 + 4
if bytesEqual(tab.magic, pclntabMagics[1]) {
offset += 4
}
if offset+8 > len(data) {
return 0, 0
}
nameOff := binary.LittleEndian.Uint32(data[offset : offset+4])
_ = binary.LittleEndian.Uint32(data[offset+4 : offset+8])
nameStart := nameOff
nameEnd := nameStart
for i := nameStart; i < uint32(len(data)); i++ {
if data[i] == 0 {
if i+1 < uint32(len(data)) && data[i+1] == 0 {
nameEnd = i
break
}
}
}
if nameEnd == nameStart {
nameEnd = uint32(len(data))
}
return nameStart, nameEnd
}
func injectFakeEntries(data []byte) []byte {
fakeEntries := make([]byte, 256)
_, _ = rand.Read(fakeEntries)
fakeNames := []string{
"runtime.fakeGoroutine",
"crypto/internal.init",
"main.processRequest",
"net/http.handleConn",
"google.golang.org/grpc.recvMsg",
"github.com/user/repo.processData",
}
offset := 0
for _, name := range fakeNames {
if offset+len(name)+1 > len(fakeEntries) {
break
}
copy(fakeEntries[offset:], name)
offset += len(name) + 1
}
return append(data, fakeEntries...)
}
func readUint(data []byte, offset int, size int) uint64 {
if offset+size > len(data) {
return 0
}
switch size {
case 4:
return uint64(binary.LittleEndian.Uint32(data[offset : offset+4]))
case 8:
return binary.LittleEndian.Uint64(data[offset : offset+8])
default:
return 0
}
}
func bytesEqual(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
+57
View File
@@ -0,0 +1,57 @@
//go:build linux
package pclntab
import (
"debug/elf"
"fmt"
)
func readPclntabSection(binaryPath string) ([]byte, error) {
f, err := elf.Open(binaryPath)
if err != nil {
return nil, fmt.Errorf("pclntab: failed to open ELF: %w", err)
}
defer f.Close()
sectionNames := []string{
".gopclntab",
"runtime.pclntab",
"runtime.pclntab_v1",
}
for _, name := range sectionNames {
section := f.Section(name)
if section != nil {
data, err := section.Data()
if err != nil {
return nil, fmt.Errorf("pclntab: failed to read section %s: %w", name, err)
}
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
for _, section := range f.Sections {
data, err := section.Data()
if err != nil {
continue
}
if len(data) >= 8 {
for _, magic := range pclntabMagics {
if len(data) >= len(magic) && bytesEqual(data[:len(magic)], magic) {
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
}
}
return nil, nil
}
func WriteCorruptedPclntab(binaryPath string, corrupted []byte, outputPath string) error {
return fmt.Errorf("pclntab: offline ELF patching not yet implemented; use runtime corruption instead")
}
+57
View File
@@ -0,0 +1,57 @@
//go:build darwin
package pclntab
import (
"debug/macho"
"fmt"
)
func readPclntabSection(binaryPath string) ([]byte, error) {
f, err := macho.Open(binaryPath)
if err != nil {
return nil, fmt.Errorf("pclntab: failed to open Mach-O: %w", err)
}
defer f.Close()
sectionNames := []string{
"__gopclntab",
"__pclntab",
}
for _, section := range f.Sections {
for _, name := range sectionNames {
if section.Name == name {
data, err := section.Data()
if err != nil {
return nil, fmt.Errorf("pclntab: failed to read section %s: %w", name, err)
}
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
}
for _, section := range f.Sections {
data, err := section.Data()
if err != nil {
continue
}
if len(data) >= 8 {
for _, magic := range pclntabMagics {
if len(data) >= len(magic) && bytesEqual(data[:len(magic)], magic) {
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
}
}
return nil, nil
}
func WriteCorruptedPclntab(binaryPath string, corrupted []byte, outputPath string) error {
return fmt.Errorf("pclntab: offline Mach-O patching not yet implemented; use runtime corruption instead")
}
+58
View File
@@ -0,0 +1,58 @@
//go:build windows
package pclntab
import (
"debug/pe"
"fmt"
)
func readPclntabSection(binaryPath string) ([]byte, error) {
f, err := pe.Open(binaryPath)
if err != nil {
return nil, fmt.Errorf("pclntab: failed to open PE: %w", err)
}
defer f.Close()
sectionNames := []string{
".gopclntab",
"runtime.pclntab",
"runtime.pclntab_v1",
}
for _, section := range f.Sections {
for _, name := range sectionNames {
if section.Name == name {
data, err := section.Data()
if err != nil {
return nil, fmt.Errorf("pclntab: failed to read section %s: %w", name, err)
}
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
}
for _, section := range f.Sections {
data, err := section.Data()
if err != nil {
continue
}
if len(data) >= 8 {
for _, magic := range pclntabMagics {
if len(data) >= len(magic) && bytesEqual(data[:len(magic)], magic) {
buf := make([]byte, len(data))
copy(buf, data)
return buf, nil
}
}
}
}
return nil, nil
}
func WriteCorruptedPclntab(binaryPath string, corrupted []byte, outputPath string) error {
return fmt.Errorf("pclntab: offline PE patching not yet implemented; use runtime corruption instead")
}
+33
View File
@@ -0,0 +1,33 @@
package pclntab
import (
"crypto/rand"
"fmt"
"os"
)
// RuntimeCorrupt finds the running binary's pclntab in memory and XOR-encrypts
// all name strings. Call this during init() — the Go runtime has already used
// pclntab to resolve function pointers, so the program keeps working but
// post-init reverse engineering tools see garbage names.
//
// WARNING: Degrades panic stack traces (function names become unreadable).
func RuntimeCorrupt(key byte) error {
if key == 0 {
var k [1]byte
if _, err := rand.Read(k[:]); err != nil {
return fmt.Errorf("pclntab: failed to generate key: %w", err)
}
if k[0] == 0 {
k[0] = 0x42
}
key = k[0]
}
selfPath, err := os.Executable()
if err != nil {
return fmt.Errorf("pclntab: cannot determine executable: %w", err)
}
return runtimeCorrupt(selfPath, key)
}
+116
View File
@@ -0,0 +1,116 @@
//go:build darwin
package pclntab
import (
"debug/macho"
"fmt"
"os"
"syscall"
)
func runtimeCorrupt(binaryPath string, key byte) error {
self, err := os.ReadFile(binaryPath)
if err != nil {
return fmt.Errorf("pclntab: cannot read binary: %w", err)
}
f, err := macho.NewFile(bytesReader(self))
if err != nil {
return fmt.Errorf("pclntab: cannot parse Mach-O from memory: %w", err)
}
sectionData, sectionAddr, sectionSize, err := findPclntabMacho(f)
if err != nil {
return err
}
nameStart, nameEnd := findMemoryNameRegion(sectionData)
if nameStart == 0 || nameEnd <= nameStart {
return fmt.Errorf("pclntab: could not locate name region")
}
pageSize := syscall.Getpagesize()
baseAddr := sectionAddr &^ uintptr(pageSize-1)
protSize := sectionSize + (sectionAddr - baseAddr)
if err := mprotectRW(baseAddr, protSize); err != nil {
return fmt.Errorf("pclntab: mprotect failed: %w", err)
}
nameSlice := ptrToSlice(sectionAddr+uintptr(nameStart), uintptr(nameEnd-nameStart))
for i := range nameSlice {
if nameSlice[i] != 0 {
nameSlice[i] ^= key
}
}
mprotectRO(baseAddr, protSize)
return nil
}
func findPclntabMacho(f *macho.File) ([]byte, uintptr, uintptr, error) {
sectionNames := []string{
"__gopclntab",
"__pclntab",
}
for _, sec := range f.Sections {
for _, name := range sectionNames {
if sec.Name == name {
data, err := sec.Data()
if err != nil {
continue
}
buf := make([]byte, len(data))
copy(buf, data)
return buf, uintptr(sec.Addr), uintptr(sec.Size), nil
}
}
}
for _, sec := range f.Sections {
data, err := sec.Data()
if err != nil {
continue
}
if len(data) >= 8 {
for _, magic := range pclntabMagics {
if len(data) >= len(magic) && bytesEqual(data[:len(magic)], magic) {
buf := make([]byte, len(data))
copy(buf, data)
return buf, uintptr(sec.Addr), uintptr(sec.Size), nil
}
}
}
}
return nil, 0, 0, fmt.Errorf("pclntab: __gopclntab section not found in memory")
}
func findMemoryNameRegion(data []byte) (uint32, uint32) {
tab := &pclntabParser{data: data}
if err := tab.parse(); err != nil {
return 0, 0
}
return findNameRegion(tab)
}
type bytesReaderWrapper struct {
data []byte
}
func bytesReader(data []byte) *bytesReaderWrapper {
return &bytesReaderWrapper{data: data}
}
func (r *bytesReaderWrapper) ReadAt(p []byte, off int64) (int, error) {
if off < 0 || int(off) >= len(r.data) {
return 0, fmt.Errorf("invalid offset")
}
n := copy(p, r.data[int(off):])
if n < len(p) {
return n, fmt.Errorf("EOF")
}
return n, nil
}
+133
View File
@@ -0,0 +1,133 @@
//go:build linux
package pclntab
import (
"debug/elf"
"fmt"
"os"
"syscall"
"unsafe"
)
func runtimeCorrupt(binaryPath string, key byte) error {
self, err := os.ReadFile("/proc/self/exe")
if err != nil {
return fmt.Errorf("pclntab: cannot read /proc/self/exe: %w", err)
}
f, err := elf.NewFile(bytesReader(self))
if err != nil {
return fmt.Errorf("pclntab: cannot parse ELF from memory: %w", err)
}
sectionData, sectionAddr, sectionSize, err := findPclntabELF(f)
if err != nil {
return err
}
nameStart, nameEnd := findMemoryNameRegion(sectionData)
if nameStart == 0 || nameEnd <= nameStart {
return fmt.Errorf("pclntab: could not locate name region")
}
baseAddr := sectionAddr &^ uintptr(syscall.Getpagesize()-1)
protSize := sectionSize + (sectionAddr - baseAddr)
if err := mprotectRW(baseAddr, protSize); err != nil {
return fmt.Errorf("pclntab: mprotect failed: %w", err)
}
basePtr := unsafe.Pointer(sectionAddr)
namePtr := unsafe.Add(basePtr, nameStart)
nameSlice := (*[1 << 30]byte)(namePtr)[: nameEnd-nameStart : nameEnd-nameStart]
for i := range nameSlice {
if nameSlice[i] != 0 {
nameSlice[i] ^= key
}
}
mprotectRO(baseAddr, protSize)
return nil
}
func findPclntabELF(f *elf.File) ([]byte, uintptr, uintptr, error) {
sectionNames := []string{
".gopclntab",
"runtime.pclntab",
"runtime.pclntab_v1",
}
for _, name := range sectionNames {
sec := f.Section(name)
if sec == nil {
continue
}
data, err := sec.Data()
if err != nil {
continue
}
buf := make([]byte, len(data))
copy(buf, data)
return buf, uintptr(sec.Addr), uintptr(sec.Size), nil
}
for _, sec := range f.Sections {
data, err := sec.Data()
if err != nil {
continue
}
if len(data) >= 8 {
for _, magic := range pclntabMagics {
if len(data) >= len(magic) && bytesEqual(data[:len(magic)], magic) {
buf := make([]byte, len(data))
copy(buf, data)
return buf, uintptr(sec.Addr), uintptr(sec.Size), nil
}
}
}
}
return nil, 0, 0, fmt.Errorf("pclntab: .gopclntab section not found in memory")
}
func mprotectRW(addr uintptr, size uintptr) error {
return syscall.Mprotect(
(*[1]byte)(unsafe.Pointer(addr))[:size:size],
syscall.PROT_READ|syscall.PROT_WRITE,
)
}
func mprotectRO(addr uintptr, size uintptr) {
_ = syscall.Mprotect(
(*[1]byte)(unsafe.Pointer(addr))[:size:size],
syscall.PROT_READ,
)
}
func findMemoryNameRegion(data []byte) (uint32, uint32) {
tab := &pclntabParser{data: data}
if err := tab.parse(); err != nil {
return 0, 0
}
return findNameRegion(tab)
}
type bytesReaderWrapper struct {
data []byte
}
func bytesReader(data []byte) *bytesReaderWrapper {
return &bytesReaderWrapper{data: data}
}
func (r *bytesReaderWrapper) ReadAt(p []byte, off int64) (int, error) {
if off < 0 || int(off) >= len(r.data) {
return 0, fmt.Errorf("invalid offset")
}
n := copy(p, r.data[int(off):])
if n < len(p) {
return n, fmt.Errorf("EOF")
}
return n, nil
}
+35
View File
@@ -0,0 +1,35 @@
//go:build darwin || linux
package pclntab
import (
"syscall"
"unsafe"
)
// mprotectRW makes a memory region read-write using an unsafe.Pointer conversion
// from a page-aligned uintptr. This is intentional for runtime memory manipulation.
//
//go:nosplit
func mprotectRW(addr uintptr, size uintptr) error {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
return syscall.Mprotect(
unsafe.Slice((*byte)(p), size),
syscall.PROT_READ|syscall.PROT_WRITE,
)
}
//go:nosplit
func mprotectRO(addr uintptr, size uintptr) {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
_ = syscall.Mprotect(
unsafe.Slice((*byte)(p), size),
syscall.PROT_READ,
)
}
//go:nosplit
func ptrToSlice(addr uintptr, size uintptr) []byte {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
return unsafe.Slice((*byte)(p), size)
}
+9
View File
@@ -0,0 +1,9 @@
//go:build windows
package pclntab
import "fmt"
func runtimeCorrupt(binaryPath string, key byte) error {
return fmt.Errorf("pclntab: runtime corruption not supported on windows")
}
+51
View File
@@ -0,0 +1,51 @@
package typewipe
import (
"crypto/rand"
"fmt"
"runtime"
)
// WipeOptions controls what metadata to destroy.
type WipeOptions struct {
WipeTypelink bool // Destroy .typelink section (type linkage info)
WipeItab bool // Destroy itab entries (interface method tables)
WipeTypeNames bool // XOR-encrypt type name strings in ._type structs
Key byte // XOR key (0 = random)
}
// WipeAtInit should be called during init(). It finds and destroys
// Go type metadata in the running binary's memory.
// WARNING: This degrades reflection and panic output.
func WipeAtInit(opts WipeOptions) error {
if opts.Key == 0 {
var k [1]byte
if _, err := rand.Read(k[:]); err != nil {
return fmt.Errorf("typewipe: failed to generate key: %w", err)
}
if k[0] == 0 {
k[0] = 0x5A
}
opts.Key = k[0]
}
if runtime.GOOS == "windows" {
return fmt.Errorf("typewipe: not supported on windows")
}
return wipePlatform(opts)
}
func xorSlice(data []byte, key byte) {
for i := range data {
if data[i] != 0 {
data[i] ^= key
}
}
}
func zeroSlice(data []byte) {
for i := range data {
data[i] = 0
}
}
+250
View File
@@ -0,0 +1,250 @@
//go:build darwin
package typewipe
import (
"debug/macho"
"fmt"
"os"
"syscall"
)
type wipeType int
const (
wipeZero wipeType = iota
wipeXOR
)
type wipeTarget struct {
name string
addr uintptr
size uintptr
xtype wipeType
}
func wipePlatform(opts WipeOptions) error {
selfPath, err := os.Executable()
if err != nil {
return fmt.Errorf("typewipe: cannot determine executable: %w", err)
}
self, err := os.ReadFile(selfPath)
if err != nil {
return fmt.Errorf("typewipe: cannot read binary: %w", err)
}
return wipeFromMacho(self, opts)
}
func wipeFromMacho(raw []byte, opts WipeOptions) error {
f, err := macho.NewFile(newBytesReader(raw))
if err != nil {
return fmt.Errorf("typewipe: cannot parse Mach-O: %w", err)
}
var targets []wipeTarget
if opts.WipeTypelink {
for _, sec := range f.Sections {
if sec.Name == "__typelink" {
targets = append(targets, wipeTarget{
name: "__typelink",
addr: uintptr(sec.Addr),
size: uintptr(sec.Size),
xtype: wipeZero,
})
break
}
}
}
if opts.WipeItab {
for _, sec := range f.Sections {
if sec.Name == "__itablink" || sec.Name == "__itab" {
targets = append(targets, wipeTarget{
name: sec.Name,
addr: uintptr(sec.Addr),
size: uintptr(sec.Size),
xtype: wipeXOR,
})
break
}
}
}
if opts.WipeTypeNames {
for _, sec := range f.Sections {
if sec.Name == "__gopclntab" || sec.Name == "__pclntab" {
data, err := sec.Data()
if err != nil {
continue
}
nameStart, nameEnd := findTypeNameRegion(data)
if nameStart > 0 && nameEnd > nameStart {
targets = append(targets, wipeTarget{
name: sec.Name + ":names",
addr: uintptr(sec.Addr) + uintptr(nameStart),
size: uintptr(nameEnd - nameStart),
xtype: wipeXOR,
})
}
break
}
}
}
if len(targets) == 0 {
return fmt.Errorf("typewipe: no target sections found")
}
for _, t := range targets {
if err := wipeSection(t, opts.Key); err != nil {
return fmt.Errorf("typewipe: failed to wipe %s: %w", t.name, err)
}
}
return nil
}
func wipeSection(target wipeTarget, key byte) error {
pageSize := uintptr(syscall.Getpagesize())
baseAddr := target.addr &^ (pageSize - 1)
protSize := target.size + (target.addr - baseAddr)
if err := mprotectRW(baseAddr, protSize); err != nil {
return fmt.Errorf("mprotect RW failed: %w", err)
}
sectionSlice := ptrToSlice(target.addr, target.size)
switch target.xtype {
case wipeZero:
zeroSlice(sectionSlice)
case wipeXOR:
xorSlice(sectionSlice, key)
}
_ = mprotectRO(baseAddr, protSize)
return nil
}
func findTypeNameRegion(data []byte) (uint32, uint32) {
magics := [][]byte{
{0xF1, 0xFF, 0xFF, 0xFA},
{0xF1, 0xFF, 0xFF, 0xF0},
{0xF1, 0xFF, 0xFF, 0xF1},
}
if len(data) < 8 {
return 0, 0
}
valid := false
for _, m := range magics {
if len(data) >= len(m) && bytesEqualLocal(data[:len(m)], m) {
valid = true
break
}
}
if !valid {
return 0, 0
}
ptrSize := int(data[6])
if ptrSize != 4 && ptrSize != 8 {
ptrSize = 8
}
isGo118 := bytesEqualLocal(data[:4], magics[2])
if isGo118 {
if len(data) < 16+4+6*ptrSize {
return 0, 0
}
offset := 16 + 4
offset += ptrSize // nfunc
offset += ptrSize // nfiles
offset += ptrSize // textStart
funcnametab := readUintLocal(data, offset, ptrSize)
nameStart := funcnametab
if nameStart >= uint32(len(data)) {
return 0, 0
}
nameEnd := findDoubleNull(data, nameStart)
return nameStart, nameEnd
}
offset := 8 + 4 + 4
if bytesEqualLocal(data[:4], magics[1]) {
offset += 4
}
if offset+8 > len(data) {
return 0, 0
}
nameStart := readUintLocal(data, offset, 4)
if nameStart >= uint32(len(data)) {
return 0, 0
}
nameEnd := findDoubleNull(data, nameStart)
return nameStart, nameEnd
}
func findDoubleNull(data []byte, start uint32) uint32 {
for i := start; i < uint32(len(data)); i++ {
if data[i] == 0 && i+1 < uint32(len(data)) && data[i+1] == 0 {
return i
}
}
return uint32(len(data))
}
func readUintLocal(data []byte, offset int, size int) uint32 {
if offset+size > len(data) {
return 0
}
switch size {
case 4:
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
case 8:
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
default:
return 0
}
}
func bytesEqualLocal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
type localBytesReader struct {
data []byte
}
func newBytesReader(data []byte) *localBytesReader {
return &localBytesReader{data: data}
}
func (r *localBytesReader) ReadAt(p []byte, off int64) (int, error) {
if off < 0 || int(off) >= len(r.data) {
return 0, fmt.Errorf("invalid offset")
}
n := copy(p, r.data[int(off):])
if n < len(p) {
return n, fmt.Errorf("EOF")
}
return n, nil
}
+254
View File
@@ -0,0 +1,254 @@
//go:build linux
package typewipe
import (
"debug/elf"
"fmt"
"os"
"syscall"
"unsafe"
)
type wipeType int
const (
wipeZero wipeType = iota
wipeXOR
)
type wipeTarget struct {
name string
addr uintptr
size uintptr
xtype wipeType
}
func wipePlatform(opts WipeOptions) error {
self, err := os.ReadFile("/proc/self/exe")
if err != nil {
return fmt.Errorf("typewipe: cannot read /proc/self/exe: %w", err)
}
return wipeFromELF(self, opts)
}
func wipeFromELF(raw []byte, opts WipeOptions) error {
f, err := elf.NewFile(newBytesReader(raw))
if err != nil {
return fmt.Errorf("typewipe: cannot parse ELF: %w", err)
}
var targets []wipeTarget
if opts.WipeTypelink {
sec := f.Section(".typelink")
if sec != nil {
targets = append(targets, wipeTarget{
name: ".typelink",
addr: uintptr(sec.Addr),
size: uintptr(sec.Size),
xtype: wipeZero,
})
}
}
if opts.WipeItab {
for _, name := range []string{".itablink", ".itab"} {
sec := f.Section(name)
if sec != nil {
targets = append(targets, wipeTarget{
name: name,
addr: uintptr(sec.Addr),
size: uintptr(sec.Size),
xtype: wipeXOR,
})
break
}
}
}
if opts.WipeTypeNames {
for _, name := range []string{".gopclntab", "runtime.pclntab", "runtime.pclntab_v1"} {
sec := f.Section(name)
if sec == nil {
continue
}
data, err := sec.Data()
if err != nil {
continue
}
nameStart, nameEnd := findTypeNameRegion(data)
if nameStart > 0 && nameEnd > nameStart {
targets = append(targets, wipeTarget{
name: name + ":names",
addr: uintptr(sec.Addr) + uintptr(nameStart),
size: uintptr(nameEnd - nameStart),
xtype: wipeXOR,
})
}
break
}
}
if len(targets) == 0 {
return fmt.Errorf("typewipe: no target sections found")
}
for _, t := range targets {
if err := wipeSection(t, opts.Key); err != nil {
return fmt.Errorf("typewipe: failed to wipe %s: %w", t.name, err)
}
}
return nil
}
func wipeSection(target wipeTarget, key byte) error {
pageSize := uintptr(syscall.Getpagesize())
baseAddr := target.addr &^ (pageSize - 1)
protSize := target.size + (target.addr - baseAddr)
if err := syscall.Mprotect(
(*[1]byte)(unsafe.Pointer(baseAddr))[:protSize:protSize],
syscall.PROT_READ|syscall.PROT_WRITE,
); err != nil {
return fmt.Errorf("mprotect RW failed: %w", err)
}
ptr := unsafe.Pointer(target.addr)
sectionSlice := (*[1 << 30]byte)(ptr)[:target.size:target.size]
switch target.xtype {
case wipeZero:
zeroSlice(sectionSlice)
case wipeXOR:
xorSlice(sectionSlice, key)
}
_ = syscall.Mprotect(
(*[1]byte)(unsafe.Pointer(baseAddr))[:protSize:protSize],
syscall.PROT_READ,
)
return nil
}
func findTypeNameRegion(data []byte) (uint32, uint32) {
magics := [][]byte{
{0xF1, 0xFF, 0xFF, 0xFA},
{0xF1, 0xFF, 0xFF, 0xF0},
{0xF1, 0xFF, 0xFF, 0xF1},
}
if len(data) < 8 {
return 0, 0
}
valid := false
for _, m := range magics {
if len(data) >= len(m) && bytesEqualLocal(data[:len(m)], m) {
valid = true
break
}
}
if !valid {
return 0, 0
}
ptrSize := int(data[6])
if ptrSize != 4 && ptrSize != 8 {
ptrSize = 8
}
isGo118 := bytesEqualLocal(data[:4], magics[2])
if isGo118 {
if len(data) < 16+4+6*ptrSize {
return 0, 0
}
offset := 16 + 4
offset += ptrSize // nfunc
offset += ptrSize // nfiles
offset += ptrSize // textStart
funcnametab := readUintLocal(data, offset, ptrSize)
nameStart := funcnametab
if nameStart >= uint32(len(data)) {
return 0, 0
}
nameEnd := findDoubleNull(data, nameStart)
return nameStart, nameEnd
}
offset := 8 + 4 + 4
if bytesEqualLocal(data[:4], magics[1]) {
offset += 4
}
if offset+8 > len(data) {
return 0, 0
}
nameStart := readUintLocal(data, offset, 4)
if nameStart >= uint32(len(data)) {
return 0, 0
}
nameEnd := findDoubleNull(data, nameStart)
return nameStart, nameEnd
}
func findDoubleNull(data []byte, start uint32) uint32 {
for i := start; i < uint32(len(data)); i++ {
if data[i] == 0 && i+1 < uint32(len(data)) && data[i+1] == 0 {
return i
}
}
return uint32(len(data))
}
func readUintLocal(data []byte, offset int, size int) uint32 {
if offset+size > len(data) {
return 0
}
switch size {
case 4:
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
case 8:
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
default:
return 0
}
}
func bytesEqualLocal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
type localBytesReader struct {
data []byte
}
func newBytesReader(data []byte) *localBytesReader {
return &localBytesReader{data: data}
}
func (r *localBytesReader) ReadAt(p []byte, off int64) (int, error) {
if off < 0 || int(off) >= len(r.data) {
return 0, fmt.Errorf("invalid offset")
}
n := copy(p, r.data[int(off):])
if n < len(p) {
return n, fmt.Errorf("EOF")
}
return n, nil
}
+32
View File
@@ -0,0 +1,32 @@
//go:build darwin || linux
package typewipe
import (
"syscall"
"unsafe"
)
//go:nosplit
func mprotectRW(addr uintptr, size uintptr) error {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
return syscall.Mprotect(
unsafe.Slice((*byte)(p), size),
syscall.PROT_READ|syscall.PROT_WRITE,
)
}
//go:nosplit
func mprotectRO(addr uintptr, size uintptr) error {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
return syscall.Mprotect(
unsafe.Slice((*byte)(p), size),
syscall.PROT_READ,
)
}
//go:nosplit
func ptrToSlice(addr uintptr, size uintptr) []byte {
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
return unsafe.Slice((*byte)(p), size)
}
+9
View File
@@ -0,0 +1,9 @@
//go:build windows
package typewipe
import "fmt"
func wipePlatform(opts WipeOptions) error {
return fmt.Errorf("typewipe: runtime wiping not supported on windows")
}