艾尔登法环 修改卢恩程序(逆向开发学习)

艾尔登法环修改卢恩

艾尔登法环肝魂太累了,考研党没有时间刷魂升级,反正单机游戏于是就自己写了一个修改卢恩的外挂。(用CE每次都要搜索内存太麻烦了)
先放github链接:https://github.com/ZKAFKA/EldenRingChangeRune
以管理员身份运行“修改卢恩.exe”文件就可以了。

地址偏移分析

首先分析卢恩地址。CE查到的地址都是动态基址+N次偏移后的地址,所以每次关闭重启游戏后,卢恩的地址都会发生改变。这时候就需要我们寻找卢恩的偏移。

找到卢恩以后首先点击CE的指针地址扫描。
在这里插入图片描述
在这里插入图片描述
扫描后会得到一个偏移列表
在这里插入图片描述
发现有许多偏移都能得到卢恩的地址,这里我们思考一下,一般卢恩作为角色的基础属性应该是第一个压入内存栈中的,所以我们通过地址由小到大排序,基址+最小偏移值的应当是最基础的那个卢恩值。
同时我们也可以通过重启游戏后不关闭指针扫描窗口,选择Rescan重新检索游戏重新打开后的卢恩地址,从而去除变化的偏移地址,多次筛选得到最终列表。
在这里插入图片描述
最终我们得到这样一个动态偏移的地址
在这里插入图片描述
由上图可以得知,取[游戏基址地址+03A45AE8]中的值,这个值为另一个地址7EF3F2A3FFE8,取[7EF3F2A3FFE8+180]中的值得到另一个地址7EF3F3AFC700,再取[7EF3F3AFC700+6C]中的值即为当前的卢恩值。

程序代码

偏移逻辑清晰后,就可以进行代码编写。
第一步先找到游戏的基址,这里我们通过进程快照的方式获取。

PVOID GetProcessImageBase1(DWORD dwProcessId)
{
	PVOID pProcessImageBase = NULL;
	MODULEENTRY32 me32 = { 0 };
	me32.dwSize = sizeof(MODULEENTRY32);

	// 获取指定进程全部模块的快照
	HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPALL, dwProcessId);
	if (INVALID_HANDLE_VALUE == hModuleSnap)
	{
		cout << "failed" << endl;
		return 0;
	}

	// 获取快照中第一条信息
	BOOL bRet = ::Module32First(hModuleSnap, &me32);
	if (bRet)
	{
		// 获取加载基址
		pProcessImageBase = (PVOID)me32.modBaseAddr;
		cout << pProcessImageBase << endl;
	}

	// 关闭句柄
	CloseHandle(hModuleSnap);
	
	return pProcessImageBase;
}

注意测试时要选择x64编译,同时运行时要右键选择以管理员身份运行。否则会无法获取进程快照。

接下来就是三次偏移获取卢恩值了。

int main()
{
	HWND hwnd = FindWindowW(NULL, L"ELDEN RING™");
	DWORD pid = 0;
	DWORD realWrite = 0;
	if (hwnd == NULL)
	{
		MessageBoxA(0, "游戏未运行", "提示", MB_OK);
		return 0;
	}
	GetWindowThreadProcessId(hwnd, &pid);
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

	//获取进程基址	
	PVOID runeBaseAddress = GetProcessImageBase1(pid);
	DWORDLONG runeAddressStart = (DWORDLONG)runeBaseAddress + offset0;
	cout << (LPVOID)runeAddressStart << endl;
	DWORD getLastError;
	SIZE_T dwSize = 0;

	//一级偏移
	DWORDLONG OffsetFirstValue = 0;
	if (0 == ReadProcessMemory(hProcess, (LPVOID)(runeAddressStart), &OffsetFirstValue, sizeof(DWORDLONG), &dwSize))
	{
		printf_s("一级偏移获取失败\n");
		getLastError = GetLastError();
	}
	cout << "一级偏移后值" << (LPVOID)OffsetFirstValue << endl;

	//二级偏移
	DWORDLONG OffsetSecondValue = 0;
	if (0 == ReadProcessMemory(hProcess, (LPVOID)(OffsetFirstValue + offset1), &OffsetSecondValue, sizeof(DWORDLONG), &dwSize))
	{
		printf_s("二级偏移获取失败\n");
		getLastError = GetLastError();
	}
	cout << "二级偏移后值" << (LPVOID)OffsetSecondValue << endl;

	//三级偏移
	DWORD RuneNum = 0;
	if (0 == ReadProcessMemory(hProcess, (LPVOID)(OffsetSecondValue + offset2), &RuneNum, sizeof(DWORD), &dwSize))
	{
		printf_s("三级偏移获取失败\n");
		getLastError = GetLastError();
	}
	cout << "三级偏移后值" << RuneNum << endl;

	DWORDLONG runeRealAddress = OffsetSecondValue + offset2;
	int modifyRune;
	cout << "当前卢恩值: " << RuneNum << endl;
	cout <<"输入你要修改后的值: ";
	scanf_s("%d", &modifyRune);
	WriteProcessMemory(hProcess, (LPVOID)runeRealAddress, &modifyRune, sizeof(DWORD), &dwSize);

	CloseHandle(hProcess);
	system("pause");
	return 0;
}

由于操作系统版本等其他原因,offset偏移值可能不确定,所以如果读者要使用代码建议参照上面的方法自己查一下偏移值,确认并修改一下offset值。为了便于使用者参考和修改,把每一次偏移结果都打印出来以方便调试。后续有机会再加优化及UI界面。

以上内容仅做学习使用,也欢迎大家批评交流。如果对你有帮助的话可以点个赞,谢谢!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值