前面需要了解一下CE的使用,学会基本的CE操作就可以,一些CE的使用方法,除了这个就是要用到C,汇编和Win32的一些知识

阳光值

找阳光值:每次改变阳光的值,用CE不断缩小范围去确定阳光值的数据

找阳光基址:“某个程序每次运行都不会改变的地址(基地址)里面,存着当前程序的阳光地址,我们需要找到这个程序不会改变的地址,通过它偏移拿到阳光地址

xxxx

找到阳关值后复制地址,放到搜索栏中去搜索

2024-05-07T13:17:51.png

可能搜到很多没用的这里就凭运气去找了

看开头地址和下面行或者上面一行是否是相同的地址,相同的话是没有价值的,随便找一个合适的,然后右键看什么访问了这个地址,此时会弹出一个弹窗,谈的很快

需要及时按下ESC键,这里再随便找一个,然后计算它 的偏移/?????😵‍💫😵

PlantsVsZombies.exe+355E0C

静态基址,知到静态基址后手动添加地址,添加两个指针,因为找了两次,分别填入第一层偏移(第一个找到的),和第二个偏移(第二个找到的)

2024-05-07T13:18:07.png

大致思路:

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;
        
}

卡槽冷却:

找卡槽冷却倒计时:

冷却完成是某个值 冷却完成冷却是共用代码,达到某个值后暂停,当把值设置成大于这个值时就可以实现无冷却,所以肯定有一个地方存放了所有植物卡槽冷却倒计时

读内存整数型,用第一个植物计次循环首,计次循环尾

2024-05-07T13:19:13.png

代码:

思路是在内存中选择一个没有内存块,向内存块中写入指令集,修改原先的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,动态调试一下,

![image](assets/image-20230806152041-3dqiese.png)

![image](assets/image-20230806152054-pu0rhxl.png)

![image](assets/image-20230806152111-u4a6pzt.png)

![image](assets/image-20230806152121-2bnn45v.png)

先种种植物,找到植物增加地址

![image](assets/image-20240507205536-jbn9co5.png)

种植植物的参数是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;
}