Windows程序多开一般处理

一般部分程序为了利益可能会防止你多开,所以我这里介绍一下自己摸索半年学会的一些知识点并结合某款市面上现在的游戏进行讲解.

我们要实现多开前,一般需要知道如何防止多开.

如何防止多开

文件共享

通过文件共享判断文件是否存在,如果存在即为多开.(弊端:可能程序crash导致文件句柄没有得到释放)

内部网络

通过建立Server socket进行IPC通信;

命名管道

通过建立NamedPipe进行IPC通信;(目前笔者接触到的程序大部分用的NamedPipe操作的)

//CreateNamedPipe函数创建一个命名管道的实例,并返回一个后续管道操作的句柄。命名管道服务器进程使用此函数来创建特定命名管道的第一个实例,并建立其基本属性或创建现有命名管道的新实例。HANDLE CreateNamedPipe(LPCTSTR 【lpName】,  //指向管道名称的指针
DWORD 【dwOpenMode】, //管道打开模式
DWORD 【dwPipeMode】, //管道特定模式
DWORD 【nMaxInstances】,  //最大实例数
DWORD 【nOutBufferSize】, //输出缓冲区大小,以字节为单位
DWORD 【nInBufferSize】,  //输入缓冲区大小,以字节为单位
DWORD 【nDefaultTimeOut】,    //超时时间,以毫秒为单位
LPSECURITY_ATTRIBUTES 【lpSecurityAttributes】    //指向安全属性结构的指针
);
  • 通过互斥体检测;
//CreateMutex函数创建一个命名或未命名的互斥对象。HANDLE CreateMutex(LPSECURITY_ATTRIBUTES 【lpMutexAttributes】,  //指向安全属性的指针
BOOL 【bInitialOwner】,   //标志初始所有权
LPCTSTR 【lpName】    //指向mutex对象名称的指针
);
  • 通过OpenProcess;
//OpenProcess函数返回现有进程对象的句柄。HANDLE OpenProcessDWORD 【dwDesiredAccess】,    //访问标志
BOOL 【bInheritHandle】,  //处理继承标志
DWORD 【dwProcessId】 //进程标识符en
);
  • 通过FindWindow;
//FindWindow函数检索顶级窗口的句柄,其类名和窗口名称与指定的字符串相匹配。此函数不搜索子窗口。HWND FindWindow(LPCTSTR 【lpClassName】,    //指向类名的指针
LPCTSTR 【lpWindowName】  //指向窗口名称的指针
);

等等常见方式

多开方式有哪些

我们从上面知道了如何防止多开,那么我们就可以实现如何实现多开,任何东西都是相对的,而攻防也异如此.

以下代码均采用Detours库实现hook. 库连接:Detours库

OpenProcess函数处理

通过HOOK NtOpenProcess实现进程判断,一般程序调用OpenProcess判断进程ID与之前是否相同等:

bool Hook_NtOpenProcess(bool enable){// 自定义下NtOpenProcess函数类型,包含返回值,参数类型等,  因为这个函数不是标准函数,也不是公开的函数,所以需要我们自己定义类型.设计到Nt都需要这样.// 网上都可以查到Nt相关函数原型.typedef NTSTATUS(NTAPI* NtOpenProcess_t)(PHANDLE ProcessHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);// 通过ntdll找到NtOpenProcess的地址,所以这儿的_NtOpenProcess是原函数地址static NtOpenProcess_t _NtOpenProcess = reinterpret_cast<NtOpenProcess_t>(GetProcAddress(GetModuleHandleA("ntdll"), "NtOpenProcess"));NtOpenProcess_t NtOpenProcess_hook = [](PHANDLE ProcessHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) -> NTSTATUS{#ifdef IS_NEED_VMVMProtectBegin("Hook_NtOpenProcess");
#endif // IS_NEED_VM// 某进程只能访问自己游戏进程和NGS进程还有游戏聊天进程,否则一律拒绝,这样可以解决多开的时候第二个窗口能启动起来,因为游戏在启动的时候会去取系统进程ID,如果找到有游戏的进程,// 就会给它关了,然后重启.if (ClientId && ClientId->UniqueProcess != 游戏进程ID && ClientId->UniqueProcess != NGS进程ID && ClientId->UniqueProcess != 游戏聊天进程ID) {#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:break;case ALL_LOG:MyDbgPrintFun("Hook_NtOpenProcess STATUS_ACCESS_DENIED:%d", ClientId->UniqueProcess);break;}
#endif // DEBUG_TYPEreturn STATUS_ACCESS_DENIED;}
#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:break;case ALL_LOG:MyDbgPrintFun("Hook_NtOpenProcess 放行:%d, NGS进程ID:%d, 游戏进程ID:%d, 游戏聊天进程ID:%d", ClientId->UniqueProcess, NGS进程ID, 游戏进程ID, 游戏聊天进程ID);break;}
#endif // DEBUG_TYPE#ifdef IS_NEED_VMVMProtectEnd();
#endif // IS_NEED_VM// 放行,调用原函数即可return _NtOpenProcess(ProcessHandle, AccessMask, ObjectAttributes, ClientId);};// Hook原函数地址指向NtOpenProcess_hook函数地址return DetourFunction(enable, reinterpret_cast<void**>(&_NtOpenProcess), NtOpenProcess_hook);}

FindWindow函数处理

通过Hook FindWIndow:

// 该Hook在游戏初始化的时候会调用两次,分别进行:1.类名;2.标题 查找; 我们将返回空即过掉该检测
bool Hook_FindWindow(bool enable){static decltype(&FindWindow) FindWindow_Old = FindWindow;decltype(&FindWindow) FindWindow_Hook = [](LPCWSTR lpClassName, LPCWSTR lpWindowName) -> HWND{#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:case ALL_LOG:if ((lpWindowName && wcsstr(lpWindowName, L"XXX Client")) || (lpClassName && wcsstr(lpClassName, L"XXX Client")))MyDbgPrintFun("Hook_FindWindow_Kart:%S,%S", lpClassName, lpWindowName);break;}
#endif // DEBUG_TYPEreturn 0;//不去查找了,全部找不到};return DetourFunction(enable, reinterpret_cast<void**>(&FindWindow_Old), FindWindow_Hook);}

CreateMutex函数处理

通过HOOK CreateMutex实现:

bool Hook_CreateMutexW(bool enable){static decltype(&CreateMutexW) CreateMutexW_Old = CreateMutexW;decltype(&CreateMutexW) CreateMutexW_Hook = [](LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName) -> HANDLE{#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:break;case ALL_LOG:MyDbgPrintFun("Hook_CreateMutexW_Kart:%S", lpName);break;}
#endif // DEBUG_TYPE#ifdef IS_NEED_VMVMProtectBegin("Hook_CreateMutexW");
#endif // IS_NEED_VMwchar_t *mutextObjectName = new wchar_t[lstrlenW(lpName) + 11];lstrcpyW(mutextObjectName, lpName);if (wcsstr(mutextObjectName, L"Global\\vcpkgsrvmgr")) {//通过DebugView可以查看到该句柄,一般我们可以通过CloseHandle去关闭即可,但是由于该游戏检测了该句柄是否存在,所以我们随机命名该句柄++createTimes;if (createTimes > 2) {createTimes = 3;return FALSE;}wsprintf(mutextObjectName, TEXT("Global\\vcpkgsrvmgr_%d%d"), 游戏进程ID, 游戏线程ID);return CreateMutexW_Old(lpMutexAttributes, bInitialOwner, mutextObjectName);}#ifdef IS_NEED_VMVMProtectEnd();
#endif // IS_NEED_VMreturn CreateMutexW_Old(lpMutexAttributes, bInitialOwner, lpName);};return DetourFunction(enable, reinterpret_cast<void**>(&CreateMutexW_Old), CreateMutexW_Hook);}

通过以上三点hook,我们大致可以实现多开了.但是某游戏在多开后五分钟会断开连接,经过Debug是因为多开后导致游戏保护不能跟游戏客户端建立连接从而客户端没有心跳而被服务器踢下去, 所以我们还需要进一步处理;

通过Debug及HOOK 一些API观察到该游戏与保护是通过命名管道进行通信的.所以我们需要查阅一些命名管道的知识,这里笔者不再阐述一些命名管道的基础.

命名管道处理

一般命名管道分为客户端及服务端,在这里,游戏客户端是命名管道的客户端(后叫NamedClient),保护进程是命名管道的服务端(后叫NamedServer),所以我们需要HOOK NamedServer的CreateNamedPipe. 但是要想HOOK到这个函数,就必须在NamedServer进程里面去HOOK就得在NamedClient里面去HOOK CreateProcess去拦截, 同样NamedClient也需要去HOOK 连接对应的命名管道的名字的API,这样客户端与服务端才能建立通信.

一旦我们勾到CreateNamedPipe在NamedServer里面后,具体实现如下:

bool Hook_CreateNamedPipe(bool enable){typedef HANDLE(WINAPI* CreateNamedPipeW_t)(LPCWSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize,DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes);static CreateNamedPipeW_t CreateNamedPipeW_old = reinterpret_cast<CreateNamedPipeW_t>(GetProcAddress(GetModuleHandleA("KernelBase"), "CreateNamedPipeW"));CreateNamedPipeW_t CreateNamedPipeW_hook = [](LPCWSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize,DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes)->HANDLE{#ifdef IS_NEED_VMVMProtectBegin("Hook_CreateNamedPipe");
#endif // IS_NEED_VM#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:case ALL_LOG:MyDbgPrintFun("Hook_CreateNamedPipe_NGS:%S, nMaxInstances:%d", lpName, nMaxInstances);break;}
#endif // DEBUG_TYPE// 即核心思想是: 取当前NamedServer的进程ID或者NamedClient的进程ID来做为重新命名的命名管道if (wcsstr(lpName, L"\\\\.\\pipe\\XXXX")){const wchar_t *theBCNumberPoint = wcsstr(lpName, L"1"); // 命名管道特征lpName = L"\\\\.\\pipe\\";int 追加游戏进程BufferSize = 1 + log10(游戏进程ID);追加游戏进程BufferSize += 1 + log10(游戏线程ID);追加游戏进程BufferSize += 13;//XXXX\\占13个字符追加游戏进程BufferSize += lstrlenW(theBCNumberPoint) + 1;// 将XX每个服务器的命名拷贝回来wchar_t *追加游戏进程ID = new wchar_t[追加游戏进程BufferSize];StringCchPrintfW(追加游戏进程ID, 追加游戏进程BufferSize, L"%d%dXXXX\\", 游戏进程ID, 游戏线程ID);StringCchCatW(追加游戏进程ID, 追加游戏进程BufferSize, theBCNumberPoint);wchar_t *newObjectName = new wchar_t[lstrlenW(lpName) + 追加游戏进程BufferSize + 1];wmemset(newObjectName, 0, lstrlenW(lpName) + 追加游戏进程BufferSize + 1);StringCchCopyNW(newObjectName, lstrlenW(lpName) + 追加游戏进程BufferSize + 1, lpName, lstrlenW(lpName) + 1);StringCchCatW(newObjectName, lstrlenW(lpName) + 追加游戏进程BufferSize + 1, 追加游戏进程ID);
#ifdef DEBUG_TYPEswitch (DEBUG_TYPE){case KART_NGS_LOG:case ALL_LOG:MyDbgPrintFun("Hook_CreateNamedPipe_NGS:%S,%d, dwPipeMode:%d, dwOpenMode:%d", newObjectName, lstrlenW(newObjectName), dwPipeMode, dwOpenMode);break;}
#endif // DEBUG_TYPEreturn CreateNamedPipeW_old(newObjectName, dwOpenMode, dwPipeMode, nMaxInstances, nOutBufferSize, nInBufferSize, nDefaultTimeOut, NULL);}
#ifdef IS_NEED_VMVMProtectEnd();
#endif // IS_NEED_VMreturn CreateNamedPipeW_old(lpName, dwOpenMode, dwPipeMode, nMaxInstances, nOutBufferSize, nInBufferSize, nDefaultTimeOut, lpSecurityAttributes);};return DetourFunction(enable, reinterpret_cast<void**>(&CreateNamedPipeW_old), CreateNamedPipeW_hook);}

开源地址:源码地址

以上内容请勿使用非法用途,造成后果与本人无关.若有侵权,请联系笔者删除.

Windows程序多开一般处理相关推荐

  1. Windows电脑软件程序多开软件的简单介绍:

    Windows电脑软件程序多开软件的简单介绍: 软件多开器(又叫程序多开器)即是一个虚拟系统程序,允许你在隔离环境中运行浏览器或其他程序,因此运行所产生的变化可以随后删除.它创造了一个类似隔离的独立作 ...

  2. Windows防止程序多开并在多开时弹出已运行程序

    Windows下防止程序多开并在多开时弹出已运行的程序 1.功能说明 在Windows客户端开发时,往往需要禁止客户多开程序的情况,并且在客户再次双击启动图标时显示已启动的程序界面.故而需要下面的功能 ...

  3. windows程序移植linux

    1,路径名统一用正斜杠"/".(windows下正反斜杠都识别,linux只认正斜杠.) 2,统一使用UTF-8格式编码. vim中无法保存汉字时,可输入下列命令: :set fi ...

  4. jquery程序 windows移植到linux显示不了,windows程序移植linux

    1,路径名统一用正斜杠"/".(windows下正反斜杠都识别,linux只认正斜杠.) 2,统一使用UTF-8格式编码. vim中无法保存汉字时,可输入下列命令: :set fi ...

  5. 获取windows程序界面数据

    文章目录 结论 可以尝试的其他方向 最后的挣扎 基础知识 win7/win10查看某个进程的句柄数 句柄的概念 Windows MFC.SDK和API的区别和联系 Windows MFC(C++开发) ...

  6. Windows编程 Windows程序的生与死(下)

    再谈程序之"死" 记得在第二回中我对程序的"死"只是一句话带过,因为我还没有铺垫好,好了现在我们可以详细的分析一下这个过程了. 这还要从while消息循环说起, ...

  7. Windows编程 第四回 Windows程序的生与死(下)

    致歉 很抱歉,读者看到的这篇文章将是我写的最没什么条理的一篇了,由于这一块儿内容是所有Windows程序的核心与基础,所以我分了三回来写.我想讲 得既易懂有又丰富,无奈本人才疏学浅,每每一提笔就要查很 ...

  8. 真正理解微软Windows程序运行机制——窗口机制(第一部分)

    我是荔园微风,作为一名在IT界整整25年的老兵,今天说说Windows程序的运行机制.经常被问到MFC到底是一个什么技术,为了解释这个我之前还写过帖子,但是很多人还是不理解.其实这没什么,我在学生时代 ...

  9. Windows程序员初学Linux内核(附Linux内核各版本历史纪年表)

    我是荔园微风,作为一名在IT界整整25年的老兵,最近受邀给年轻人讲了一场Windows内核和Linux内核相关的讲座.大家听得非常认真.下面我把其中一些PPT放上来和大家分享. Windows内核(右 ...

最新文章

  1. 【STL源码剖析读书笔记】【第5章】关联式容器之hashtable
  2. win8iis和php,Win8下IIS装PHP扩展
  3. WPF界面UI设计开发心得
  4. php url乱码java接收,java中url乱码解决方法
  5. mysql datasource.url_SpringBoot配置数据源DataSource
  6. 需求规格说明书5.0版本
  7. 【C#进阶3-4】C#设计模式
  8. TFIDF与BM25
  9. GEE实现夜光遥感数据分析
  10. HHL论文第一弹(总结算法基本思想、QRAM制备量子态)
  11. 抖音壁纸小程序怎么做?手把手教你0元拥有自己的壁纸小程序
  12. Web服务器及性能优化
  13. 77 关于 java.util.concurrent.RejectedExecutionException:Thread limit exceeded replacing blocked worker
  14. Guns二次开发(十二):自定义图片上传插件
  15. 带你了解“不拘一格去创新,别出心裁入场景”的锐捷
  16. git cherry-pick 的时候出现git cherry-pick xxx  fatal: bad object xxx
  17. fanyibishe
  18. 解决“适配器 无线网络/本地网络 可能出现问题”
  19. dell远程控制卡的原理
  20. 在 VS Code 中使用 Git

热门文章

  1. 你应该知道的ChatGPT提示语
  2. ftp传输最后一直卡在最后10秒_迅雷下载总卡在最后99 9%详细处理方法【图文】...
  3. java+opencv修改白色背景为图片
  4. html overflow隐藏滚动条,html – 隐藏滚动条,但仍然可以滚动
  5. webpack file-loader和url-loader
  6. phaser运用中,dota战术板
  7. 记一次挖洞实战经历(高危信息泄露)
  8. java毕业设计选题方向_一份java方向毕业设计题目分享学习_东哥毕设
  9. window.showModalDialog 窗口刷新本窗口
  10. 简单分享Maven中的groupId和artifactId