ZTool 应用层远控对抗工具
ZTool 应用层远控对抗工具
功能简介
用途
对抗远程控制程序、本地访问限制程序、全屏锁屏恶搞程序、以及部分通过循环置顶锁屏的勒索程序。
比如各类远控病毒、安装在本地的控制软件、电子教室和机房管理助手程序、各类“青少年守护”、假蓝屏恶搞、还有什么“不叫爸爸不让退出”之类的恶搞整蛊等等。
基本远控对抗
- 阻止其他应用挂钩控制鼠标键盘
- 防止截屏录屏捕获 ZTool 窗口
- ZTool 窗口始终保持置顶,System 权限下为 UIAccess 超级置顶
进程操作
- 根据进程名或 pid 结束进程
- 根据进程名或 pid 挂起/恢复 进程
- 根据进程名或 pid 将进程权限降为 Untrusted
- 关闭所有非系统进程
- 禁止创建进程
可选是否启用”强力模式“。
窗口操作
- 关闭窗口
- 根据窗口查找所在进程,并终止进程
- 将窗口嵌套在可操作的窗口内,以绕过窗口对关闭消息的拦截。
电源选项
- 强行关机/重启/注销
- 触发蓝屏
自保护与实时保护
- 保护进程不被其他应用层进程结束
- 剥夺其他非系统进程的调试、关机、远程关机、加载驱动特权
其他
- 打开 System 权限的任务管理器和命令提示符
- 自带 2048 摸鱼小游戏
- 自动提权,打开时可选是否让 ZTool 提权至 System 并获取 UIAccess 权限。
原理简介
基本远控对抗
阻止其他应用挂钩控制鼠标键盘
目前远控程序控制鼠标键盘的方式有两种:
- 使用
SetWindowsHookEx
注册鼠标键盘钩子,实现屏蔽鼠标键盘的操作。 - 使用
ClipCursor
或者SetCursorPos
限制鼠标范围。
针对第一种方式,可以使用双循环钩子覆盖掉远控程序的钩子,代码如下:
void AntikeybdHook(){
while(1){
UnhookWindowsHookEx(keyboardHook);
keyboardHook=SetWindowsHookEx(WH_KEYBOARD_LL,Proc0,NULL,0);
UnhookWindowsHookEx(keyboardHook2);
keyboardHook2=SetWindowsHookEx(WH_KEYBOARD_LL,Proc0,NULL,0);
Sleep(25);
}
}
void AntimouseHook(){
while(1){
UnhookWindowsHookEx(mouseHook);
mouseHook=SetWindowsHookEx(WH_MOUSE_LL,Proc0,NULL,0);
UnhookWindowsHookEx(mouseHook2);
mouseHook2=SetWindowsHookEx(WH_MOUSE_LL,Proc0,NULL,0);
Sleep(25);
}
}
针对第二种方式,先写一个强制移动鼠标的函数,通过 ClipCursor
实现:
void MySetCursorPos(int x,int y){
ClipCursor(NULL);
RECT rect={};
rect.top=y;
rect.left=x;
rect.bottom=y;
rect.right=x;
ClipCursor(&rect);
Sleep(1);
ClipCursor(NULL);
}
ClipCursor
也是可覆盖的,直接循环 ClipCursor(NULL)
,然后检测如果鼠标移动过快就强制拉回来。
void AntiHook(){
POINT pt={-1,-1},lpt={-1,-1};
while(1){
GetCursorPos(&pt);
RECT clip,full;
GetClipCursor(&clip);
GetWindowRect(GetDesktopWindow(),&full);
int q=dis(pt.x,pt.y,lpt.x,lpt.y);
if(q>50000&&lpt.x!=-1){
MySetCursorPos(lpt.x,lpt.y);
}else if(!EqualRect(&clip,&full)&&out_of_rect(lpt.x,lpt.y,clip)){
MySetCursorPos(lpt.x,lpt.y);
}else lpt=pt;
ClipCursor(NULL);
Sleep(5);
}
}
防止截屏录屏捕获 ZTool 窗口
可以使用 API SetWindowDisplayAffinity(hwnd,0x11)
来阻止窗口被截屏。
窗口始终保持置顶
循环保持置顶并让窗口在屏幕范围内。
void KeepOnTop(HWND kkey){
int lx=0,ly=0;
while(1){
EnableWindow(kkey,1);
SetWindowPos(kkey,HWND_TOPMOST,0,0,400,400,SWP_NOMOVE);
ShowWindow(kkey,SW_SHOW);
ShowWindow(kkey,SW_NORMAL);
RECT rect;
int w=GetSystemMetrics(SM_CXSCREEN),h=GetSystemMetrics(SM_CYSCREEN);
if (GetWindowRect(kkey,&rect)) {
int cx=rect.right;
int cy=rect.bottom;
int x=rect.left;
int y=rect.top;
if(x<0||cx>w||y<0||cy>h){
SetWindowPos(kkey,HWND_TOPMOST,lx,ly,400,400,0);
}else{
lx=x,ly=y;
}
}
Sleep(5);
}
}
效果:配合消息屏蔽可无视火绒的弹窗拦截。UIAccess 权限下能覆盖置于顶层的任务管理器。
进程操作
结束进程
在应用层结束被保护的进程本身就是一件非常困难的事情。
ZTool 采用了这些方法:
- 向进程所在窗口发送关闭消息
- 正常方法(使用NtOpenProcess和NtTerminateProcess)
- 将进程附加到 JobObject 并终止
- 远程线程注入 ExitProcess
- 以 NtDuplicateHandle 代替 NtOpenProcess,然后走正常方法。
- 关闭进程所有句柄
- 结束进程所有线程
- 劫持进程所有线程执行 ExitProcess
- 将进程所有内存页设为不可访问状态
- 使用 NtDebugActiveProcess 结束进程
- 卸载进程的 ntdll 模块
- 使用 NtGetNextProcess 代替 NtOpenProcess,然后走正常方法。
- 远程线程注入到 lsass.exe,在 lsass.exe 中使用 DebugActiveProcess 结束进程。
- 降权进程后对进程所在窗口进行消息轰炸。
- 使用
EndTask
API。
强力模式关闭时,ZTool 会执行第一种方法和第二种方法。
强力模式开启时,ZTool 会依次尝试上述十五种方法。
强力模式下可以结束一些小品牌的杀毒软件,对付远控程序应该是足够了。
挂起/恢复 进程
使用 NtSuspendProcess/NtResumeProcess
实现进程的挂起和恢复。
将进程权限降为 Untrusted
以 PROCESS_QUERY_LIMITED_INFORMATION
权限打开进程,之后获取带有调试权限的令牌句柄,更改进程令牌为 Untrusted 级别。
bool EnableDebugPrivilege(HANDLE ProcHandle,HANDLE* hToken){
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(ProcHandle,TOKEN_ALL_ACCESS|TOKEN_QUERY, hToken)){
return FALSE;
}
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)){
CloseHandle(*hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(*hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)){
return false;
}
return true;
}
void KillIce(DWORD pid){
HANDLE phandle=NULL,ptoken=NULL;
phandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
EnableDebugPrivilege(phandle,&ptoken);
DWORD integrityLevel = SECURITY_MANDATORY_UNTRUSTED_RID;
SID integrityLevelSid{};
integrityLevelSid.Revision = SID_REVISION;
integrityLevelSid.SubAuthorityCount = 1;
integrityLevelSid.IdentifierAuthority.Value[5] = 16;
integrityLevelSid.SubAuthority[0] = integrityLevel;
TOKEN_MANDATORY_LABEL tokenIntegrityLevel = {};
tokenIntegrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
tokenIntegrityLevel.Label.Sid = &integrityLevelSid;
SetTokenInformation(ptoken,
TokenIntegrityLevel,
&tokenIntegrityLevel,
sizeof(TOKEN_MANDATORY_LABEL)+GetLengthSid(&integrityLevelSid));
}
进程被降权的效果:
-
任务管理器被降权后,进程列表中只能看到 Idle(系统空闲进程) 和 Interrupts(系统中断)。
-
进程被降权后创建新进程会显示“应用程序无法启动”。
-
进程被降权后执行某些操作可能崩溃。
注意降权是不可逆的,降权后系统不再信任这个进程,这个进程对系统进行的大部分操作将被阻止。
但是降权后 仍然可以 和驱动通信,因此对加载了内核驱动的进程无效。
关闭所有非系统进程
硬编码出来系统必要进程的白名单即可。
白名单:
if(pid!=GetCurrentProcessId()&&
strcmp("TextInputHost.exe",pe.szExeFile)!=0&&
strcmp("dllhost.exe",pe.szExeFile)!=0&&
strcmp("StartMenuExperienceHost.exe",pe.szExeFile)!=0&&
strcmp("ShellExperienceHost.exe",pe.szExeFile)!=0&&
strcmp("taskhostw.exe",pe.szExeFile)!=0&&
strcmp("WUDFHost.exe",pe.szExeFile)!=0&&
strcmp("spoolsv.exe",pe.szExeFile)!=0&&
strcmp("lsm.exe",pe.szExeFile)!=0&&
strcmp("LMS.exe",pe.szExeFile)!=0&&
strcmp("audiodg.exe",pe.szExeFile)!=0&&
strcmp("explorer.exe",pe.szExeFile)!=0&&
strcmp("Registry",pe.szExeFile)!=0&&
strcmp("System",pe.szExeFile)!=0&&
strcmp("[System Process]",pe.szExeFile)!=0&&
strcmp("sihost.exe",pe.szExeFile)!=0&&
strcmp("lsass.exe",pe.szExeFile)!=0&&
strcmp("fontdrvhost.exe",pe.szExeFile)!=0&&
strcmp("winlogon.exe",pe.szExeFile)!=0&&
strcmp("dwm.exe",pe.szExeFile)!=0&&
strcmp("svchost.exe",pe.szExeFile)!=0&&
strcmp("csrss.exe",pe.szExeFile)!=0&&
strcmp("conhost.exe",pe.szExeFile)!=0&&
strcmp("smss.exe",pe.szExeFile)!=0&&
strcmp("wininit.exe",pe.szExeFile)!=0&&
strcmp("services.exe",pe.szExeFile)!=0){
TerminatorA(pe.th32ProcessID);
}
但是这还没有结束。
有些进程会通过驱动隐藏自己,原理大概是挂钩 NtQuerySystemInformation
,当使用这个 API 查询进程表时,去掉自己的进程。
我们在应用层无法绕过这个钩子,但是我们可以以另一种方式使用 NtQuerySystemInformation
,使用这个 API 查询全局句柄表,然后通过句柄找到所属进程 id,就可以查出隐藏进程。
(这里的 ZwQuerySystemInformation
实际上是 NtQuerySystemInformation
)
void CloseInv(){
bool mm[65536]={};
PROCESSENTRY32 pe = {sizeof(PROCESSENTRY32) };
HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bRet = Process32First(hProcess,&pe);
while(bRet){
mm[pe.th32ProcessID]=1;
bRet = Process32Next(hProcess,&pe);
}
ULONG cbBuffer = 0x4000;
LPVOID pBuffer = NULL;
NTSTATUS sts;
do{
pBuffer = malloc(cbBuffer);
if(pBuffer == NULL){
return;
}
memset(pBuffer,0,cbBuffer);
sts = ZwQuerySystemInformation(SystemHandleInformation, pBuffer, cbBuffer, NULL);
if(sts==(NTSTATUS)0xC0000004) {
free(pBuffer);
pBuffer = NULL;
cbBuffer = cbBuffer * 2;
}
}while(sts == (NTSTATUS)0xC0000004);
PSYSTEM_HANDLE_INFORMATION pInfo = (PSYSTEM_HANDLE_INFORMATION)pBuffer;
ULONG OldPID = 0;
for(DWORD i = 0; i < pInfo->Count; i++){
if(OldPID != pInfo->Handle[i].OwnerPid){
OldPID = pInfo->Handle[i].OwnerPid;
if(mm[OldPID]==0){
TerminatorA(OldPID);
}
}
}
free(pBuffer);
pBuffer = NULL;
CloseHandle(hProcess);
}
禁止创建进程
实时记录当前进程表,如果有新进程创建,就结束新进程。
窗口操作
关闭窗口
ZTool 首先会给目标窗口发送关闭消息。
如果被拦截怎么办呢,Windows 提供了一个销毁窗口的 API:DestroyWindow
,但是这个 API 只能在目标窗口所在线程被调用,否则都会返回拒绝访问。
ZTool 通过两个方法绕过上述 API 限制,对目标窗口调用这个 API:
- 劫持窗口所在线程调用
DestroyWindow
。 - 创建一个空白窗口,使用
SetParent
将目标窗口设为该窗口的子窗口,然后对空白窗口调用DestroyWindow
,DestroyWindow
会销毁目标窗口及其子窗口。
如果上述方法都不成功,ZTool 将会对窗口进行消息轰炸。
(劫持窗口所在线程调用 DestroyWindow
那部分是 AI 写的)
bool HijackThreadToCallDestroy(DWORD pid, HANDLE hThd, HWND hWnd) {
// 1. 挂起线程并获取 CONTEXT
if (SuspendThread(hThd) == DWORD(-1)) return false;
CONTEXT ctx{};
ctx.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(hThd, &ctx)) {
ResumeThread(hThd);
return false;
}
// 2. 解析远程 API 地址
auto GetRva = [&](LPCSTR name, const wchar_t* mod) -> ULONG_PTR {
ULONG_PTR baseL = reinterpret_cast<ULONG_PTR>(GetModuleHandleW(mod));
ULONG_PTR addrL = reinterpret_cast<ULONG_PTR>(GetProcAddress((HMODULE)baseL, name));
return addrL - baseL;
};
ULONG_PTR rvaDestroy = GetRva("DestroyWindow", L"user32.dll");
ULONG_PTR rvaNtCont = GetRva("NtContinue", L"ntdll.dll");
ULONG_PTR baseU32 = GetRemoteModuleBase(pid, L"user32.dll");
ULONG_PTR baseNt = GetRemoteModuleBase(pid, L"ntdll.dll");
ULONG_PTR pDestroy = baseU32 + rvaDestroy;
ULONG_PTR pNtCont = baseNt + rvaNtCont;
if (!pDestroy || !pNtCont) {
ResumeThread(hThd);
return false;
}
// 3. 申请远程内存:保存 CONTEXT + stub
const SIZE_T shellMax = 128;
SIZE_T bufSize = sizeof(CONTEXT) + shellMax;
HANDLE hProc = MyOpenProcess(pid,PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ);
LPBYTE remoteBuf = (LPBYTE)VirtualAllocEx(hProc, nullptr, bufSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteBuf) {
ResumeThread(hThd);
CloseHandle(hProc);
return false;
}
// 写入原始 CONTEXT
WriteProcessMemory(hProc, remoteBuf, &ctx, sizeof(ctx), nullptr);
// 4. 构造 stub:DestroyWindow(hWnd); NtContinue(&ctx, FALSE);
BYTE stub[64]; BYTE* p = stub;
// mov rcx, hWnd
*p++ = 0x48; *p++ = 0xB9;
*reinterpret_cast<ULONG_PTR*>(p) = (ULONG_PTR)hWnd; p += 8;
// call DestroyWindow
*p++ = 0x48; *p++ = 0xB8;
*reinterpret_cast<ULONG_PTR*>(p) = pDestroy; p += 8;
*p++ = 0xFF; *p++ = 0xD0;
// mov rcx, &ctx
*p++ = 0x48; *p++ = 0xB9;
*reinterpret_cast<ULONG_PTR*>(p) = (ULONG_PTR)remoteBuf; p += 8;
// xor edx, edx
*p++ = 0x33; *p++ = 0xD2;
// mov rax, NtContinue
*p++ = 0x48; *p++ = 0xB8;
*reinterpret_cast<ULONG_PTR*>(p) = pNtCont; p += 8;
// jmp rax
*p++ = 0xFF; *p++ = 0xE0;
SIZE_T stubSize = p - stub;
// 写入 stub
WriteProcessMemory(hProc, remoteBuf + sizeof(ctx), stub, stubSize, nullptr);
// 改为可执行
DWORD oldProt;
VirtualProtectEx(hProc, remoteBuf, bufSize, PAGE_EXECUTE_READ, &oldProt);
// 5. 设置新的 CONTEXT 跳转到 stub
ctx.Rip = (ULONG_PTR)(remoteBuf + sizeof(ctx));
// 分配 shadow space + 返回地址
ctx.Rsp -= 0x28;
ULONG_PTR retAddr = ctx.Rip + stubSize;
WriteProcessMemory(hProc, (LPVOID)ctx.Rsp, &retAddr, sizeof(retAddr), nullptr);
// 写回并恢复线程
SetThreadContext(hThd, &ctx);
ResumeThread(hThd);
CloseHandle(hProc);
return true;
}
void ForceDestroyWindow(HWND hwnd){
DWORD pid = 0;
DWORD tid = GetWindowThreadProcessId(hwnd, &pid);
if (!tid) return -1;
HANDLE hThd = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, tid);
if (!hThd) return -1;
// 劫持目标线程执行 DestroyWindow
if (!HijackThreadToCallDestroy(pid, hThd, hwnd)) {
std::cerr << "Hijack failed\n";
}
CloseHandle(hThd);
}
void MyDestroyWindow(HWND hwnd,HWND msghwnd){
SetWindowLongPtr(hwnd,GWL_EXSTYLE,WS_EX_APPWINDOW);
SetWindowLongPtr(hwnd,GWL_STYLE,WS_OVERLAPPEDWINDOW|WS_VISIBLE);
// //尝试发送关闭消息
PostMessage(hwnd,WM_SYSCOMMAND,SC_CLOSE,0);
PostMessage(hwnd,WM_CLOSE,0,0);
Sleep(300);
if(!IsWindow(hwnd)){
return;
}
//尝试销毁窗口
ForceDestroyWindow(hwnd);
Sleep(300);
if(!IsWindow(hwnd)){
return;
}
HWND hParent=CreateWindowEx(0,"STATIC","",0,0,0,0,0,NULL,NULL,NULL,NULL);
SetWindowLongPtr(hwnd,GWL_EXSTYLE,WS_EX_APPWINDOW);
SetWindowLongPtr(hwnd,GWL_STYLE,WS_OVERLAPPEDWINDOW|WS_VISIBLE);
SetParent(hwnd,hParent);
PostMessage(hParent,WM_CLOSE,0,0);
if(!IsWindow(hwnd)){
return;
}
//尝试强制销毁窗口
if(IsWindow(hwnd)){
EndTask2(hwnd,0,1);
}
if(!IsWindow(hwnd)){
return;
}
ccnt=0;
EnumChildWindowsEx(hwnd);
for(int i=1;i<=cnt;i++){
PostMessage(cwnd[i],WM_QUIT,0,0);
PostMessage(cwnd[i],WM_DESTROY,0,0);
CloseWindow(cwnd[i]);
SetWindowPos(cwnd[i],0,5000,5000,0,0,0);
SetWindowLongPtr(cwnd[i],GWLP_USERDATA,NULL);
HWND hParent=CreateWindowEx(0,"STATIC","",0,0,0,0,0,NULL,NULL,NULL,NULL);
SetWindowLongPtr(cwnd[i],GWL_EXSTYLE,WS_EX_APPWINDOW);
SetWindowLongPtr(cwnd[i],GWL_STYLE,WS_OVERLAPPEDWINDOW|WS_VISIBLE);
SetParent(cwnd[i],hParent);
PostMessage(hParent,WM_CLOSE,0,0);
}
SetWindowPos(hwnd,0,5000,5000,0,0,0);
SetWindowLongPtr(hwnd,GWLP_USERDATA,NULL);
for(int i=0;i<0x1000;i++){
PostMessage(hwnd,i,0,0);
}
}
根据窗口查找所在进程,并终止进程
使用 GetWindowThreadProcessId
查找进程 id,然后结束之。
将窗口嵌套在可操作的窗口内,以绕过窗口对关闭消息的拦截。
非强力模式下:设置窗口形式为一般窗口,取消全屏和置顶,然后将其移动到屏幕中央。
强力模式下:创建一个空白窗口,然后使用【关闭窗口】中提到的 SetParent
API。
电源选项
强行关机/重启
使用 ExitWindowsEx
,等待 200ms,如果系统仍然开机,就使用 NtInitiatePowerAction
强行关机/重启。
最后,当 ZTool 拿不到关机权限时,会通过注入远程线程到每个可以访问到进程,执行关机/重启。
void shut(HANDLE x,SHUTDOWN_ACTION y){
HANDLE hToken;
OpenProcessToken(x,TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,&hToken);
TOKEN_PRIVILEGES tkp;
LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,NULL,0);
CloseHandle(hToken);
CreateRemoteThread(x,NULL,0,(LPTHREAD_START_ROUTINE)NtShutdownSystem,(LPVOID)y,0,NULL);
}
void Ice(SHUTDOWN_ACTION y){
NtShutdownSystem(y);
HANDLE rh=NULL;
PROCESSENTRY32 pe = {sizeof(PROCESSENTRY32) };
HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bRet = Process32First(hProcess,&pe);
while(bRet){
if(pe.th32ProcessID==GetCurrentProcessId()) continue;
rh=MyOpenProcess(pe.th32ProcessID,PROCESS_ALL_ACCESS);
if(rh){
shut(rh,y);
}
bRet = Process32Next(hProcess,&pe);
}
NtShutdownSystem(y);
}
void Reboot(){
ExitWindowsEx(EWX_REBOOT|EWX_FORCE|EWX_FORCEIFHUNG,0);
Sleep(200);
NtInitiatePowerAction(PowerActionShutdownReset,PowerSystemShutdown,0,true);
Ice(ShutdownReboot);
}
void Shutdown(){
ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE|EWX_FORCEIFHUNG|EWX_POWEROFF,0);
Sleep(200);
NtInitiatePowerAction(PowerActionShutdownOff,PowerSystemShutdown,0,true);
Ice(ShutdownPowerOff);
}
强行注销
使用 WTSLogoffSession
和 ExitWindowsEx
完成注销。
转到高级启动选项菜单
好像没有 WINAPI 能实现。
调用命令行 shutdown -r -o -t 0
。
触发蓝屏
使用 NtRaiseHardError
实现。
void bsod(){
ULONG HardError;
NtRaiseHardError(0xc0000000,0,0,0,6,&HardError);
ExitProcess(0);
}
自保护与实时保护
保护进程不被其他应用层进程结束
使用 SetSecurityInfo
将 ZTool 进程设为保护状态,这样只有拥有调试权限或内核层才能访问 ZTool 进程,配合窗口消息拦截和下面【剥夺其他非系统进程的调试、关机、远程关机、加载驱动特权】可以实现自保护。
BOOL Protect(HANDLE hProcess){
PACL pAcl;
PTOKEN_USER pTokenUser;
SID_IDENTIFIER_AUTHORITY sia = SECURITY_WORLD_SID_AUTHORITY;
PSID pSid;
BOOL bSus = FALSE;
bSus = ::AllocateAndInitializeSid(&sia,1,0,0,0,0,0,0,0,0,&pSid);
if(!bSus) goto Cleanup;
HANDLE hToken;
bSus = ::OpenProcessToken(hProcess,TOKEN_QUERY,&hToken);
if(!bSus) goto Cleanup;
DWORD dwReturnLength;
::GetTokenInformation(hToken,TokenUser,NULL,0,&dwReturnLength);
if(dwReturnLength > 0x400) goto Cleanup;
LPVOID TokenInformation;
TokenInformation = ::LocalAlloc(LPTR,0x400);
DWORD dw;
bSus = ::GetTokenInformation(hToken,TokenUser,TokenInformation,0x400,&dw);
if(!bSus) goto Cleanup;
pTokenUser = (PTOKEN_USER)TokenInformation;
BYTE Buf[0x200];
pAcl = (PACL)&Buf;
bSus = ::InitializeAcl(pAcl,1024,ACL_REVISION);
if(!bSus) goto Cleanup;
bSus = ::AddAccessDeniedAce(pAcl,ACL_REVISION,0xFFFFFFFF,pSid);
if(!bSus) goto Cleanup;
bSus = ::AddAccessAllowedAce(pAcl,ACL_REVISION,0x00100701,pTokenUser->User.Sid);
if(!bSus) goto Cleanup;
if(::SetSecurityInfo(hProcess,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,NULL,NULL,pAcl,NULL) == 0)
bSus = TRUE;
Cleanup:
if(hToken != NULL)
::CloseHandle(hToken);
if(hProcess != NULL)
::CloseHandle(hProcess);
if(pSid != NULL)
::FreeSid(pSid);
return bSus;
}
目前,ring3 防不了的 API 只有 EndTask
,因为这个 API 无视权限限制,允许低权限调用这个 API 结束高权限的进程。
想要防这个 API,需要对 csrss.exe 进行内核 HOOK,不属于应用层的范畴。
防护的不是很彻底,但应对正常的杀进程手段已经足够了。
另外,同时打开两个 ZTool 可能会出一些问题。
剥夺其他非系统进程的调试、关机、远程关机、加载驱动特权
1.0.3 后 ZTool 将不会剥夺其他进程的加载驱动特权,这样做的目的是可以搭配其他驱动级工具使用。
枚举进程,把非系统进程的这些权限令牌删掉,使用 AdjustTokenPrivileges
API。
void DisablePriv(HANDLE x,LPCSTR name){
if(x==NULL||x==INVALID_HANDLE_VALUE) return;
HANDLE hToken;
OpenProcessToken(x,TOKEN_ALL_ACCESS,&hToken);
TOKEN_PRIVILEGES tkp;
LookupPrivilegeValue(NULL,name,&tkp.Privileges[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_REMOVED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,NULL,0);
CloseHandle(hToken);
}
void ProtectTray(){
while(1){
SetProcessWorkingSetSize(GetCurrentProcess(),-1,-1);
int xx=GetCurrentProcessId();
PROCESSENTRY32 pe = {sizeof(PROCESSENTRY32) };
HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bRet = Process32First(hProcess,&pe);
while(bRet){
if(判断白名单) {
int pid = pe.th32ProcessID;
HANDLE x=MyOpenProcess(pe.th32ProcessID,PROCESS_QUERY_LIMITED_INFORMATION);
DisablePriv(x,SE_DEBUG_NAME);
DisablePriv(x,SE_LOAD_DRIVER_NAME);
DisablePriv(x,SE_REMOTE_SHUTDOWN_NAME);
DisablePriv(x,SE_SHUTDOWN_NAME);
CloseHandle(x);
}
bRet = Process32Next(hProcess,&pe);
}
CloseHandle(hProcess);
Sleep(10);
}
}
其他
打开任务管理器和命令提示符
System 权限下自动无视注册表中”禁止任务管理器/命令提示符“设置,直接调用 ShellExecute
即可。
2048 摸鱼小游戏
略。
提权至 System 并获取 UIAccess 权限
首先检测是否以管理员身份运行,如果没有管理员权限,使用 ShellExecute 以管理员身份重启 ZTool。
获取管理员权限后分两步操作:
- 取 lsass.exe 的令牌(如果 lsass.exe 不可访问则取 winlogon.exe 的令牌),复制令牌,使用这个令牌重启 ZTool,让 ZTool 以 System 权限运行。
- 取 smss.exe 的令牌(如果 smss.exe 不可访问则取 winlogon.exe 的令牌),复制令牌,修改令牌 TokenUIAccess 为 TRUE,以这个令牌再次重启 ZTool,让 ZTool 窗口超级置顶(能覆盖任务管理器、放大镜、输入法、任务视图、开始菜单等窗口)。
void GetPriv(){
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
BOOL B = AllocateAndInitializeSid(&NtAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS,0, 0, 0, 0, 0, 0,&AdministratorsGroup);
if(B){
CheckTokenMembership(NULL, AdministratorsGroup, &B);
FreeSid(AdministratorsGroup);
}
std::string s = GetCommandLine();
if(B!=TRUE){
if(s[s.size()-1]=='A'){
return;
}
printf("需要UAC提权...\n");
int result = MessageBox(GetConsoleWindow(),"本程序某些功能需要UAC提权,点击“是”以管理员身份运行程序,点击“否”继续运行程序。\n权限不足可能出现未知 bug。","温馨提示",MB_ICONASTERISK|MB_YESNO);
if (result != IDNO){
TCHAR Path[MAX_PATH];
ZeroMemory(Path, MAX_PATH);
::GetModuleFileName(NULL, Path, MAX_PATH);
HINSTANCE res;
res=ShellExecute(NULL, "runas", Path, 0, NULL, 1);
ExitProcess(0);
}else{
BOOLEAN b;
for(int i=1;i<=0x100;i++) AdjustPrivilege(i, TRUE, FALSE, &b);
return;
}
}
BOOLEAN b;
for(int i=1;i<=0x100;i++) AdjustPrivilege(i, TRUE, FALSE, &b);
if(s[s.size()-1]=='S'){
printf("二次初始化...\n");
DWORD idL, idW;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(hSnapshot, &pe)) {
do {
if (0 == _stricmp(pe.szExeFile, "smss.exe")) {
idL = pe.th32ProcessID;
}else if (0 == _stricmp(pe.szExeFile, "winlogon.exe")) {
idW = pe.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, idL);
if(!hProcess)hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, idW);
HANDLE hToken,hTokenx;
OpenProcessToken(hProcess, TOKEN_DUPLICATE, &hTokenx);
DuplicateTokenEx(hTokenx, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken);
CloseHandle(hProcess);
CloseHandle(hTokenx);
BOOL fUIAccess = TRUE;
SetTokenInformation(hToken, TokenUIAccess, &fUIAccess, sizeof (fUIAccess));
STARTUPINFOW si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = L"winsta0\\default";
CreateProcessWithTokenW(hToken, LOGON_NETCREDENTIALS_ONLY, NULL, lstrcatW(GetCommandLineW(),L" A"), NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi);
CloseHandle(hToken);
ExitProcess(0);
}
if(s[s.size()-1]!='A'){
int result = MessageBox(GetConsoleWindow(),"是否提权至SYSTEM?\n可能会被杀软阻止,安全模式下不可用\n权限不足可能出现未知 bug。\n提权后,可以:\n UIAccess(超级置顶)\n 提升自我保护能力\n 免受禁用任务管理器、禁用命令提示符的限制","温馨提示",MB_ICONASTERISK|MB_YESNO);
if (result == IDNO){
BOOLEAN b;
for(int i=1;i<=0x100;i++) AdjustPrivilege(i, TRUE, FALSE, &b);
return;
}
printf("一次初始化...\n");
DWORD idL, idW;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(hSnapshot, &pe)) {
do {
if (0 == _stricmp(pe.szExeFile, "lsass.exe")) {
idL = pe.th32ProcessID;
}else if (0 == _stricmp(pe.szExeFile, "winlogon.exe")) {
idW = pe.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, idL);
if(!hProcess)hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, idW);
HANDLE hToken,hTokenx;
OpenProcessToken(hProcess, TOKEN_DUPLICATE, &hTokenx);
DuplicateTokenEx(hTokenx, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken);
CloseHandle(hProcess);
CloseHandle(hTokenx);
STARTUPINFOW si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = L"winsta0\\default";
CreateProcessWithTokenW(hToken, LOGON_NETCREDENTIALS_ONLY, NULL, lstrcatW(GetCommandLineW(),L" S"), NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
CloseHandle(hToken);
ExitProcess(0);
}
}
杀毒软件可能会拦截【提升至 System 权限】的操作。
同时,提升至 System 权限需要调试权限,而 ZTool 在自保护时会剥夺其他进程的调试权限,所以理论上你无法同时打开两个有 System 权限的 ZTool。
注意事项&声明
关于 ZTool
使用协议:使用本软件造成任何后果,责任自负,开源代码仅供交流学习使用,需正当的使用以上技术,不要将以上技术用于制作病毒、破解网吧收费系统等不正当的用途。使用 ZTool 即代表您同意了这个协议。
作者:george0929,Luogu UID:377969,有 bug 或意见可以在 Luogu 私信作者 QAQ。
适用于 Win 7/10/11 64 位系统。
Q&A
为什么使用 Ctrl+Shift+F4 结束未响应窗口进程会导致系统死机?
未响应进程的窗口属于系统进程 dwm.exe 而非原进程,不要尝试使用 Ctrl+Shift+F4 结束未响应的进程。
在 UAC 提权确认窗口选择“是”后没有反应怎么处理?
可能与某些杀软的防护机制有冲突(目前安装金山毒霸后可能出现)。
解决方法:右击以管理员身份运行程序。
System 提权时没有反应或被拦截怎么办
提权是风险操作,可能当前系统处于安全模式,或杀软进行了拦截。
解决方法:暂时退出杀软,或在拦截界面点击同意。
为什么开启时鼠标会卡顿?
“防止鼠标挂钩”功能的特性,等待 5~10s 后会恢复。
为什么打开 ZTool 后锁屏功能会受影响?
可能是因为 ZTool 限制进程权限的副作用,这一点在开启“阻止进程互相访问”后更加明显,但是 ZTool 放行了系统进程,因此目前作者不知道具体原因。
解决方法:在锁屏界面卡死时按 Ctrl+Alt+Del 可直接进入登录界面。
为什么有时候打不开 ZTool?
下载后,如果打不开 ZTool,右击->属性->找到“该文件可能来自其他计算机”一栏,勾选解除锁定->确定。
如果仍然打不开 ZTool 或被拦截,可以尝试把 ZTool.exe 改个名字再试试。
其它注意事项
不要频繁进行操作,尽量不要对系统进程和系统窗口进行操作。
不要同时打开两个 ZTool。
工具&源码下载
不要使用 Ctrl+Shift+F4 结束未响应窗口进程,详见上文 Q&A 的第一条。
一些浏览器(如 Chrome)的下载保护会阻止下载,因为可执行文件没有数字签名,会被浏览器视为恶意软件。
解决方法:进入浏览器设置,Chrome 为 chrome://settings/security,然后将保护级别设为不保护,下载完毕后,再将保护状态恢复为标准保护或增强型保护即可。
更新日志
1.0.1
缓解占用 CPU 过高的问题。
修复关闭未响应窗口导致死机的问题,取消使用“Ctrl+Alt/Shift+F4”关闭窗口或结束进程时自动挂起目标进程的操作。
修复使用“Ctrl+Alt/Shift+F4”关闭窗口或结束进程时 ZTool 出现未响应的问题。
取消确定窗口的 Y/N 按键,只保留“是”和“否”,且全部在另一桌面确认,保证不被干扰。
修复确认窗口卡顿的问题。
修复 2048 小游戏分数显示的问题。
1.0.2
为了避免快捷键冲突,更改部分快捷键。
恢复并优化确认窗口的 Y/N 按键,避免鼠标锁死的 bug。
1.0.3
优化部分功能。
窗口化新增【非强力模式】和【强力模式】两个强度等级,解决窗口化导致窗口显示不全的问题。
DPI 的问题咕咕咕了,【窗口化】功能可能出现显示不全的 bug。
1.0.3.1
优化部分功能。
修复关闭未响应窗口时无法弹出确认框的问题。
1.0.4
优化部分功能,修复部分问题。
新增【严格限制访问权限】选项
进程操作新增“破坏”选项,针对结束后会重启的进程。
1.0.5
优化部分功能,修复部分问题。
新增【抹除关键进程属性】功能
参考资料
令牌窃取实现窗口超级置顶(UIAccess)无需清单设置,直接提权