分析某高校学习通考试客户端(一)

📅 2026/7/4 4:19:35
分析某高校学习通考试客户端(一)
分析某高校学习通考试客户端一这个学习通终端貌似是某个高校专用的运行之后只能使用该高校的账号登录。听说一些高校使用的东西ppt或者软件都是远古版本这次分析的客户端也挺老的可能是为了兼容性采用了32位架构。下面的分析基于最新版本的Windows11相关调用和堆栈变量经过了我的重命名这样大家看起来就比较直观。基本分析思路首先它没有被加壳并且字符串和IAT都是未被处理的看一眼导入表和字符串后很快就可以找到关键代码。结合IAT和字符串引用下断点放到调试器里稍微分析一下很快就有了结果。虽然有些反调试手段但是拿个x96插件直接绕过就行。它有两个进程我们选择有标题那个虚拟机检测分析.text:00423C80 CXECheckEnv.text:00423C80 ; 这是总检测函数包括时间、摄像头、虚拟机等检测 .text:00423C80 ; Attributes: bp-based frame .text:00423C80 .text:00423C80 ; int __thiscall CXECheckEnv(char *this) .text:00423C80 CXECheckEnv proc near ; CODE XREF: sub_4253503↓jCXECheckEnv - sub_4209C0 - CXEFindProcessInfo.text:0047E1D0 CXEFindProcessInfo.text:0047E1D0 ; CXEFindProcessInfo: 枚举系统所有进程并收集信息 .text:0047E1D0 ; 参数 a1: 进程信息容器(vector), 每个元素大小 0x80 字节 .text:0047E1D0 ; 功能: 遍历所有进程, 收集进程名(小写)、完整路径、FileDescription .text:0047E1D0 ; Attributes: bp-based frame .text:0047E1D0 .text:0047E1D0 ; char __stdcall CXEFindProcessInfo(_DWORD *) .text:0047E1D0 CXEFindProcessInfo proc near ; CODE XREF: sub_4209C0564↑p这里调用了CreateToolhelp32SnapshotTH32CS_SNAPPROCESS、0。我一度以为这是检测虚拟机相关的代码CXEFindProcessInfo可能为虚拟机检测提供相关信息但是经过调试后发现这里似乎没什么影响。检测真正发挥作用的是在另外一个函数。在找到真正的虚拟机检测函数之前我尝试过hook CreateToolhelp32Snapshot和ProcessNextW函数过滤VM相关的信息但是貌似这个学习通终端对这两个函数有检测经过我的调试我的hook是没问题的但是运行一段时间后学习通程序就直接弹出崩溃对话框我怀疑它对这两个函数进行了检测。具体怎样我没分析因为我后面找到了真正发挥作用的虚拟机检测函数。sub_47F720 VM检测函数里面的相关调用我重命名了这样看起来比较直观。首先是CXECheckVMByVpcext函数vpcext 是 Virtual PC / Hyper-V 的 hypervisor 接口指令。物理机和普通虚拟机上执行会触发 非法指令异常 (UD)被 SEH 捕获。如果在 VPC 中指令正常执行并修改 ebx。然后是sub_47F670端口 0x5658 是 VMware 的 后门 I/O 端口。in 指令在非 VMware 环境会触发 GP异常被 SEH 捕获返回 false。在 VMware 中hypervisor 拦截此 I/O 并返回 “VMXh” 到 ebx。绕过直接打补丁就行它没检测关键函数是否被篡改。我直接让sub_47F720返回0键盘检测分析看到SetWindowsHookExW就知道它肯定没干好事在sub_4525B0中分析KeyBoardCallbackFunc回调// KeyBoardCallbackFunc - WH_KEYBOARD_LL 低级键盘钩子回调LRESULT __stdcallKeyBoardCallbackFunc(intcode,WPARAM wParam,WPARAM*lParam){// code!0时直接放行(非本钩子处理的消息)bool ctrlDown;// blSHORT AsyncKeyState;// axWPARAM vkCode;// edibool altDown;// albool isShiftKey;// bhbool isCtrlKeyRaw;// clbool isKeyUp;// blintunused_ecx;// ecxcharisWinKey2;// clcharisKeyDown2;// albool ctrlKeyDown2;// bhWPARAM vkCodeTmp1;// eaxWPARAM vkCodeTmp2;// eaxcharaltAndShiftKey;// [esp8h] [ebp-410h]charshiftAndCtrlKey;// [esp9h] [ebp-40Fh]bool ctrlAndShiftOrSpace;// [espAh] [ebp-40Eh]bool altKeyDown;// [espBh] [ebp-40Dh]bool isCtrlKey;// [espCh] [ebp-40Ch]charisAltKey;// [espDh] [ebp-40Bh]unsigned__int8 winKeyDown;// [espEh] [ebp-40Ah]bool shiftKeyDown;// [esp10h] [ebp-408h]charisWinKey;// [esp11h] [ebp-407h]bool ctrlKeyDown;// [esp12h] [ebp-406h]charisKeyDown;// [esp13h] [ebp-405h]CHAR OutputString[1024];// [esp14h] [ebp-404h] BYREFif(code)returnCallNextHookEx(hhk,code,wParam,(LPARAM)lParam);if(GetKeyState(91)0||(winKeyDown0,GetKeyState(92)0))// 阶段1: 检测所有修饰键状态 // VK_LWIN0x5B, VK_RWIN0x5CwinKeyDown1;ctrlDownGetAsyncKeyState(17)0;// 检测 Ctrl键 (VK_CONTROL0x11)ctrlKeyDownctrlDown;shiftKeyDownGetAsyncKeyState(16)0;// 检测 Shift键 (VK_SHIFT0x10)AsyncKeyStateGetAsyncKeyState(18);// 检测 Alt键 (VK_MENU0x12)vkCode*lParam;altDownAsyncKeyState0;altKeyDownaltDown;if(*lParam91||(isWinKey0,vkCode92))// 判断当前按键是否为Win键 (0x5B/0x5C)isWinKey1;isShiftKeyvkCode160||vkCode161;// 判断当前按键是否为Shift (VK_LSHIFT0xA0 / VK_RSHIFT0xA1)isCtrlKeyRawvkCode162||vkCode163;// 判断当前按键是否为Ctrl (VK_LCONTROL0xA2 / VK_RCONTROL0xA3)isCtrlKeyisCtrlKeyRaw;if(vkCode164||(isAltKey0,vkCode165))// 判断当前按键是否为Alt (VK_LMENU0xA4 / VK_RMENU0xA5)isAltKey1;ctrlAndShiftOrSpacectrlDown(isShiftKey||vkCode32);// 组合条件: ctrlAndShiftOrSpace Ctrl按下 (Shift键 或 Space键)if(!shiftKeyDown||(shiftAndCtrlKey1,!isCtrlKeyRaw))// 组合条件: shiftAndCtrlKey Shift按下 Ctrl键shiftAndCtrlKey0;if(!altDown||(altAndShiftKey1,!isShiftKey))// 组合条件: altAndShiftKey Alt按下 Shift键altAndShiftKey0;if(wParam256||(isKeyDown0,wParam260))// 判断键盘事件类型: WM_KEYDOWN(0x100)/WM_SYSKEYDOWN(0x104)→按下; WM_KEYUP(0x101)/WM_SYSKEYUP(0x105)→释放isKeyDown1;isKeyUpwParam257||wParam261;memset(OutputString,0,sizeof(OutputString));sub_435640(unused_ecx,(int)OutputString,(int)win:[%d,%d],alt:[%d,%d],ctrl:[%d,%d],shift:[%d,%d],key:[%d],down[%d]\r\n,winKeyDown);OutputDebugStringA(OutputString);// OutputDebugStringA 输出调试日志: win/alt/ctrl/shift/key状态isWinKey2isWinKey;isKeyDown2isKeyDown;if(isWinKey)// 【规则1】Win键处理: 按下时置标志byte_707D801; 释放时若标志已置位→PostMessage(0x61E)屏蔽或直接return 1{if(isKeyDown){byte_707D801;}else{if(byte_707D80){if(dword_715780){PostMessageW(dword_715780,0x61Eu,*lParam,68);// 规则1a: PostMessage(0x61E, vkCode, 68) 通知Win键释放屏蔽return1;}return1;}if(dword_715780){PostMessageW(dword_715780,0x61Cu,0,0);// 规则1b: PostMessage(0x61C) Win键释放通知(未通过标志检测)isWinKey2isWinKey;}isKeyDown20;}}if(winKeyDown!isWinKey2)// 【规则2】Win键屏蔽: Win按下时,非Space/Shift/Alt/Ctrl的KeyDown→return 1屏蔽{if(*lParam!32!isShiftKey!isAltKey!isCtrlKeyisKeyDown2)return1;byte_707D800;// Win修饰键按下列外: 清除byte_707D80标志}if(ctrlAndShiftOrSpace||shiftAndCtrlKey||altAndShiftKey)// 【规则3】组合键通知: CtrlShift/Space 或 ShiftCtrl 或 AltShift → PostMessage(0x61C)通知窗口后跳转LABEL_82{if(dword_715780)PostMessageW(dword_715780,0x61Cu,0,0);// PostMessage(0x61C) 通知窗口: 组合键状态gotoLABEL_82;}if(shiftKeyDownvkCode32isKeyUp||isShiftKeyisKeyUp||altKeyDownisKeyUp*lParam25)// 【规则4b】Shift键KeyUp 或 Alt↓(0x19)KeyUp → sub_452980(){sub_452980();// 【规则4a】ShiftSpaceKeyUp → sub_452980() (触发反调试/反分析)LABEL_82:ctrlKeyDown2ctrlKeyDown;gotoLABEL_83;}ctrlKeyDown2ctrlKeyDown;if(ctrlKeyDown*lParam190isKeyUp)gotoLABEL_71;if(!shiftKeyDown!ctrlKeyDown!altKeyDown)gotoLABEL_103;if(isKeyUp){vkCodeTmp1*lParam;if(*lParam240||vkCodeTmp1241||vkCodeTmp1242)// 【规则6】修饰键F240/F241/F242(0xF0/0xF1/0xF2)KeyUp → sub_452980()LABEL_71:sub_452980();// 【规则5】Ctrl句号(0xBE)KeyUp → sub_452980() (可能是隐藏/显示窗口)}LABEL_83:if(altKeyDown(GetAsyncKeyState(32)0(*lParam78||*lParam110)||vkCode32))return1;// 【规则7】AltSpaceN/n 或 AltSpace → return 1 屏蔽 (阻止AltSpace系统菜单)if(ctrlKeyDown2){if(shiftKeyDown){// 【规则8a】CtrlShiftEsc → return 1 屏蔽 (阻止任务管理器)if(vkCode27)return1;}elseif(vkCode27)// 【规则8b】CtrlEsc → return 1 屏蔽 (阻止开始菜单){return1;}}if(altKeyDown){vkCodeTmp2*lParam;if(*lParam9||vkCode27||vkCodeTmp20x70vkCodeTmp20x87)return1;// 【规则9b】AltEsc 或 AltF1~F24(0x70~0x87) → return 1 屏蔽}if(ctrlKeyDown2(altKeyDown*lParam9||*lParam87||*lParam119))return1;// 【规则10b】CtrlW/w → return 1 屏蔽 (阻止关闭窗口/标签页)LABEL_103:if(vkCode27){if(dword_715780)PostMessageW(dword_715780,0x621u,0,0);// 公共屏蔽出口: return 1 (吞噬按键,阻止传递) return1;}returnCallNextHookEx(hhk,0,wParam,(LPARAM)lParam);// 放行出口: CallNextHookEx → 按键正常传递给系统 }具体检测规则#规则触发条件动作目的1Win键单按Win键PostMessage(0x61E/0x61C)return 1阻止开始菜单2WinXWin按下时按任意非修饰键return 1 屏蔽阻止WinR/ WinE等3组合键通知CtrlShift/Space, ShiftCtrl, AltShiftPostMessage(0x61C) 通知窗口监视组合键4修饰键释放ShiftSpace释放 / Shift释放 / Alt↓释放sub_452980()触发反调试5Ctrl.Ctrl句号(0xBE) 释放sub_452980()隐藏/显示窗口6F功能键修饰键F240/F241/F242 释放sub_452980()特殊功能触发7AltSpaceAltSpace 或 AltSpaceN/nreturn 1阻止系统菜单8CtrlEscCtrlShiftEsc 或 CtrlEscreturn 1阻止任务管理器/开始菜单9Alt组合AltTab / AltEsc / AltF1~F24return 1阻止窗口切换10Ctrl组合CtrlAltTab / CtrlWreturn 1阻止关闭窗口11Esc单按EscPostMessage(0x621)return 1阻止退出绕过思路都差不多让它直接放行就行。看看反汇编那我们不让它跳转到loc_4529e0直接执行CallNextHookEx