Plants vs. Zombies Modifier Production--From Entry to Burial

Basic preparation

  1. Basic usage of CheatEngine tool

    Recommended Video Cheat Engine Zero-Basic Introductory Tutorial You Can Learn

    Just clear the level-breaking game officially given by ce

  2. Basics of C/C++ and Assembly Language

    Attach the assembly code conversion website

  3. Basics of WIN32 Development

    Understand the WIN32 naming rules, use GPT and find Microsoft official documents

    It is recommended to read Microsoft's official documents Win32 and C++ Getting Started to be able to create the first windows program

Sample game version: Chinese annual enhanced version 1.1.0.1056

main reference

  1. [Replenishment] Haoge Plants vs. Zombies Modification Tutorial Video Collection

  2. C/C++ full-stack software security course (debugging, anti-debugging, game anti-cheat, software reverse) is being continuously updated~~~~

  3. Compilation/disassembly of reverse engineering actual combat (win32+ game reverse actual combat)

base offset table

Base address offset of common variables

Part of the reference: publish all the base addresses I found and the implementation methods of various functions

Base address 0x00355E0C

Sunshine +868 +5578

Money +950 +50

Flower fertilizer +950 +220

Chocolate +950 +250

Tree fertilizer +950 +258

Tree height +950 +11C

Insecticide +950 +224

Number of card slots +868 +15C +24

Card slot bar +868 +15C +5C After that, each plant bar is separated by 0x50

The current cooling value of the plant is +868 +15C +4C After that, the cooling interval of each plant is 0x50

The upper limit of plant cooling value +868 +15C +50 After that, the upper limit of cooling value of each plant is separated by 0x50

Current number of plants +868 +D4

Plant Planting Function EBP +868

Current number of zombies +868 +B8

Zombie planting function EBP +868 +178

regular items

Use CE to search according to the change of the variable, and then scan the pointer to find the available base address after finding it

The actual value of sunshine memory = game display value

Wisdom tree height memory actual value = game display value

Actual value of money memory = game display value/10

Flower fertilizer, insecticide, chocolate, tree fertilizer memory actual value = game display value + 1000

Key Functions and Variables

enum Type {
    
    
    Sunlight, Money, TreeHeight, Chocolate, TreeFood, FlowerFood, Insecticide
};

//定义映射表用于保存各项偏移值
unsigned int offsetTable[10] = {
    
     0x5578,0x50,0x11c,0x250,0x258,0x220,0x224 };

//获取某些项目的值
unsigned int getSomething(HANDLE handle, DWORD BaseAddr,unsigned int type) {
    
    
    unsigned int num = 0;
    DWORD addr = BaseAddr + 0x00355E0C;
    ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
    if (type == Sunlight) 
        addr += 0x868;
    else
        addr += 0x950;
    ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
    addr += offsetTable[type];
    ReadProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
    return num;
}

//设置某些项目的值
void setSomething(HANDLE handle, DWORD BaseAddr,unsigned int type, unsigned int num) {
    
    
    DWORD addr = BaseAddr + 0x00355E0C;
    ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
    if (type == Sunlight)
        addr += 0x868;
    else
        addr += 0x950;
    ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
    addr += offsetTable[type];
    WriteProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
}

card slot plant

Ten card slots, each card slot corresponds to a plant, you can find the address of the card slot according to the change of nuts in card slot 1 (the leftmost card slot) in Nut Bowling 2, and then look for the base address

Specific method: The initial value is unknown, if the plant in slot 1 is the same as the plant in the new slot 1 (original slot 2), scan the unchanged value, otherwise scan the changed value

The offset between card slots can be seen by browsing the memory area of ​​card slot 1, which is 0x50

Nut Plant Card Slot No.:

common nuts 3

Exploding Nuts 49

Giant Nut 50

Set the card slot plant function

//设置卡槽植物
BOOL SetPlantCard(HANDLE hProcess,DWORD BaseAddr,DWORD nCard,DWORD plantType) {
    
    
    DWORD cardAddr = BaseAddr + 0x355E0C;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x868;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x15C;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x5C+nCard*0x50;//卡槽偏移
    return WriteProcessMemory(hProcess, cardAddr, &plantType, sizeof(DWORD), NULL);
}

Plant without cooling

Specific method: Only for one card slot, the initial value is unknown, it will continue to change after planting, and will not change after cooling, repeatedly scan and find the base address, check the corresponding memory area and compare the plant number to find that the offset between the card slots is 0x50

Cooling features: The cooling value in the plantable state is 0, and the cooling value continues to increase after planting. After reaching the cooling upper limit, the cooling value is reset to zero, and the plant can be planted again

Note: directly setting the cooling value to 0 will result in failure to plant

Modification method:

  1. Modify the speed of recovery after the cooling is over, and modify the inc command to a larger value of mov.
  2. Jump directly to the function where the cooling value and cooling upper limit are compared successfully

Take method 2 as an example

7E 16 corresponds to the assembly instruction jle 0x18

Change to jmp $+2 ie eb 00 (command after 2 bytes relative to the current command)

Directly execute the function after the cooling value reaches the cooling upper limit (the cooling value is cleared, and the plants can be planted after cooling down)

Attach the assembly code conversion website

Please add a picture description

key code

//修改进程代码区代码 参数: 进程句柄 修改代码起始地址 硬编码指针 代码字节数
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
    
    
    DWORD dwOldProtect;
    //取消页保护
    if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
    
    
        return FALSE;
    }
    BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//写入代码
    VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//开启页保护
    return bResult;
}

//无限冷却
BOOL Uncooled(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char code[2] = {
    
     0xeb,0x00 };
    return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, code, 2);//jle 0x18修改为jmp $+2
}

//恢复冷却
BOOL RecoveryCooling(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char OriginalCode[2] = {
    
     0x7E ,0x16 };//jmp $+2恢复为jle 0x18
    return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, OriginalCode, 2);
}

infinite sunshine

The previous article has given the address base address of Sunshine as 0x355E0C offset +868 +5578

Just find the code that modifies the sun

sun reduction code
Please add a picture description

sunshine increase code

Please add a picture description

Basic process:

  1. Set the sunlight value to 9999

  2. Modify the sunlight reduction code so that planting does not consume sunlight

  3. Modify the sunlight increase code so that the sunlight does not change (to prevent overflow caused by too much sunlight)

//修改进程代码区代码 参数: 进程句柄 修改代码起始地址 硬编码指针 代码字节数
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
    
    
    DWORD dwOldProtect;
    //取消页保护
    if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
    
    
        return FALSE;
    }
    BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//写入代码
    VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//开启页保护
    return bResult;
}
//无限阳光,锁定阳光为9999
BOOL UnlimitedSun(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char Code[3] = {
    
     0x29,0xdb,0 };//cmp ebx,eax 修改为sub ebx,ebx   and ecx,0x32修改为and ecx,0
    BOOL flag;
    flag = setSomething(hProcess, BaseAddr, Sunlight, 9999);//修改阳光
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, Code, 2);//修改阳光减少代码
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &Code[2], 1);//修改阳光增加代码
    return flag;
}

//恢复阳光消耗
BOOL RecoverySunConsume(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char OriginalCode[3] = {
    
     0x3B,0xD8,0x32 };//sub ebx,ebx恢复为cmp ebx,eax and ecx,0恢复为and ecx,0x32
    BOOL flag = WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, OriginalCode, 2);//恢复阳光减少代码
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &OriginalCode[2], 1);//恢复阳光增加代码
    return flag;
}

Fog Perspective

Fundamental

Specific method: In the thick fog of the survival mode, the initial value is unknown. Judging by the changes caused by planting and eradicating street lamp flowers in the fog area, it can be found that it is 4 bytes of data. The value represents the concentration of fog, 255 represents dense fog, and 0 Represents no fog, and then find the code to modify the fog value

Find the Fog Address

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-Pl7X1l0X-1690904306130) (6 Find the address of dense fog.png)]

Dense Fog Modification Code

mov [ecx], edx This line of code modifies the fog value, which can be changed to mov [ecx], 0

Note that the hard codes are 0xc7, 0x01, 0x00, 0x00, 0x00, 0x00. Since the code is too long, the code cannot be directly modified, so the hook technology is used here.

Please add a picture description

HOOK

The basic process of hook

  1. Read and save the original code of the destination address
  2. The application space (PVZ game process space) is used to store the original code hook code jmp return code
  3. Write the original code hook code jmp return code into the applied space
  4. Modify the code of the destination address to jmp HookCode
  5. Return the first address of HookCode to release the hook

It is worth mentioning that the offset value following the jmp instruction is calculated based on the first address of the next instruction of jmp

jmp instruction offset value = destination address - (jmp instruction first address + 5) where 5 is the length of the jmp instruction itself + 5 is the next instruction

offset=desAddr-(jmpAddr+5)

//修改进程代码区代码 参数: 进程句柄 修改代码起始地址 硬编码指针 代码字节数
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
    
    
    DWORD dwOldProtect;
    //取消页保护
    if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
    
    
        return FALSE;
    }
    BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//写入代码
    VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//开启页保护
    return bResult;
}

//hook指定地址,申请新空间保存原始代码并写入hookcode,返回申请空间的地址
LPVOID SetHook(HANDLE hProcess, LPVOID desAddr, LPCVOID hookCode, SIZE_T hookCodeSize, SIZE_T origCodeSize) {
    
    
    BYTE origCode[10] = {
    
     0 }, jmpCode[5] = {
    
     0xE9,0,0,0,0 };
    //1. 读取并保存原始代码
    if (!ReadProcessMemory(hProcess, desAddr, origCode, origCodeSize, NULL))
        return NULL;

    //2. 申请空间用于存储原始代码,hook代码,jmp返回代码
    LPVOID allocAddr = VirtualAllocEx(hProcess, NULL, hookCodeSize + origCodeSize + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!allocAddr)
        return NULL;

    //3. 向申请空间写入原始代码,hook代码,jmp返回代码  jmp xxx 偏移为目的地址-jmp下一条指令地址
    *(DWORD*)(jmpCode + 1) = (DWORD)desAddr + 5 - ((DWORD)allocAddr + hookCodeSize + origCodeSize + 5);//hook返回地址的偏移
    if (!WriteProcessCodeMemory(hProcess, allocAddr, origCode, origCodeSize)                      //写入原始代码
        || !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize, hookCode, hookCodeSize)//写入hook代码
        || !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize + hookCodeSize, jmpCode, 5))//写入jmpcode
    {
    
    
        VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//写入失败则释放空间
        return NULL;
    }

    //4. 修改目的地址处的代码  jmp xxx偏移 原始代码后才是需要执行的hook代码
    *(DWORD*)(jmpCode + 1) = ((DWORD)allocAddr + origCodeSize) - ((DWORD)desAddr + 5);
    WriteProcessCodeMemory(hProcess, desAddr, jmpCode, 5);//在源地址处写入跳转代码
    if (origCodeSize > 5)//原始代码长度大于5时nop多余字节
    {
    
    
        BYTE nopCode[5] = {
    
     0x90,0x90,0x90,0x90,0x90 };
        if (!WriteProcessCodeMemory(hProcess, (DWORD)desAddr + 5, nopCode, origCodeSize - 5))
        {
    
    
            VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//写入nopcode失败则释放空间并返回
            return NULL;
        }
    }

    //5. hook成功则返回hookCode所在地址
    return allocAddr;
}

//取消hook指定地址,写回原始代码并释放申请空间
BOOL UnHook(HANDLE hProcess, LPVOID desAddr, SIZE_T origCodeSize, LPVOID allocAddr) {
    
    
    BYTE origCode[10] = {
    
     0 };
    //1. 从申请空间中读出原始代码
    if (!ReadProcessMemory(hProcess, allocAddr, origCode, origCodeSize, NULL))
        return FALSE;
    //2. 将原始代码写回目的地址
    if (!WriteProcessCodeMemory(hProcess, desAddr, origCode, origCodeSize))
        return FALSE;
    //3. 释放申请空间
    if (!VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE))
        return FALSE;
    return TRUE;
}

Defog code

//除雾 注意保留hook代码首地址
LPVOID DeFogByHook(HANDLE hProcess, LPVOID BaseAddr) {
    
    

    unsigned char hookCode[9] = {
    
    
        0xc7,0x01,0x00,0x00,0x00,0x00,  //mov [ecx],0
        0x83,0xc1,0x04                  //add ecx,0x4
    };
    //写入hook代码进行hook
    return SetHook(hProcess, (DWORD)BaseAddr + 0x26173, hookCode, sizeof(hookCode), 5);
}

//恢复雾
BOOL RecoveryFogByUnHook(HANDLE hProcess, LPVOID BaseAddr, LPVOID allocAddr) {
    
    
    return UnHook(hProcess, (DWORD)BaseAddr + 0x26173, 5, allocAddr);
}

The instruction before hook is mov [ecx],edx add ecx,04

Please add a picture description

After the hook, the instruction is changed to jmp
Please add a picture description

hookcode The first 5 bytes of the newly allocated space are exactly the original code, followed by the hook code and jmp return code
Please add a picture description

grow plants

Fundamental

The program is to execute the function of planting plants and then execute the function of increasing the number of plants

First find the number of plants on the lawn, the initial value is 0, as the number of plants increases, the base address is 0x355E0C offset +868 +D4

Then find out what modified the number of plants, and then plant a plant after the breakpoint

After disconnecting, check the return address in the call stack to find the planting function

Please add a picture description

This function was initially implemented by injecting dll from remote threads. Although injecting dll is relatively simple, it is not universal. This is just an introduction. It is recommended to use remote code injection.

remote thread injection dll function

The remote thread is that the current process creates a thread in the target process and executes specific code (this code must be in the target process instead of the current process)

The dll is injected because the dll executes the dll's DllMain function when it is loaded by a process or thread. Through this feature, we can achieve some special functions

Pros: Easy to implement

Cons: dll injection is easily detected

Basic process:

  1. Open a process to get a process handle
  2. Apply for space in the target process to store the dll path name
  3. Write the dll path name into the requested space
  4. Create a remote thread and execute the LoadLibrary function (loading dll)
  5. After the target process loads the dll, it automatically executes the DllMain function of the dll
//创建远程线程方式向指定进程注入dll
BOOL InjectDllByRemoteThread(DWORD desProcId,WCHAR* dllPath) {
    
    
    //打开进程获取进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, desProcId);
    if (!hProcess)
        return FALSE;

    //申请空间
    DWORD pathSize = (wcslen(dllPath) + 1) * 2; 
    LPVOID newMemAddr = VirtualAllocEx(hProcess, 0, pathSize, MEM_COMMIT, PAGE_READWRITE);
    if (!newMemAddr)
        return FALSE;

    //写入dll路径
    if (!WriteProcessMemory(hProcess, newMemAddr, dllPath, pathSize, NULL))
    {
    
    
        VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
        return FALSE;
    }
       
    //创建远程线程
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, newMemAddr, 0, NULL);
    if (!hThread)
    {
    
    
        VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);//等待线程信号,保证成功注入

    //回收资源
    VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);

    //返回成功
    return TRUE;
}

Remote thread unload dll function

Many tutorials only show how to inject dll, but do not demonstrate how to unload

If you only inject without unloading, the specific function will not be executed the next time you inject (because the dll has already been loaded), it is inconvenient to debug and update the dll in real time and other issues

Basic process:

  1. Apply for memory in the target process, and write the name of the dll module that needs to be uninstalled into the memory
  2. Find the specified module by enumerating modules
  3. If the dll module is successfully found, a remote thread is created to execute the FreeLibrary function to unload the dll
BOOL UnLoadDllByRemoteThread(DWORD dwProcessId, LPCWSTR lpDllName)
{
    
    
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
        return FALSE;

    // 在目标进程中申请一块内存,并将需要卸载的DLL模块的名称写入该内存
    LPVOID lpRemoteDllName = VirtualAllocEx(hProcess, NULL, (wcslen(lpDllName) + 1) * sizeof(WCHAR), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (lpRemoteDllName == NULL)
    {
    
    
        CloseHandle(hProcess);
        return FALSE;
    }
    if (!WriteProcessMemory(hProcess, lpRemoteDllName, lpDllName, (wcslen(lpDllName) + 1) * sizeof(WCHAR), NULL))
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    //查找dll模块
    HMODULE hModules[1024],DesModule=NULL;
    DWORD dwSize = 0;
    if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &dwSize))
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    // 遍历模块列表,查找需要卸载的DLL模块
    for (DWORD i = 0; i < (dwSize / sizeof(HMODULE)); i++)
    {
    
    
        WCHAR szModuleName[MAX_PATH] = {
    
     0 };
        if (GetModuleFileNameExW(hProcess, hModules[i], szModuleName, MAX_PATH) > 0)
        {
    
    
            // 获取模块句柄
            if (wcsicmp(szModuleName, lpDllName) == 0)
            {
    
    
                DesModule = hModules[i];
            }
        }
    }
    //没有查找到模块
    if (!DesModule) {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }
    // 在目标进程中创建远程线程,执行FreeLibrary函数
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, DesModule, 0, NULL);
    if (hThread == NULL)
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    // 关闭句柄
    CloseHandle(hThread);
    VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    return TRUE;
}

key dll functions

Three methods are used here

Note: Do not write code outside of switch(reason) , otherwise it may cause multiple executions

#include<windows.h>
#include<stdio.h>

//调用函数
BOOL GrowPlant(DWORD BaseAddr, DWORD x, DWORD y, DWORD TypePlant) {
    
    
    LPVOID PlantFunc = BaseAddr + 0x18D70;
    __asm {
    
    
        pushad
        push -1         //-1
        push TypePlant   //植物类型
        mov eax, y       //y
        push x           //x
        mov ecx, BaseAddr
        mov ecx, [ecx+0x355E0C]
        mov ecx, [ecx + 0x868]
        push ecx		//植物种植ebp
        call PlantFunc
        popad
    }
    return TRUE;
}


BOOL WINAPI DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved) {
    
    
    DWORD BaseAddr = GetModuleHandle(NULL);
    DWORD pid = GetCurrentProcessId();
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    LPVOID PlantFunc = BaseAddr + 0x18D70;
    DWORD ebpAddr = BaseAddr+0x355E0C,num=0;
    ReadProcessMemory(hProcess, ebpAddr, &ebpAddr, sizeof(DWORD), NULL);
    ebpAddr += 0x868;
    ReadProcessMemory(hProcess, ebpAddr, &ebpAddr, sizeof(DWORD), NULL);//必须使用带hProcess参数的才能正确读取到地址,NULL不可以
    DWORD x = 1, y = 1, TypePlant = 16;
    
    //注意不要写到switch外,否则可能会一次种多株植物,猜测是dll被多个线程加载导致的
    switch (fdwReason)
    {
    
    
    case DLL_PROCESS_ATTACH:    //当进程加载dll模块时执行
        //MessageBoxW(0, L"ProcessAttach!", L"window2", 0);
        //1.直接通过使用ReadProcessMemory函数读取内存获取ebp参数
        __asm {
    
    
            pushad
            push - 1         //-1
            push TypePlant   //植物类型
            mov eax, y       //y
            push x           //x
            push  ebpAddr    //ebp
            call PlantFunc   
            popad
        }

        //2.通过利用寄存器获取ebp(推荐)
        x = 3, y = 2, TypePlant = 18;
        __asm {
    
    
            pushad
            push - 1         //-1
            push TypePlant   //植物类型
            mov eax, y       //y
            push x           //x
            mov ecx, BaseAddr
            mov ecx, [ecx+0x355E0C]
            mov ecx, [ecx + 0x868]
            push ecx
            call PlantFunc
            popad
        }
       //3. 通过调用函数(推荐)     
        GrowPlant(BaseAddr,7,3,23);         
                     
        break;
    //case DLL_THREAD_ATTACH:        
    //    printf("ThreadAttach!\n");
    //    break;
    //case DLL_THREAD_DETACH:       
    //  if (lpReserved == NULL)
    //    {
    
    
    //        FreeLibrary(hInstance);
    //    }
        break;
    case DLL_PROCESS_DETACH:        //当进程卸载dll模块时执行
        MessageBoxW(0, L"ProcessDeTachDll!", L"window2", 0);
        break;
    }
    return TRUE; 
}

Results of the

Please add a picture description

failure code

This is a problem encountered when writing dll functions. If you use mov ecx directly, [BaseAddr+0x355E0C] will cause the code to fail to execute. It is speculated that this instruction is too slow to access memory so it is invalid

It is recommended to achieve the goal by operating the register after mov ecx and BaseAddr

__asm {
    
    
        pushad
        push - 1         //-1
        push TypePlant   //植物类型
        mov eax, y       //y
        push x           //x
        mov ecx,[BaseAddr+ 0x355E0C]//这样不行,推测是访存过慢
        mov ecx,[ecx+0x868]
        mov num, ecx
        push ecx
        call PlantFunc
        popad
    }

Remote thread code injection (recommended)

Similar to remote thread dll injection, the function prototype required by the CreateRemoteThread function is

DWORD WINAPI ThreadProc(
  _In_ LPVOID lpParameter//使用CreateThread函数传递的参数 该参数是一个指向其他数据的指针,当然也可以强转为其他类型直接使用
);

Basic process:

  1. open process
  2. Define injection code (function)
  3. Apply for space in the target process and write injection code
  4. Create a remote thread to execute the injected code (function)
  5. Release space after execution
//以创建远程线程方式种植植物
BOOL GrowPlantByInjectCode(DWORD dwProcessId,DWORD BaseAddr,DWORD x,DWORD y,DWORD PlantType)
{
    
    
    BOOL bSuccess = FALSE;
    //1. 打开进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);    
    if (hProcess != NULL)
    {
    
    
        //2. 定义注入代码(函数)

        BYTE InjectCode[50] = {
    
                         //汇编指令              //修正点偏移
            0x55,                                   //0 push ebp
            0x89, 0xE5,                             //1 mov ebp,esp
            0x60,                                   //3 pushad                
            0x68, 0xFF, 0xFF, 0xFF, 0xFF,           //4 push -1           
            0x68, 0x00, 0x00, 0x00, 0x00,           //9 push PlantType        //10     
            0xB8, 0x00, 0x00, 0x00, 0x00,           //14 mov eax,y             //15
            0x68, 0x00, 0x00, 0x00, 0x00,           //19 push x                //20
            0xB9, 0x00, 0x00, 0x00, 0x00,           //24 mov ecx,BaseAddr      //25
            0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00,     //29 mov ecx,[ecx+0x355E0C]
            0x8B, 0x89, 0x68, 0x08, 0x00, 0x00,     //35 mov ecx,[ecx+0x868]
            0x51,                                   //41 push ecx
            0xE8, 0x00, 0x00, 0x00, 0x00,           //42 call PlantFunc        //43     //被调方平栈
            0x61,                                   //47 popad
            0xC9,                                   //48 leave
            0xC3                                    //49 ret
        };

        //3. 申请空间用于存储代码
        DWORD  dwCodeSize = 50, desFunc = BaseAddr + 0x18D70;
        LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

        //4. 修正参数
        *(DWORD*)&InjectCode[10] = PlantType;
        *(DWORD*)&InjectCode[15] = y;
        *(DWORD*)&InjectCode[20] = x;
        *(DWORD*)&InjectCode[25] = BaseAddr;
        *(DWORD*)&InjectCode[43] = desFunc-((DWORD)lpRemoteCodeMem+42+5) ;
        //call指令与jmp类似,相对于当前指令的下一条指令计算偏移,offset=des-(source+5),减去call自身长度5

        if (lpRemoteCodeMem != NULL)
        {
    
    
            SIZE_T dwBytesWritten = 0;
            //5. 注入代码
            if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
                dwBytesWritten == dwCodeSize)
            {
    
    
                //6. 创建远程线程执行代码
                HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem,NULL, 0, NULL);
                if (hThread != NULL)
                {
    
    
                    //7. 等待线程信号
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                    bSuccess = TRUE;
                }
            }
            //8. 执行完后释放空间
            VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
        }
        CloseHandle(hProcess);
    }
    
    return bSuccess;
}

Plant Zombies

Fundamental

Similar to the idea of ​​planting plants

First find the address of the number of zombies by planting zombies in the brainstorming

Then find the code to increase the number of zombies

Then find the planting zombie call by looking at the call stack and parameters

The parameter should also be xy type ebp (note there is no -1)

The x value of the zombie planting function is above a call, which is a switch structure with no parameters, so the x value has not been modified

Please add a picture description

Plant zombie function – dll injection version

BOOL GrowZombie(DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
    
    
    LPVOID PlantZombieFunc = BaseAddr + 0x35390;
    __asm {
    
    
        pushad 
        push x
        push ZombieType
        mov eax,y
        mov ecx,BaseAddr
        mov ecx,[ecx+0x355E0C]
        mov ecx,[ecx+0x868]
        mov ecx,[ecx+0x178]    //ebp
        call PlantZombieFunc
        popad 
    }
    return TRUE;
}

Remote code injection version

//以创建远程线程方式种植僵尸
BOOL GrowZombieByRemoteThread(DWORD dwProcessId,DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
    
    

    BOOL bSuccess = FALSE;
    //1. 打开进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess != NULL)
    {
    
    
        //2. 定义注入代码(函数)

        BYTE InjectCode[50] = {
    
    
            0x55,                                       //0 push ebp
            0x89, 0xE5,                                 //1 mov ebp,esp
            0x60,                                       //3 pushad
            0x68, 0x00, 0x00, 0x00, 0x00,               //4 push x
            0x68, 0x00, 0x00, 0x00, 0x00,               //9 push ZombieType
            0xB8, 0x00, 0x00, 0x00, 0x00,               //14 mov eax,y
            0xB9, 0x00, 0x00, 0x00, 0x00,               //19 mov ecx,BaseAddr
            0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00,         //24 mov ecx,[ecx+0x355E0C]
            0x8B, 0x89, 0x68, 0x08, 0x00, 0x00,         //30 mov ecx,[ecx+0x868]
            0x8B, 0x89, 0x78, 0x01, 0x00, 0x00,         //36 mov ecx,[ecx+0x178]
            0xE8, 0x00, 0x00, 0x00, 0x00,               //42 call PlantZombieFunc
            0x61,                                       //47 popad
            0xC9,                                       //48 leave 
            0xC3                                        //49 ret
        };

        //3. 申请空间用于存储代码
        DWORD  dwCodeSize = 50, desFunc = BaseAddr + 0x35390; //种植僵尸函数
        LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

        //4. 修正参数
        *(DWORD*)&InjectCode[5] = x;
        *(DWORD*)&InjectCode[10] = ZombieType;
        *(DWORD*)&InjectCode[15] = y;
        *(DWORD*)&InjectCode[20] = BaseAddr;
        *(DWORD*)&InjectCode[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);//call指令与jmp类似,相对于当前指令的下一条指令计算偏移,要减去call长度5

        if (lpRemoteCodeMem != NULL)
        {
    
    
            SIZE_T dwBytesWritten = 0;
            //5. 注入代码
            if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
                dwBytesWritten == dwCodeSize)
            {
    
    
                //6. 创建远程线程执行代码
                HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem, NULL, 0, NULL);
                if (hThread != NULL)
                {
    
    
                    //7. 等待线程信号
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                    bSuccess = TRUE;
                }
            }
            //8. 执行完后释放空间
            VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
        }
        CloseHandle(hProcess);
    }

    return bSuccess;
}

complete program code

#include<stdio.h>
#include<windows.h>
#include <tlhelp32.h>
#include <string.h>
#include <shlwapi.h>
#include <psapi.h>

enum Type {
    
    
    Sunlight, Money, TreeHeight, Chocolate, TreeFood, FlowerFood, Insecticide
};
unsigned int offsetTable[10] = {
    
     0x5578,0x50,0x11c,0x250,0x258,0x220,0x224 };


// 根据进程名获取进程ID
DWORD GetProcessIdByName(const wchar_t* processName) {
    
    

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);// 创建一个进程快照
    if (snapshot == INVALID_HANDLE_VALUE) {
    
    
        return 0;// 如果创建失败,返回 0
    }

    // 定义一个 PROCESSENTRY32 结构体,用于存储进程信息
    PROCESSENTRY32 processEntry = {
    
     0 };
    processEntry.dwSize = sizeof(PROCESSENTRY32);   //必须初始化,否则调用Process32First会失败
    if (!Process32First(snapshot, &processEntry)) {
    
    
        CloseHandle(snapshot);
        return 0;// 如果获取第一个进程信息失败,关闭进程快照句柄并返回 0
    }

    // 遍历进程列表
    do {
    
    
        wchar_t currentProcessName[MAX_PATH];                           // 获取当前进程的名称
        wcscpy_s(currentProcessName, MAX_PATH, processEntry.szExeFile); //szExeFile存储了进程对应可执行文件的名称
        if (wcscmp(currentProcessName, processName) == 0) {
    
    
            CloseHandle(snapshot);                                  // 如果当前进程名称和指定的进程名称相同,返回进程 ID
            return processEntry.th32ProcessID;
        }
    } while (Process32Next(snapshot, &processEntry));               //获取快照中下一个进程的信息

    // 如果遍历完整个进程列表都没有找到指定进程,关闭进程快照句柄并返回 0
    CloseHandle(snapshot);
    return 0;
}

//根据进程模块名获取基址
LPVOID GetModuleBaseAddress(DWORD processId, LPCWSTR moduleName) {
    
    
    LPVOID lpBaseAddress = NULL;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); // 打开进程句柄
    if (hProcess != NULL) {
    
    
        // 枚举进程中的所有模块
        HMODULE hMods[1024];
        DWORD cbNeeded;
        if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
    
    
            DWORD dwModuleCount = cbNeeded / sizeof(HMODULE);// 计算模块数量
            // 获取指定模块的信息
            for (DWORD i = 0; i < dwModuleCount; i++) {
    
    
                TCHAR szModName[MAX_PATH];
                //获取指定模块的完整路径名
                if (GetModuleFileNameEx(hProcess, hMods[i], szModName, MAX_PATH)) {
    
    //函数成功返回字符串长度,注意第四个参数的单位为字符而非字节
                    if (wcsstr(szModName, moduleName)) {
    
    //查找模块名,若成功则返回子串第一次出现的指针
                        MODULEINFO modInfo = {
    
     0 };
                        if (GetModuleInformation(hProcess, hMods[i], &modInfo, sizeof(MODULEINFO))) {
    
    //获取模块信息并保存到modInfo中
                            lpBaseAddress = modInfo.lpBaseOfDll;//模块基地址
                            break;
                        }
                    }
                }
            }
        }
        CloseHandle(hProcess); // 关闭进程句柄
    }

    return lpBaseAddress;
}


//修改进程代码区代码 参数: 进程句柄 修改代码起始地址 硬编码指针 代码字节数
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
    
    
    DWORD dwOldProtect;
    //取消页保护
    if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
    
    
        return FALSE;
    }
    BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//写入代码
    VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//开启页保护
    return bResult;
}

//hook指定地址,申请新空间保存原始代码并写入hookcode,返回申请空间的地址
LPVOID SetHook(HANDLE hProcess, LPVOID desAddr, LPCVOID hookCode, SIZE_T hookCodeSize, SIZE_T origCodeSize) {
    
    
    BYTE origCode[10] = {
    
     0 }, jmpCode[5] = {
    
     0xE9,0,0,0,0 };
    //1. 读取并保存原始代码
    if (!ReadProcessMemory(hProcess, desAddr, origCode, origCodeSize, NULL))
        return NULL;

    //2. 申请空间用于存储原始代码,hook代码,jmp返回代码
    LPVOID allocAddr = VirtualAllocEx(hProcess, NULL, hookCodeSize + origCodeSize + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!allocAddr)
        return NULL;

    //3. 向申请空间写入原始代码,hook代码,jmp返回代码  jmp xxx 偏移为目的地址-jmp下一条指令地址
    *(DWORD*)(jmpCode + 1) = (DWORD)desAddr + 5 - ((DWORD)allocAddr + hookCodeSize + origCodeSize + 5);//hook返回地址的偏移
    if (!WriteProcessCodeMemory(hProcess, allocAddr, origCode, origCodeSize)                      //写入原始代码
        || !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize, hookCode, hookCodeSize)//写入hook代码
        || !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize + hookCodeSize, jmpCode, 5))//写入jmpcode
    {
    
    
        VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//写入失败则释放空间
        return NULL;
    }

    //4. 修改目的地址处的代码  jmp xxx偏移 原始代码后才是需要执行的hook代码
    *(DWORD*)(jmpCode + 1) = ((DWORD)allocAddr + origCodeSize) - ((DWORD)desAddr + 5);
    WriteProcessCodeMemory(hProcess, desAddr, jmpCode, 5);//在源地址处写入跳转代码
    if (origCodeSize > 5)//原始代码长度大于5时nop多余字节
    {
    
    
        BYTE nopCode[5] = {
    
     0x90,0x90,0x90,0x90,0x90 };
        if (!WriteProcessCodeMemory(hProcess, (DWORD)desAddr + 5, nopCode, origCodeSize - 5))
        {
    
    
            VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//写入nopcode失败则释放空间并返回
            return NULL;
        }
    }

    //5. hook成功则返回hookCode所在地址
    return allocAddr;
}

//取消hook指定地址,写回原始代码并释放申请空间
BOOL UnHook(HANDLE hProcess, LPVOID desAddr, SIZE_T origCodeSize, LPVOID allocAddr) {
    
    
    BYTE origCode[10] = {
    
     0 };
    //1. 从申请空间中读出原始代码
    if (!ReadProcessMemory(hProcess, allocAddr, origCode, origCodeSize, NULL))
        return FALSE;
    //2. 将原始代码写回目的地址
    if (!WriteProcessCodeMemory(hProcess, desAddr, origCode, origCodeSize))
        return FALSE;
    //3. 释放申请空间
    if (!VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE))
        return FALSE;
    return TRUE;
}

//获取某些项目的值
unsigned int getSomething(HANDLE handle, DWORD BaseAddr, unsigned int type) {
    
    
    unsigned int num = 0;
    DWORD addr = BaseAddr + 0x00355E0C;
    ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
    if (type == Sunlight)
        addr += 0x868;
    else
        addr += 0x950;
    ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
    addr += offsetTable[type];
    ReadProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
    return num;
}

//设置某些项目的值
BOOL setSomething(HANDLE handle, DWORD BaseAddr, unsigned int type, unsigned int num) {
    
    
    DWORD addr = BaseAddr + 0x00355E0C;
    ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
    if (type == Sunlight)
        addr += 0x868;
    else
        addr += 0x950;
    ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
    addr += offsetTable[type];
    return WriteProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
}

//无限冷却
BOOL Uncooled(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char code[2] = {
    
     0xeb,0x00 };
    return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, code, 2);//jle 0x18修改为jmp $+2
}

//恢复冷却
BOOL RecoveryCooling(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char OriginalCode[2] = {
    
     0x7E ,0x16 };//jmp $+2修改为jle 0x18
    return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, OriginalCode, 2);
}

//无限阳光,锁定阳光为9999
BOOL UnlimitedSun(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char Code[3] = {
    
     0x29,0xdb,0 };//cmp ebx,eax 修改为sub ebx,ebx   and ecx,0x32修改为and ecx,0
    BOOL flag;
    flag = setSomething(hProcess, BaseAddr, Sunlight, 9999);//修改阳光
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, Code, 2);//修改阳光减少代码
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &Code[2], 1);//修改阳光增加代码
    return flag;
}

//恢复阳光消耗
BOOL RecoverySunConsume(HANDLE hProcess, DWORD BaseAddr) {
    
    
    unsigned char OriginalCode[3] = {
    
     0x3B,0xD8,0x32 };//sub ebx,ebx恢复为cmp ebx,eax and ecx,0恢复为and ecx,0x32
    BOOL flag = WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, OriginalCode, 2);//恢复阳光减少代码
    flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &OriginalCode[2], 1);//恢复阳光增加代码
    return flag;
}

//除雾
LPVOID DeFogByHook(HANDLE hProcess, LPVOID BaseAddr) {
    
    

    unsigned char hookCode[9] = {
    
    
        0xc7,0x01,0x00,0x00,0x00,0x00,  //mov [ecx],0
        0x83,0xc1,0x04                  //add ecx,0x4
    };
    //写hook代码进行hook
    return SetHook(hProcess, (DWORD)BaseAddr + 0x26173, hookCode, sizeof(hookCode), 5);
}

//恢复雾
BOOL RecoveryFogByUnHook(HANDLE hProcess, LPVOID BaseAddr, LPVOID allocAddr) {
    
    
    return UnHook(hProcess, (DWORD)BaseAddr + 0x26173, 5, allocAddr);
}

//创建远程线程向指定进程注入dll
BOOL InjectDllByRemoteThread(DWORD desProcId,WCHAR* dllPath) {
    
    
    //打开进程获取进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, desProcId);
    if (!hProcess)
        return FALSE;

    //申请空间
    DWORD pathSize = (wcslen(dllPath) + 1) * 2; 
    LPVOID newMemAddr = VirtualAllocEx(hProcess, 0, pathSize, MEM_COMMIT, PAGE_READWRITE);
    if (!newMemAddr)
        return FALSE;

    //写入dll路径
    if (!WriteProcessMemory(hProcess, newMemAddr, dllPath, pathSize, NULL))
    {
    
    
        VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
        return FALSE;
    }
       
    //创建远程线程
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, newMemAddr, 0, NULL);
    if (!hThread)
    {
    
    
        VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);//等待线程信号,保证成功注入

    //回收资源
    VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);

    //返回成功
    return TRUE;
}

//创建远程线程释放指定进程dll
BOOL UnLoadDllByRemoteThread(DWORD dwProcessId, LPCWSTR lpDllName)
{
    
    
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
        return FALSE;

    // 在目标进程中申请一块内存,并将需要卸载的DLL模块的名称写入该内存
    LPVOID lpRemoteDllName = VirtualAllocEx(hProcess, NULL, (wcslen(lpDllName) + 1) * sizeof(WCHAR), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (lpRemoteDllName == NULL)
    {
    
    
        CloseHandle(hProcess);
        return FALSE;
    }
    if (!WriteProcessMemory(hProcess, lpRemoteDllName, lpDllName, (wcslen(lpDllName) + 1) * sizeof(WCHAR), NULL))
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    //查找dll模块
    HMODULE hModules[1024],DesModule=NULL;
    DWORD dwSize = 0;
    if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &dwSize))
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    // 遍历模块列表,查找需要卸载的DLL模块
    for (DWORD i = 0; i < (dwSize / sizeof(HMODULE)); i++)
    {
    
    
        WCHAR szModuleName[MAX_PATH] = {
    
     0 };
        if (GetModuleFileNameExW(hProcess, hModules[i], szModuleName, MAX_PATH) > 0)
        {
    
    
            // 获取模块句柄
            if (wcsicmp(szModuleName, lpDllName) == 0)
            {
    
    
                DesModule = hModules[i];
            }
        }
    }
    //没有查找到模块
    if (!DesModule) {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }
    // 在目标进程中创建远程线程,执行FreeLibrary函数
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, DesModule, 0, NULL);
    if (hThread == NULL)
    {
    
    
        VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    // 关闭句柄
    CloseHandle(hThread);
    VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    return TRUE;
}

//以创建远程线程方式种植植物
BOOL GrowPlantByInjectCode(DWORD dwProcessId,DWORD BaseAddr,DWORD x,DWORD y,DWORD PlantType)
{
    
    
    BOOL bSuccess = FALSE;
    //1. 打开进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);    
    if (hProcess != NULL)
    {
    
    
        //2. 定义注入代码(函数)

        BYTE InjectCode[50] = {
    
                         //汇编指令              //修正点偏移
            0x55,                                   //0 push ebp
            0x89, 0xE5,                             //1 mov ebp,esp
            0x60,                                   //3 pushad                
            0x68, 0xFF, 0xFF, 0xFF, 0xFF,           //4 push -1           
            0x68, 0x00, 0x00, 0x00, 0x00,           //9 push PlantType        //10     
            0xB8, 0x00, 0x00, 0x00, 0x00,           //14 mov eax,y             //15
            0x68, 0x00, 0x00, 0x00, 0x00,           //19 push x                //20
            0xB9, 0x00, 0x00, 0x00, 0x00,           //24 mov ecx,BaseAddr      //25
            0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00,     //29 mov ecx,[ecx+0x355E0C]
            0x8B, 0x89, 0x68, 0x08, 0x00, 0x00,     //35 mov ecx,[ecx+0x868]
            0x51,                                   //41 push ecx
            0xE8, 0x00, 0x00, 0x00, 0x00,           //42 call PlantFunc        //43     //被调方平栈
            0x61,                                   //47 popad
            0xC9,                                   //48 leave
            0xC3                                    //49 ret
        };

        //3. 申请空间用于存储代码
        DWORD  dwCodeSize = 50, desFunc = BaseAddr + 0x18D70;
        LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

        //4. 修正参数
        *(DWORD*)&InjectCode[10] = PlantType;
        *(DWORD*)&InjectCode[15] = y;
        *(DWORD*)&InjectCode[20] = x;
        *(DWORD*)&InjectCode[25] = BaseAddr;
        *(DWORD*)&InjectCode[43] = desFunc-((DWORD)lpRemoteCodeMem+42+5) ;
        //call指令与jmp类似,相对于当前指令的下一条指令计算偏移,offset=des-(source+5),减去call自身长度5

        if (lpRemoteCodeMem != NULL)
        {
    
    
            SIZE_T dwBytesWritten = 0;
            //5. 注入代码
            if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
                dwBytesWritten == dwCodeSize)
            {
    
    
                //6. 创建远程线程执行代码
                HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem,NULL, 0, NULL);
                if (hThread != NULL)
                {
    
    
                    //7. 等待线程信号
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                    bSuccess = TRUE;
                }
            }
            //8. 执行完后释放空间
            VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
        }
        CloseHandle(hProcess);
    }
    
    return bSuccess;
}

//以创建远程线程方式种植僵尸
BOOL GrowZombieByInjectCode(DWORD dwProcessId,DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
    
    

    BOOL bSuccess = FALSE;
    //1. 打开进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess != NULL)
    {
    
    
        //2. 定义注入代码(函数)

        BYTE InjectCode[50] = {
    
    
            0x55,                                       //0 push ebp
            0x89, 0xE5,                                 //1 mov ebp,esp
            0x60,                                       //3 pushad
            0x68, 0x00, 0x00, 0x00, 0x00,               //4 push x
            0x68, 0x00, 0x00, 0x00, 0x00,               //9 push ZombieType
            0xB8, 0x00, 0x00, 0x00, 0x00,               //14 mov eax,y
            0xB9, 0x00, 0x00, 0x00, 0x00,               //19 mov ecx,BaseAddr
            0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00,         //24 mov ecx,[ecx+0x355E0C]
            0x8B, 0x89, 0x68, 0x08, 0x00, 0x00,         //30 mov ecx,[ecx+0x868]
            0x8B, 0x89, 0x78, 0x01, 0x00, 0x00,         //36 mov ecx,[ecx+0x178]
            0xE8, 0x00, 0x00, 0x00, 0x00,               //42 call PlantZombieFunc
            0x61,                                       //47 popad
            0xC9,                                       //48 leave 
            0xC3                                        //49 ret
        };

        //3. 申请空间用于存储代码
        DWORD  dwCodeSize = 50, desFunc = BaseAddr + 0x35390; //种植僵尸函数
        LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

        //4. 修正参数
        *(DWORD*)&InjectCode[5] = x;
        *(DWORD*)&InjectCode[10] = ZombieType;
        *(DWORD*)&InjectCode[15] = y;
        *(DWORD*)&InjectCode[20] = BaseAddr;
        *(DWORD*)&InjectCode[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);//call指令与jmp类似,相对于当前指令的下一条指令计算偏移,要减去call长度5

        if (lpRemoteCodeMem != NULL)
        {
    
    
            SIZE_T dwBytesWritten = 0;
            //5. 注入代码
            if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
                dwBytesWritten == dwCodeSize)
            {
    
    
                //6. 创建远程线程执行代码
                HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem, NULL, 0, NULL);
                if (hThread != NULL)
                {
    
    
                    //7. 等待线程信号
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                    bSuccess = TRUE;
                }
            }
            //8. 执行完后释放空间
            VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
        }
        CloseHandle(hProcess);
    }

    return bSuccess;
}

//设置卡槽植物
BOOL SetPlantCard(HANDLE hProcess,DWORD BaseAddr,DWORD nCard,DWORD plantType) {
    
    
    DWORD cardAddr = BaseAddr + 0x355E0C;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x868;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x15C;
    ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
    cardAddr += 0x5C+nCard*0x50;//卡槽偏移
    return WriteProcessMemory(hProcess, cardAddr, &plantType, sizeof(DWORD), NULL);
}

//选择菜单
void choiceMenu(HANDLE hProcess,DWORD Pid, LPVOID BaseAddr) {
    
    
    BYTE choice = 0;
    unsigned int num = 0;
    DWORD fogAddr = 0;
    unsigned int x, y, Type;
    do {
    
    
        system("cls");
        printf("\t\t\t\tWelcome to PVZ Modifier!\n");
        printf("\t\t\t\t\t0.退出\n");
        printf("\t\t\t\t\t1.修改阳光数\n");
        printf("\t\t\t\t\t2.修改金钱数\n");
        printf("\t\t\t\t\t3.修改智慧树高\n");
        printf("\t\t\t\t\t4.修改巧克力数\n");
        printf("\t\t\t\t\t5.修改树肥\n");
        printf("\t\t\t\t\t6.修改花肥\n");
        printf("\t\t\t\t\t7.修改杀虫剂\n");
        printf("\t\t\t\t\t8.无限冷却\n");
        printf("\t\t\t\t\t9.恢复冷却\n");
        printf("\t\t\t\t\t10.无限阳光\n");
        printf("\t\t\t\t\t11.恢复阳光消耗\n");
        printf("\t\t\t\t\t12.除雾\n");
        printf("\t\t\t\t\t13.恢复雾\n");
        printf("\t\t\t\t\t14.种植植物\n");
        printf("\t\t\t\t\t15.生成僵尸\n");

        printf("\t\t\t\tPlease choose your option:[ ]\b\b");
        scanf("%d", &choice);

        switch(choice){
    
    
        case 0:
            return;
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
            printf("\t\t\t\tPlease input Num:");
            scanf("%d", &num);
            setSomething(hProcess, BaseAddr, choice - 1, num);
            break;
        case 8:
            Uncooled(hProcess, BaseAddr);
            break;
        case 9:
            RecoveryCooling(hProcess, BaseAddr);
            break;
        case 10:
            UnlimitedSun(hProcess,BaseAddr);
            break;
        case 11:
            RecoverySunConsume(hProcess, BaseAddr);
            break;
        case 12:
            fogAddr=(DWORD)DeFogByHook(hProcess, BaseAddr);
            break;
        case 13:
            RecoveryFogByUnHook(hProcess, BaseAddr,fogAddr );
            break;
        case 14:
            printf("请输入X Y PlantType: ");
            scanf("%d%d%d", &x, &y, &Type);
            GrowPlantByInjectCode(Pid, BaseAddr,x,y,Type );
            break;
        case 15:
            printf("请输入X Y ZombieType: ");
            scanf("%d%d%d", &x, &y, &Type);
            GrowZombieByInjectCode(Pid, BaseAddr, x, y, Type);
            break;
        }

        printf("\t\t\t\t请选择是否继续:(y/Y or n/N): ");
        getchar();
    } while (scanf("%c", &choice) && (choice == 'y' || choice == 'Y'));
}


int main() {
    
    

	//获取进程pid
    DWORD Pid = GetProcessIdByName(L"PlantsVsZombies.exe");
	//打开进程,获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,Pid);
    //获取进程基址
    DWORD BaseAddr=GetModuleBaseAddress(Pid, L"PlantsVsZombies.exe");


    choiceMenu(hProcess, Pid, BaseAddr);


    //dll注入
    //InjectDllByRemoteThread(Pid, L"E:\\MyProject\\vsProjects\\Project1\\Debug\\DllPlant3.dll");
    //int op = 1;
    //printf("输入0卸载dll:");
    //scanf("%d", &op);
    //if(op==0)
    //    UnLoadDllByRemoteThread(Pid, L"E:\\MyProject\\vsProjects\\Project1\\Debug\\DllPlant3.dll");//加载完dll之后释放掉

    CloseHandle(hProcess);
    return 0;
}

DLL code

#include<windows.h>
#include<stdio.h>

//调用函数
BOOL GrowPlant(DWORD BaseAddr, DWORD x, DWORD y, DWORD TypePlant) {
    
    
    LPVOID PlantFunc = BaseAddr + 0x18D70;
    __asm {
    
    
        pushad
        push -1         //-1
        push TypePlant   //植物类型
        mov eax, y       //y
        push x           //x
        mov ecx, BaseAddr
        mov ecx, [ecx+0x355E0C]
        mov ecx, [ecx + 0x868]
        push ecx
        call PlantFunc
        popad
    }
    return TRUE;
}

BOOL GrowZombie(DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
    
    
    LPVOID PlantZombieFunc = BaseAddr + 0x35390;
    __asm {
    
    
        pushad 
        push x
        push ZombieType
        mov eax,y
        mov ecx,BaseAddr
        mov ecx,[ecx+0x355E0C]
        mov ecx,[ecx+0x868]
        mov ecx,[ecx+0x178]    //ebp
        call PlantZombieFunc
        popad 
    }
    return TRUE;
}


BOOL WINAPI DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved) {
    
    
    DWORD BaseAddr = GetModuleHandle(NULL);
    DWORD pid = GetCurrentProcessId();
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    
    switch (fdwReason)
    {
    
    
    case DLL_PROCESS_ATTACH:    
        MessageBoxW(0, L"ProcessAttachDll!", L"window2", 0);

        GrowPlant(BaseAddr,5,3,23);        
        GrowZombie(BaseAddr, 6, 2, 23);
                     
        break;
   /* case DLL_THREAD_ATTACH:        
        printf("ThreadAttach!\n");
        break;
    case DLL_THREAD_DETACH:       
        if (lpReserved == NULL)
        {
            FreeLibrary(hInstance);
        }
        break;*/
    case DLL_PROCESS_DETACH:        
        MessageBoxW(0, L"ProcessDeTachDll!", L"window2", 0);
        break;
    }
    return TRUE; 
}

References

  1. You can learn the Cheat Engine zero-based introductory tutorial
  2. [Replenishment] Haoge Plants vs. Zombies Modification Tutorial Video Collection
  3. Announce all the base addresses and various function realization methods I found
  4. C/C++ full-stack software security course (debugging, anti-debugging, game anti-cheat, software reverse) is being continuously updated~~~~
  5. Compilation/disassembly of reverse engineering actual combat (win32+ game reverse actual combat)
  6. [Original] Several common DLL injection techniques
  7. C++ method of calling dll
  8. A Preliminary Study on DLL Injection
  9. Dll injection remote thread injection

Guess you like

Origin blog.csdn.net/OrientalGlass/article/details/132052965