@yufengtadian/freedom-cli 1.13.1 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +40 -0
- package/README.md +119 -15
- package/lib/agents.js +448 -0
- package/lib/build.js +189 -45
- package/lib/cli.js +258 -9
- package/lib/config.js +20 -1
- package/lib/desktop.js +133 -0
- package/lib/dev.js +149 -0
- package/lib/mcp.js +217 -0
- package/lib/release.js +90 -0
- package/lib/security.js +113 -54
- package/lib/shell.js +170 -63
- package/lib/toolchain.js +339 -0
- package/lib/tui.js +37 -1
- package/lib/utils.js +51 -0
- package/lib/verify.js +48 -12
- package/lib/webkit.js +45 -0
- package/package.json +3 -2
- package/shell/darwin-arm64/freedom-shell +0 -0
- package/shell/linux-x64/freedom-shell +0 -0
- package/shell/win-x64/freedom-shell.exe +0 -0
- package/skill/freedom/SKILL.md +159 -0
- package/templates/desktop/app.html +559 -0
- package/templates/desktop/backend/desktop.mjs +327 -0
- package/templates/desktop/freedom.config.js +32 -0
- package/templates/desktop/icon.ico +0 -0
- package/templates/go/go.mod +3 -0
- package/templates/go/pkg/freedom/anti_debug_linux.go +69 -0
- package/templates/go/pkg/freedom/anti_debug_other.go +5 -3
- package/templates/go/pkg/freedom/anti_debug_windows.go +238 -11
- package/templates/go/pkg/freedom/assets/freedom.js +3 -0
- package/templates/go/pkg/freedom/assets_embed.go +1 -0
- package/templates/go/pkg/freedom/backend_proc.go +5 -5
- package/templates/go/pkg/freedom/events_windows.go +5 -5
- package/templates/go/pkg/freedom/freedom.go +64 -10
- package/templates/go/pkg/freedom/msgwindow_windows.go +8 -8
- package/templates/go/pkg/freedom/resources.go +119 -14
- package/templates/go/pkg/freedom/securetemp.go +61 -0
- package/templates/go/pkg/freedom/securetemp_other.go +20 -0
- package/templates/go/pkg/freedom/securetemp_windows.go +25 -0
- package/templates/go/pkg/freedom/security.go +293 -64
- package/templates/go/pkg/freedom/shutdown.go +55 -0
- package/templates/go/pkg/freedom/shutdown_windows.go +47 -0
- package/templates/go/pkg/freedom/store.go +11 -3
- package/templates/go/pkg/freedom/syscap_windows.go +33 -33
- package/templates/go/pkg/freedom/sysint_windows.go +2 -2
- package/templates/go/pkg/freedom/tray_windows.go +18 -18
- package/templates/go/pkg/freedom/updater.go +3 -3
- package/templates/go/pkg/freedom/window_other.go +7 -1
- package/templates/go/pkg/freedom/window_windows.go +185 -17
- package/templates/go/third_party/webview_go/CHANGELOG.md +15 -0
- package/templates/go/third_party/webview_go/FREEDOM-PATCH.md +32 -0
- package/templates/go/third_party/webview_go/LICENSE +22 -0
- package/templates/go/third_party/webview_go/README.md +50 -0
- package/templates/go/third_party/webview_go/glue.c +36 -0
- package/templates/go/third_party/webview_go/go.mod +3 -0
- package/templates/go/third_party/webview_go/go.sum +0 -0
- package/templates/go/third_party/webview_go/libs/mswebview2/LICENSE +27 -0
- package/templates/go/third_party/webview_go/libs/mswebview2/include/WebView2.h +23568 -0
- package/templates/go/third_party/webview_go/libs/mswebview2/include/vendor.go +2 -0
- package/templates/go/third_party/webview_go/libs/mswebview2/vendor.go +2 -0
- package/templates/go/third_party/webview_go/libs/mswebview2/version.txt +1 -0
- package/templates/go/third_party/webview_go/libs/webview/LICENSE +22 -0
- package/templates/go/third_party/webview_go/libs/webview/include/vendor.go +2 -0
- package/templates/go/third_party/webview_go/libs/webview/include/webview.h +3598 -0
- package/templates/go/third_party/webview_go/libs/webview/vendor.go +2 -0
- package/templates/go/third_party/webview_go/libs/webview/version.txt +1 -0
- package/templates/go/third_party/webview_go/webkit2_40.go +21 -0
- package/templates/go/third_party/webview_go/webkit2_41.go +8 -0
- package/templates/go/third_party/webview_go/webview.cc +1 -0
- package/templates/go/third_party/webview_go/webview.go +332 -0
- package/templates/installer/app.nsi +43 -0
- package/templates/project/freedom.config.js +14 -0
- package/templates/project/freedom.d.ts +77 -0
- package/templates/project-minimal/freedom.config.js +14 -0
- package/templates/project-minimal/freedom.d.ts +77 -0
|
@@ -2,9 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
package freedom
|
|
4
4
|
|
|
5
|
-
// 反调试(Windows 实现):high 安全模式下由 Run()
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// 反调试(Windows 实现):high 安全模式下由 Run() 在资源解密前后各调用一次。
|
|
6
|
+
// 命中时静默退出(退出码 77,无提示),防止攻击者在调试器下单步追踪
|
|
7
|
+
// 密钥派生 / 容器解密逻辑,或直接读内存里的明文密钥。
|
|
8
|
+
//
|
|
9
|
+
// 七道独立信号(任一命中即判定被调试),覆盖不同层次的附加手段:
|
|
10
|
+
// 1. IsDebuggerPresent —— 读 PEB.BeingDebugged 的快捷 API(最易被 hook 篡改);
|
|
11
|
+
// 2. CheckRemoteDebuggerPresent —— 内核调试器 / 远程调试器;
|
|
12
|
+
// 3. 直接读 PEB.BeingDebugged —— 不经 API,绕过"只 patch API 返回值"的调试辅助;
|
|
13
|
+
// 4. NtQueryInformationProcess(ProcessDebugPort) —— 调试端口句柄;
|
|
14
|
+
// 5. NtQueryInformationProcess(ProcessDebugObjectHandle) —— 调试对象(无端口也命中);
|
|
15
|
+
// 6. NtQueryInformationProcess(ProcessDebugFlags) —— 标志位反向确认;
|
|
16
|
+
// 7. 线程上下文 DR0–DR3 —— 硬件断点(数据/内存断点常常不落在前六道上)。
|
|
17
|
+
//
|
|
18
|
+
// 第七道的实现约束:对本线程直接 GetThreadContext 会返回 ERROR_ACCESS_DENIED,
|
|
19
|
+
// 故用 Toolhelp 枚举进程内线程、以 THREAD_GET_CONTEXT 打开后读取(不挂起任何线程),
|
|
20
|
+
// 读不到上下文一律记未命中。只在启动期跑、只在 high 模式跑;
|
|
21
|
+
// 已知局限:抓的是"检测时刻已设下的硬件断点",运行中途 attach 再下断点不在射程内。
|
|
22
|
+
//
|
|
23
|
+
// 未纳入:时间差检测在 CI/低配机误判率高,误杀正常用户的代价高于其收益。
|
|
24
|
+
// 所有信号都遵循"探测失败即视为未命中",只在确证被调试时退出,杜绝误杀。
|
|
25
|
+
//
|
|
26
|
+
// 诚实边界:这些是抬升成本的对抗,不是不可绕过的墙。绕开者仍需逆向壳二进制、
|
|
27
|
+
// 复现掩码后的主密钥与 PBKDF2 参数,并在无调试器环境下取密钥。
|
|
8
28
|
|
|
9
29
|
import (
|
|
10
30
|
"os"
|
|
@@ -13,25 +33,232 @@ import (
|
|
|
13
33
|
)
|
|
14
34
|
|
|
15
35
|
var (
|
|
16
|
-
kernel32AntiDebug
|
|
36
|
+
kernel32AntiDebug = syscall.NewLazyDLL("kernel32.dll")
|
|
37
|
+
ntdllAntiDebug = syscall.NewLazyDLL("ntdll.dll")
|
|
38
|
+
|
|
17
39
|
procIsDebuggerPresent = kernel32AntiDebug.NewProc("IsDebuggerPresent")
|
|
18
40
|
procCheckRemoteDebuggerPresent = kernel32AntiDebug.NewProc("CheckRemoteDebuggerPresent")
|
|
41
|
+
procNtQueryInformationProcess = ntdllAntiDebug.NewProc("NtQueryInformationProcess")
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// 进程信息类别(PROCESSINFOCLASS)与各类别的返回长度,编号与长度均为本机实测校准:
|
|
45
|
+
//
|
|
46
|
+
// ProcessBasicInformation(0) 48 字节(x64,PEB 在偏移 8)
|
|
47
|
+
// ProcessDebugPort(7) 8 字节(HANDLE);len 给 4 会返回 STATUS_INFO_LENGTH_MISMATCH
|
|
48
|
+
// ProcessDebugObjectHandle(30) 8 字节;未被调试时返回 STATUS_PORT_NOT_SET(0xC0000353)
|
|
49
|
+
// ProcessDebugFlags(31) 4 字节(ULONG);未被调试时返回 1
|
|
50
|
+
//
|
|
51
|
+
// 注意 30/31 两号易写成 32/33(那是 LUIDDeviceMapsEnabled / BreakOnTermination),
|
|
52
|
+
// 编号或长度写错不会报错、只会静默失去一路信号,故此处配套测试锁死。
|
|
53
|
+
const (
|
|
54
|
+
processBasicInformation = 0
|
|
55
|
+
processDebugPort = 7
|
|
56
|
+
processDebugObjectHandle = 30
|
|
57
|
+
processDebugFlags = 31
|
|
58
|
+
|
|
59
|
+
basicInfoSize = unsafe.Sizeof(processBasicInfo{}) // 48B(x64)
|
|
60
|
+
debugPortSize = 8
|
|
61
|
+
debugObjectSize = 8
|
|
62
|
+
debugFlagsSize = 4
|
|
63
|
+
|
|
64
|
+
pebBeingDebuggedOff = 2 // PEB+0x02:BYTE BeingDebugged
|
|
19
65
|
)
|
|
20
66
|
|
|
21
|
-
// antiDebugCheck
|
|
67
|
+
// antiDebugCheck 检测调试器;确证被调试则立即结束进程(不显示窗口、不回显原因)。
|
|
22
68
|
func antiDebugCheck() {
|
|
23
|
-
if
|
|
69
|
+
if debuggerPresent() {
|
|
24
70
|
os.Exit(77)
|
|
25
71
|
}
|
|
26
72
|
}
|
|
27
73
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
func isBeingDebugged() bool {
|
|
74
|
+
// debuggerPresent 汇总七道信号。lazy DLL 加载失败(Proc.Find 出错)时该信号记为未命中。
|
|
75
|
+
func debuggerPresent() bool {
|
|
31
76
|
if r, _, _ := procIsDebuggerPresent.Call(); r != 0 {
|
|
32
77
|
return true
|
|
33
78
|
}
|
|
34
79
|
var present int32
|
|
35
|
-
procCheckRemoteDebuggerPresent.Call(^uintptr(0), uintptr(unsafe.Pointer(&present)))
|
|
36
|
-
|
|
80
|
+
if r, _, _ := procCheckRemoteDebuggerPresent.Call(^uintptr(0), uintptr(unsafe.Pointer(&present))); r != 0 {
|
|
81
|
+
return present != 0
|
|
82
|
+
}
|
|
83
|
+
if pebBeingDebuggedSet() {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
if r, ok := ntQueryProcessInfo(processDebugPort, debugPortSize); ok && r != 0 {
|
|
87
|
+
return true // 调试端口非 0
|
|
88
|
+
}
|
|
89
|
+
// 调试对象:未被调试时返回 STATUS_PORT_NOT_SET(查询失败即未命中),
|
|
90
|
+
// 能成功取到句柄说明挂了调试对象。
|
|
91
|
+
if _, ok := ntQueryProcessInfo(processDebugObjectHandle, debugObjectSize); ok {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
// DebugFlags:1 = 无调试器;0 = 有。
|
|
95
|
+
if r, ok := ntQueryProcessInfo(processDebugFlags, debugFlagsSize); ok && r == 0 {
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
// 硬件断点:数据断点 / 内存断点常不改动上面任何一处状态,只能看 DR0–DR3。
|
|
99
|
+
if hardwareBreakpointSet() {
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ntQueryProcessInfo 以 GetCurrentProcess 伪句柄(-1)查询进程信息,size 必须与
|
|
106
|
+
// class 的约定结构体大小一致(见上方常量表),否则系统直接返回长度不匹配。
|
|
107
|
+
// 返回查询成功时的数值;API 不可用、状态非 SUCCESS 时 ok=false(调用方按未命中处理)。
|
|
108
|
+
func ntQueryProcessInfo(class, size uintptr) (uint64, bool) {
|
|
109
|
+
if err := procNtQueryInformationProcess.Find(); err != nil {
|
|
110
|
+
return 0, false
|
|
111
|
+
}
|
|
112
|
+
var buf [8]byte
|
|
113
|
+
r, _, _ := procNtQueryInformationProcess.Call(
|
|
114
|
+
^uintptr(0), class, uintptr(unsafe.Pointer(&buf[0])), size, 0)
|
|
115
|
+
if r != 0 { // 非 STATUS_SUCCESS 一律按未命中,避免误杀
|
|
116
|
+
return 0, false
|
|
117
|
+
}
|
|
118
|
+
if size >= 8 {
|
|
119
|
+
return *(*uint64)(unsafe.Pointer(&buf[0])), true
|
|
120
|
+
}
|
|
121
|
+
return uint64(*(*uint32)(unsafe.Pointer(&buf[0]))), true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// processBasicInfo 是 PROCESS_BASIC_INFORMATION 的 x64 布局(字段按指针宽度对齐)。
|
|
125
|
+
type processBasicInfo struct {
|
|
126
|
+
exitStatus uintptr
|
|
127
|
+
pebAddress uintptr
|
|
128
|
+
affinityMask uintptr
|
|
129
|
+
basePriority uintptr
|
|
130
|
+
uniqueProcessId uintptr
|
|
131
|
+
inheritedFromUniquePid uintptr
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// pebBeingDebuggedSet 不经 API 直接读自身 PEB.BeingDebugged。
|
|
135
|
+
// 与 IsDebuggerPresent 读同一字段,但绕过对该 API 的 hook / patch。
|
|
136
|
+
// 本进程内解引用自身 PEB 指针,无并发与生命周期问题。
|
|
137
|
+
func pebBeingDebuggedSet() bool {
|
|
138
|
+
var info processBasicInfo
|
|
139
|
+
if err := procNtQueryInformationProcess.Find(); err != nil {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
r, _, _ := procNtQueryInformationProcess.Call(
|
|
143
|
+
^uintptr(0), processBasicInformation,
|
|
144
|
+
uintptr(unsafe.Pointer(&info)), unsafe.Sizeof(info), 0)
|
|
145
|
+
if r != 0 || info.pebAddress == 0 {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
return *(*byte)(unsafe.Pointer(info.pebAddress + pebBeingDebuggedOff)) != 0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---- 第七道信号:硬件断点(DR0–DR3)----
|
|
152
|
+
//
|
|
153
|
+
// 数据断点与部分内存断点不改 PEB、不建调试端口,只写在线程上下文的调试寄存器里。
|
|
154
|
+
// 对本线程调 GetThreadContext 会被拒(ERROR_ACCESS_DENIED),因此走 Toolhelp 枚举
|
|
155
|
+
// 进程内线程 + THREAD_GET_CONTEXT 读取;任何一步失败都记未命中(宁漏不误杀)。
|
|
156
|
+
|
|
157
|
+
const (
|
|
158
|
+
th32csSnapThread = 0x00000002
|
|
159
|
+
threadQueryInfo = 0x0040
|
|
160
|
+
threadGetContext = 0x0010
|
|
161
|
+
threadAllAccess = threadQueryInfo | threadGetContext
|
|
162
|
+
// CONTEXT_AMD64(0x00100000) | CONTEXT_DEBUG_REGISTERS(0x10):不置此标志,
|
|
163
|
+
// GetThreadContext 根本不回填 Dr0–Dr7(读到的只是栈上残值,会误报)。
|
|
164
|
+
contextDebugRegisters = 0x00100000 | 0x10
|
|
165
|
+
dr7LocalBitsMask = 0b1111
|
|
166
|
+
invalidHandleVal = ^uintptr(0)
|
|
167
|
+
threadEntrySize = unsafe.Sizeof(threadEntry32{})
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
var (
|
|
171
|
+
procCreateToolhelp32Snapshot = kernel32AntiDebug.NewProc("CreateToolhelp32Snapshot")
|
|
172
|
+
procThread32First = kernel32AntiDebug.NewProc("Thread32First")
|
|
173
|
+
procThread32Next = kernel32AntiDebug.NewProc("Thread32Next")
|
|
174
|
+
procOpenThread = kernel32AntiDebug.NewProc("OpenThread")
|
|
175
|
+
procGetThreadContext = kernel32AntiDebug.NewProc("GetThreadContext")
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// threadEntry32 是 Toolhelp 线程快照项(x64 布局:32 字节,末尾有对齐填充)。
|
|
179
|
+
type threadEntry32 struct {
|
|
180
|
+
dwSize uint32
|
|
181
|
+
cntUsage uint32
|
|
182
|
+
th32ThreadID uint32
|
|
183
|
+
th32OwnerPID uint32
|
|
184
|
+
tpBasePrio int32
|
|
185
|
+
dwFlags uint32
|
|
186
|
+
hModule uintptr
|
|
187
|
+
_pad uint32
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// contextAMD64 是 Windows x64 CONTEXT 的局部视图,偏移严格按 winnt.h:
|
|
191
|
+
// P1Home–P6Home 占 0x00–0x2F,ContextFlags@0x30,MxCsr@0x34,段寄存器 0x38–0x43,
|
|
192
|
+
// EFlags@0x44,Dr0@0x48、Dr1@0x50、Dr2@0x58、Dr3@0x60、Dr6@0x68、Dr7@0x70,
|
|
193
|
+
// 其后寄存器/XMM/XSTATE 一并归入 padding(总长取 0x400)。
|
|
194
|
+
// 偏移错一位就会把 EFlags/残值当成断点地址(误杀),故 anti_debug_windows_test.go 逐字段挂断言。
|
|
195
|
+
type contextAMD64 struct {
|
|
196
|
+
_ [48]byte
|
|
197
|
+
contextFlags uint32
|
|
198
|
+
_ [20]byte // MxCsr + 6 个段寄存器 + EFlags
|
|
199
|
+
dr0 uint64
|
|
200
|
+
dr1 uint64
|
|
201
|
+
dr2 uint64
|
|
202
|
+
dr3 uint64
|
|
203
|
+
dr6 uint64
|
|
204
|
+
dr7 uint64
|
|
205
|
+
_ [1024 - 120]byte
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
func hardwareBreakpointSet() bool {
|
|
209
|
+
for _, fn := range []uintptr{
|
|
210
|
+
procCreateToolhelp32Snapshot.Addr(), procThread32First.Addr(), procThread32Next.Addr(),
|
|
211
|
+
procOpenThread.Addr(), procGetThreadContext.Addr(),
|
|
212
|
+
} {
|
|
213
|
+
if fn == 0 {
|
|
214
|
+
return false // DLL 未加载成功:本信号记未命中
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
snap, _, _ := procCreateToolhelp32Snapshot.Call(th32csSnapThread, 0)
|
|
218
|
+
if snap == invalidHandleVal || snap == 0 {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
defer syscall.CloseHandle(syscall.Handle(snap))
|
|
222
|
+
pid := uintptr(syscall.Getpid())
|
|
223
|
+
var te threadEntry32
|
|
224
|
+
te.dwSize = uint32(threadEntrySize)
|
|
225
|
+
for ok, _, _ := procThread32First.Call(snap, uintptr(unsafe.Pointer(&te))); ok != 0; ok, _, _ = procThread32Next.Call(snap, uintptr(unsafe.Pointer(&te))) {
|
|
226
|
+
if te.th32OwnerPID != uint32(pid) {
|
|
227
|
+
continue // 只看本进程的线程
|
|
228
|
+
}
|
|
229
|
+
if threadHasHardwareBreakpoint(uintptr(te.th32ThreadID)) {
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// threadHasHardwareBreakpoint 读一个线程的调试寄存器;读不到即未命中。
|
|
237
|
+
// 刻意不 SuspendThread:挂起 Go 运行时线程有死锁风险,而调试器 attach 时本就把线程挂住了
|
|
238
|
+
// ——那种状态下 GetThreadContext 才会成功并带回 Dr0–Dr7,正是这一路要抓的场景。
|
|
239
|
+
func threadHasHardwareBreakpoint(tid uintptr) bool {
|
|
240
|
+
h, _, _ := procOpenThread.Call(threadAllAccess, 0, tid)
|
|
241
|
+
if h == invalidHandleVal || h == 0 {
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
defer syscall.CloseHandle(syscall.Handle(h))
|
|
245
|
+
// 16 字节留白供 round16 向上对齐(GetThreadContext 要求 CONTEXT 16 字节对齐)。
|
|
246
|
+
var ctxRaw [unsafe.Sizeof(contextAMD64{}) + 16]byte
|
|
247
|
+
ctx := (*contextAMD64)(unsafe.Pointer(round16(&ctxRaw[0])))
|
|
248
|
+
ctx.contextFlags = contextDebugRegisters // 输入:只取调试寄存器
|
|
249
|
+
r, _, _ := procGetThreadContext.Call(h, uintptr(unsafe.Pointer(ctx)))
|
|
250
|
+
if r == 0 || ctx.contextFlags&contextDebugRegisters == 0 {
|
|
251
|
+
return false // 线程在跑 / 权限不足 / 未回填:一律未命中
|
|
252
|
+
}
|
|
253
|
+
return (ctx.dr7&dr7LocalBitsMask) != 0 &&
|
|
254
|
+
(ctx.dr0 != 0 || ctx.dr1 != 0 || ctx.dr2 != 0 || ctx.dr3 != 0)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// round16 把切片首地址向上对齐到 16 字节(GetThreadContext 要求 CONTEXT 16 字节对齐)。
|
|
258
|
+
func round16(p *byte) *byte {
|
|
259
|
+
a := uintptr(unsafe.Pointer(p))
|
|
260
|
+
if d := a % 16; d != 0 {
|
|
261
|
+
a += 16 - d
|
|
262
|
+
}
|
|
263
|
+
return (*byte)(unsafe.Pointer(a))
|
|
37
264
|
}
|
|
@@ -127,6 +127,9 @@
|
|
|
127
127
|
// 聚合信息:title/outerX/outerY/outerWidth/outerHeight/innerWidth/innerHeight/
|
|
128
128
|
// scaleFactor/visible/focused/maximized/minimized/alwaysOnTop/skipTaskbar/frameless/fullscreen。
|
|
129
129
|
getInfo: function () { return windowAction('getInfo'); },
|
|
130
|
+
// appIcon():exe 内嵌图标 → PNG data URL(无边框自绘标题栏用);
|
|
131
|
+
// 未注入图标或非 Windows 平台返回空字符串。
|
|
132
|
+
appIcon: function () { return windowAction('appIcon'); },
|
|
130
133
|
// 显示器列表(经 sys 桥接):[{x,y,width,height,workX,workY,workWidth,workHeight,scaleFactor,isPrimary}]
|
|
131
134
|
monitors: function () { return sysCall('window.monitors', {}); },
|
|
132
135
|
// 窗口视觉效果(Win11):backdrop: auto|none|solid|mica|acrylic;corner: round|roundSmall|square|default;borderColor: 0x00BBGGRR
|
|
@@ -42,11 +42,11 @@ type ProcBackend struct {
|
|
|
42
42
|
maxLine int // 单条 stdout 行(一次响应)上限字节;超限即那条响应过大,不得中断整个通道
|
|
43
43
|
|
|
44
44
|
// —— W4 崩溃自愈(对标 Tauri shell spawn 的失败处理 / sidecar 重启)——
|
|
45
|
-
rp RestartPolicy
|
|
46
|
-
gen int
|
|
47
|
-
pdone chan struct{}
|
|
48
|
-
attempts int
|
|
49
|
-
startedAt time.Time
|
|
45
|
+
rp RestartPolicy // 重启策略(零值=不重启,保持旧行为)
|
|
46
|
+
gen int // 每次拉起递增;readLoop 据此识别"自己那一期",过期实例不触发重启
|
|
47
|
+
pdone chan struct{} // 当前这期进程 Wait 完成后由 readLoop 关闭(Close 等它,不再自己 Wait,避免双重 Wait)
|
|
48
|
+
attempts int // 连续崩溃重启计数
|
|
49
|
+
startedAt time.Time // 本期进程启动时刻(存活够久则重置 attempts)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// RestartPolicy 是后端进程崩溃后的自动重启策略(经 SetRestartPolicy 启用)。
|
|
@@ -18,10 +18,10 @@ import (
|
|
|
18
18
|
|
|
19
19
|
const (
|
|
20
20
|
wmSize = 0x0005
|
|
21
|
-
wmMove
|
|
21
|
+
wmMove = 0x0003
|
|
22
22
|
wmExitSizeMove = 0x0212
|
|
23
|
-
wmSetFocus
|
|
24
|
-
wmKillFocus
|
|
23
|
+
wmSetFocus = 0x0007
|
|
24
|
+
wmKillFocus = 0x0008
|
|
25
25
|
|
|
26
26
|
sizeMaximized = 1
|
|
27
27
|
sizeMinimized = 2
|
|
@@ -244,8 +244,8 @@ func enumMonitorsProc(hmon, hdc, lprci, data uintptr) uintptr {
|
|
|
244
244
|
Width: int(mi.rcMonitor.right - mi.rcMonitor.left),
|
|
245
245
|
Height: int(mi.rcMonitor.bottom - mi.rcMonitor.top),
|
|
246
246
|
WorkX: int(mi.rcWork.left), WorkY: int(mi.rcWork.top),
|
|
247
|
-
WorkWidth:
|
|
248
|
-
WorkHeight:
|
|
247
|
+
WorkWidth: int(mi.rcWork.right - mi.rcWork.left),
|
|
248
|
+
WorkHeight: int(mi.rcWork.bottom - mi.rcWork.top),
|
|
249
249
|
ScaleFactor: scale,
|
|
250
250
|
IsPrimary: mi.dwFlags&monitorInfofPrimary != 0,
|
|
251
251
|
})
|
|
@@ -22,6 +22,7 @@ import (
|
|
|
22
22
|
"errors"
|
|
23
23
|
"fmt"
|
|
24
24
|
"os"
|
|
25
|
+
"strings"
|
|
25
26
|
"sync"
|
|
26
27
|
"sync/atomic"
|
|
27
28
|
|
|
@@ -64,11 +65,24 @@ type Config struct {
|
|
|
64
65
|
MinHeight int
|
|
65
66
|
// Debug 为 true 时开启 WebView 开发者工具(目标平台支持时)。
|
|
66
67
|
Debug bool
|
|
68
|
+
// DisableAntiDebug 关闭 high 安全模式的调试器探测(见 antiDebugEnabled)。
|
|
69
|
+
// 默认 false:探测在位。仅在合法运行环境被误判(部分沙箱 / EDR / 虚拟化环境会让
|
|
70
|
+
// NtQueryInformationProcess 一类调用给出命中值)时由发布方显式置真。
|
|
71
|
+
// 关闭不影响容器解密与 .integrity 校验——那两道才是源码保护的本体。
|
|
72
|
+
DisableAntiDebug bool
|
|
67
73
|
// Backend 指定后端适配器。为空时默认使用内嵌 Go 后端(配合 Bind 使用)。
|
|
68
74
|
Backend Backend
|
|
69
75
|
// HTML 返回要加载到窗口的前端页面内容(内存加载,无本地端口)。
|
|
70
76
|
// 为 nil 时使用框架内置的默认占位页。
|
|
71
77
|
HTML func() (string, error)
|
|
78
|
+
// URL 非空时主窗口直接 Navigate 到该地址(如 `freedom dev` 的 vite 服务器),
|
|
79
|
+
// 优先于 HTML/resources 页面。SDK 经 Init 在每次导航前注入,桥接不失效。
|
|
80
|
+
// 仅放行 http/https(dev/HMR 走 localhost ws,页面源本身不许 file:// 等面)。
|
|
81
|
+
URL string
|
|
82
|
+
// SingleInstance 启用单实例:二次启动把命令行参数转发给主实例后自行退出;
|
|
83
|
+
// 主实例前端收 `app.secondInstance` 事件({args})。Windows 生效,
|
|
84
|
+
// 其他平台为无操作(始终视为唯一实例)。
|
|
85
|
+
SingleInstance bool
|
|
72
86
|
// AppID 是应用标识,决定数据/配置目录名与单实例窗口类标题。
|
|
73
87
|
// 为空时回退可执行文件名(去扩展名)。一经发布不要更改,否则用户数据"丢失"。
|
|
74
88
|
AppID string
|
|
@@ -83,6 +97,11 @@ type Config struct {
|
|
|
83
97
|
Capabilities *Capabilities
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
// pageURLAllowed 校验主窗口 URL 页面源:仅 http/https 放行(file:// 等本地面拒绝)。
|
|
101
|
+
func pageURLAllowed(raw string) bool {
|
|
102
|
+
return strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://")
|
|
103
|
+
}
|
|
104
|
+
|
|
86
105
|
// App 是 Freedom 应用实例。
|
|
87
106
|
type App struct {
|
|
88
107
|
cfg Config
|
|
@@ -93,9 +112,15 @@ type App struct {
|
|
|
93
112
|
onReady func(a *App)
|
|
94
113
|
|
|
95
114
|
// 运行时资源覆盖(resources.go):secure = high 模式(app.bin 解密成功),
|
|
96
|
-
// backendExplicit = 用户经 Bind 显式使用内嵌后端(config.json 的 backend
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
// backendExplicit = 用户经 Bind 显式使用内嵌后端(config.json 的 backend 不得再替换),
|
|
116
|
+
// secureBackendDir = high 模式容器内后端源码解密落地用的私有临时目录(退出即删)。
|
|
117
|
+
secure bool
|
|
118
|
+
backendExplicit bool
|
|
119
|
+
secureBackendDir string
|
|
120
|
+
|
|
121
|
+
// runtimeVersion = config.json / app.bin 容器里声明的「本应用」版本。
|
|
122
|
+
// 与编译期的 Version(通用壳的版本)分属两个版本域,自更新比较只认前者。
|
|
123
|
+
runtimeVersion string
|
|
99
124
|
|
|
100
125
|
// upMu 保护 upPending:CheckUpdate 验签通过的更新条目,install 桥接只认它
|
|
101
126
|
//(前端无法注入未验签的 URL/哈希)。见 updater.go。
|
|
@@ -213,6 +238,11 @@ func (a *App) Unbind(name string) {
|
|
|
213
238
|
|
|
214
239
|
// Run 启动窗口并进入主事件循环,阻塞直到窗口被关闭。
|
|
215
240
|
func (a *App) Run() {
|
|
241
|
+
// high 产物:解密前先跑一次反调试(此时密钥与明文都还没进内存),
|
|
242
|
+
// 解密后再跑一次(兜住"启动后才附加调试器")。非 high 应用不受影响。
|
|
243
|
+
if hasSecureResources() && a.antiDebugEnabled() {
|
|
244
|
+
antiDebugCheck()
|
|
245
|
+
}
|
|
216
246
|
// 通用壳:先加载 exe 同目录 resources/ 覆盖窗口与后端配置(CLI build 写入;
|
|
217
247
|
// 缺失则使用编译期/默认配置)。配置存在但非法时打印告警不中断启动。
|
|
218
248
|
if err := a.loadRuntimeConfig(); err != nil {
|
|
@@ -225,13 +255,22 @@ func (a *App) Run() {
|
|
|
225
255
|
}
|
|
226
256
|
fmt.Printf("freedom: warning: %v\n", err)
|
|
227
257
|
}
|
|
228
|
-
// high
|
|
229
|
-
|
|
258
|
+
// high 模式的临时后端目录随进程退出清理(defer 早于后端 Close 注册 → 后于其执行)。
|
|
259
|
+
defer a.cleanupSecureBackend()
|
|
260
|
+
// Ctrl+C / SIGTERM / 控制台关闭:defer 不会执行,须显式清扫(见 installSecureShutdown)。
|
|
261
|
+
a.installSecureShutdown()
|
|
262
|
+
// high 模式:解密后复查调试器(见 Run 开头的"解密前一次")。
|
|
263
|
+
if a.secure && a.antiDebugEnabled() {
|
|
230
264
|
antiDebugCheck()
|
|
231
265
|
}
|
|
232
266
|
// 平台层回调(单实例转发/热键)经 currentApp 找到本实例推送事件。
|
|
233
267
|
appForEvents.Store(a)
|
|
234
268
|
defer appForEvents.Store(nil)
|
|
269
|
+
// 单实例:二次启动的参数已在 RequestSingleInstance 内转发给主实例,
|
|
270
|
+
// 本进程不建窗直接返回(主实例前端收 app.secondInstance 事件)。
|
|
271
|
+
if a.cfg.SingleInstance && !RequestSingleInstance(a.effectiveAppID()) {
|
|
272
|
+
return
|
|
273
|
+
}
|
|
235
274
|
// M2:标记运行中(NewWindow 的前置条件)并登记主窗口;退出前回收次级窗口。
|
|
236
275
|
a.winMu.Lock()
|
|
237
276
|
a.running = true
|
|
@@ -247,10 +286,19 @@ func (a *App) Run() {
|
|
|
247
286
|
a.running = false
|
|
248
287
|
a.winMu.Unlock()
|
|
249
288
|
}()
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
289
|
+
// URL 模式(dev 服务器/远程页):跳过页面内容解析,稍后 Navigate。
|
|
290
|
+
// scheme 白名单:只放行 http/https,file:// 等本地面一律拒绝。
|
|
291
|
+
if a.cfg.URL != "" && !pageURLAllowed(a.cfg.URL) {
|
|
292
|
+
fmt.Printf("freedom: 拒绝非 http/https 的 URL %q,回退页面源\n", a.cfg.URL)
|
|
293
|
+
a.cfg.URL = ""
|
|
294
|
+
}
|
|
295
|
+
var html string
|
|
296
|
+
if a.cfg.URL == "" {
|
|
297
|
+
var err error
|
|
298
|
+
if html, err = a.resolveHTML(); err != nil {
|
|
299
|
+
fmt.Printf("freedom: failed to resolve HTML: %v\n", err)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
254
302
|
}
|
|
255
303
|
|
|
256
304
|
w := webview.New(a.cfg.Debug)
|
|
@@ -288,6 +336,8 @@ func (a *App) Run() {
|
|
|
288
336
|
}
|
|
289
337
|
a.applyCenter()
|
|
290
338
|
a.applyTitleBar()
|
|
339
|
+
// 标题栏 / 任务栏图标同步为 exe 内嵌图标(Windows;其他平台 no-op)。
|
|
340
|
+
a.setWindowIcon()
|
|
291
341
|
// 窗口事件与关闭拦截(Windows 经 WndProc 子类化;其他平台 no-op)。
|
|
292
342
|
a.installWindowEvents()
|
|
293
343
|
// 恢复上次退出的窗口几何(仅 Config.RememberWindowState 时生效)。
|
|
@@ -352,7 +402,11 @@ func (a *App) Run() {
|
|
|
352
402
|
|
|
353
403
|
// 注:onReady 不再在 SetHtml 前触发(H3),改由前端 SDK 在页面
|
|
354
404
|
// DOMContentLoaded 后经 __freedom__ready 回调,保证初始化事件不丢失。
|
|
355
|
-
|
|
405
|
+
if a.cfg.URL != "" {
|
|
406
|
+
w.Navigate(a.cfg.URL) // Init 注入对每次导航生效,URL 模式桥接同样可用
|
|
407
|
+
} else {
|
|
408
|
+
w.SetHtml(html)
|
|
409
|
+
}
|
|
356
410
|
w.Run()
|
|
357
411
|
}
|
|
358
412
|
|
|
@@ -35,14 +35,14 @@ func validCopyData(dwData uintptr, cbData uint32, lpData uintptr) bool {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
type msgWindow struct {
|
|
38
|
-
mu
|
|
39
|
-
hwnd
|
|
40
|
-
ready
|
|
41
|
-
created
|
|
42
|
-
hotkeys
|
|
43
|
-
nextHKID
|
|
44
|
-
onHotkey
|
|
45
|
-
onData
|
|
38
|
+
mu sync.Mutex
|
|
39
|
+
hwnd uintptr
|
|
40
|
+
ready chan struct{}
|
|
41
|
+
created bool
|
|
42
|
+
hotkeys map[string]uint32 // 前端 id → 热键数值 id
|
|
43
|
+
nextHKID uint32
|
|
44
|
+
onHotkey func(id string)
|
|
45
|
+
onData func(payload []byte)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
var (
|