上文终于找到了行棋的起始位置和结束位置,这样就可以动态扫描的方式进行处理。这种方式是一种拉的方式,和使用HTTP协议非常相似。也就是说,你不知道游戏内存的数据什么时候,变成了什么。与之相似的处理方式,比如不停扫描数据库,看看是否记录增加等。因为对方不会通知,所以,需要主动去读,而缺点也显现出来了,与其它主动扫描一样有很多毛病。这里就不详细讲这个了,很多游戏外挂也是用的这样的思路,比如自动喝血喝蓝外挂,一般都是主动扫描内存,初级一点呢就是模拟键盘,高级一点就是使用CALL。实际上任何语言都可以使用CALL,包括C#、VB之类。原理就是向内存中写入2进制代码,计算机执行代码本质上就是2进制数字。

现在来说说HOOK API的方式化主动为被动,与IOC注入思想很相似,我提供一个函数,让游戏主动调用,而不是我主动去查询游戏的状态。

要用API注入,那就要寻找一个好的注入点,为了方便测试,这里以吃子为例。当玩家A的棋子吃掉了玩家B的棋子,我们现在就要在这个地方做处理。

HOOK API有多种处理方式,抛开C#之类的非标准PE文件格式,下面讲C++的实现。C++也有两种实现方式,一个是钩子注入,一个是DLL注入。钩子注入和DLL注入有一个本质区别,钩子注入一般是热键注入,比如,可以让游戏使用F12键,调出我们需要执行的代码,本文也是讲的这种方式。而DLL注入需要在游戏进程分配一个空间,然后把DLL放到该空间,这样DLL和游戏在同一个起始地址下,可以使用偏移直接操作。他们的本质区别就是DLL加载到的进程不一样。而我觉得钩子注入相对来说方便一些。

首先,创建两个项目,一个是对话框应用程序,一个是DLL,方便起见都用MFC库。应用程序相对简单,上面有两个按钮,两个按钮对应的事件就是加载和释放钩子的过程。

//dll句柄
HINSTANCE hmod;
DWORD pid;
//开启记牌功能
void CJunqiRpgPlugDlg::OnBnClickedOk()
{
    // TODO: 在此添加控件通知处理程序代码
    HWND hWnd = ::FindWindow(NULL,L"四国军棋角色版");
    if(!hWnd){
        AfxMessageBox(L"请先启动游戏。");
        return;
    }
    DWORD tid = GetWindowThreadProcessId(hWnd,&pid);
    typedef BOOL (WINAPI * InstallHook)(DWORD dwThreadID);
    hmod = LoadLibrary(L"JunqiRpgExt.dll");
    if(hmod==NULL){
        AfxMessageBox(L"DLL加载失败。");
        return;
    }
        
    InstallHook enbleHook;
    enbleHook = (InstallHook)GetProcAddress(hmod,"_InstallHook@4");
    
    if(enbleHook){
        (*enbleHook)(tid);
    }else{
        AfxMessageBox(L"调用方法失败");
    }
}
//退出记牌器
void CJunqiRpgPlugDlg::OnBnClickedCancel()
{
    // TODO: 在此添加控件通知处理程序代码
    typedef BOOL (WINAPI * UninstallHook)();
    UninstallHook uHook;
    uHook = (UninstallHook)GetProcAddress(hmod,"_UninstallHook@0");
    if(uHook != NULL){
        (*uHook)();
        OnCancel();
    }else{
        AfxMessageBox(L"无法找到卸载函数",MB_OK,NULL);
        OnCancel();
    }
}
而DLL才是最重要的东西。这里选择注入点为播放吃子音频的那个地方:
004119ED  |. /75 0F         JNZ SHORT JunQiRpg.004119FE
004119EF  |. |68 ACCE4500   PUSH JunQiRpg.0045CEAC                   ;  res\eat.wav
004119F4  |> |B9 00BC4900   MOV ECX,JunQiRpg.0049BC00
004119F9  |. |E8 C22B0100   CALL JunQiRpg.004245C0
004119FE  |> \F605 C59A4900>TEST BYTE PTR DS:[499AC5],40
选择004119EF  |. |68 ACCE4500   PUSH JunQiRpg.0045CEAC                   ;  res\eat.wav这句话作为注入口。
注入的方法一般选用call或jmp,这两个语句加上后面的数据都是5个字节,而四国军旗的这个地方占用5个字节的好几句话,这样操作起来更加方便。如果这附近没有5个自己的代码,那自己写的注入的汇编代码就要复杂一些。
首先,DLL需要实现应用程序调用的InstallHook和UninstallHook这两个导出函数。
在.h文件定义
#ifndef JunqiRpgAPI
#define JunqiRpgAPI extern "C" __declspec(dllimport)
#endif
JunqiRpgAPI BOOL WINAPI InstallHook(DWORD dwProcessID);
JunqiRpgAPI BOOL WINAPI UninstallHook();
在.cpp文件定义
#define JunqiRpgAPI extern "C" __declspec(dllexport)    //这句话一定要在文件头之前
#include "JunqiRpgExt.h"
//定义变量和回调函数
HHOOK g_hhook;
HANDLE hProcess;
bool enable = false;
//键盘事件回调(这就是钩子)
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
// CJunqiRpgExtApp 初始化
BOOL CJunqiRpgExtApp::InitInstance()
{
CWinApp::InitInstance();
DWORD pid = GetCurrentProcessId();
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
return TRUE;
}
//导出函数入口点
JunqiRpgAPI BOOL WINAPI InstallHook(DWORD dwProcessID){
if (g_hhook == NULL) {
g_hhook = ::SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, theApp.m_hInstance, dwProcessID);
if (g_hhook != NULL)
return TRUE;
}
return FALSE;
}
//键盘事件,按F12键打开或关闭记牌器
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LPARAM bKeyUp = lParam & (1 << 31);
if (bKeyUp && wParam == VK_F12 && nCode == HC_ACTION) {
if(!enable){
enable = true;
AfxMessageBox(L"记牌器被打开!",MB_OK,NULL);
JunqiRpgCall call;
call.RegisterCall();
}else{
AfxMessageBox(L"记牌器关闭!",MB_OK,NULL);
JunqiRpgCall call;
call.UnRegisterCall();
}
}
return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);
}
//导出函数入口点
JunqiRpgAPI BOOL WINAPI UninstallHook(){
return ::UnhookWindowsHookEx(g_hhook);
}
现在就要实现JunqiRpgCall 这个类了,这个类的RegisterCall负责注入HOOK API而UnRegisterCall负责还原。下面是吃子部分的实现。
//吃子发生时,我们要做的事情
void __stdcall InsideEatAction(){
AfxMessageBox(L"吃子!",MB_OK,NULL);
}
//设定地址访问权限
BOOL JunqiRpgCall::InsideTo(LPCVOID lpAddress)
{
MEMORY_BASIC_INFORMATION mInfo;
MEMORY_BASIC_INFORMATION *lpmInfo = &mInfo;
VirtualQuery(lpAddress, lpmInfo, sizeof(mInfo));
if(lpmInfo->Protect == PAGE_EXECUTE_READWRITE)
{
return TRUE;
}
else
{
DWORD dwOldProtect;
if( VirtualProtect(lpmInfo->BaseAddress, lpmInfo->RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect) )
{
return TRUE;
}
else
{
return FALSE;
}
}
}
//游戏吃子注入点
/*
004119EF  |.  68 ACCE4500   PUSH JunQiRpg.0045CEAC                   ;  res\eat.wav //注入点
004119F4  |>  B9 00BC4900   MOV ECX,JunQiRpg.0049BC00
004119F9  |.  E8 C22B0100   CALL JunQiRpg.004245C0      
*/
__declspec(naked) BYTE* __stdcall InsideEat(){
__asm{
pushad
call InsideEatAction   //调用我们自己的方法
popad
PUSH 0x0045CEAC  //还原堆栈
MOV ECX,0x004119F4  
jmp ECX;    //跳转到下一步执行
}
}
/**********************************************************
注入吃子CALL
**********************************************************/
void JunqiRpgCall::InsideEatCall(void)
{
if(InsideTo((LPCVOID)0x004119F4)){   //这个参数的地址不重要,重要的是这个地址的数组是5字节的
*(BYTE*)0x004119EF = 0xE9; // E9为jmp长跳的机器码
*(DWORD*)0x004119F0 = (DWORD)InsideEat - 0x004119EF - 5;   //计算InsideEat的便宜地址
}
}
/**********************************************************
还原数据
**********************************************************/
void JunqiRpgCall::UnInsideEatCall(void)
{
if(InsideTo((LPCVOID)0x004119EF)){
*(BYTE*)0x004119EF = 0x68; // 68为push的机器码
*(DWORD*)0x004119F0 = 0x45CEAC;  //0x45CEAC就是原先的值,被压入堆栈后会变成ACCE4500
}
}
这里的还原数据段一定要有,要不然,当游戏没退出,而外挂退出了,那么就会报错。原因就是游戏代码被更改了,会跳转到DLL的某个函数指针上,而现在DLL退出,这个函数也就没了,就会报内存读写错误。
这样以后,测试(一定要按F12启动,这是钩子注入的毛病),发现在有吃子动作时,就会弹出吃子对话框,而这个对话框出现次数太多,对游戏有很大干扰,并且子也没了。这表明,这地方不能用对话框来中断掉,有一个状态没有更改造成了这个问题。解决方法一般就是在附近找到只调用一次的地方再次注入代码就行了。

qq四国军旗2.1 beat03 builde018记牌器开发思路(四)相关推荐

  1. qq四国军旗2.1 beat03 builde018记牌器开发思路(三)

    今天发现,四国的版本更新为18了.对应棋子内存起始值变为0x4979fc.全地址为: //C#代码 private static readonly IntPtr baseAddresss = (Int ...

  2. qq四国军旗2.1 beat03 builde017记牌器开发思路(一)

    一.棋盘结构分析 首先,根据存档文件找到棋子 军旗:0x00000002 地雷:0x00000003 炸弹:0x00000004 司令:0x00000005 军长:0x00000006 师长:0x00 ...

  3. JJ斗地主记牌器java开发_【欢乐斗地主记牌器制作】遇到两个问题

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 本菜笔准备用swing做个记牌器,但是遇到俩问题,求助大神. 先抛出最简单的问题:点击button1文本框textfield1中数字会减一,但是我怎么实现 ...

  4. 基于opencv2的斗地主记牌器(python)

    一.开发该系统软件环境及使用的技术说明 开发环境:JetBrains PyCharm Community Edition 2019.2.5 x64 远端仓库:GitHub 语言版本:python3.7 ...

  5. python 实现记牌器原理

    python 实现记牌器原理 方案一(减牌记牌) 方案二(增牌记牌) 字典Update更新初学者常见报错问题 方案一(减牌记牌) 假设与这"333445667788QQJ"十五张牌 ...

  6. 【弹子兵法】四国军棋棋盘、棋子与记谱【基础篇】

    http://hi.baidu.com/%B5%AF%D7%D3%B1%F8%B7%A8/blog/item/80fdea96fbadfd55d0135eb9.html 第二课      四国军棋棋盘 ...

  7. 四国军旗人工智能探析提纲20070301

    四国军旗人工智能探析提纲 2007.03.01 小面胡子   一.         一般棋类游戏的构成 a)         棋盘 b)        棋子 c)        规则定义 i.     ...

  8. 【181012】VC++ 四国军旗网络游戏源代码

    VC++ 四国军旗网络游戏+毕业论文,实现了一个具有小型网络游戏特征的四国军旗游戏,有一个完整的框架,数据库.服务器.客户端.大厅.游戏框架.玩家信息.游戏中的聊天.广告信息.以及防止了闪烁的画面,游 ...

  9. 四国军旗辅助记忆小工具

    最近迷上四国军旗,四国军旗需要记忆另外三方的棋子,虽然军旗软件本身可以做标记,但是,记忆空间有限,不方便,专门用c#开发了一个小工具,辅助记忆,比较简陋,共享给有需要的朋友. 链接:https://p ...

  10. 使用双标记写html代码时如何进行嵌套,【2020Python修炼记】前端开发之 前端基础和HTML入门...

    发表于:2020-11-12 18:21 阅读: 119次 这篇教程主要讲解了[2020Python修炼记]前端开发之 前端基础和HTML入门,并附有相关的代码样列,我觉得非常有帮助,现在分享出来大家 ...

最新文章

  1. 使用OpenCV进行直播(附代码)
  2. android 打包 混淆配置_android 实际项目中混淆文件的配置(参考做法)
  3. R语言对dataframe进行行数据筛选(row selection)多种方案:使用R原生方法、data.table、dplyr等方案
  4. 可以通过无线充电的软脑植入物来控制大脑中的脑细胞
  5. 漫步数理统计二十一——变换:随机向量
  6. python认证考试mac_Mac OS 平台使用 Python 和 Docker 创建测试用 Https Server
  7. Flink 在快手实时多维分析场景的应用
  8. 普及一下行业尖端知识——腾讯自研分布式数据库TBase
  9. C# WPF做的漂亮的登陆界面[附源码]
  10. 调研之路 --- MXF
  11. oracle 将钱转换万元单位,oracle中单位换算。
  12. 程序员叫啥名字_网友:什么是好程序员?腾讯员工:首先起个“配”自己的网名!...
  13. 思考技术人员需要沉淀的能力
  14. 盘点10个堪称神器,却不为人所知的小众软件
  15. win10 系统识别不了移动硬盘
  16. 禅道登录显示用户名密码错误
  17. 夏敏捷第28本著作《Flash ActionScript3.0动画基础与游戏设计》(Flash CC版)
  18. 如何在项目中使用ECharts
  19. 关于为老年人服务的简单创业计划书
  20. ubuntu14.04 LTS安装nvidia 驱动 [联想Y470 GT550M]

热门文章

  1. Linux中常用查看日志命令
  2. Redis官方中文翻译系列 - Redis文档
  3. 概率论经典问题之匹配问题
  4. oracle gis费用,MapGIS 10 for Desktop 标准版_Oracle
  5. 学习C语言——字母金字塔
  6. energy plus matlab,Energyplus教程系列1—Energyplus到底能干啥.ppt
  7. WINDOWS XP 桌面主题的安装制作
  8. 联想小新电脑摄像头黑屏、检测不到设备、指示灯不亮解决方案
  9. 算命师傅的好帮手:一款简单好用又使用的排盘工具--灵棋排盘
  10. IDEA生成SerialVersionUID