This commit is contained in:
2026-06-30 09:31:33 +07:00
parent c736326162
commit bbf8336664
77 changed files with 12601 additions and 556 deletions

View File

@@ -0,0 +1,317 @@
package blocker
import (
"log"
"os"
"strings"
"sync"
"syscall"
"time"
"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")
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
}
type Blocker struct {
mu sync.Mutex
allowedKeywords []string
running bool
stopChan chan struct{}
OnBlocked func(procName string, title string)
OnKill func(procName string, title string)
}
var Instance = &Blocker{
allowedKeywords: []string{"chrome", "idea64", "vscode", "wails", "simple_care", "client"},
stopChan: make(chan struct{}),
}
func getProcessMap() (map[uint32]string, error) {
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return 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, err
}
pm := make(map[uint32]string)
for {
name := syscall.UTF16ToString(pe.ExeFile[:])
pm[pe.ProcessID] = name
err = syscall.Process32Next(snapshot, &pe)
if err != nil {
break
}
}
return pm, 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
)
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
}
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, error) {
pMap, err := getProcessMap()
if err != nil {
return nil, err
}
enumWindowsMutex.Lock()
defer enumWindowsMutex.Unlock()
enumWindowsList = make([]WindowInfo, 0, 100)
enumProcessMap = pMap
procEnumWindows.Call(enumWindowsCallback, 0)
// Clean up map reference so GC can reclaim it
enumProcessMap = nil
// Copy to a new slice to return safely
res := make([]WindowInfo, len(enumWindowsList))
copy(res, enumWindowsList)
return res, nil
}
func (b *Blocker) SetKeywords(keywords string) {
b.mu.Lock()
defer b.mu.Unlock()
parts := strings.Split(keywords, ",")
var list []string
for _, p := range parts {
trimmed := strings.TrimSpace(strings.ToLower(p))
if trimmed != "" {
list = append(list, trimmed)
}
}
b.allowedKeywords = list
log.Printf("[BLOCKER] Keywords updated: %v", b.allowedKeywords)
}
var systemAllowed = map[string]bool{
"explorer.exe": true,
"taskmgr.exe": true,
"cmd.exe": true,
"powershell.exe": true,
"conhost.exe": true,
"client.exe": true, // App Wails của ta
"wails.exe": true,
"msedgewebview2.exe": true, // WebView2 runtime của Wails
"code.exe": true, // VSCode
"cursor.exe": true, // Cursor
"windsurf.exe": true, // Windsurf
"goland.exe": true, // GoLand
"goland64.exe": true, // GoLand
"idea64.exe": true, // IntelliJ IDEA
"clion64.exe": true, // CLion
"webstorm64.exe": true, // WebStorm
"pycharm64.exe": true, // PyCharm
"rider64.exe": true, // Rider
"studio64.exe": true, // Android Studio
"eclipse.exe": true, // Eclipse
"sublime_text.exe": true, // Sublime Text
"notepad++.exe": true, // Notepad++
"devenv.exe": true, // Visual Studio
"git-bash.exe": true, // Git Bash
"bash.exe": true, // Bash
}
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
}
windows, err := EnumerateGUIWindows()
if err != nil {
return
}
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
if w.PID == uint32(os.Getpid()) || systemAllowed[pNameLower] || strings.Contains(pNameLower, "antigravity") || strings.Contains(wTitleLower, "antigravity") {
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 strings.Contains(pNameLower, kw) || strings.Contains(wTitleLower, kw) {
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)
}
}
}
}
}
func (b *Blocker) Start() {
b.mu.Lock()
if b.running {
b.mu.Unlock()
return
}
b.running = true
b.stopChan = make(chan struct{})
b.mu.Unlock()
go func() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
b.checkAndKill()
case <-b.stopChan:
return
}
}
}()
log.Println("[BLOCKER] Application Blocker Daemon Started.")
}
func (b *Blocker) Stop() {
b.mu.Lock()
defer b.mu.Unlock()
if !b.running {
return
}
b.running = false
close(b.stopChan)
log.Println("[BLOCKER] Application Blocker Daemon Stopped.")
}

View File

@@ -0,0 +1,35 @@
package screen
import (
"bytes"
"encoding/base64"
"fmt"
"image/jpeg"
"github.com/kbinani/screenshot"
)
// CaptureScreen chụp màn hình chính và trả về chuỗi Base64 dạng "data:image/jpeg;base64,..."
func CaptureScreen() (string, error) {
n := screenshot.NumActiveDisplays()
if n <= 0 {
return "", fmt.Errorf("no active displays found")
}
// Chụp màn hình chính (index 0)
bounds := screenshot.GetDisplayBounds(0)
img, err := screenshot.CaptureRect(bounds)
if err != nil {
return "", fmt.Errorf("failed to capture screen: %w", err)
}
var buf bytes.Buffer
// Nén chất lượng JPEG khoảng 50% để truyền tải mượt mà qua mạng
err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 50})
if err != nil {
return "", fmt.Errorf("failed to encode jpeg: %w", err)
}
encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
return "data:image/jpeg;base64," + encoded, nil
}

View File

@@ -0,0 +1,82 @@
package winapi
import (
"bytes"
"os/exec"
"strings"
"syscall"
)
// WifiConnection — SSID + BSSID (MAC) của điểm phát đang kết nối
type WifiConnection struct {
SSID string
BSSID string
}
// GetWifiSSID trả về SSID Wifi đang kết nối
func GetWifiSSID() string {
return GetWifiConnection().SSID
}
// GetWifiConnection đọc SSID và BSSID từ netsh (Windows)
func GetWifiConnection() WifiConnection {
cmd := exec.Command("netsh", "wlan", "show", "interfaces")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return WifiConnection{}
}
var conn WifiConnection
for _, line := range strings.Split(out.String(), "\n") {
trimmed := strings.TrimSpace(line)
lower := strings.ToLower(trimmed)
if strings.HasPrefix(lower, "ssid") && !strings.Contains(lower, "bssid") {
if v := valueAfterColon(trimmed); v != "" {
conn.SSID = v
}
}
// Windows: "AP BSSID" (EN) hoặc dòng có chứa "bssid" (locale khác)
if strings.Contains(lower, "bssid") {
if v := valueAfterColon(trimmed); v != "" {
conn.BSSID = normalizeMAC(v)
}
}
}
return conn
}
func valueAfterColon(line string) string {
idx := strings.Index(line, ":")
if idx < 0 {
return ""
}
return strings.TrimSpace(line[idx+1:])
}
func normalizeMAC(mac string) string {
var hex []rune
for _, c := range strings.ToLower(mac) {
if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') {
hex = append(hex, c)
}
}
if len(hex) != 12 {
return strings.TrimSpace(mac)
}
return string(hex[0:2]) + ":" + string(hex[2:4]) + ":" + string(hex[4:6]) + ":" +
string(hex[6:8]) + ":" + string(hex[8:10]) + ":" + string(hex[10:12])
}
// BSSIDKey chuẩn hóa MAC để so khớp (12 ký tự hex)
func BSSIDKey(bssid string) string {
var hex []rune
for _, c := range strings.ToLower(bssid) {
if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') {
hex = append(hex, c)
}
}
return string(hex)
}