Tulip 2021 Game Assistive Technology Junior Class (Part 1)
-
- %p、size_t、%zd、%llu、FindWindow、GetWindowText、SetWindowText
- GetWindowThreadProcessId、OpenProcess、ReadProcessMemory
- Sealing read memory interface function int R4(void* address)
- Write a value across processes to the memory address of the target process WriteProcessMemory
- C, C++ 32-bit and 64-bit process memory data reading and writing function interface, project management through header files
- X64 environment masm assembly asm file
- Six common parameter calling conventions: passing and flat stack
- Assembly conditional transfer instructions and loops
- C, C++ uses code to call CALL across processes
- Miscellaneous
%p、size_t、%zd、%llu、FindWindow、GetWindowText、SetWindowText
HMODULE hModule = GetModuleHandle(nullptr);
// %p 表示格式化指针
printf("ddd %p\n", hModule);
// sizeof输出的类型为size_t,%zd 表示格式化size_t类型大小
// 在x64系统下sizeof运算符 返回值是size_t 占用空间8字节 要使用%llu格式化输出
printf("sizeof(HMODULE)=%zd 字节\n", sizeof(hModule)); // sizeof(HMODULE)
//HWND hWnd = reinterpret_cast<HWND>(0x001A03DE);
printf("sizeof(DWORD)=%zd sizeof(LONG)=%zd\n", sizeof(DWORD), sizeof(LONG));
// char short int的表示范围
// 头文件 #include<limits.h>
printf("char类型表示范围 %d至%d\r\n", SCHAR_MIN, SCHAR_MAX);
printf("unsigned char类型表示范围 %u至%u\r\n", 0, UCHAR_MAX);
// short
printf("short类型表示范围 %d至%d\r\n", SHRT_MIN, SHRT_MAX);
printf("unsigned short类型表示范围 %u至%u\r\n", 0, USHRT_MAX);
// int -21亿 +21亿
printf("int类型表示范围 %d至%d\r\n", INT_MIN, INT_MAX);
printf("unsigned int类型表示范围 %u至%u\r\n", 0, UINT_MAX);
HWND calcHwnd = FindWindow(L"CalcFrame", nullptr);
printf("窗口句柄=%p\r\n", calcHwnd);
TCHAR buf[MAX_PATH] = {
0 };
wchar_t outBuf[MAX_PATH] = {
0 };
GetWindowText(calcHwnd, buf, MAX_PATH);
// %ws 表示格式化宽字符串
swprintf_s(outBuf, L"calc %ws\n", buf);
// 宽字符串也可以使用大写的%S格式化输出
//_stprintf_s(outBuf, _T("calc caption %S\n"), buf);
MessageBox(nullptr, outBuf, buf, MB_OK);
SetWindowText(calcHwnd, L"我的计算器zzz");
GetWindowThreadProcessId、OpenProcess、ReadProcessMemory
GetWindowThreadProcessId //Return thread TID and process PID
#include <iostream> // 包含了<stdio.h>头文件
#include<Windows.h>
int main()
{
// 1 通过窗口标题或者类名 获取目标窗口句柄
// 2 通过窗口句柄获取进程的PID,TID
HWND 窗口句柄 = FindWindowA("MainWindow", "植物大战僵尸中文版");
printf("窗口句柄 h=%p\r\n", h);
DWORD pid = 0, tid = 0;
printf("&pid=%p\r\n", &pid);
// int 和 long是等价
// DWORD 等价于 unsigned long
// DWORD* 等价于 LPDWORD
tid = GetWindowThreadProcessId(窗口句柄, &pid);
printf("tid=%d pid=%d 16进制tid=%X 16进制pid=%X\r\n", tid, pid, tid, pid);
// 获取 进程 权限 句柄
// 通过pid获取进程句柄,同时要指定渴望得到的访问权限
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); // PROCESS_VM_READ
printf("进程句柄=%p \r\n", 进程句柄);
unsigned int 返回值 = 0;
//跨进程读取目标进程指定地址处的4字节数据
//ReadProcessMemory(进程句柄, (LPCVOID)0x0000000077B2C934/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, 0);
ReadProcessMemory(进程句柄, reinterpret_cast<LPCVOID>(0x77B2C934)/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, 0);
printf("返回值=%X 返回值=%d 返回值=%u\r\n", 返回值, 返回值, 返回值);// GetLastError();
}
Sealing read memory interface function int R4(void* address)
Read the data in the base address offset formula through the interface function.
DWORD R4(UINT_PTR 内存地址)
{
HWND 窗口句柄 = FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); // PROCESS_VM_READ
DWORD 返回值 = 0;
ReadProcessMemory(进程句柄, (LPCVOID)内存地址/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, 0);
return 返回值;
}
Write a value across processes to the memory address of the target process WriteProcessMemory
#include <iostream>
#include<Windows.h>
HWND 获取游戏窗口句柄()
{
return FindWindowA("MainWindow", "植物大战僵尸中文版");
}
//unsigned
DWORD R4(UINT_PTR 内存地址)
{
HWND 窗口句柄 = 获取游戏窗口句柄(); //FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
DWORD 返回值 = 0;
ReadProcessMemory(进程句柄, (LPCVOID)内存地址/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, nullptr);
CloseHandle(进程句柄);//释放掉句柄资源
return 返回值;
}
//写入成功 W4返回 非0的数值
//写入失败 W4返回 0
int W4(UINT_PTR 内存地址/*要写入目标进程的内存地址*/, DWORD 要写入的值)
{
HWND 窗口句柄 = 获取游戏窗口句柄(); //FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
unsigned int 返回值 = WriteProcessMemory(进程句柄, (LPCVOID)内存地址/*要写的目标进程地址*/, &要写入的值/*存放待写入数据的地址*/, 4, nullptr);
CloseHandle(进程句柄);//释放掉句柄资源
return 返回值;
}
int main()
{
//[[6A9EC0]+768]+5560 =14F2A3D8
// [[[6A9EC0]+768]+5560]=75
int 阳光 = R4(R4(R4(0x6A9EC0) + 0x768) + 0x5560);
UINT_PTR 阳光地址 = R4(R4(0x6A9EC0) + 0x768) + 0x5560;
printf("阳光=%d\r\n", 阳光);
printf("阳光地址=%X\r\n", 阳光地址);
W4(阳光地址, 567);
getchar();//等待键盘输入
}
int main_0()
{
//[[6A9EC0]+768]+5560 =14F2A3D8
// [[[6A9EC0]+768]+5560]=75
int 阳光 = R4(R4(R4(0x6A9EC0) + 0x768) + 0x5560);
UINT_PTR 阳光地址 = R4(R4(0x6A9EC0) + 0x768) + 0x5560;
printf("阳光=%d\r\n", 阳光);
printf("阳光地址=%X\r\n", 阳光地址);
HWND 窗口句柄 = 获取游戏窗口句柄(); //FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
int 新阳光的值 = 66666;
WriteProcessMemory(进程句柄, (LPVOID)阳光地址, &新阳光的值, 4, nullptr);
CloseHandle(进程句柄);//释放掉句柄资源
getchar();//等待键盘输入
}
C, C++ 32-bit and 64-bit process memory data reading and writing function interface, project management through header files
// 内存读写.h
#pragma once
#include <iostream>
#include<Windows.h>
HWND 获取游戏窗口句柄();
DWORD R4(UINT_PTR 内存地址/*从这个地址开始读取4字节*/);
int W4(UINT_PTR 内存地址/*从这个地址开始写入4字节*/, DWORD 要写入的值);
// 内存读写.cpp
#include "内存读写.h"
HWND 获取游戏窗口句柄()
{
return FindWindowA("MainWindow", "植物大战僵尸中文版");
}
// 读取4字节数据
DWORD R4(UINT_PTR 内存地址)
{
HWND 窗口句柄 = 获取游戏窗口句柄(); //FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
DWORD 返回值 = 0;
ReadProcessMemory(进程句柄, (LPCVOID)内存地址/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, nullptr);
CloseHandle(进程句柄);//释放掉句柄资源
return 返回值;
}
//写入成功 W4返回 非0的数值
//写入失败 W4返回 0
int W4(UINT_PTR 内存地址/*要写入目标进程的内存地址*/, DWORD 要写入的值)
{
HWND 窗口句柄 = 获取游戏窗口句柄(); //FindWindowA("MainWindow", "植物大战僵尸中文版");
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
unsigned int 返回值 = WriteProcessMemory(进程句柄, (LPCVOID)内存地址/*要写的目标进程地址*/, &要写入的值/*存放待写入数据的地址*/, 4, nullptr);
CloseHandle(进程句柄);//释放掉句柄资源
return 返回值;
}
// main程序入口点.cpp
#include "内存读写.h"
//[[6A9EC0]+768]+5560 =14F2A3D8
// [[[6A9EC0]+768]+5560]=75
int 阳光 = R4(R4(R4(0x6A9EC0) + 0x768) + 0x5560);
UINT_PTR 阳光地址 = R4(R4(0x6A9EC0) + 0x768) + 0x5560;
printf("阳光=%d\r\n", 阳光);
printf("阳光地址=%X\r\n", 阳光地址);
DWORD 新的阳光值 = 5567;
W4(阳光地址, 新的阳光值);
getchar();//等待键盘输入
X64 environment masm assembly asm file
When we cannot find the main entry point in the x32dbg debugger, we can locate the entry point by adding a MessageBoxA or MessageBeep function at the beginning of the main function body:
As shown in the figure above, by jumping to the MessageBoxA function and setting a breakpoint, after running and breaking here, you can see the _main return address in the stack in the lower right corner.
When we write our own code for debugging and testing, it is best to disable the random base address so that it can be turned into a fixed address to facilitate our testing:
call指令
77256C78 | E8 EF02FBFF | call ntdll.77206F6C
EIP=77206F6C
push 77256C78+5 //esp=esp-4 //64 rsp=rsp-8
ret指令
77256CAF | C3 | ret
跳转到 [esp]
esp=esp+4 //64 rsp=rsp+8
Six common parameter calling conventions: passing and flat stack
x86 environment
#include<iostream>
//VS环境默认的调用约定
//add esp,参数数量*4
//add rsp,参数数量*8
int _cdecl call_cdecl(int a, int b)
{
return a + b;
}
//ret 参数数量*4 //x86 //ret 8
//ret 参数数量*8 //x64 //ret 10
int _stdcall call_std(int a, int b)
{
return a + b;
}
//快速
int _fastcall call_fast(int a, int b)
{
return a + b;
}
// thiscall 类成员函数独有
/*
int __thiscall call_vector(int a, int b)
{
return a + b;
}
*/
// __vectorcall (/ Gv)
int __vectorcall call_vector(int a, int b)
{
return a + b;
}
//int __clrcall test()
//{
// return 1;
//}
int abc(int a, int b, int c)
{
printf("技能释放 a=%d,b=%d c=%d\r\n", a, b, c);
return a + b + c;
}
int main()
{
int a = 333;
int b = 123;
printf(" call_cdecl()=%X行号=%d\r\n", call_cdecl(0x11A, 0xA11), __LINE__);
printf(" call_std()=%X行号=%d\r\n", call_std(0x11B, 0xB11), __LINE__);
printf(" call_fast()=%X行号=%d\r\n", call_fast(0x11C, 0xC11), __LINE__);
printf(" call_vector()=%X行号=%d\r\n", call_vector(0x11D, 0xD11), __LINE__);
printf(" 释放技能 abc=%d\r\n", abc(0x123, 2, 6));
getchar();
return 1;
}
Functions can be labeled using colons and comments can be added using semicolons.
In the x86 environment, the _fastcall calling convention passes parameters: ecx parameter 1, edx parameter 2, and the remaining parameters are passed through the stack (push), and the stack is flattened (ret n) within the function.
In the x86 environment, the _vectorcall calling convention passes parameters: ecx parameter 1, edx parameter 2, and the remaining parameters are passed through the stack (push), which is also flattened within the function (< a i=1>). Since the parameters passed now are all integers, we can see through disassembly that parameter passing and stack balancing are the same for _fastcall and _vectorcall; in fact, there is a difference between them, and the difference should lie in the parameters. is a floating point number. ret 数值
In the x86 environment, the _cdecl and _stacall calling conventions all use push to pass parameters.
x64 environment
#include<iostream>
//VS环境默认的调用约定
//add esp,参数数量*4
//add rsp,参数数量*8
int _cdecl call_cdecl(int a, int b,int a3,int a4,int a5,int a6)
{
return a + b+a3+a4+a5+a6;
}
//ret 参数数量*4 //x86 //ret 8
//ret 参数数量*8 //x64 //ret 10
int _stdcall call_std(int a, int b, int a3, int a4, int a5, int a6)
{
return a + b + a3 + a4 + a5 + a6;
}
//快速
int _fastcall call_fast(int a, int b, int a3, int a4, int a5, int a6)
{
return a + b + a3 + a4 + a5 + a6;
}
// thiscall 类成员函数独有
/*
int __thiscall call_vector(int a, int b)
{
return a + b;
}
*/
// __vectorcall (/ Gv)
int __vectorcall call_vector(int a, int b, int a3, int a4, int a5, int a6)
{
return a + b + a3 + a4 + a5 + a6;
}
//int __clrcall test()
//{
// return 1;
//}
int abc(int a, int b, int c)
{
printf("技能释放 a=%d,b=%d c=%d\r\n", a, b, c);
return a + b + c;
}
int main()
{
int a = 333;
int b = 123;
printf(" call_cdecl()=%X行号=%d\r\n", call_cdecl(0x11A, 0xA11,3,4,5,6), __LINE__);
printf(" call_std()=%X行号=%d\r\n", call_std(0x11B, 0xB11,3,4,5,6), __LINE__);
printf(" call_fast()=%X行号=%d\r\n", call_fast(0x11C, 0xC11,3,4,5,6), __LINE__);
printf(" call_vector()=%X行号=%d\r\n", call_vector(0x11D, 0xD11,3,4,5,6), __LINE__);
printf(" 释放技能 abc=%d\r\n", abc(0x123, 2, 6));
getchar();
return 1;
}
In the x64dbg debugger, if you want to go to the main function, press ctrl+G and enter main to confirm. There is no need to add an underline in front of main (x86).
In the x64 environment, we found that the four calling conventions of _cdecl, _stacall, _fastcall, and _vectorcall are all the same. They all use rcx, rdx, r8, and r9 to pass parameters. If more than 4 parameters are used, the stack (rsp+20) is used. to pass.
We see that the fifth parameter is rsp+20, and the first four parameters occupy a space of 0x20 size. However, in the x64 environment, the first four parameters are passed through rcx, rdx, r8, and r9 respectively. In order Alignment (also for compatibility with x86 assembly), the fifth parameter is passed through rsp+20, and the sixth parameter is passed through rsp+28;
and the last instruction inside the function is a < /span>ret
, not followed by a value.
After testing, we found that in the x64 environment, there is no difference between these four calling conventions. The parameter passing and flat stacking methods are the same, so for the time being, these four calling conventions can be considered to be the same.
Since the call instruction of this function is executed, rsp=rsp-8, so the above picturemov ss:[rsp+20],r9d
does not cover the fifth parameter before the call instruction is executed, mov ss:[rsp +20], r9d is equivalent to mov ss:[rsp+18],r9d for the environment before the call instruction is executed, that is, the fourth parameter is given to ss:[rsp+18], this kind of stack in the function The parameter passing method is also for compatibility with x86 assembly.
That is, the stack change diagram is as follows:
x64 register | Lower 4 bytes | Lower 2 bytes | Lower 1 byte |
---|---|---|---|
rcx | ecx | cx | cl |
rdx | edx | dx | dl |
r8 | r8d | r8w | r8b |
r9 | r9d | r9w | r9b |
Looking at another function, the stack changes in the same way:
Can be set for all configurations, all platforms:
Assembly conditional transfer instructions and loops
int main_while01()
{
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
// SF(符号标志位)
// OF(溢出标志位)
//cmp a-20 从而影响到 符号标志位
while (a < 20) // SF=OF
{
printf("a 的值: %d 行号=%d \n", a, __LINE__);
a++; //a=a+1; add [esp+??],1// inc [esp+??]
}
return 0;
}
int main()
{
printf("main start 行号=%d \n", __LINE__);
main_while01();
printf("main end 行号=%d \n", __LINE__);
return 1;
}
We see that OF is not equal to SF in the above picture, and SF is 1. This is the condition of the signed jump instructionJL
, so the conditionjge
is not true. , does not jump, enters the loop.
(The assembly instruction cmp
performs a subtraction operation, that is, a - 20
, thus affecting the sign flag SF)
As shown in the figure above, when ss:[ebp-8] is equal to 14, execute the cmp instruction, you can see that ZF is equal to 1, OF is equal to SF, both are 0:
When OF equals 0 and SF equals 0, that is, OF==SF, then the signed jump instructionjge
The jump condition is established and the loop terminates.
Subtraction affects the SF and ZF flags;
Addition affects the CF and OF flags.
Let's take a look at the while loop code containing the continue statement:
int main_continue()
{
/* 局部变量定义 */
int a = 0;
/* while 循环执行 */
while (a < 10) //0x0A=10
{
a++;
if (a == 5)
{
/* 使用 continue 进入下一次循环 */
continue;
}
printf("for循环测试:a 的值: %d 行号=%d \n", a, __LINE__);
}
return 0;
}
int main()
{
printf("main start 行号=%d \n", __LINE__);
main_continue();
printf("main end 行号=%d \n", __LINE__);
return 1;
}
We can see from the above figure that if it is less than, use the jge
command, and if it is equal to, use the jne
command.
C, C++ uses code to call CALL across processes
Call CALL with code injector
#include <iostream>
#include <Windows.h>
int call00()
{
printf("call00 无参数 call0_test 行号=%d\r\n", __LINE__);
return 0x123;
}
int call01(int a)
{
printf("call01 参数1=%d 行号=%d\r\n", a, __LINE__);
return a + 2;
}
int call02(int a, int b)
{
printf("call02 参数1=%d 参数2=%d 行号=%d\r\n", a, b,__LINE__);
return a + b+3;
}
int main()
{
// MessageBoxA(0, 0, 0, 0);//ctrl+G 转到 MessageBoxA 下断
printf("MessageBoxA=%p 行号=%d \r\n", MessageBoxA, __LINE__);
int 计数 = 1;
while (1)
{
printf("call00=%p call01=%p,call02=%p \n", call00, call01, call02);
getchar();
printf("计数=%d,行号=%d \r\n", 计数++, __LINE__);
}
return 1;
}
Note,The thread number 5236 on the title bar of x64dbg is in decimal, and the 1474 in the lower left corner is in hexadecimal. These two numbers are the same .
C, C++ creates a remote thread and calls CALL
- Remote CALL process
- Use spy++ to get the target window handle, FindWindow
- With a window handle, use GetWindowThreadProcessID to obtain the target process PID.
- Obtain the thread creation permission of the target process through OpenProcess and obtain the corresponding handle.
- Use CreateRemoteThread to call remote CALL
#include <iostream>
#include<Windows.h>
//unsigned
//int R4(UINT_PTR 内存地址)
//{
// HWND 窗口句柄 = FindWindowA("MainWindow", "植物大战僵尸中文版");
// DWORD pid = 0, tid = 0;
// tid = GetWindowThreadProcessId(窗口句柄, &pid); // a&b
// HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);//HWND
// unsigned int 返回值 = 0;
// ReadProcessMemory(进程句柄, (LPCVOID)内存地址/*要读取的地址*/, &返回值/*存放数据的地址*/, 4, 0);
// return 返回值;
//}
// \n
// \ \\
// 窗口标题= "C:\\Users\\Administrator\\source\\repos\\A001\\Debug\\A023_测试CALL.exe"
//#define 窗口标题 "C:/moshou/A001-游戏辅助技术(初级班)/代码\A001-024/Debug"
//#define 窗口标题 "C:\moshou\A001-游戏辅助技术(初级班)\代码\A001-024\Debug"
#define 窗口标题 R"(C:\moshou\A001-游戏辅助技术(初级班)\代码\A001-024\Debug\A023_测试CALL.exe)"
DWORD 获取PID()
{
HWND 窗口句柄 = FindWindowA(0, 窗口标题);
DWORD pid = 0, tid = 0;
tid = GetWindowThreadProcessId(窗口句柄, &pid); // a&b
return pid;
}
void 调用call01测试()
{
DWORD pid = 4180; //直接在任务管理器里 取得PID
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);//HWND
//1,4,5 重视这3个参数
CreateRemoteThread(
进程句柄,//1
0, //2
0, //3
(LPTHREAD_START_ROUTINE)0x411370,//4 CALL地址
(LPVOID)123,//5 CALL的参数
0,
0);
}
void 调用call01测试2(int 参数1)
{
DWORD pid = 4180;//任务管理器取的pid
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);//HWND
//1,4,5 重视这3个参数
CreateRemoteThread(
进程句柄,//1
0, //2
0, //3
(LPTHREAD_START_ROUTINE)0x411370,//4 CALL地址
(LPVOID)参数1,//5 CALL的参数
0,
0);
CloseHandle(进程句柄);
}
void 调用call01测试3(int 参数1)
{
DWORD pid = 获取PID();//任务管理器取的pid
printf("pid=%d \r\n", pid);
HANDLE 进程句柄 = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);//HWND
//1,4,5 重视这3个参数
CreateRemoteThread(
进程句柄,//1
0, //2
0, //3
(LPTHREAD_START_ROUTINE)0x4113CA,//4 call01地址
(LPVOID)参数1,//5 CALL的参数
0,
0);
CloseHandle(进程句柄);
}
int main()
{
调用call01测试3(12666);
调用call01测试3(12667);
调用call01测试3(12668);
//R忽略 转义字符
char test[]= R"(C:\Users\Administrator\source\repos\A001\Debug\A023_测试CALL.exe)";
//char test2[] = "C:\Users\Administrator\source\repos\A001\Debug\A023_测试CALL.exe";
printf("%s\r\n",test);
std::cout << "Hello World!\n";
}
Miscellaneous
Commonly used software games
This code injector can only be used for testing 32-bit programs. There is no free code injection tool that can be used by 64-bit processes. We can only write our own code to inject it.
Signature-free loading driver:
https://github.com/TheCruZ/kdmapper
win10 signature:
https://github.com/HyperSine/Windows10-CustomKernelSigners
If the Inter C++ Compiler compiler is installed in the vs platform tool set, Intel's compiler supports __asm int 3
these inline assembly:
Locating pattern
After editing the feature code, move to the beginning of the function or above, Ctrl+B to open the输入要查找的二进制字串
dialog box, paste the feature code search, if it can be found, it means the feature we wrote There is no problem with the code;
You should also restart the game, open the OD add-on game, and search for the feature code as described above for verification.
d3d8thk hijack injection
PE View Analysis Tools
PE Viewer (PE_Study) https://www.cr173.com/soft/47081.html
PE File Viewer (PETool) https://www.cr173. com/soft/89855.html
Niyuan PE file extraction https://www.cr173.com/soft/85171.html
PE analysis tool (PE_Hacker) https://www.cr173.com/soft/1438600.html
https://blog.csdn.net/PE_Hacker/article/details /115766034
PE-Analysis:
Driver development and debugging tool DriverTool
Driver development and debugging tools, such as digital signature Windows 64Signer V1.2 in test mode, and tools such as DebugView.
/