前面需要了解一下CE的使用,学会基本的CE操作就可以,一些CE的使用方法,除了这个就是要用到C,汇编和Win32的一些知识
阳光值
找阳光值:每次改变阳光的值,用CE不断缩小范围去确定阳光值的数据
找阳光基址:“某个程序每次运行都不会改变的地址(基地址)里面,存着当前程序的阳光地址,我们需要找到这个程序不会改变的地址,通过它偏移拿到阳光地址
xxxx
找到阳关值后复制地址,放到搜索栏中去搜索
可能搜到很多没用的这里就凭运气去找了
看开头地址和下面行或者上面一行是否是相同的地址,相同的话是没有价值的,随便找一个合适的,然后右键看什么访问了这个地址,此时会弹出一个弹窗,谈的很快
需要及时按下ESC键,这里再随便找一个,然后计算它 的偏移/?????😵💫😵
PlantsVsZombies.exe+355E0C
静态基址,知到静态基址后手动添加地址,添加两个指针,因为找了两次,分别填入第一层偏移(第一个找到的),和第二个偏移(第二个找到的)
大致思路:
1.定义变量
2.取进程ID
3.调试输出一下,看结果,能够找到进程ID即可(不等于0或者-1)
4.创建一个变量来存放基址,获取进程的内存基地址
5.创建一个全局变量读取内存信息
6.对阳光值数据进行修改操作
前面5步是基本操作,几乎实现每个功能都需要
DWORD processId;//进程ID变量
//获取进程句柄
HWND GethWnd() {
LPCWSTR targetWindowTitle = L"Plants vs. Zombies";
HWND hWnd = FindWindow(NULL, targetWindowTitle);//获取窗口句柄
if (hWnd == NULL) {
printf("未找到目标窗口\n");
}
else return hWnd;
}
//获取进程ID
DWORD GetProcessID(HWND hWnd) {
GetWindowThreadProcessId(hWnd, &processId);
return processId;
}
//获取进程基地址
LPVOID GetProcessBaseAddress(DWORD ProcessID) {
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, ProcessID);
HMODULE hModules[1024];
DWORD needed;//枚举进程所有模块
if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &needed)) {
int moduleCount = needed / sizeof(HMODULE);//计算模块数量
//获取指定模块信息
for (int i = 0; i < moduleCount; ++i) {
TCHAR moduleName[MAX_PATH];
if (GetModuleBaseName(hProcess, hModules[i], moduleName, sizeof(moduleName) / sizeof(TCHAR))) {
if (_wcsicmp(moduleName, L"PlantsVsZombies.exe") == 0) {
MODULEINFO moduleInfo;
if (GetModuleInformation(hProcess, hModules[i], &moduleInfo, sizeof(moduleInfo))) {
return moduleInfo.lpBaseOfDll;
}
else {
printf("获取模块信息失败\n");
}
}
}
} printf("未找到名为\"PlantsVsZombies.exe\"的模块\n");
CloseHandle(hProcess); // Close the handle before returning NULL
return NULL;
}
else {
printf("枚举进程模块失败\n");
}
CloseHandle(hProcess);//关闭
}
//访问内存数据(根据基地址+传进来的偏移值计算最后要写入数据前的地址)
LPVOID VisitMemory(DWORD processId, LPVOID BaseAddress, intptr_t* offsets, int numOffsets) {
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
int i;
int value = 0;
LPVOID address;
for (i = 0; i < numOffsets; i++) {//循环找完偏移值
if (i == 0)
address = (LPVOID)((DWORD)BaseAddress + offsets[i]);//第一次是基地址+偏移
else
address = (LPVOID)((DWORD)value + offsets[i]);//后面就是内存中的值加偏移
if (i == numOffsets - 1) {
return address;
}
ReadProcessMemory(hProcess, address, &value, sizeof(int), NULL);
}
}
修改阳光值代码:
主要思路是:
sun_x:传入进程号ID和基地址,像内存VisitMemory(processId, BaseAddress, offsets, numOffsets)处写入数值x,VisitMemory(processId, BaseAddress, offsets, numOffsets)的作用就是访问内存数据(根据基地址+传进来的偏移值计算最后要写入数据前的地址)
sun_change:通过写入内存函数把阳光值减少的指令机器码改为0x90,即把指令nop掉,然后调用sun_x函数去写入新的阳光值
//直接让阳光值为x
BOOL sun_x(DWORD processId, LPVOID BaseAddress, intptr_t* offsets, int numOffsets, int x) {
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, processId);
if(WriteProcessMemory(hProcess, VisitMemory(processId, BaseAddress, offsets, numOffsets), &x, sizeof(int), NULL))
return 1;
}
//修改阳光值
BOOL sun_change(DWORD processId, LPVOID BaseAddress) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);//获取进程句柄
BYTE newBytes[] = { 0x90, 0x90};//字符集
intptr_t offsets[] = { 0x355E0C, 0x868,0x5578 };//偏移地址集
WriteProcessMemory(hProcess, reinterpret_cast<LPVOID>(reinterpret_cast<uintptr_t>(BaseAddress) + 0x27694), newBytes, sizeof(newBytes), NULL);//不减阳光
sun_x(processId, BaseAddress, offsets, 3, 8899);//修改阳光为9999
return 1;
}
卡槽冷却:
找卡槽冷却倒计时:
冷却完成是某个值 冷却完成冷却是共用代码,达到某个值后暂停,当把值设置成大于这个值时就可以实现无冷却,所以肯定有一个地方存放了所有植物卡槽冷却倒计时
读内存整数型,用第一个植物计次循环首,计次循环尾
代码:
思路是在内存中选择一个没有内存块,向内存块中写入指令集,修改原先的jmp指令,使其跳转到我们写入的指令集处,执行完代码再跳转回原来的位置
BOOL cooldown_for_slots(DWORD processId, LPVOID BaseAddress,int count_slots) {
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
///////*卡槽冷却方法1 :未完成还有时间戳没搞*/
//intptr_t offsets_slots[] = { 0x355E0C, 0x868,0x15C,0x4C };//偏移地址集
//VisitMemory(processId, BaseAddress, offsets_slots, 4);
//int newValue = 5000; // 要写入内存的新值
//LPVOID targetAddress = VisitMemory(processId, BaseAddress, offsets_slots, 4);
//for (int i = 0; i < count_slots; i++) {
// SIZE_T bytesWritten;
// if (::WriteProcessMemory(hProcess, targetAddress, &newValue, sizeof(newValue), &bytesWritten)) {
// if (bytesWritten == sizeof(newValue)) {
// std::cout << "修改内存成功,地址: " << targetAddress<< ",新值: " << newValue << std::endl;
// }
// else {
// std::cout << "修改内存失败,写入大小不匹配!" << std::endl;
// break;
// }
// }
// else {
// std::cout << "修改内存失败!" << std::endl;
// break;
// }
// // 每次循环后地址加上0x50
// targetAddress = (LPVOID)((DWORD)targetAddress + 0x50);
//}
/*::CloseHandle(hProcess);*/
//卡槽冷却方法二,写内存汇编
BYTE newBytes1[] = { 0xe9,0x3F,0x6e ,0x2a ,0x00,0x90 };// jmp 0x2a6e44;nop
BYTE newBytes2[] = {
0xC7, 0x46, 0x24, 0x88, 0x13, 0x00, 0x00, // mov [esi+24],00001388
// add dword ptr [esi+24]
0x03, 0x46, 0x24,
0x8B, 0x46, 0x24, // mov eax, [edi+24]
0xe9 ,0xb0 ,0x91 ,0xd5 ,0xff }; //jmp 0xffd591b5
WriteProcessMemory(hProcess,(LPVOID)0x49CDF9, newBytes1, sizeof(newBytes1), NULL);
//向跳转地区写入指令
if( WriteProcessMemory(hProcess, (LPVOID)0x743C3D, newBytes2, sizeof(newBytes2), NULL))printf("成功");
return 1;
CloseHandle(hProcess);
}
植物种植:
草坪上的植物数量(每次种植物会调用call,植物数量增加),执行到返回,找调用的call,动态调试一下,
先种种植物,找到植物增加地址
种植植物的参数是x , y, 植物ID,看压入栈有几个参数,找到植物种植的函数
大概思路是:
1,分析植物安放call代码
1,先定位代表鼠标右键选中的植物类型的地址
2,分析call函数代码的参数及堆栈
3,将代码注入
自己写的植物种植汇编代码注入到游戏进程空间中,验证是否执行成功
远程线程代码注入的过程:
步骤:
1,打开目标进程 openProcess获取目标进程
2,在目标进程中分配内存空间 VirtualAllocEx
3,往分配的内存空间写入代码看
4,远程调用 CreateRemoteThread 执行目标进程指定地址代码
5,等待代码执行完(使用WaitForSingleObject函数)
6,释放内存
7,关闭目标进程
代码:
BOOL Sort(HANDLE hProcess,LPVOID pRemoteMemory,DWORD size, BYTE* newBytes1, SIZE_T BytesWritten) {//这里的数据要用BYTE*
if (WriteProcessMemory(hProcess, pRemoteMemory, (LPCVOID)newBytes1, size, &BytesWritten)) {
//创建远程线程
HANDLE remoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteMemory, NULL, 0, NULL);
if (!remoteThread) {
std::cerr << "Failed to create remote thread." << std::endl;
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);//释放内存
CloseHandle(hProcess);
return FALSE;
}
WaitForSingleObject(remoteThread, INFINITE);//等待线程结束
CloseHandle(remoteThread);//关闭线程
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);//释放内存
CloseHandle(hProcess);//关闭进程
return TRUE;
}
else {
printf("写入失败");
return FALSE;
}
}
植物种植代码:
这里注意一下:call offset中的offset地址是目标地址-(下一跳指令地址+5),5是call指令语句的大小
BOOL Plants_call(DWORD ProcessID, LPVOID BaseAddress, DWORD x, DWORD y, DWORD cnt) {//这个size大小很重要,注意注意,因为是在别人的线程中进程,所以sizeof(a)/sizeof(a[0])的结果是指针呢个的大小,这样子就会报错,在此段代码中就是8
BYTE newBytes1[50] = {//这里一定要注意了
0x55, //0 push esp
0x89, 0xE5, //1 mov ebp,esp
0x60, // 3 pushad
0x68, 0xff , 0xff,0xff,0xff, // 4 push 0xffffffff,
0x68, 0x00, 0x00, 0x00, 0x00 , //9push 0x0
0xb8, 0x00, 0x00, 0x00, 0x00, //14mov eax,0x0
0x68, 0x00 ,0x00, 0x00, 0x00, //19 push 0x0
0xba, 0x00, 0x00, 0x00, 0x00, //24mov edx,0x0,基址
0x8b, 0x92, 0x0c ,0x5e ,0x35, 0x00, //29mov edx,DWORD PTR[edx + 0x355e0c]
0x8b, 0x92 ,0x68, 0x08 ,0x00, 0x00, //35mov edx,DWORD PTR[edx + 0x868]
0x52, //41push edx
0xe8, 0x00, 0x00, 0x00 ,0x00, //42call 22 < _main + 0x22 >
0x61 , //47popad
0xC9, //48leave
0xc3 //49ret
};//注意,call和jmp都一样 call offset 中offset=目的地址-(下一条指令地址+5),这个5是call指令语句的大小
DWORD size = 50;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);//这里是获取全部权限
if (hProcess != NULL) {
LPVOID lpRemoteCodeMem = VirtualAllocEx((HANDLE)hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpRemoteCodeMem != NULL) {
DWORD desFunc = (DWORD)BaseAddress + 0x18D70;//这里是目的地址
*(DWORD*)&newBytes1[10] = cnt;
*(DWORD*)&newBytes1[15] = y;
*(DWORD*)&newBytes1[20] = x;
*(DWORD*)&newBytes1[25] = (DWORD)BaseAddress;
*(DWORD*)&newBytes1[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);
// 在调用前修改数组内部数据
// 将汇编代码写入进程内存
SIZE_T BytesWritten = 0;
if(Sort(hProcess, lpRemoteCodeMem, size, newBytes1, BytesWritten))return TRUE;
}
else {
printf("申请内存失败");
return FALSE;
}
}
printf("权限申请失败");
return FALSE;
}
僵尸种植:
僵尸种植的原理和植物种植相同,首先要找到僵尸数量存放地址,通过僵尸数量来找到僵尸数量增加代码,然后查看堆栈,找到调用的call
代码:
BOOL Zombie_call(DWORD ProcessID,DWORD BaseAddress,int x,int y,int cnt){
BYTE newBytes2[50] = {
0x55, // 0
0x89,0xe5, // 1
0x60, // 3
0x68,0x00,0x00,0x00,0x00, // 4
0x68 ,0x00 ,0x00 ,0x00 ,0x00,// 9
0xb8,0x00,0x00,0x00,0x00, // 14
0xb9,0x00,0x00,0x00,0x00, // 19
0x8b,0x89,0x0c,0x5e,0x35,0x00, // 24push 0x0 type
0x8b,0x89,0x68,0x08,0x00,0x00, // 30mov eax,0x0 y
0x8b,0x89,0x78,0x01,0x00,0x00, // 36 30mov ecx,edx
0xe8,0x00,0x00,0x00,0x00, // 42call 21 < _main + 0x21 >
0x61, // 47popad
0xc9, // 48leave
0xc3 };// 49
DWORD size = 50;
SIZE_T BytesWritten = 0;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);//这里是获取全部权限
if (hProcess != NULL) {
LPVOID lpRemoteCodeMem = VirtualAllocEx((HANDLE)hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpRemoteCodeMem != NULL) {
DWORD desFunc = (DWORD)BaseAddress + 0x35390;//这里是目的地址
*(DWORD*)&newBytes2[5] = x;
*(DWORD*)&newBytes2[10] = cnt;
*(DWORD*)&newBytes2[15] = y;
*(DWORD*)&newBytes2[20] = (DWORD)BaseAddress;
*(DWORD*)&newBytes2[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);
if(Sort(hProcess, lpRemoteCodeMem, size, newBytes2, BytesWritten))return TRUE;
}
}
return FALSE;
}