虚荣手记:关于外挂与自动化开发的泛姿势总结(上篇)

供新手入门和老手参考的教程和相关资料,包括中文帮助

Moderators: tmplinshi, arcticir

User avatar
cusong
Posts: 7
Joined: 14 Jun 2019, 08:36

虚荣手记:关于外挂与自动化开发的泛姿势总结(上篇)

04 Apr 2022, 05:15

Code: Select all

# 虚荣手记:关于外挂与自动化开发的泛姿势总结(上篇)

# 前言

    * 鄙人是坚定的共产主义信仰者兼骄傲的无产阶级革命志士, 并承诺遵守我国法律法规
    
    * 对先进科学技术探索与分享是吾辈的本分, 愿祖国因信息技术的繁荣而繁荣

# 正文前言

    一天晚上有个做web前端开发朋友, 可能随口问了关于那些偏门开发的道道

    当时没有好好回答, 睡前琢磨了一番, 第二天手机打了三屏字给他

    本文尽量不涉及特别具体工具的姿势, 算是一篇总结性质的概念扫盲文章
    
    做了简单的润色, 希望入得看客的法眼, 也希望大佬们莫嘲笑笔者的浅薄吧

    诸如 针对游戏的 外挂 / 辅助 / 脚本(黑话), 针对办公软件的 办公自动化程序 此类开发需求
    
    多为实现对 现有的程序 的 自动化信息访问, 自动化操作, 用户执行辅助效率优化等

    业务范围属于五花八门, 所以涉及姿势面会非常广, 且少有全才

    这篇文章是用vscode纯文本编辑的, 我不确定发布到各大论坛是否能够保留缩进格式, 见谅



# 你的程序与目标程序

    无论用何种技术手段, 对目标程序进行编程干预, 编程手段基本可以从 2 个方向入手
    
    足够的信息 + 有效的操作

    考验 对目标程序 的 暴露信息 的 认知 与 获取 能力
    
    有效的操作 则是 基于目标的状态 判定 进行的 逻辑干预触发
    
    最终目的是 使目标程序 能够达到一个 你理想中的 "完成我设定的任务" 这样一个状态

    两者都需要十分心力去反复查阅资料, 学习, 测试, 寻找专门渠道等

    目标程序 可以视为 你开发的程序 中的一个 没有血缘关系 的组件
    
    哪怕你访问形式像个插件, 最终也是 反客为主
    
    很多时候你看它会像是一个美味又无从下口的刺猬
    
    同时也是时间黑洞


# 三类外挂

    此处不对 "外挂" 作太多的程序表现形式上的描述, 姑且当作能够自动操作游戏的那一类程序吧
    
    不论是 "辅助" 还是 "脚本", 或洋气点 "cheat" 或者其他什么接地气的称谓

    外挂对程序的干预形式, 至今有流行这样3种比较通俗的说法, 1. 图色模拟 2.内存 3. 封包
    
    其中姿势, 其实不限于游戏程序, 但是本人阅历有限, 将以 windows 平台下的外挂开发姿势为例

    希望对您有帮助


    1. 图色, 模拟
        
        听闻一位前辈说 "辅助" 可能是早期没有涉及到 内存入侵 的外挂作者对自己作品的一种比较 "不自信" 的叫法

        图色, 指基于程序 暴露到屏幕上 的图形元素的状态识别
        
        这样的功能, 开发逻辑实现上 与 设备差异, 游戏程序本身实现的 技术栈关系较浅

            此类功能开发中一般得关注两个方面, "截图" 与 "识别"
            
            有些现成工具将两项流程直接合并了, 比如挂圈一哥家喻户晓的 "大漠插件"

            若不是游戏这种类似视频频繁帧切换的UI程序, 普通UI程序界面大都只是相对有限的图像动画状态

            UI界面对普通用户而言十分重要, 且视觉冲击力更甚有利广告传播
            
            可见自动化操作程序的需求还是有很大市场潜力的

            关于 "截图", 具体的截图方式包括不限于 

                1. 拦截 目标程序 图像渲染 的 内存数据, 一般是 hook 绘图函数

                2. 通过 操作系统 提供的现成的 API 接口 获取目标程序的UI图像像素的 拷贝

                3. 外置的辅助硬件工具, 如 摄像头, 其他显示分流工具等

                4. 显卡缓存数据, 个人并不了解这方面知识

            如何获取程序的UI图像信息
            
            基本思路就是 拦截 在 程序的图像 到 屏幕屏幕 之间的某一环
            
            或者钻研程序的显示图像的"副本"的获取手段

            这里仅做windows图像截取的一些姿势, 在大漠插件的手册说明也可以得知 windows 系统下常见的截图手段

                gdi(p) 这是windows非常古老的绘图接口, 已经被玩烂了的截图手段

                DirectX / OpenGL 这两个同为流行的渲染引擎, 图像数据获取手段会相对有门槛

            其他的如:

                MirrorDriver 这本来是用于远程桌面的工具

                DWM, 这是windows 底部菜单缩略图的功能, 相当于窗口的图像副本, 好处是不用介入游戏内部, 相对安全

            
            关于 "识别", 这方面可以分为 2 类识别对象

                一类是纯图像元素或色值的识别, 即像素匹配或特征匹配, 仅判断目标状态 或 操作对象的位置
                
                此类工具大同小异, 大部分情况下仅需要考虑性能问题

                    如大漠插件(包括其他山寨插件),是 小图像 在 大图像中进行 搜索匹配, 实际是针对矩阵像素点的匹配方式

                    这种基于 矩阵色值区间 识别手段 一般速度较快, 但限制并不少

                    它在 画面变化较小 的场景 会非常有优势, 但受限制于设备与系统差异, 对肉眼分别不出的图像差异可能会带来意想不到的调试麻烦
                    
                    比如不同的显卡色值输出不同, 不同的 系统分辨率 显示的UI比例不同, 包括透明背景的图像都将导致这种识别功能 可靠性 大大降低

                    此类识别比较适合静态的, 设备环境单一的 UI图像的匹配, 对小图 和 大图 环境统一性要求相对苛刻

                    类似的 单点, 多点找色情况类似, 注: 一张完整的不透明的图像即是二维色值点阵, 因此搜图即是另一种形式的多点找色功能

                关于图像识别的一些黑科技

                    * 针对图像处理, 不得不提到权威2d 图像工具库 OpenCV, 带有 特征匹配(缩放/遮挡/旋转/透视形变) 的 这种十分黑科技的识别功能

                    * 工业级图像处理工具 Halcon, 如果说 OpenCV 是权威的2d图像处理基础库, 那 Halcon 就是图形编程的史诗级IDE

                    这两种工具的使用门槛会比集成外挂工具的门槛要高, 一些学习能力较强的大佬会专门用上此类工具弥补图像识别的短板, 两者的功能都十分适用于集成在外挂工具

                    AI, 有很多通过 "Yolo" 为主打识别方案 实现的外挂, 这种工具的暂不提使用相对门槛更高, 且基础环境较大, 占用资源较大,不便于迁移, 有人会选择将识别功能整成web服务

                    这里还要感谢一个热爱到处找搜罗黑科技的逞强同志, 他冲浪发现了一个名为 "Lobe" 的 AI图像 识别训练工具, 大家大可以自行尝试一下, 智障级难度的AI训练体验


                文字类的识别需求, 即OCR, 从图像反推出实际的可读的字符数据

                    此类工具基本上没有唯一稳定的方案, 比图像特征识别有更多的不确定性

                    同样的有基于色值转换到字形形状的点阵的传统算法的文字识别

                    也有 AI 训练形式的 OCR 识别, OCR 不似图像元素匹配, 元素数量较大, 所以很难做到相对稳定的输出, 大部分情况下只能限制识别范围
                    
                    因为属于有限集的识别功能, 所以市面上有 现成的 商用 api 识别服务, 如百度OCR等, 不做赘述, 网络服务是没有语言局限的
                    
                    这种服务只需要你实现 截图 与 上传 功能
                    
                    至于其他本地的服务工具, 而这一方面最好是能够了解一下脚本语言 python


        模拟, 指用户操作的模拟

            鼠标, 在pc端是一个指针在图形界面上悬浮 的 指针, 到移动端则是触屏按击

            鼠标 与 UI控件同样是基于坐标的逻辑元素, 一般带3个常用的按钮, 左,右,中键, 相关操作逻辑有 按下, 弹起, 移动, 按下移动(拖行), 上下滚轮等

            它的动作来源于外置硬件, 因此是外部输入的操作信号, 经操作系统一道传递给实际程序的事件功能回调

            键盘, 与鼠标按键的情况类似

            正常情况下通过系统 API 发送操作信息即可模拟操作
            
            windows系统下, 许多游戏 通过 DX形式获取用户键鼠输入 并不接受 windows 键鼠消息, 这时可能需要动用hook功能(大漠同样内嵌此功能)

            但毕竟是外部信号传入, 所以市面上有相关的可编程外置硬件达到欺骗的目的

            也有更夸张的, 定制 自动化设备(工业机械臂之类) 操作真实键盘的情况


        关于输入的稳定性

            怎样 按下一个鼠标算是稳重的操作, 怎样 输入一段文字又算是稳重的操作

            鼠标操作 对状态的依赖比较单调, 它依赖于图形的大状态

            而稳重的按键输入, 则是一个比较大的考验

            你怎么保证它完完整整地接受你的输入, 实际上, 为了一次稳定输入的前置工作是非常冗长的, 个人认为理想状态是这样的

            清空 > 键入 > 读取并比较全等


        欺骗手段

            简而言之就是 "拟人", 人为操作 与 程序操作的频率 效率差异是非常明显的

            姑且不提2 4小时在线升级打怪 的这种 挂比特征 十分明显 的行为

            在这里从小的方面说, 如果有兴趣有条件可以 编程记录 一下你平常的键鼠操作, 延迟, 与鼠标轨迹特征

            这里可以将欺骗手段寄托在 "随机数" 上, 可以随机的部分有
            
            游戏任务流程

            按键的击键速率

            不同按键的击键间隔

            另外也有鼠标的坐标移动的拟人化, 普通的外挂库功能的现成函数会提供的鼠标移动功能, 可能是瞬时到达目标位置, 可能是相对偏直线的运动轨迹

            曾有机会碰上过滑块验证, 当时找到一个贝塞尔曲线的 javascript 算法
            
            顺利移植到脚本工程中, 增加了随机的 移动位置, 速率变化, 抖动等, 实测获得了比较高的通过率


        目标程序的静态图像资源

            许多游戏的图形资源大部分是储存在程序外部的文件中
            
            因此就有为了识别效率而修改资源文件的这种骚操作

        图色模拟的总结

            这是一种相对没有效率的程序干预操作方式, 截图, 识别, 模拟用户输入操作, 这几种操作都是存在很严重的实际状态延迟的情况

            所以很常见的在普通的外挂程序里能够看到 延迟 操作的函数, sleep, delay等, 乃至没有状态判断的纯靠延迟和操作沙雕程序
            
            短板较为明显, 它并不能很简单地直接获取目标程序的信息, 有时需要许多多余的工作去实现一个看上去很小的动作
            
            大部分情况下只通过优化程序流程, 来应付 "图色模拟" 这种带有许多概率性和延迟问题的基础功能

            它的优点也很明显, 因为 大部分情况下 并不 访问/改变 目标程序 的 内部逻辑
            
            在安全性上会有比较大的优势, 理论上可以某种层面上完全绕过目标被控程序的环境检测机制


        编程建议

            这里不说具体编程的实现

            仅用伪代码谈谈 图像状态 与 实际操作 的关联逻辑

            实际上你哪怕拥有最基础的 截图, 识图, 操作 的各种函数功能

            在实际编程上仍然存在更大的挑战

            图色操作的本质是

            得到当前状态 > 操作 > 得到期望的下一个状态

            如此反复得到最终期望的完成状态
            
            编程嘛, 为了更好的可读性, 更少的代码量, 更容易的后期维护

            理想情况下我们可以编程实现为以下这种单一的线性流程, 但是这属于有点闪失就维护火葬场, 业务代码的代码量与维护难度成正比

            // -----------------------
            循环 {
                若 找到图(登录钮激活) { // 获取到当前状态
                    点击(登录钮坐标)    // 基于当前状态的操作
                }
                若 找到图(进入游戏按钮) {    // 可以将角色选择页面的状态用某个此页面下唯一特征的图像元素来代替
                    退出循环                // 当前状态完结
                }        
            }

            循环 {
                若 找到图(进入游戏按钮)) { // 获取到当前状态
                    点击(进入游戏按钮)    // 基于当前状态的操作
                }
                若 找到图(游戏中) {
                    退出循环            // 当前状态完结
                }
            }
            // -----------------------

            后来将搜图和点击封装到一起, 在很多情况下可以简单实现为, 但相比上面那一种识别噪音更多点, 所以可以按任务段拆出多个函数

            // -----------------------
            循环 {
                找到图则点击(登录钮激活, 登录钮坐标)

                找到图则点击([进入游戏钮, 进入游戏钮2, 进入游戏按钮3], 进入游戏钮坐标)  // 有时需要在同一个界面截图多次判断一个状态
                ... 
            }
            // -----------------------

            换一种方式, 我们可以将识别与操作解耦, 将状态逻辑合并为具体的操作需要的状态

            // -----------------------
            若 找到图(登录钮激活)
                状态 = "登录页"

            若 ( 找到图(进入游戏按钮) 或 找到图(进入游戏按钮2) )  且 不找到图( 进入游戏按钮灰 )
                状态 = "角色选择页"

            判断 (状态) {
                为 "等待登录"   : 点击(登录钮坐标)
                为 "角色选择页" : 点击(进入游戏按钮)
            }
            // -----------------------

            也有一种分支逻辑的特殊状态判别写法, 用于复杂情况下的状态与操作解耦, 将单位逻辑结果统一到一个变量中

            当前可以登录 = 找到图(登录钮激活) 且 不找到图(登录页广告弹出)

            在我初时接触状态机这个概念时, 觉得有点难以理解, 实际上下面功能可以省脑, 可以参见正经测试的 check point 表开发逻辑, 在某种需要复杂的判断条件下宛如神作

            // -----------------------
            循环 {
                状态1 = 找图(图1)
                状态2 = 找图(图2)
                状态3 = 找图(图3)
                状态4 = 其他逻辑判断1()
                状态5 = 其他逻辑判断2()

                状态结果 = 状态1 + 状态2 + 状态3 + 状态4 + 状态5    // 假如 + 号为字符串拼接的符号
                输出( 状态结果 )
            }
            // -----------------------

            在这样的循环中, 不断输出到控制台, 比如能够 00100 这样的一串字符串, 随着你的不断操作, 这个数字将不断的变化, 比如 00010, 11111 等

            对于这种工具的用法, 也许有点玄学的意味, 如果有一定的编程经验, 这种形式的状态判断方式, 能够在很大程度上扁平化你的嵌套屎山代码

            其他也有统一识别并 根据识别结果统一操作, 如 以消息回调方式去执行动作, 也许这种外挂这种用JS函数式的脚本可能比较具有可读性

            这种事件回调的任务形式

            // -----------------------
            类 事件 {
                当(状态, 回调函数) {
                    ...
                }
                执行(状态) {
                    若 不存在状态(状态)
                        抛出异常
                    事件字典[状态]()
                }
            }

            类 游戏 {
                进入游戏() {
                    点击坐标(xxx)
                }
                喝药() {
                    发送按键(xxx)
                }
            }

            事件.当("允许进入游戏时", ()=> { 游戏.进入游戏() } )
            事件.当("生命值过低时", ()=> { 游戏.喝药() } )

            循环 {
                xxx状态判定()
                xxx状态判定()
                xxx状态判定()
                xxx状态判定()

                事件.执行(状态)
            }

    2. 内存 

        内存挂算是一种泛称, 实际我们会碰上的情况要复杂的多得多

        理论上来讲, 一个程序能够在计算机上运行, 它必然是会暴露出所有的执行逻辑

        像浏览器中的网页, javascript 就是一种明文形式的逻辑暴露
        
        但说道程序执行的最小组成单位, 它是机器码, 二进制指令, 哪怕经过反汇编, 也比混淆过的脚本代码要更难以阅读与调试

        它是最不抽象的逻辑承载, 但万能的计算机大佬没有什么事情是干不出来的, 目前就有很多流行的 逆向辅助工具 与 逆向教程

        对外挂开发而言, 并不需要很完整地去逆向一个程序, 基本就是见缝插针, 在能够满足实际业务需求上的探索与开发实现

        姑且当 "内存挂" 是一种对 目标程序 的入侵式的逻辑干预

        c / cpp 

            游戏程序底层惯见于用c系的代码实现, 它最终执行形式是优化过的机器码, 暂不提在性能需求较高的场景下, 比如游戏引擎, 基本是首选
        
            c系哪怕是明文工程代码, 也并不能说很容易理解, 并且在编译之后直接暴露的信息相对有限

            一般只能通过工具运行时的程序反编译到汇编代码进行调试
            
            这里需要对 对应设备的 cpu汇编指令 与 操作系统 提供给程序的接口 有所了解

        java / c# ...

            这两个语言是托管给虚拟机运行的 介于C编译型语言与解释型语言之间, 多有现成的反编译工具, 很多时候能够一定程度上保留原始的工程代码结构

        lua / javacript / python ... 

            有些软件会专门引用一些脚本语言来专门实现业务代码, 这种形式的代码多为明文, 至多做了字符串形式的混淆, 商业程序一般不宜将重要的逻辑直接写在脚本中


        暂且不提能够很方便的直接修改的程序
        
        你怎样去访问并改变别人的程序呢, 用大象怎么装进冰箱的思想精神, 可以总结出2个答案: 
        
            1. 修改可执行文件 

            2. 运行时入侵改动程序

        此处仅谈谈 windows 系统下的 内存挂 的姿势, 假如只有一个可执行文件, 这可以说是被摸烂了且参考资料很多, 神秘, 并带有一定门槛, 最赤裸裸的程序的本质, 有人说是01
        
        当然大都一上来直接说道01都是玩笑或装逼的没跑, 希望能通过我有限的姿势描述清楚
        
        一个是修改可执行文件

            譬如 直接修改二进制文件

            譬如 通过某些调试工具从内存中分离(dump)出已经修改过的的可执行文件结构
            
            譬如 是反编译后 修改反编译后的文件 并重新编译, 注: 只能一定程度反编译到汇编代码, 不提被加壳加密过的程序

        一个是在程序运行时, 强加进其他的代码以改变程序的逻辑(shell code)

            譬如 最常见的 dll注入(dll文件为 动态链接库, 可视为外置的程序组件, 可以动态载入) 

            整个过程浓缩下来可以简单地描述为, 将一段二进制代码写入目标进程, 并能够开辟线程执行该注入的代码

            譬如 直接擦写掉程序现有的部分指令, 这也是 hook 的本质, 仅拦截函数调用指令到其他位置, 改变程序的顺序

            由于写死的二进制指令有尺寸的约束, 无法很灵活的增加代码, 所以对原有程序的改动仅做关键部分函数跳转指针(hook) 的改动, 这种方法比较灵活
            
            或者擦除部分不需要的代码 (填充无用的指令如nop)

        外挂编程的目标大体相近, 上面说道, 获取状态与信息 并 进行干预操作

        到内存这个层面, 效率则要高得多, 但是其难度也几何倍的上升, 这需要的知识积累, 要远远超过普通的 图色模拟 类外挂

        这一层面的开发需要的的知识范围很广, 但大体上可以分为以下方面

            1. 关键语言姿势: 

                该如何哄骗这个陌生的尤物呢
                
                首先我们得有共同语言
                
                最难不过 c/cpp, 汇编的底子, 对数据类型与数据结构的差异, 内存指针概念, 寄存器的用途, 函数堆栈的分析
                
                一定程度能够将汇编转写可读的高级语言的提升可读性的能力

            2. 进程结构姿势: 
            
                要在它身体内部获得愉悦
                
                你要了解它的生理构造, 精准刺激, 快乐加倍
                
                包括 程序在 文件形式的存储结构 与 内存中的数据表现差异 此类泛姿势
                
                包括具体结构与地址转换, 对应进程结构的共性, 资源与代码段, 不同程序类型的属性参数等

            3. 操作系统的姿势与驱动编程姿势: 

                它就是窝在操作系统怀里任君鱼肉的小可爱
                
                无脑至最基础的系统api调用
                
                牛批至直接模拟硬件接口的编程
                
                内核级别的逻辑修改等
            
            4. 专业逆向工具姿势: 
            
                你要用各种视角, 各种工具侵犯一个扭捏的小傲娇
                
                才能获得各种不同意义上的愉悦
                
                二进制编辑器, 内存搜索工具, 文本检索工具, 反编译工具, 动态调试工具, 静态分析工具, 还有不同语言的 解释器, 编译器, 语言虚拟运行环境, 开发工具的差异等


        
        一些关于内存挂会碰上的问题
            
            外部通信

                如果你已经有能力进入目标进程 读取信息 和 执行它的功能
                
                那剩下的问题就是就是与外部通信, 早期很多内存挂是单纯的显示一个内嵌的窗体

                比较麻烦的做法是, 注入后能够与外部管理进程通信, 并能够统一管理脚本的执行逻辑与信息获取

                一般注入后使用socket向外部传递信号, 或者直接将被注入的目标程序用作服务器, 提供操作个功能的接口

            逻辑代码

                一般情况下shell code可以是任意的最终可编译为二进制执行指令的语言生成的

                汇编 , c, 易语言, 当然以 汇编 与 c系 近乎能够得到 最原始且最小依赖的环境与最好的底层访问支持

                实际开发很多会希望能够得到现代特性的语言, 或者脚本语言的开发体验

                如脚本语言 javascript, python, lua, autohotkey等, 如编译型语言 rust, go  当然这不免使注入的代码趋向复杂
                
                对内存的访问除了c系相对直接, 其他语言或多或少都得绕几个弯子做类型转换, 量力而行吧

            
            数据的价值
                
                关键的数据意味着关键的函数访问, 有些游戏程序/UI程序是数据循环驱动的, 即修改了数据 = 即将调用程序的处理函数
                
                写挂的一切目标为了价值量高的数据
                
                也许不得不为此绕绕弯子去做一些多余的工作

            
            数据获取: 基址

                在windows平台下, exe程序在载入内存之后, 会在内存中展开

                一个 可执行文件会载入 其他的模块如dll, 模块 是一个 进程在内存中的主要组成部分

                包括 逻辑代码, 常量数据, 资源数据等, 这是模块的全部, 所有可执行文件的内容, 正常情况下, 展开后所有的数据的相对(基址的)地址是一致的
                
                如 被启动的 exe 文件 是主要的模块 一般基地址在 0x00400000 左右浮动, 同一个exe文件 主要模块 载入后的地址正常情况不会变动
                
                其他外部 dll模块(动态链接库) 载入地址正常情况下则是不定的, 但是可以通过 winapi 遍历 进程模块确定模块基址

                不提经过特殊加密压缩处理的exe程序, 文件中的数据大部分会原封不动的保留, 但是会通过固定的规则(参考PE文件结构相关的文章, DLL反射注入一类的源码), 展开到内存中

                这些数据的位置正常情况下将不会变动的, 故基址, 是相对模块偏移的一个地址, 如果程序没有被修改过, 它的位置始终会相对模块的首地址

                基址, 则是碰这个运气吧, 压注某个数据的地址, 或者相邻 的 内存地址 是在 某个 基址 保存 的指针 的指针 的指针... 里存放的

                这也算面向软件,经验,运气的辛苦工作了


            数据获取: 特征码

                内存中的程序是纯数据, 逻辑 与 数据是直接混杂在一起的

                特征码是一种 近乎非常有效的 "玄学" 概念

                它只有一个作用, 定位数据

                假如你在 C 程序中嵌入了汇编, 写下一连串的重复指令, 譬如 

                __asm {
                    MOV EDX, 32
                }

                这句指令将编译成 ba 20 00 00 00

                此时, 你可以用内存搜索工具搜索 ba 20 00 00 00 这么一串连续的16进制连续数据, 也许能够立刻定位到这个函数

                有时你辛苦定位到一个功能, 但是目标程序被更新, 可能会浪费时间再去寻找, 这时特征码也许就能派上用场

                是的, 特征码就是数据特征, 无论定位的是 逻辑指令 或是 纯数据, 在一些场合都非常有效

                另有一种 特征码, 是来自一个的老师傅的课程, 属于纯玄学的奇技淫巧

                不知你是否听过 "基址", 这个可能为难过很多初学cheat engine的同志,  注: cheat engine可以看作威力加强版加强版金山游侠

                这种特征码的应用, 可以完全绕过 "找基址" 这种如果姿势不够就费神费力的手段

                说道具体方案之前, 我们来列几行数据, 代表同一个地址在内存中的不同时间的表现

                AA BB CC 10 CC CC DD
                AA BB CC 11 00 CC DD
                AA BB CC 12 CC CC DD
                AA BB C0 13 CC CC DD
                AA BB C0 13 00 CC DD

                你该如何用一个固定的值去表述这几个字节的数据呢

                假如我们把第一行一点点地往下拉, 然后变动的符号改为?, 不变的字节保持原状

                它会像这样

                AA BB C? ?? ?? CC DD

                额, 这个时候, 这个地方的特征就很明显了

                我们可以在内存不断变化的状态下, 截取相对于 目标数据 加上 前 后 N 个 字节的数据切片, 注意是相对这个目标数据哈

                许多数据是在程序运行时中申请的, 所以它的内存地址, 近乎可以确信是始终在变化的

                这种特征码需要有足够多个样本, 不断重启, 保留数据, 最终筛选出这个目标数据段 前后 不变的数据特征

                可以在某些情况下代替 "基址", 并有不错的定位表现, 顺带一提, 那个老师傅示范特征码的示例程序是 安卓模拟器

                整体获取流程大概就是:

                    1. 确定一个你的目标数据, 比如血值

                    2. 启动程序, 在cheat engine中找到对应数据, 并记录该数据前后 n 位的字节数, 保存

                    3. 重启程序 + 重启系统等, 收集同样的 前后 n 位字节数的数据, 保存

                你也可以选择在程序动态执行时通过临时过滤筛选, 也可以保存数据样本到文件, 最后一次性匹配过滤
                
                我个人实现了一个不太好看的小工具, 匹配前后1000位, 实际长度可以按需增减

                当然这种方案并不是说没有缺点, 在安卓虚拟机中, 应用重启之后, 此前启动的应用的内存脏数据会被保留, 将影响匹配结果


            数据获取: Hook

                玄学不提, 获取数据最稳重的方法应当是, 拦截目标程序的有效传输通道, Hook

                这里讲讲最原始的 Hook 手段

                汇编指令中的函数调用方式是这样的

                push 很重要的数据3
                push 很重要的数据2
                push 很重要的数据1
                call 某函数的地址

                传参, 然后调用(跳转到)函数地址, 这里就不贴具体函数的汇编代码了, 有点恶心, 感兴趣的同志可以用od或者visual studio调试查看


                push 是压栈, 每一个线程都会维护一个栈空间用于函数调用, 存放函数的临时实参与返回(调用的)的路径, 在函数内部逻辑处理完毕之后
                
                将临时数据pop指令形式推出以平衡堆栈, 完成一次函数的调用, 所以 函数调用 理论上 是比 一条龙的屎山 更低效的
                
                我们作为愚蠢的人类还是选择可读性, 将冗长过程抽象为函数乃至混合了数据和方法的对象结构, 以迎合人类的维护体验

                在内存中所有数据包括逻辑指令与其他模块的数据, 因此函数递归或者调用层级过深, 可能覆盖到其他的内存地址, 是不是一听就非常刺激

                
                这段汇编指令在高级语言中的调用形式是:
                某函数(很重要的数据1, 很重要的数据2, 很重要的数据3)

                假如它的原型是
                某函数(形参1, 形参2, 形参3) {
                    巴拉巴拉巴拉巴拉
                }

                那我们现在要做一个hook

                首先, 我们构建一个一个函数, 过分的函数, 用来拦截这段数据

                我很过分地擦写了的函数(形参1, 形参2, 形参3) {
                    形参1 += 1
                    形参2 += 2
                    形参3 += 3
                    某函数(形参1, 形参2, 形参3)
                }

                接着我们擦掉那个即将被执行的诱人的 某函数, 像这样

                我很过分地擦写了的函数(很重要的数据1, 很重要的数据2, 很重要的数据3)

                在反汇编代码中应该像这样

                push 很重要的数据3
                push 很重要的数据2
                push 很重要的数据1
                call 我很过分地擦写了的函数的地址   ; 是的, 这里我修改了它的调用位置

                
                这就是hook的全部, 写一个拦截的函数, 改写实际代码中的调用地址, 此处用call

                它能够得到参数, 也能够改写参数, 你也可以选择在拦截函数中调用原来的函数以保持原始程序整个逻辑不变

                hook 同时是危险的, 徒手写的汇编代码需要去计算堆栈平衡, 保证在整个过程中压入多少, 弹出多少, 内存是一条线的, 无论你弹多了少了, 是不同方向的溢出体验

                hook 一般不用作特别大的改动, 机器指令形式的代码与普通我们在编辑器中的代码不一样, 它的存放形式相对约束, 是定长的, 所以正常情况下会选择hook形式的修改

            中断:

                try {
                    xxx代码
                    throw "这里是中断"
                    xxx代码
                } catch {
                    发生错误执行的代码
                }

                中断可以类比为这种异常处理, 它也是一种特殊的指令跳转方式, 不展开说明



            shellcode:

                正常在windows下, 注入一个dll文件是非常简单的, 调用有限的几个api函数完事

                高级语言中依赖成分相对严重, 所以有一种比较高级的 "DLL反射注入" 方式

                它的作用逻辑是(大象冰箱警告), 模拟程序加载器 将 dll 文件 在目标进程中展开, 重写各种索引表, 并调用入口函数, 最后创建线程执行
                
                除了这种形式, 也有相关的工具来分离可执行文件的 shellcode, 或者徒手写一段汇编, 找到目标代码等等

                在一个进程的隔离空间内部, 它们资源是共享的, 包括数据与方法
                
                游戏程序一般是非常复杂的UI程序, 模块调用交错频繁的
                
                因此见缝插针的机遇, 仿佛是一定存在的

                进程内部 获取游戏数据, 干涉游戏逻辑, 比经过用户键鼠模拟一道, 更为效率
                
                在调用游戏中的函数(如配置, 移动等), 临时修改数据, 若编程逻辑无误也保证了更少的错误排查必要

    3. 封包挂

        这一块就非常黑客了ss

        封包挂到底是什么呢, 它是指网络数据包

        封包挂的概念与爬虫基本相似吧, 爬虫大都是伪装浏览器请求服务器以获取网页的信息, 那封包挂, 则是伪装游戏客户端与服务器交互

        它的难度要远大于普通爬虫, 游戏外挂的所有目标无非是积累虚拟财产, 基于此, 游戏性, 高质量的游戏画面似乎是一种累赘

        也许积累虚拟财产的终极手段是直接修改游戏服务器的数据库, 但是封包挂, 无疑是客户端欺骗的终极手段

        摆脱了游戏程序的束缚, 大部分游戏客户端也许能够开到 个位至数 三十位 数量的窗口

        摆脱了游戏客户端, 一个成熟封包挂能够很夸张地得到单台机器上个百个乃至更多游戏窗口的业务效率

        碍于姿势有限, 我也仅能从有限的姿势描述

        封包挂的开发, 继续大象装冰箱

        1. 定位封包

        2. 分析原始数据封包 / 分析代码中构建封包的逻辑

        3. 构造发包

        定位封包

            需要你拥有动态拦截网络数据包的工具, 然后通过对应业务需求的操作, 去过滤的目标封包对象

            如 hook 游戏在 windows 程序中常见的网络接口, socket 库中的 receive send 函数, 目标精准则不需要做太多过滤操作

            或使用代理拦截, 在发包的路由过程中过滤拦截数据包

        封包构造

            大多数封包是经过加密的, 但是不意味着所有的程序都有充分的加密与服务器数据校验手段

            因此就出现一种场景, 就校对分析同一逻辑类型的封包差异, 修改某个特定字段就能够达到目的的情况, 这属于玄学操作

            实际封包构建逻辑是在客户端程序中的, 加密过程与数据来源

            所以稳重的封包协议构造是对客户端程序的逆向分析中获取的

        构造发包
            
            大致2个环境3种选择

                1. 在目标进程内拦截发包函数, 过滤并修改部分关键数据

                2. 在目标进程内调用对方的函数主动发起请求

                3. 实现伪客户端, 脱离目标进程, 代替目标进程实行客户端的工作
        
# 最后

    鉴于本人断更的前科累累, 下篇可能遥遥无期

    这里打个广告, 欢迎各路水笔与各种开发者前来交流心得, * 注:不接游戏外挂单子 *

    779453387: C#脚本开发交流群

        我的偶像王大佬的技术分享群, 王大佬是正经人, 该群有部分职业的逆向(黑话)开发者

        王大佬有收费教程的业务, 但个人并未参与教程开发或任意形式的分红, 自干五

        王大佬b站id: 知识就是暴li

        王大佬的另一个小粉丝的id, 同为野生极客, 从不正经开发到上岸转型正经码农的好基友逞强小弟弟: 奶茶叔叔不喝奶茶

    690058080: Coder Club | 码农民工会
        
        我的个人娱乐水笔群, 正经开发居多, web方向开发居多

    最后再推一个我的启蒙脚本语言AutoHotkey, 入门到至今成为程序员, 我仍深爱着AHK, 这个操蛋的语言

    --- 虚荣 2022-04-04

Return to “教程资料”

Who is online

Users browsing this forum: No registered users and 50 guests