ZTool 应用层远控对抗工具

ZTool 应用层远控对抗工具

功能简介

用途

对抗远程控制程序、本地访问限制程序、全屏锁屏恶搞程序、以及部分通过循环置顶锁屏的勒索程序。

比如各类远控病毒、安装在本地的控制软件、电子教室和机房管理助手程序、各类“青少年守护”、假蓝屏恶搞、还有什么“不叫爸爸不让退出”之类的恶搞整蛊等等。

基本远控对抗

  1. 阻止其他应用挂钩控制鼠标键盘
  2. 防止截屏录屏捕获 ZTool 窗口
  3. ZTool 窗口始终保持置顶,System 权限下为 UIAccess 超级置顶

进程操作

  1. 根据进程名或 pid 结束进程
  2. 根据进程名或 pid 挂起/恢复 进程
  3. 根据进程名或 pid 将进程权限降为 Untrusted
  4. 关闭所有非系统进程
  5. 禁止创建进程

可选是否启用”强力模式“。

窗口操作

  1. 关闭窗口
  2. 根据窗口查找所在进程,并终止进程
  3. 将窗口嵌套在可操作的窗口内,以绕过窗口对关闭消息的拦截。

电源选项

  1. 强行关机/重启/注销
  2. 触发蓝屏

自保护与实时保护

  1. 保护进程不被其他应用层进程结束
  2. 剥夺其他非系统进程的调试、关机、远程关机、加载驱动特权

其他

  1. 打开 System 权限的任务管理器和命令提示符
  2. 自带 2048 摸鱼小游戏
  3. 自动提权,打开时可选是否让 ZTool 提权至 System 并获取 UIAccess 权限。

原理简介

基本远控对抗

阻止其他应用挂钩控制鼠标键盘

目前远控程序控制鼠标键盘的方式有两种:

  1. 使用 SetWindowsHookEx 注册鼠标键盘钩子,实现屏蔽鼠标键盘的操作。
  2. 使用 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 采用了这些方法:

  1. 向进程所在窗口发送关闭消息
  2. 正常方法(使用NtOpenProcess和NtTerminateProcess)
  3. 将进程附加到 JobObject 并终止
  4. 远程线程注入 ExitProcess
  5. 以 NtDuplicateHandle 代替 NtOpenProcess,然后走正常方法。
  6. 关闭进程所有句柄
  7. 结束进程所有线程
  8. 劫持进程所有线程执行 ExitProcess
  9. 将进程所有内存页设为不可访问状态
  10. 使用 NtDebugActiveProcess 结束进程
  11. 卸载进程的 ntdll 模块
  12. 使用 NtGetNextProcess 代替 NtOpenProcess,然后走正常方法。
  13. 远程线程注入到 lsass.exe,在 lsass.exe 中使用 DebugActiveProcess 结束进程。
  14. 降权进程后对进程所在窗口进行消息轰炸。
  15. 使用 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:

  1. 劫持窗口所在线程调用 DestroyWindow
  2. 创建一个空白窗口,使用 SetParent 将目标窗口设为该窗口的子窗口,然后对空白窗口调用 DestroyWindowDestroyWindow 会销毁目标窗口及其子窗口。

如果上述方法都不成功,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);
}

强行注销

使用 WTSLogoffSessionExitWindowsEx 完成注销。

转到高级启动选项菜单

好像没有 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。

获取管理员权限后分两步操作:

  1. 取 lsass.exe 的令牌(如果 lsass.exe 不可访问则取 winlogon.exe 的令牌),复制令牌,使用这个令牌重启 ZTool,让 ZTool 以 System 权限运行。
  2. 取 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,然后将保护级别设为不保护,下载完毕后,再将保护状态恢复为标准保护或增强型保护即可。

ZTool1.0.0 下载

ZTool1.0.1 下载

ZTool1.0.2 下载

ZTool1.0.3 下载

ZTool1.0.3.1 下载

ZTool1.0.4 下载

ZTool1.0.5 下载

更新日志

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

优化部分功能,修复部分问题。

新增【抹除关键进程属性】功能

参考资料

ProcessHacker工具中对于进程结束的具体实现代码

结束进程的12种方法

Ring3下实现进程保护,不用hook

令牌窃取实现窗口超级置顶(UIAccess)无需清单设置,直接提权

\(\color{blue}{\to}\)ZTool最新版下载链接

posted @ 2025-07-26 21:43  george0929  阅读(99)  评论(0)    收藏  举报