【外挂编程】外挂编程技术揭秘(一)
引言:本人并不是这方面专业的人员,我是一名PHP工程师。写这个主题,源自于我的“初心”。在最初我没有接触技术的时候,那会我是个网虫,刚开始也学习一些黑客相关的知识,当用到一些工具时,就很好奇这些东西是怎么做的。我就特别想去写一些相关工具和外挂以及一些小病毒。在一次与一名老师的偶遇中,我进入了技术领域。但是最初学习的是Java的web方向,后来由于工作原因转的PHP...
引言:
本人并不是这方面专业的人员,我是一名基础架构工程师。写这个主题,源自于我的“初心”。在最初我没有接触技术的时候,那会我是个网虫,刚开始也学习一些黑客相关的知识,当用到一些工具时,就很好奇这些东西是怎么做的。我就特别想去写一些相关工具和外挂以及一些小病毒。在一次与一名老师的偶遇中,我进入了技术领域。但是最初学习的是Java的web方向,后来由于工作原因转的PHP。但是我对这个没有放弃对兴趣的追求,自学了C/C++以及其他相关技术,并且摸索如何自己编程实现那些小工具。经过很漫长的研究,我弄了一些小工具,几年前弄了简单的炫舞外挂和DNF外挂给同事玩了玩。不过没有商业用途仅限于学习。
希望这边文章能让喜欢该项技术的朋友有一些帮助,后续我会更新相关技术或其他技术作品。
文章中代码github地址:
https://github.com/Diamonds-ZhaoYu/hz_example
环境要求:
系统:Windows
语言:Windows C++
开发工具:Visual Studio 2012
章节概述:
外挂编程技术揭秘这个题材我会分为三个部分概述。
外挂编程技术揭秘(一):围绕一些基础知识点描述和消息类的外挂写一个实践的小案例和QQ消息炸弹(消息级)
外挂编程技术揭秘(二):围绕内存挂一些实践的解析揭秘。
外挂编程技术揭秘(三):围绕DLL注入做一些解密。
外挂编程技术揭秘(四):围绕解除游戏保护做一些解析揭秘。
外挂编程技术揭秘(五):围绕封包挂做一些实践的解析揭秘。
如果有相对一些知识点比较干感兴趣的,可以单独私聊我,我再去整理一些相关内容。
开篇:
本文中外挂以windows客户端为主,会涉及到一些需要掌握的基础知识点,如图:
应用层编程能力:
需要掌握一门编程语言,本文使用的以C++为主,不过要掌握一些windows SDK,掌握一些基础汇编能力是为了使用OD(Ollydbg)工具时能找到相应的call,call这里指的是函数调用。其次一些编程中会有一小部分。如果我们想做一个网游外挂,网络协议必不可少。
windows系统:
windows系统是一个消息驱动的系统,比如A进程程序要向C进程程序发送关闭消息,就要了解系统消息和窗口句柄。另外一个例子:比如在没有游戏驱动保护下,A进程程序要向C进程程序写入内存,这种情况就要了解进程和内存。而PE结构极为关键重要,在程序加载时,可以通过PE结构定位程序入口,其次一些蠕虫病毒也是串改PE结构。我们鼎鼎大名的看雪论坛,域名都是pediy,可谓PE结构的重要性。除了途中列举之外还有系统的很多特性需要掌握,只是我们本章内容不会涉及。
HOOK技术:
hook技术可以应用到很多地方比如说数据封包,比如说破解游戏驱动保护,杀毒软件技术,很多都用到hook技术。
WDK:
内核编程技术掌握系统结构和SSDT表是为了破解游戏驱动保护,除此之外他不光是游戏保护,一些杀毒软件技术和防火墙技术包括窗口保护都是用到内核编程技术的hook技术,文章中会详细描述。
工具:
CE(CheatEngine)可用于分析调试找基地址和call地址等游戏信息,OD(Ollydbg)是一个强大的汇编分析调试器,也可以分析调试找基地址,可以动态调试应用程序。大家经常看到一些软件被破解,其实OD就有这样的功能,不过你要掌握一些汇编相关知识。
算法:
其实外挂中也会有一些算法,比如说拿一个迷宫游戏而言,如果是把地图转化为一个二维数组。我要从a点到b点,这种情况就可以通过最短路径算法,模拟游戏操作就可以快速通关。当然还有其他场景,就不一一列举。
以上知识点延伸的话会有很多,一篇文章是无法概述完,本文主要以揭秘为导向,如对知识点有兴趣者可以私下联系我,或者未来补充相关文章。
外挂介绍
在我们经常看到的外挂中大致可以分为三类,分别为:消息挂,内存挂,封包挂。
消息挂:
消息挂主要特点是一些鼠标自动点击,窗口移动,键盘事件等等,这一类的模拟消息事件外挂都可以归类为消息挂。
内存挂:
内存挂比如说,拿植物大战僵尸而言,我们可以修改游戏中的金币数量,这一类的内存数据操作可以归类为内存挂。
封包挂:
网游中截获应用通信中的数据包,然后封装后发送到服务端。这一类的外挂归类于封包挂。
消息挂实践
拿一个桌面程序举例,其实一个桌面程序在windows运行时,它是一个进程。我想针对这个进程去发送鼠标点击事件和键盘点击事件,其实这些都是一些相应的消息。我们这一块会用到windows api中的SendMessage宏和FindWindow。
FindWindow
该函数获得一个顶层窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。
这个函数不查找子窗口。在查找时不区分大小写。
函数原型
HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName);
参数
IpClassName :指向一个指定了类名的空结束字符串,或一个标识类名字符串的成员的指针。如果lpClassName为NULL,它将找到标题与lpWindowName参数匹配的任何窗口。
IpWindowName:指向一个指定了窗口名(窗口标题)的空结束字符串。如果该参数为空,则为所有窗口全匹配。
详细信息请查看官方文档:
https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-findwindowa
SendMessage
该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。
而和函数PostMessage不同,PostMessage是将一个消息寄送到一个线程的消息队列后就立即返回。
函数原型
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam)
参数
hWnd:指定要接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息特定信息。
IParam:指定附加的消息特定信息。
返回值:返回值指定消息处理的结果,依赖于所发送的消息。
详细信息请查看官方文档:
https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-sendmessage?redirectedfrom=MSDN
实践的小案例一
我们如果要对一个程序控制它的关闭,或者窗口的改变窗口位置,或者是拿去文本框中的信息。我们都要先获取一个窗口的句柄,文本框也属于窗口。
这一块我们可以使用一下Visual Studio 2012的Spy++小工具可以查看到窗口句柄,如下:
根据上面所示,可以看到我们的主窗口句柄为0x00401FE,然后主窗口下面有很多子控件也是有窗口句柄。没错,其实像文本框,按钮这些控件都是有窗口句柄的。我们可以编写一个小程序,这个小程序以计数器为例,隐藏点所有的按钮。
代码如下:
// example1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include "conio.h"
#include <Windows.h> //windows api 头
//枚举字窗口
BOOL CALLBACK EnumChildProc(HWND hwndChild, LPARAM lParam)
{
int i, idChild;
idChild = ::GetWindowLong(hwndChild, GWL_ID);
printf("子窗口句柄:0x%x ,idChild: %d \n",hwndChild,idChild);
::ShowWindow(hwndChild,SW_HIDE);
return TRUE;
}
int main(int argc, _TCHAR* argv[])
{
//查找窗口句柄
HWND hWnd = ::FindWindow(NULL,_TEXT("计算器"));
if (hWnd != 0)
{
printf("主窗口句柄: 0x%x \n",hWnd);
//枚举子窗口
EnumChildWindows(hWnd, EnumChildProc, NULL);
//::CloseWindow(hWnd);
} else {
printf("没有找到窗口句柄\n");
}
getchar();
return 0;
}
其实上一段代码很简单,首先通过FindWindow获取“计算器”的主窗口句柄,然后通过主窗口句柄枚举所有子窗口。在回调函数EnumChildProc中获取子窗口的句柄,根据句柄隐藏掉所有按钮。
那么大家其实可以想想,既然我能隐藏掉控件。我是不是可以改变文案,是不是可以移动,点击。
或者是获取文本框输入的信息,截获输入的数据。这个都是可以做到的。
实践的小案例二
我们来写一个QQ的消息炸弹,如图中所示:
该个小程序功能:
hot键盘: 全局hot HOME键,当在一个窗口置顶时候点击home键,则直接拿到该窗口的窗口句柄。
发送:发送前需要文本框输入文本,然后点击发送的话,开启一个线程每隔1秒循环发送消息。
停止:停止线程,则停止发送消息。
窗口变化: 改变窗口的x,y还有高宽。
关闭: 退出程序
程序结构
├── example2
│ ├── ReadMe.txt
│ ├── Resource.h 资源文件头
│ ├── example2.cpp 程序WinMain函数入口
│ ├── example2.h 头文件
│ ├── example2.ico 图标资源
│ ├── example2.rc 窗口资源
│ ├── example2.vcxproj 工程配置文件(vs生成)
│ ├── example2.vcxproj.filters 工程配置文件(vs生成)
│ ├── main.cpp 主程序代码
│ ├── main.h 主程序头文件
│ ├── stdafx.cpp 标准系统包含文件的包含文件(vs生成)
│ ├── stdafx.h 标准系统包含文件的包含文件头(vs生成)
│ └── targetver.h 定义运行环境头(vs生成)
├── example2.opensdf
├── example2.sln
└── example2.v11.suo
main.cpp文件内 hot键盘代码
BOOL WINAPI Main_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//省略
switch(uMsg)
{
case WM_INITDIALOG:
{
//注册热键
::RegisterHotKey(hWnd,100,/**MOD_ALT*/ NULL,VK_HOME ); //VK_HOME
hInst = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
/* Set app icons */
hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICONAPP));
::SendMessage(hWnd, WM_SETICON, TRUE, (LPARAM)hIcon);
::SendMessage(hWnd, WM_SETICON, FALSE, (LPARAM)hIcon);
}
return TRUE;
case WM_HOTKEY: //hotkey全局消息接收事件
{
TCHAR szTitle[1024];
//获取获取的置顶窗口名称,改变本窗口名称
getqq = GetForegroundWindow();
::SendMessage(getqq,WM_GETTEXT,sizeof(szTitle),(LPARAM)szTitle);
::SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)szTitle);
}
//省略...
该段程序在程序创建消息WM_INITDIALOG时注册一个全局的home键,接受到home键盘事件后,WM_HOTKEY消息改变本程序的标题。
main.cpp 发送-点击操作创建线程
BOOL WINAPI Main_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//省略。。。
case IDC_OK: //开始发送消息
{
if (getqq > 0)
{
lp.parentHwnd = hWnd;
lp.qqHwnd = getqq;
hThread = ::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,&lp,0,&dwThreadId);
if (hThread == INVALID_HANDLE_VALUE)
{
MessageBox(NULL,TEXT("线程创建失败"),TEXT("提示"),NULL);
return -1;
}
else
{
MessageBox(NULL,TEXT("请选择窗口"),TEXT("提示"),NULL);
}
}
}
//省略。。。
}
确认按钮对应的宏为IDC_OK,点击确认时必须通过home键获得getqq中的句柄,通过CreateThread创建一个线程,这一块必须用线程去做。不然程序会卡死。线程传参数时,传了一个lp值,该值对应一个PARANFUNC结构体。
main.h PARANFUNC 原型
#ifndef _MAIN_H
#define _MAIN_H
typedef struct ParamFunc
{
HWND parentHwnd;
HWND qqHwnd;
} PARANFUNC;
DWORD WINAPI ThreadProc( LPVOID lpParameter);
DWORD WINAPI FuncProc( LPVOID lpParameter);
BOOL WINAPI Main_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif
PARANFUNC结构体对应两个参数一个parentHwnd,一个qqHwnd。分别存储当前窗口的句柄,和qq会话窗口句柄。
main.cpp 发送-ThreadProc方法
DWORD WINAPI ThreadProc( LPVOID lpParameter)
{
TCHAR szText[1024],Tz[1024];
int i;
PARANFUNC *lps = (PARANFUNC *)(lpParameter);
HWND pHwnd = lps->parentHwnd;
HWND qq = lps->qqHwnd;
HWND q1 = GetWindow(qq,GW_HWNDNEXT);
//获得数据
::SendDlgItemMessage(pHwnd,IDC_MSGTEXT,WM_GETTEXT,sizeof(szText),(LPARAM)szText);
//模拟输入发送消息
while(1)
{
::Sleep(1000);
::SendMessage(qq,WM_SYSCOMMAND,SC_KEYMENU,(WPARAM)0x4c);
for(i = 0;i < strlen(szText);i++)
{
//1 & 0 = 0 1 & 1=1R
::SendMessage(qq, WM_CHAR, szText[i] & 0xFF , NULL);
}
::SendMessage(qq, WM_KEYDOWN, VK_RETURN , NULL);
::SendMessage(qq, WM_CHAR, VK_RETURN , NULL);
::SendMessage(qq, WM_KEYUP, VK_RETURN , NULL);
}
return NULL;
}
ThreadProc函数为线程的回调函数,该函数主要处理往QQ会话框发送消息。SendDlgItemMessage函数是获取程序中的文本框,文本框的宏对应IDC_MSGTEXT。获取后通过::SendMessage(qq,WM_SYSCOMMAND,SC_KEYMENU,(WPARAM)0x4c); 返回窗口菜单。WM_SYSCOMMAND消息是往窗体菜单上发送命令,SC_KEYMENU击键结果检索窗口菜单。例如,按ALT + F显示“文件”弹出窗口将导致WM_SYSCOMMAND,其wParam等于SC_KEYMENU,lParam等于“ f”。0x4c其实是一个键盘码。
WM_SYSCOMMAND消息文档
WM_SYSCOMMAND 消息 (Winuser.h) - Win32 apps | Microsoft Docs
键盘码表
下面部分::SendMessage(qq, WM_CHAR, szText[i] & 0xFF , NULL); 是把输入的数据,通过WM_CHAR发送给QQ会话窗口,对应的只是字符而不是按键字符是指的0-127内的ASCII码。 WM_CHAR发送中文需要通过 ch & 0xFF运算一下。
VK_RETURN部分消息都是模拟回车事件。
结尾:
其余部分代码过于简单,大家可以看看github中代码,然后自行研究。本人内容比较简单,不过对于消息通信的挂,可以起到一些启发。该篇文章就先到这了,后面会继续更新。谢谢大家。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)