//go:build windows package blocker import ( "log" "os" "path/filepath" "strings" "sync" "syscall" "unsafe" ) var ( user32 = syscall.NewLazyDLL("user32.dll") procEnumWindows = user32.NewProc("EnumWindows") procIsWindowVisible = user32.NewProc("IsWindowVisible") procGetWindowTextW = user32.NewProc("GetWindowTextW") procGetWindowTextLengthW = user32.NewProc("GetWindowTextLengthW") procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId") procGetWindow = user32.NewProc("GetWindow") procGetWindowLongW = user32.NewProc("GetWindowLongW") procGetAncestor = user32.NewProc("GetAncestor") dwmapi = syscall.NewLazyDLL("dwmapi.dll") procDwmGetWindowAttribute = dwmapi.NewProc("DwmGetWindowAttribute") kernel32 = syscall.NewLazyDLL("kernel32.dll") ) const ( GW_OWNER = 4 WS_EX_TOOLWINDOW = 0x00000080 DWMWA_CLOAKED = 14 ) func isRealGUIWindow(hwnd uintptr) bool { // 1. Phải đang hiển thị ret, _, _ := procIsWindowVisible.Call(hwnd) if ret == 0 { return false } // 2. Không được có chủ sở hữu (phải là cửa sổ chính - top-level) owner, _, _ := procGetWindow.Call(hwnd, GW_OWNER) if owner != 0 { return false } // 3. Không phải là tool window (WS_EX_TOOLWINDOW) gwlExStyle := int32(-20) // GWL_EXSTYLE = -20 style, _, _ := procGetWindowLongW.Call(hwnd, uintptr(gwlExStyle)) if (style & WS_EX_TOOLWINDOW) != 0 { return false } // 4. Không bị cloaked bởi DWM (ví dụ: app UWP bị treo/chạy ngầm, màn hình ảo) var cloaked uint32 hr, _, _ := procDwmGetWindowAttribute.Call(hwnd, DWMWA_CLOAKED, uintptr(unsafe.Pointer(&cloaked)), 4) if hr == 0 && cloaked != 0 { return false } return true } type WindowInfo struct { PID uint32 Title string ProcessName string } func getProcessMap() (map[uint32]string, map[uint32]uint32, error) { snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) if err != nil { return nil, nil, err } defer syscall.CloseHandle(snapshot) var pe syscall.ProcessEntry32 pe.Size = uint32(unsafe.Sizeof(pe)) err = syscall.Process32First(snapshot, &pe) if err != nil { return nil, nil, err } pm := make(map[uint32]string) parents := make(map[uint32]uint32) for { name := syscall.UTF16ToString(pe.ExeFile[:]) pm[pe.ProcessID] = name parents[pe.ProcessID] = pe.ParentProcessID err = syscall.Process32Next(snapshot, &pe) if err != nil { break } } return pm, parents, nil } func getWindowText(hwnd uintptr) string { length, _, _ := procGetWindowTextLengthW.Call(hwnd) if length == 0 { return "" } buf := make([]uint16, length+1) procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), length+1) return syscall.UTF16ToString(buf) } var ( enumWindowsMutex sync.Mutex enumWindowsList []WindowInfo enumProcessMap map[uint32]string enumParentMap map[uint32]uint32 ) var enumWindowsCallback = syscall.NewCallback(func(hwnd uintptr, lParam uintptr) uintptr { defer func() { if r := recover(); r != nil { log.Printf("[BLOCKER] Callback panic recovered: %v", r) } }() if !isRealGUIWindow(hwnd) { return 1 } // Kiểm tra xem chủ sở hữu gốc (root owner) của cửa sổ này có thuộc về app ta hay không rootHwnd, _, _ := procGetAncestor.Call(hwnd, 3) // GA_ROOTOWNER = 3 if rootHwnd != 0 { var rootPid uint32 procGetWindowThreadProcessId.Call(rootHwnd, uintptr(unsafe.Pointer(&rootPid))) if rootPid == uint32(os.Getpid()) { return 1 // Cửa sổ thuộc về WebView2 / app của ta, bỏ qua không quét } } title := getWindowText(hwnd) if title == "" { return 1 } var pid uint32 procGetWindowThreadProcessId.Call(hwnd, uintptr(unsafe.Pointer(&pid))) procName := enumProcessMap[pid] if procName == "" { procName = "Unknown" } enumWindowsList = append(enumWindowsList, WindowInfo{ PID: pid, Title: title, ProcessName: procName, }) return 1 }) func EnumerateGUIWindows() ([]WindowInfo, map[uint32]uint32, error) { pMap, parentMap, err := getProcessMap() if err != nil { return nil, nil, err } enumWindowsMutex.Lock() defer enumWindowsMutex.Unlock() enumWindowsList = make([]WindowInfo, 0, 100) enumProcessMap = pMap enumParentMap = parentMap procEnumWindows.Call(enumWindowsCallback, 0) // Clean up map reference so GC can reclaim it enumProcessMap = nil enumParentMap = nil // Copy to a new slice to return safely res := make([]WindowInfo, len(enumWindowsList)) copy(res, enumWindowsList) return res, parentMap, nil } var systemAllowed = map[string]bool{ // ── Windows Shell & Explorer ─────────────────────────────────────────────── "explorer.exe": true, // Windows Explorer / desktop shell "shellexperiencehost.exe": true, // Start menu, Action Center shell "startmenuexperiencehost.exe": true, // Start menu host "searchhost.exe": true, // Windows Search UI "searchapp.exe": true, // Windows Search (older) "searchindexer.exe": true, "sihost.exe": true, // Shell Infrastructure Host (taskbar, notification icons) "taskhostw.exe": true, // Task Host Window "taskbar.exe": true, "lockapp.exe": true, // Lock screen "logonui.exe": true, // Login/Logon UI "winlogon.exe": true, // Windows Logon Process "userinit.exe": true, "dwm.exe": true, // Desktop Window Manager — kill = black screen "csrss.exe": true, // Client Server Runtime — kill = BSOD // ── UWP / Modern App Infrastructure ─────────────────────────────────────── "applicationframehost.exe": true, // Host for ALL UWP apps (Camera, Calculator, Photos...) "runtimebroker.exe": true, // UWP permission broker "backgroundtaskhost.exe": true, // UWP background tasks "wwahost.exe": true, // Web app host "microsoftedgecp.exe": true, // Edge content process (older) "textinputhost.exe": true, // Touch keyboard / handwriting panel // ── Input Methods & Language ─────────────────────────────────────────────── "ctfmon.exe": true, // Collaborative Translation Framework — input method manager "chsime.exe": true, // Chinese IME "imetip.exe": true, // IME tip "imecmnt.exe": true, "googlepinyin.exe": true, "baidu.exe": true, // Baidu IME "openkey.exe": true, // OpenKey Vietnamese IME "openkey64.exe": true, "gotiengviet.exe": true, // GoTiengViet "unikeyvnt.exe": true, // Unikey "unikey.exe": true, // ── Security / Credential / UAC ─────────────────────────────────────────── "consent.exe": true, // UAC consent dialog — kill breaks all elevation "credentialuibroker.exe": true, // Credential UI "smartscreen.exe": true, // Windows SmartScreen "securityhealthsystray.exe": true, // Windows Security tray icon "securityhealthservice.exe": true, "wscsvc.exe": true, "msseces.exe": true, // Microsoft Security Essentials tray "msmpeng.exe": true, // Windows Defender engine (has no GUI but safety) "nisSrv.exe": true, "antimalware service executable": true, // Window title of Defender // ── Notifications & Action Center ───────────────────────────────────────── "notificationplatformcontroller": true, // ── System Tray / Taskbar helpers ───────────────────────────────────────── "systemsettings.exe": true, // Windows Settings "settingssynchostservice.exe": true, "settingssynchost.exe": true, "regsvc.exe": true, "spoolsv.exe": true, "tabtip.exe": true, // Touch keyboard "tabtip32.exe": true, "onedrive.exe": true, // OneDrive tray (common, safe to keep) "onedriveupdater.exe": true, // ── Accessibility ───────────────────────────────────────────────────────── "narrator.exe": true, "magnify.exe": true, "osk.exe": true, // On-Screen Keyboard // ── Audio ───────────────────────────────────────────────────────────────── "audiodg.exe": true, // Windows Audio Device Graph — kill = no sound "sndvol.exe": true, // Volume mixer "cmediaaudiocontrolpanel.exe": true, // C-Media audio panel // ── Windows Update / Store ───────────────────────────────────────────────── "wuauclt.exe": true, // Windows Update "musnotifyicon.exe": true, // Update tray notification "windowsstore.exe": true, "winstore.app.exe": true, // ── Drivers / Hardware UI ────────────────────────────────────────────────── "nvdisplay.container.exe": true, // NVIDIA display container "nvcontainer.exe": true, "nvinject.exe": true, "nvtelemetrycontainer.exe": true, "nvvsvc.exe": true, "nvspcaps64.exe": true, "geforce experience.exe": true, "radeonsoftware.exe": true, // AMD Radeon Software "amddvr.exe": true, "igfxtray.exe": true, // Intel graphics tray "igfxem.exe": true, "igfxhk.exe": true, "hkcmd.exe": true, "atk hub.exe": true, // ASUS ATK "atkex.exe": true, "asusoptimization.exe": true, // ── Antivirus / endpoint security (from app_pool + common) ──────────────── "avastui.exe": true, "avgui.exe": true, "mbam.exe": true, // Malwarebytes "mbamtray.exe": true, "bdagent.exe": true, // Bitdefender "bdwtxag.exe": true, // Bitdefender widget agent "uiseagnt.exe": true, // Trend Micro "eguiproxy.exe": true, // ESET proxy "egui.exe": true, // ESET NOD32 GUI (app_pool) "microsoftsecurityapp.exe": true, // Microsoft Defender app (app_pool) "msascuil.exe": true, // Defender notification icon "pickerhost.exe": true, // Windows Security picker (app_pool) "seccenter.exe": true, // Security center dialogs (app_pool) "mc-web-view.exe": true, // McAfee web view (app_pool) "rsappui.exe": true, // RAV Endpoint Protection (app_pool) "avpui.exe": true, // Kaspersky UI "ksdeui.exe": true, // Kaspersky Secure Connection "n360.exe": true, // Norton 360 "nortonsecurity.exe": true, "sophos ui.exe": true, // Sophos UI // ── Task Manager & System tools ──────────────────────────────────────────── "taskmgr.exe": true, "resmon.exe": true, // Resource Monitor "perfmon.exe": true, "mmc.exe": true, // Management Console // ── Terminal / Shell ─────────────────────────────────────────────────────── "cmd.exe": true, "powershell.exe": true, "pwsh.exe": true, // PowerShell Core "conhost.exe": true, // Console Host "windowsterminal.exe": true, // Windows Terminal "wt.exe": true, "bash.exe": true, "git-bash.exe": true, "mintty.exe": true, // Git Bash window "wsl.exe": true, "wslhost.exe": true, // ── Our app + IDE/dev tools ──────────────────────────────────────────────── "client.exe": true, "simple_care_v1.0.exe": true, "simple_care_v1.1.exe": true, "simple_care.exe": true, "wails.exe": true, "msedgewebview2.exe": true, // WebView2 runtime (Wails renderer) "code.exe": true, "cursor.exe": true, "windsurf.exe": true, "goland.exe": true, "goland64.exe": true, "idea64.exe": true, "clion64.exe": true, "webstorm64.exe": true, "pycharm64.exe": true, "rider64.exe": true, "studio64.exe": true, "eclipse.exe": true, "sublime_text.exe": true, "notepad++.exe": true, "devenv.exe": true, } func isDescendant(pid, targetPid uint32, parentMap map[uint32]uint32) bool { curr := pid for i := 0; i < 16; i++ { parent, ok := parentMap[curr] if !ok || parent == 0 { return false } if parent == targetPid { return true } curr = parent } return false } func (b *Blocker) checkAndKill() { b.mu.Lock() keywords := make([]string, len(b.allowedKeywords)) copy(keywords, b.allowedKeywords) b.mu.Unlock() // Nếu không cấu hình keyword thì không chặn gì cả if len(keywords) == 0 { return } currentExec := "" if execPath, err := os.Executable(); err == nil { currentExec = strings.ToLower(filepath.Base(execPath)) } windows, parentMap, err := EnumerateGUIWindows() if err != nil { return } myPid := uint32(os.Getpid()) for _, w := range windows { pNameLower := strings.ToLower(w.ProcessName) wTitleLower := strings.ToLower(w.Title) // 1. Luôn cho phép hệ thống/app cốt lõi hoặc chính tiến trình này (bao gồm tiến trình con/cháu, và đổi tên) isOurApp := w.PID == myPid || isDescendant(w.PID, myPid, parentMap) if isOurApp || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] { continue } // 2. Kiểm tra xem có chứa bất kỳ từ khóa nào được cho phép không allowed := false for _, kw := range keywords { if matchesAllowedKeyword(kw, pNameLower, wTitleLower) { allowed = true break } } // 3. Nếu không nằm trong whitelist, tắt ứng dụng if !allowed { if b.OnBlocked != nil { b.OnBlocked(w.ProcessName, w.Title) } log.Printf("[BLOCKER] KILLED unauthorized application: %s (PID: %d, Title: %s)", w.ProcessName, w.PID, w.Title) h, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, w.PID) if err == nil { errTerm := syscall.TerminateProcess(h, 0) _ = syscall.CloseHandle(h) if errTerm == nil && b.OnKill != nil { b.OnKill(w.ProcessName, w.Title) } } } } }