传送门 :CE嗨游戏逆向 从入门到精通辅助 进阶篇

修改汇编代码实现作弊

先通过内存查看器来找,断点步过,一步一步走,看哪个寄存器是我们需要的东西,然后修改那一段汇编代码

然后就是分析汇编代码,之前稍微看过一点汇编,所以还是能理解一点的,以后有机会就重新学习一下汇编(之前学的时候没有博客所以也没记录下来)

修改汇编代码实现同一个目的,也是有很多方法的,跟写代码是一样的。比如扣血的代码,我可以让他不执行寄存器的mov,也可以sub改成add,也可以把死亡给nop掉。

AOB注入修改游戏

用AOB注入(Array of Byte Injection字节型数组注入)可以不用找指针,直接注入修改(AOB好像是记录了什么特征码,直接可以定位到特定位置)

AOB就是汇编内存器里面类似 A0 E1 00 03 47这样的字节段,如果一个有重复的就会向后扩展,一直扩展到这段字节是程序里唯一的,可以直接定位到的。(那估计用AOB注入模板里面显示的code就是把这段唯一的串给翻译过来的汇编代码)

注入就是在指定位置加入一段jmp(或者说运行到指定位置就进入一个函数然后返回继续向下执行,函数内就是注入的汇编代码)

tip:找改写的时候找到指令以后先停止,不然一直断点游戏会变得很卡。

AOB是不是唯一的可以先复制下特征码然后用CE搜索Byte数组看是不是有唯一的地址。

这里拿出来一个PVZ阳光被修改的汇编代码做示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{ Game   : popcapgame1.exe
Version:
Date : 2022-02-04
Author : dell

This script does blah blah blah
}

[ENABLE]

aobscanmodule(INJECT,popcapgame1.exe,8B 88 78 55 00 00 83) // should be unique
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
mov ecx,[eax+00005578]
jmp return

INJECT:
jmp newmem
nop
return:
registersymbol(INJECT)

[DISABLE]

INJECT:
db 8B 88 78 55 00 00

unregistersymbol(INJECT)
dealloc(newmem)

{
// ORIGINAL CODE - INJECTION POINT: "popcapgame1.exe"+1F4D6

"popcapgame1.exe"+1F4C7: CC - int 3
"popcapgame1.exe"+1F4C8: CC - int 3
"popcapgame1.exe"+1F4C9: CC - int 3
"popcapgame1.exe"+1F4CA: CC - int 3
"popcapgame1.exe"+1F4CB: CC - int 3
"popcapgame1.exe"+1F4CC: CC - int 3
"popcapgame1.exe"+1F4CD: CC - int 3
"popcapgame1.exe"+1F4CE: CC - int 3
"popcapgame1.exe"+1F4CF: CC - int 3
"popcapgame1.exe"+1F4D0: 01 88 78 55 00 00 - add [eax+00005578],ecx
// ---------- INJECTING HERE ----------
"popcapgame1.exe"+1F4D6: 8B 88 78 55 00 00 - mov ecx,[eax+00005578]
// ---------- DONE INJECTING ----------
"popcapgame1.exe"+1F4DC: 83 EC 0C - sub esp,0C
"popcapgame1.exe"+1F4DF: 81 F9 06 27 00 00 - cmp ecx,00002706
"popcapgame1.exe"+1F4E5: 7E 0A - jle popcapgame1.exe+1F4F1
"popcapgame1.exe"+1F4E7: C7 80 78 55 00 00 06 27 00 00 - mov [eax+00005578],00002706
"popcapgame1.exe"+1F4F1: 81 B8 78 55 00 00 40 1F 00 00 - cmp [eax+00005578],00001F40
"popcapgame1.exe"+1F4FB: 7C 32 - jl popcapgame1.exe+1F52F
"popcapgame1.exe"+1F4FD: 8B 80 A4 00 00 00 - mov eax,[eax+000000A4]
"popcapgame1.exe"+1F503: 8B 88 4C 09 00 00 - mov ecx,[eax+0000094C]
"popcapgame1.exe"+1F509: C6 44 24 04 01 - mov byte ptr [esp+04],01
"popcapgame1.exe"+1F50E: C7 44 24 08 0C 00 00 00 - mov [esp+08],0000000C
}
  1. {}和//都是注释,第一段aobscanmodule(INJECT,popcapgame1.exe,8B 88 78 55 00 00 83) // should be unique这个里面的就是特征码也就是AOB,然后要确保是唯一的,可以把这段串复制到CE上看是不是只有一个指向。

  2. alloc(newmem,$1000)就是分配地址的,不是很需要管现在

  3. ```assembly
    newmem:

    code:
    mov ecx,[eax+00005578]
    jmp return

    INJECT:
    jmp newmem
    nop
    return:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
       
    这是最关键的一段,他走到你注入位置的上面就会jmp到newmem把要写的代码写进去,要注意的是如果再newmem里没有jmp到return下面的code还是会执行的。也可以再newmem里jmp code直接执行下面的code,code其实就是注入位置下面的代码,是不是需要视情况而定。他是根据特征码来划定的。

    4. 下面的一大串注释掉的就是给你做一个提示的,从哪里注入从哪里出去。

    # 调用游戏自身的CALL

    CALL是用来调用其他函数的汇编代码。

    **有参数的函数**可能call之前有push例如:

    ```assembly
    push xxx
    call xxxx

没有参数的那就没有push。

  1. call调用死亡函数会传什么参数?–>调用死亡函数的东西的地址,如果说是玩家死掉了,push的东西就是玩家的地址。可以结构分析一下,大概能发现玩家的一些参数。这样可以来找到玩家的地址!

  2. 一开始执行某个操作是单线程,但是如果自己写一个新的call来调用游戏的函数,就可以实现不执行其他操作,就改变程序(比如改游戏本身的汇编代码,如阳光+100,需要有东西调用了阳光才会+100,如果自己创建的进程就可以直接执行,不需要通过其他操作,真正的实现直接操作)。关键点:创建新的线程来调用call

  3. call前后要加sub和add保持堆栈平衡,否则会崩溃。

  4. 在某函数内加断点,然后shift+F8返回到上一级,可以找到调用它的地方

  5. 创建一个模板,然后这样,就可以做一个直接call调用函数的独立的脚本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    alloc(callHurt,256)//分配地址
    CreateThread(callHurt)

    callHurt:
    sub esp,08
    push 200
    push XXXXXXXX
    call XXXXXXXX
    add esp 10

    ret //必须要返回
  6. 下一步就是学怎么用程序来调用脚本call。(在CE里面执行一次需要点两次)

用易语言调用外部游戏的CALL

易语言模块可以大大缩减工作量(我也不是经常用易语言,只是拿来[玩一玩]而已,所以直接调用模块就可以了),我从百度精易模块官网下载的v10版本,然后引用这个模块。

模块句柄就是模块头部(首地址)

说实话用易语言还不如用C++,先忽略吧。

如何重新编译CE

目前来讲没有实际意义所以也不做深入研究了,以后有用到再说吧。