mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-14 05:48:22 +00:00
.
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build darwin
|
||||
|
||||
package antiemul
|
||||
|
||||
func init() {
|
||||
platformCheck = func() bool {
|
||||
return checkTiming()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package antisandbox
|
||||
|
||||
func Check() bool {
|
||||
return CheckNetwork()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user