iTruth 发表于 2020-3-4 14:24

【C语言】 用C写了个常用的代码注入库

前言
近期写了一个代码注入的库,因为没啥技术含量原本打算不发的.
但一来这些功能要实现起来却要费一点时间,现在有现成的库就不用自己写了. 二来我想完善下我的库使其更加完整,稳定. 最后我也想听听大佬的意见来提高自己的水平

所以请大家尽可能的挑毛病,有什么想要的功能却不在里面也提出来我会尽力去实现的
因为这个库刚起步所以目前里面也就两个功能,分别是dll注入和代码注入dll注入我也不多说了
代码注入实现了类似CE的代码注入,这样能使你脱离CE写挂,也能达到类似inline hook的效果来钩住各种函数
里面我还做了个代码nop的功能,用于nop掉一些关键跳啥的

此代码托管于: https://github.com/iTruth/cheatlib

技术细节
为了实现比较好的代码注入效果,这个库使用了两个汇编,反汇编引擎,分别是: BeaEngine和keystone
两个你需要知道的结构体指针: PDllInjectionInfo 和 PCodeInjectionInfo

它们分别由 DllInjection函数和 CodeInjection函数返回,保存了代码注出所必须的信息.除非你不打算恢复注入现场否则一定要保存这些指针
最后请注意为了保证代码的稳定性里面用了很多assert(断言),编译Release的时候注意定义NDEBUG宏

免责声明
此代码仅用作交流学习使用,请勿使用它来干任何违法的事情.使用此代码所造成的任何非法行为和经济损失均和本作者无关
如果此代码不符合版规请版主删除这个帖子


应用实例 - 写一个能使植物大战僵尸变得无聊的外挂
#include <stdio.h>
#include <conio.h>
#include "cheatlib.h"

int main()
{
        HANDLE pvzHandle = GetHandleByTitle("Plants vs. Zombies");
        if(pvzHandle == NULL){
                printf("Can't find pvz process!\nPress any key to quit\n");
                getch();
                return -1;
        }
        printf("Trying to inject code\n");
        PCodeInjectionInfo ptSubZombieHP = CodeInjection(pvzHandle,
                        (LPVOID)0x54D0BA, "xor ebp, ebp; mov dword ptr ss:,eax");
        PCodeInjectionInfo ptJmpSub1 = CodeInjection(pvzHandle,
                        (LPVOID)0x54D60F, NULL);
        PCodeInjectionInfo ptJmpSub2 = CodeInjection(pvzHandle,
                        (LPVOID)0x54D5FA, NULL);
        PCodeInjectionInfo ptSunInc = CodeInjection(pvzHandle,
                        (LPVOID)0x43C0AF, "mov dword ptr ,0x1869f");
        PCodeInjectionInfo ptSunLock = CodeInjection(pvzHandle,
                        (LPVOID)0x43C0C3, "mov dword ptr ds:,0x1869f");
        PCodeInjectionInfo ptSunDec = CodeInjection(pvzHandle,
                        (LPVOID)0x427696, NULL);
        PCodeInjectionInfo ptCdInc = CodeInjection(pvzHandle,
                        (LPVOID)0x49CDF9, "mov dword ptr ,0xffff;mov eax, dword ptr ");
        PCodeInjectionInfo ptPlantsLocal = CodeInjection(pvzHandle,
                        (LPVOID)0x41BD2D, "jmp 0041C679h");
        PCodeInjectionInfo ptAutoCollection = CodeInjection(pvzHandle,
                        (LPVOID)0x43CC72, "jmp 43CC7Dh");
        PCodeInjectionInfo ptPlantsSubHP = CodeInjection(pvzHandle,
                        (LPVOID)0x54BA6A, NULL);
        printf("Done!\nPress any key to outject\n");
        getch();
        printf("Trying to outject now\n");
        CodeOutjection(ptSubZombieHP);
        CodeOutjection(ptJmpSub1);
        CodeOutjection(ptJmpSub2);
        CodeOutjection(ptSunInc);
        CodeOutjection(ptSunLock);
        CodeOutjection(ptSunDec);
        CodeOutjection(ptCdInc);
        CodeOutjection(ptPlantsLocal);
        CodeOutjection(ptAutoCollection);
        CodeOutjection(ptPlantsSubHP);
        printf("Done!\nPress any key to quit\n");
        getch();
        return 0;
}


主体代码
cheatlib.h:
#ifndef _H_CHEATLIB
#define _H_CHEATLIB

#include <stdint.h>
#include <windows.h>

typedef struct _CheatLibRequiredAsmInfo{
        BYTE *pbOpCode;                // 机器码
        int iRequiredSize;        // jmp指令覆盖的指令总大小
        int iFirstCmdSize;        // 注入点第一条指令的大小
} CheatLibRequiredAsmInfo, *PCheatLibRequiredAsmInfo;

typedef struct _CheatLibAsmEncodeInfo{
        const char *pszAsmCode;                // 汇编代码
        uint64_t u64Address;                // 汇编代码的所在地址
        unsigned char *pbOpCode;        // 机器码
        size_t nOpCodeSize;                        // 机器码长度
        size_t nCmdCount;                        // 汇编指令数量
} CheatLibAsmEncodeInfo, *PCheatLibAsmEncodeInfo;

typedef struct _DllInjectionInfo{
        HANDLE hProcess;                        // 进程句柄
        HANDLE hThread;                                // 远程线程句柄
        LPVOID pszLibFileRemote;        // dll文件路径字符串首地址
} DllInjectionInfo, *PDllInjectionInfo;

typedef struct _CodeInjectionInfo{
        HANDLE hProcess;                                                        // 进程句柄
        LPVOID pOrigAddr;                                                        // 代码源地址
        LPVOID pVirAddr;                                                        // 申请的远程进程空间的首地址
        PCheatLibRequiredAsmInfo ptRequiredAsmInfo;        // 记录原始代码信息用于恢复
} CodeInjectionInfo, *PCodeInjectionInfo;

/* 说明:        根据窗口标题获取进程句柄
* 参数:        pszTitle        - 窗口标题
* 返回值:        成功找到该窗口返回进程句柄
*                         没有找到该窗口返回NULL */
HANDLE GetHandleByTitle(const char *pszTitle);

/* 说明:        向目标进程注入dll
* 参数:        hProcess        - 进程句柄
*                         pszLibFile        - dll文件名称
* 返回值:        PDllInjectionInfo */
PDllInjectionInfo DllInjection(HANDLE hProcess, const char *pszLibFile);

/* 说明:        注出dll
* 参数:        PDllInjectionInfo - DllInjection函数的返回值
* 返回值:        void */
void DllOutjection(PDllInjectionInfo ptInfo);

/* 说明:        代码注入 -
*                        如果pszAsmCode包含汇编指令那么函数会在远程进程中申请空间写入
*                        pszAsmCode的指令并在空间最后写入jmp指令,目标为pAddress处将被
*                        jmp指令覆盖的一条或多条指令的下一地址处.最后在pAddress处写入
*                        jmp指令,目标为函数申请的空间.最后nop填充多余的字节
*                        如果pszAsmCode是空字符串或NULL那么函数将会直接用nop填充pAddress
*                        指定的汇编指令
* 注意:        此函数的主要功能是把pAddress处的那条指令替换为pszAsmCode的指令执行,
*                         但一条jmp指令有5字节,可能会覆盖2条或以上指令.考虑到那些多覆盖的
*                         指令也有可能需要被修改所以此函数不会把那些指令在函数申请的空间
*                         中重新生成.如果不想修改那些被多覆盖的指令请在pszAsmCode的最后写入
*                         那些指令
* 参数:        hProcess        - 进程句柄
*                         pAddress        - 待替换指令的地址
*                         pszAsmCode        - 汇编指令,以分号或回车分隔.例如xor eax,eax;mov ecx,9
*                                                  此参数也可以是空字符串或NULL,这样函数将会用nop填充
*                                                  pAddress指定的汇编指令
* 返回值:        成功执行返回PCodeInjectionInfo
*                         在汇编引擎初始化失败或pszAsmCode有错误的情况下返回NULL */
PCodeInjectionInfo CodeInjection(HANDLE hProcess, LPVOID pAddress, const char *pszAsmCode);

/* 说明:        代码注出 - 恢复注入的代码
* 参数:        PCodeInjectionInfo - CodeInjection函数的返回值
* 返回值:        void */
void CodeOutjection(PCodeInjectionInfo ptInfo);

#endif

cheatlib.c:
#include <windows.h>
#include <assert.h>
#include <string.h>
#include "beaengine/BeaEngine.h"
#include "keystone/keystone.h"
#include "cheatlib.h"

HANDLE GetHandleByTitle(const char *pszTitle)
{
        assert(pszTitle!=NULL);
        HWND hWnd = FindWindow(NULL, pszTitle);
        if(hWnd == 0){
                return NULL;
        }
        DWORD dwPid = 0;
        GetWindowThreadProcessId(hWnd, &dwPid);
        return OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
}

PDllInjectionInfo DllInjection(HANDLE hProcess, const char *pszLibFile)
{
        assert(hProcess!=NULL && pszLibFile!=NULL);
        // 计算dll名称大小
        DWORD dwSize = (strlen(pszLibFile) + 1) * sizeof(char);
        // 在远程进程中为dll名称分配空间
        LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
        if (pszLibFileRemote == NULL)
                return NULL;
        // 将DLL名称复制到远程进程地址空间
        DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);
        if (n == 0)
                return NULL;
        // 从Kernel32.dll获取LoadLibraryA地址
        PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
        if (pfnThreadRtn == NULL)
                return NULL;
        // 创建远程线程调用 LoadLibraryA(DLLPathname)
        HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
        if (hThread == NULL)
                return NULL;
        PDllInjectionInfo ptInfo = (PDllInjectionInfo)malloc(sizeof(DllInjectionInfo));
        ptInfo->hProcess = hProcess;
        ptInfo->hThread = hThread;
        ptInfo->pszLibFileRemote = pszLibFileRemote;
        return ptInfo;
}

void DllOutjection(PDllInjectionInfo ptInfo)
{
        assert(ptInfo!=NULL);
        //等待远程线程结束
        WaitForSingleObject(ptInfo->hThread, INFINITE);
        if (ptInfo->pszLibFileRemote != NULL)
                VirtualFreeEx(ptInfo->hProcess, ptInfo->pszLibFileRemote, 0, MEM_RELEASE);
        if (ptInfo->hThread != NULL)
                CloseHandle(ptInfo->hThread);
        free(ptInfo);
        ptInfo = NULL;
}

static void IntToByte(int i, BYTE *bytes)
{
        assert(bytes != NULL);
        bytes = (byte) (0xff & i);
        bytes = (byte) ((0xff00 & i) >> 8);
        bytes = (byte) ((0xff0000 & i) >> 16);
        bytes = (byte) ((0xff000000 & i) >> 24);
}

static void JmpBuilder(BYTE *pCmdOutput, DWORD dwTargetAddr, DWORD dwCurrentAddr)
{
        assert(pCmdOutput != NULL);
        pCmdOutput = 0xE9;
        DWORD jmpOffset = dwTargetAddr - dwCurrentAddr - 5;
        IntToByte(jmpOffset, pCmdOutput+1);
}

static void FreeRequiredAsmInfo(PCheatLibRequiredAsmInfo ptInfo)
{
        assert(ptInfo != NULL && ptInfo->pbOpCode != NULL);
        free(ptInfo->pbOpCode);
        ptInfo->pbOpCode = NULL;
        free(ptInfo);
        ptInfo = NULL;
}

static PCheatLibRequiredAsmInfo GetRequiredAsmInfo(HANDLE hProcess, LPVOID pAddress)
{
        PCheatLibRequiredAsmInfo ptInfo = (PCheatLibRequiredAsmInfo)malloc(sizeof(CheatLibRequiredAsmInfo));
        int nOpCodeSize = 32; // opcode的字节数
        ptInfo->pbOpCode = (BYTE*)malloc(sizeof(BYTE)*nOpCodeSize);
        DISASM disAsm;
        memset(&disAsm, 0, sizeof disAsm);
        ReadProcessMemory(hProcess, pAddress, (LPVOID)ptInfo->pbOpCode, nOpCodeSize, NULL);
        disAsm.EIP = (UIntPtr)ptInfo->pbOpCode; // 保存opcode的缓冲区首地址
        disAsm.VirtualAddr = (int)pAddress; // pbOpCode 指令的地址
        disAsm.Archi = 32; // 32位
        disAsm.Options = 0x000; // masm 汇编指令格式
        int nCount = 0;// 用于记录在循环当中,反汇编了多少个字节
        int nLen = 0 ; // 用于记录当前的汇编指令的字节数
        // 调用Disasm()进行反汇编来获取指令长度
        while(nCount < nOpCodeSize)
        {
                nLen = Disasm(&disAsm); // 每次只反汇编一条汇编指令, 并且返回当前得到的汇编指令的长度
                if(nCount == 0){ // 获取第一条汇编指令大小
                        ptInfo->iFirstCmdSize = nLen;
                }
                nCount += nLen; // 累加已经反汇编的字节数
                disAsm.EIP += nLen; // 定位到下一条汇编指令
                disAsm.VirtualAddr += nLen; // 设置到下一条汇编指令的地址
                if(nCount>=5)break; // 如果已经反汇编的字节数超过了jmp指令的字节数就不用继续分析了
        }
        ptInfo->iRequiredSize = nCount;
        return ptInfo;
}

static void FreeAsmEncodeInfo(PCheatLibAsmEncodeInfo ptInfo)
{
        // 如果反汇编错误那么pbOpCode会是NULL所以不能断言pbOpCode
        assert(ptInfo != NULL);
        if(ptInfo->pbOpCode != NULL)
                ks_free(ptInfo->pbOpCode);
        free(ptInfo);
        ptInfo = NULL;
}

static PCheatLibAsmEncodeInfo EncodeAsm(const char *pszAsmCode, LPVOID pAddress)
{
        // 根据pszAsmCode编译汇编代码
        ks_engine *pengine = NULL;
        if(KS_ERR_OK != ks_open(KS_ARCH_X86 , KS_MODE_32 , &pengine)){
                return NULL;
        }
        PCheatLibAsmEncodeInfo ptInfo = (PCheatLibAsmEncodeInfo)malloc(sizeof(CheatLibAsmEncodeInfo));
        ptInfo->pszAsmCode = pszAsmCode;
        ptInfo->u64Address = (DWORD)pAddress;
        int nRet = 0; // 保存函数的返回值,用于判断函数是否执行成功
        nRet = ks_asm(pengine, /* 汇编引擎句柄,通过ks_open函数得到*/
                        pszAsmCode, /*要转换的汇编指令*/
                        (DWORD)pAddress, /*汇编指令所在的地址*/
                        &ptInfo->pbOpCode,/*输出的opcode*/
                        &ptInfo->nOpCodeSize,/*输出的opcode的字节数*/
                        &ptInfo->nCmdCount /*输出成功汇编的指令的条数*/
                        );
        // 返回值等于-1时反汇编错误
        if(nRet == -1) {
                FreeAsmEncodeInfo(ptInfo);
                return NULL;
        }
        // 关闭句柄
        ks_close(pengine);
        return ptInfo;
}

static void FreeCodeInjectionInfo(PCodeInjectionInfo ptInfo)
{
        assert(ptInfo != NULL);
        // 释放代码空间
        if(ptInfo->pVirAddr != NULL){
                VirtualFreeEx(ptInfo->hProcess, ptInfo->pVirAddr, 0, MEM_RELEASE);
        }
        FreeRequiredAsmInfo(ptInfo->ptRequiredAsmInfo);
        free(ptInfo);
        ptInfo = NULL;
}

PCodeInjectionInfo CodeInjection(HANDLE hProcess, LPVOID pAddress, const char *pszAsmCode)
{
        // 在pAddress处收集必要的信息
        PCheatLibRequiredAsmInfo ptRequiredAsmInfo = GetRequiredAsmInfo(hProcess, pAddress);
        PCodeInjectionInfo ptCodeInjectionInfo = (PCodeInjectionInfo)malloc(sizeof(CodeInjectionInfo));
        ptCodeInjectionInfo->hProcess = hProcess;
        ptCodeInjectionInfo->pOrigAddr = pAddress;
        ptCodeInjectionInfo->ptRequiredAsmInfo = ptRequiredAsmInfo;
        DWORD WrittenLen = 0;

        // 如果pszAsmCode是空字符串或NULL就使用nop填充该指令
        if(pszAsmCode == NULL || strlen(pszAsmCode) == 0){
                ptCodeInjectionInfo->pVirAddr = NULL;
                BYTE *nopCode = (BYTE*)malloc(sizeof(BYTE)*ptRequiredAsmInfo->iFirstCmdSize);
                memset(nopCode, 0x90, ptRequiredAsmInfo->iFirstCmdSize);
                // 写入空指令
                WriteProcessMemory(hProcess,
                                (LPVOID)pAddress,
                                nopCode,
                                ptRequiredAsmInfo->iFirstCmdSize,
                                &WrittenLen);
                free(nopCode);
                nopCode = NULL;
                return ptCodeInjectionInfo;
        }

        // 开始构造我们自己的代码
        // 我们不知道pszAsmCode中的汇编指令在函数申请的空间中会生成多少机器码
        // 但使用这种方式计算出来的大小一定不会小于实际所需大小
        int nCodeSize = strlen(pszAsmCode)+5;
        // 在远程进程申请空间用于存放我们自己的代码
        LPVOID virAddr = (PWSTR)VirtualAllocEx(hProcess,
                        NULL,
                        nCodeSize,
                        MEM_COMMIT,
                        PAGE_EXECUTE_READWRITE);
        ptCodeInjectionInfo->pVirAddr = virAddr;
        // 汇编pszAsmCode,需要virAddr来正确计算指令中的偏移
        PCheatLibAsmEncodeInfo ptAsmCodeInfo = EncodeAsm(pszAsmCode, virAddr);
        // 大概率是pszAsmCode有问题导致的
        if(ptAsmCodeInfo == NULL){
                FreeCodeInjectionInfo(ptCodeInjectionInfo);
                return NULL;
        }
        BYTE *exeCode = (BYTE*)malloc(sizeof(BYTE)*(nCodeSize));
        // 先使用nop填充
        memset(exeCode, 0x90, nCodeSize);
        // 将生成的汇编代码拷贝到exeCode中
        memcpy(exeCode, ptAsmCodeInfo->pbOpCode, ptAsmCodeInfo->nOpCodeSize);
        // 构建跳转指令
        JmpBuilder(exeCode+ptAsmCodeInfo->nOpCodeSize,
                        (DWORD)pAddress+ptRequiredAsmInfo->iRequiredSize,
                        (DWORD)virAddr+ptAsmCodeInfo->nOpCodeSize);
        // 把构建好的代码写入刚刚申请的空间中
        WriteProcessMemory(hProcess, (LPVOID)virAddr, exeCode, nCodeSize, &WrittenLen);
        free(exeCode);
        exeCode = NULL;
        FreeAsmEncodeInfo(ptAsmCodeInfo);

        // 开始构造注入点跳转指令
        BYTE *jmpCode = (BYTE*)malloc(sizeof(BYTE)*ptRequiredAsmInfo->iRequiredSize);
        memset(jmpCode, 0x90, ptRequiredAsmInfo->iRequiredSize);
        JmpBuilder(jmpCode, (DWORD)virAddr, (DWORD)pAddress);
        // 向注入点写入跳转指令
        WriteProcessMemory(hProcess,
                        (LPVOID)pAddress,
                        jmpCode,
                        ptRequiredAsmInfo->iRequiredSize,
                        &WrittenLen);
        free(jmpCode);
        jmpCode = NULL;
        return ptCodeInjectionInfo;
}

void CodeOutjection(PCodeInjectionInfo ptInfo)
{
        assert(ptInfo != NULL && ptInfo->ptRequiredAsmInfo != NULL);
        DWORD WrittenLen = 0;
        // 恢复原始代码
        WriteProcessMemory(ptInfo->hProcess,
                        ptInfo->pOrigAddr,
                        ptInfo->ptRequiredAsmInfo->pbOpCode,
                        ptInfo->ptRequiredAsmInfo->iRequiredSize,
                        &WrittenLen);
        FreeCodeInjectionInfo(ptInfo);
}

JuncoJet 发表于 2020-3-4 14:45

这么复杂,这里都是易语言玩家,怕是完全提不起兴趣

微笑一刀 发表于 2020-3-4 14:49

有用.不一定写G的时候用.很多时候都可以用用.

renpeng009 发表于 2020-3-4 15:06

牛逼,没看懂{:301_1009:}

庞晓晓 发表于 2020-3-4 15:27

SulioEmpty 发表于 2020-3-4 21:48

这么难么

醉情的大叔 发表于 2020-3-5 01:50

小白看不懂,收藏了,慢慢学习

intel286 发表于 2020-3-5 07:07

这个也可以用库的形式,以前没有想过

lynxtang 发表于 2020-3-5 13:58

感谢分享,一直很想把C语言学好。

不想火页 发表于 2020-3-8 11:54

先记录下,到时回头看看
页: [1]
查看完整版本: 【C语言】 用C写了个常用的代码注入库