游戏黑客圣经GHB1学习笔记 part3(11-15)

游戏黑客圣经GHB1学习笔记 part3(11-15),第1张

游戏黑客圣经GHB1学习笔记 part3(11-15) 11.Learn C++ with Step by Step Guide(一步步学习C++)

学校课程已经学习过了基本的内容,在GH中,C++有这些优势

  1. C/C++ 是非常低级的,它尽可能接近汇编。与学习解释型语言相比,通过学习 C/C++,您将与汇编有更密切的关系。
  2. 如果你想绕过内核反作弊,你需要学习制作内核驱动程序,这些驱动程序是用 C/C++ 制作的。

入门必看课程:
点击学习C++的前二十课,作者讲的非常好,人也很帅,看完受益匪浅,但是没有做记录,日后有时间再看再记录吧。
learncpp.com,去这个网站完成前12章节

12.Understanding Strings Unicode, TCHAR, MBCS(理解字符串)

字符串在所有工作中都是一个必不可少且重要且混乱的东西。
一天内搞懂所有字符串相关的内容不现实也没必要。
通读这些链接,之后需要用的时候再做查询即可

The Private Lives of Strings — Cunning Planning
Unicode & Windows - CunningPlanning
Unicode — Cunning Planning
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know about Unicode and Character Sets (No Excuses!)
The Complete Guide to C++ Strings, Part I - Win32 Character Encodings - CodeProject
String and character literals (C++)
UTF-8 Everywhere

13.Get Module base Address Tutorial dwGetModulebaseAddress(获取基地址函数教程)

无论是internal还是external,获取module基址都是必要的。
本次课程主要学习从外部获取基地址。
还记得我们上次说的ASLR技术吗
地址空间布局随机化 - ASLR
当某些东西的地址总是在同一个地方时,很容易制造病毒和漏洞。所以大多数 *** 作系统都添加了 ASLR 作为额外的安全层,但这是 25 年前发明的,到 2021 年基本上一文不值。ASLR 使 .exe 加载到随机虚拟地址中。
要绕过这个,您所要做的就是在运行时获取模块基地址并添加相对偏移量。
比如ac_client.exe+0x9ADBD,仅此而已。

重点内容是理解并学习使用winapi函数。
在此之前,必须介绍几个windows中的重要概念。

  1. process,module,thread的关系
    一个进程可以有多个模块,一个模块可以有多个线程,模块是相对独立的,可以是一个DLL或是EXE,线程是进程运行的最小执行单位。
    exe,dll都是module
  2. handle是什么?
    单纯翻译的话就是句柄,但是要真正讲清楚什么是句柄,很难很难,我现在也属于一知半解。
    贴出一些参考资料,通读之,未来用到继续详细看

wiki-eng
what is handle - stackoverfloat
msdn
中文wiki
百度百科

写一下自己的理解吧,handle是windows系统中非常重要的一个概念。

OBJECT and HANDLE
一个对象是一个数据结构,它表示一种系统资源,例如文件,线程或图形图像。应用程序不能直接访问对象数据或对象所代表的系统资源。相反,应用程序必须获得一个对象句柄,它可以用来检查或修改系统资源。每个句柄在内部维护的表中都有一个条目。这些条目包含资源的地址和识别资源类型的方法。

不确切不负责任的说,handle是windows中各种资源,窗口,模块,进程等等的“智能指针”。
说是“指针”是因为,它在C层面上,用户层上,面向程序员是透明的,使用起来和指针很像,都算是windows中一个实例的指针,给了Windows中各种东西一个标识。
说是“智能”是因为,他在内核层面上, *** 作系统通过进程句柄列表来进行维护。
更详细的,请自行查看上述资料,本人目前理解有限。
3. dll是什么?和exe的关系?怎么工作?最后给出一个dll的实际使用的C++代码。

动态链接库(英语:Dynamic-link library,缩写为DLL)是微软公司在windows系统中实现共享函数库概念的一种实现方式。
所谓动态链接,就是把一些经常会共享的代码(静态链接的OBJ程序库)制作成DLL档,当可执行文件调用到DLL档内的函数时,Windows *** 作系统才会把DLL档加载存储器内,DLL档本身的结构就是可执行档,当程序有需求时函数才进行链接。通过动态链接方式,存储器浪费的情形将可大幅降低。静态链接库则是直接链接到可执行文件。

尽管 DLL 和应用程序都是可执行模块,但它们在几个方面有所不同。最明显的区别是您不能运行 DLL。从系统的角度来看,应用程序和 DLL 之间有两个根本区别:
1.一个应用程序可以同时在系统中运行多个自身实例。一个 DLL 只能有一个实例。
2.应用程序可以作为进程加载。它可以拥有诸如堆栈、执行线程、全局内存、文件句柄和消息队列之类的东西。DLL 不能拥有这些东西。

个人浅薄不负责任的解释:dll就是一个面向对象概念下的产物,是一个库,库里存了一些函数代码和数据,在exe程序运行的时候可以动态调用。
更细节的东西可以自行搜索百度百科,wiki,msdn,Stack Overflow,都有很好的解释。
dll和exe的关系
DLL中虽然包含了可执行代码却不能单独执行,而应由Windows应用程序直接或间接调用。很多时候,exe缺少了某个dll就无法运行。dll will be atached to exe

Walkthrough: Create and use your own Dynamic link Library (C++)
教程:演练:创建和使用您自己的动态链接库 (C++)

上面的教程很重要,我学习到了很多技巧,代码不是最重要的,最重要的是设置。
首先是"设置">“C/C++”>“常规”>“附加包含目录”
这里是添加.h头文件目录的
然后是"设置">“链接器”>“常规”>“附加库目录”
这里是添加.lib头文件目录的

使用dll需注意三个文件:

•.h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。
•.LIB文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误(如果不想用lib文件或者没有lib文件,可以用WIN32
API函数LoadLibrary、GetProcAddress装载)。
•dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。

“设置”>“链接器”>“输入”>“附加依赖项”
输入lib的名字

最重要的就是:“生成事件”选项设置
我们可以设置项目在build前后的 *** 作。
这里使用一行简单的xcopy命令,将dll目录的dll复制到当前目录下,以便执行程序。


前情提要似乎有点过长了。。
dll代码中需要理解的就是下面这几个宏定义

#ifdef DLL_TEST_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif

extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();

extern "C" MATHLIBRARY_API unsigned fibonacci_index();

第一行的DLL_TEST是dll的名称,默认在生成项目的时候会预定义,之后用MATHLIBRARY_API关键字定义的函数就可以被外部exe调用了。

现在我们回归正题,来看看dwGetModulebaseAddress函数是如何实现的。

DWORD GetProcId(const wchar_t* procName) 
{
	DWORD procId = 0;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnap != INVALID_HANDLE_VALUE)
	{
		PROCESSENTRY32 procEntry;
		procEntry.dwSize = sizeof(procEntry);
		if (Process32First(hSnap, &procEntry)) 
		{
			do
			{
				if (!_wcsicmp(procEntry.szExeFile, procName)) 
				{
					procId = procEntry.th32ProcessID;
					break;
				}
			} while (Process32Next(hSnap,&procEntry));
		}
	}
	CloseHandle(hSnap);
	return procId;
}

uintptr_t GetMoudlebaseAddress(DWORD procId, const wchar_t* modName)
{
	uintptr_t modbaseAddr = 0;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, procId);
	if (hSnap != INVALID_HANDLE_VALUE)
	{
		MODULEENTRY32 modEntry;
		modEntry.dwSize = sizeof(modEntry);
		if (Module32First(hSnap, &modEntry))
		{
			do
			{
				if (!_wcsicmp(modEntry.szModule, modName))
				{
					modbaseAddr = (uintptr_t)modEntry.modbaseAddr;
					break;
				}
			} while (Module32Next(hSnap,&modEntry));
		}
		CloseHandle(hSnap);
		return modbaseAddr;
	}
	return 0;
}
int main()
{
	//get procId of the process
    DWORD procId = GetProcId(L"sauerbraten.exe");
    //get modulebaseAddress
    uintptr_t modulebase = GetMoudlebaseAddress(procId, L"sauerbraten.exe"); 
}

因为是学习笔记,所以我就用自己的方式来解释和记录,获取最准确的资料请自行上google使用关键词+msdn搜索即可。
首先要使用GetProcId函数获取进程的ID,这个过程用到了几个关键的winapi

  • CreateToolhelp32Snapshot获取指定进程的快照,以及这些进程使用的堆、模块和线程。当使用TH32CS_SNAPPROCESS作为参数时,生成一个系统中的所有进程的快照。
  • Process32Next从系统快照中找到下一个进程信息。
  • PROCESSENTRY32是一个结构体,用于描述快照中进程的entry。
  • CloseHandle是一个用于关闭一个打开的句柄的函数。

函数整体思路就是,获取当前所有进程的句柄,遍历,根据进程名找到procId并返回。
其中DWORD,uintptr_t都是自适应的无符号型整数型变量。
字符串前加L表明是unicode编码的文本。

有了procId,我们就可以找进程地址了。
这里的思路和上面类似,对进程的modules做个快照,然后遍历,对比名字,从modentry结构体中确定基地址。
使用的部分windowsAPI:

  • Module32First是用来检索有关与进程关联的第一个模块的信息。
  • MODULEENTRY32是一个结构体,用于描述某个特定proc一系列module的entry。

这个过程都是外部的,如果在内部,简单调用一个windowsAPI函数即可

uintptr_t pEngine = (uintptr_t)GetModuleHandle("engine.dll");
14.FindDMAAddy - C++ Multilevel Pointer Function(使用C++多级指针找到动态地址)

多级指针的理解,简单讲就是多个结构体之间用指针链接起来,偏移的含义其实就是每个结构体内到当前指针或者数值前分配了多少内存空间。
下面是外部的根据多级指针获取动态地址的函数

uintptr_t FindDMAddress(HANDLE hProc, uintptr_t ptr, std::vector offsets)
{
	uintptr_t addr = ptr;
	for (unsigned int i = 0; i < offsets.size(); i++) 
	{
		ReadProcessMemory(hProc, (BYTE*)addr, &addr, sizeof(addr), nullptr);
		addr += offsets[i];
	}
	return addr;
}
    //get handle of the process
    HANDLE hProcess = 0;
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId);

    //resolve base address of pointer chain
    uintptr_t dynamicPtrbaseAddr = modulebase + 0x312930;
    
    std::cout<< "DynamicPtrbaseAddr= " << "0x" < healthOffsets = { 0x0, 0x118, 0x340 };
    uintptr_t healthAddr = FindDMAddress(hProcess, dynamicPtrbaseAddr, healthOffsets);
    std::cout << "healthAddr= " << "0x" << std::hex << healthAddr << std::endl;

    //read health value 
    int healthValue = 0;
    ReadProcessMemory(hProcess, (BYTE*)healthAddr, &healthValue, sizeof(healthAddr), nullptr);
    std::cout << "Current health = " << std::dec << healthValue << std::endl;

    //write to it
    int superHealth = 2021;
    WriteProcessMemory(hProcess, (BYTE*)healthAddr, &superHealth, sizeof(healthAddr), nullptr);
    
    //read ammo again
    ReadProcessMemory(hProcess, (BYTE*)healthAddr, &healthValue, sizeof(healthAddr), nullptr);
    std::cout << "Current health = " << std::dec << healthValue << std::endl;

上面的代码中涉及了几个在外部非常重要的WindowsAPI

  • ReadProcessMemory 和 WriteProcessMemory,这两个函数的功能是根据proc句柄和地址读写内存数据,读的时候句柄必须具有对进程的 PROCESS_VM_READ 访问权限。
    写的时候句柄必须具有对进程的 PROCESS_VM_WRITE 和 PROCESS_VM_OPERATION 访问权限。
  • OpenProcess用于打开一个现在存在的进程对象,返回其句柄,参数可以设置具体的句柄权限

当然FindDMAAddress的内部版本就很简单了

uintptr_t FindDMAAddress(uintptr_t ptr, std::vector offsets)
{
    uintptr_t addr = ptr;
    for (unsigned int i = 0; i < offsets.size() ; ++i)
    {
        addr = *(uintptr_t*)addr;
        addr += offsets[i];
    }
    return addr;
}
15.How to Hack Any Game Tutorial C++ Trainer #1 - External(外部trainer教程1)

已经没什么好写的了,最后给出一个多级指针模板吧

Address = Value = ?

base ptr -> address + offset4 = address
base ptr -> address + offset3 = address
base ptr -> address +> offset2 = address
static base -> address + offset1 = address

源代码地址

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/4948802.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-11-13
下一篇2022-11-13

发表评论

登录后才能评论

评论列表(0条)

    保存