钞sir 发表于 2020-11-2 22:12

代码注入

# 简介
代码注入在分析程序或制作外挂外挂时是非常爱使用的一种手段, 通常用于调用程序功能, 比如调用程序中的`call`;其基本原理和(https://blog.csdn.net/qq_40827990/article/details/89367045)的原理一样, 目前已经有很多工具可以直接实现代码注入或DLL注入了, 本文只是学习一下原理....


# 思路
思路很简单, 基本就两大步:
1. `OpenProcess`打开需要注入的程序, 获取句柄;
2. 通过`CreateRemoteThread`函数将我们需要注入的代码以新的线程的方式进行运行, 到达注入的效果;

# 通过PEB获取模块基址
通常我们注入的代码一般都是汇编, 如果注入是很复杂的代码, 我们通常将代码写到`DLL`中, 直接用`DLL注入`了;
所以为了使我们的汇编根据有健壮性, 这里说一下如何用汇编获取程序模块的基地址, 注入的代码就相当于是写`shellcode`;
## PEB
在`fs:`地址处保存着一个指针, 指向了PEB结构, 结构基本如下:
```cpp
typedef struct _PEB { // Size: 0x1D8
/*000*/ UCHAR InheritedAddressSpace;
/*001*/ UCHAR ReadImageFileExecOptions;
/*002*/ UCHAR BeingDebugged;
/*003*/ UCHAR SpareBool; // Allocation size
/*004*/ HANDLE Mutant;
/*008*/ HINSTANCE ImageBaseAddress; // Instance
/*00C*/ VOID *DllList;//_PEB_LDR_DATA      ;进程加载的模块链表
/*010*/ PPROCESS_PARAMETERS *ProcessParameters;
/*014*/ ULONG SubSystemData;
/*018*/ HANDLE DefaultHeap;
/*01C*/ KSPIN_LOCK FastPebLock;
/*020*/ ULONG FastPebLockRoutine;
/*024*/ ULONG FastPebUnlockRoutine;
/*028*/ ULONG EnvironmentUpdateCount;
/*02C*/ ULONG KernelCallbackTable;
/*030*/ LARGE_INTEGER SystemReserved;
/*038*/ ULONG FreeList;
/*03C*/ ULONG TlsExpansionCounter;
/*040*/ ULONG TlsBitmap;
/*044*/ LARGE_INTEGER TlsBitmapBits;
/*04C*/ ULONG ReadOnlySharedMemoryBase;
/*050*/ ULONG ReadOnlySharedMemoryHeap;
/*054*/ ULONG ReadOnlyStaticServerData;
/*058*/ ULONG AnsiCodePageData;
/*05C*/ ULONG OemCodePageData;
/*060*/ ULONG UnicodeCaseTableData;
/*064*/ ULONG NumberOfProcessors;
/*068*/ LARGE_INTEGER NtGlobalFlag;
/*070*/ LARGE_INTEGER CriticalSectionTimeout;
/*078*/ ULONG HeapSegmentReserve;
/*07C*/ ULONG HeapSegmentCommit;
/*080*/ ULONG HeapDeCommitTotalFreeThreshold;
/*084*/ ULONG HeapDeCommitFreeBlockThreshold;
/*088*/ ULONG NumberOfHeaps;
/*08C*/ ULONG MaximumNumberOfHeaps;
/*090*/ ULONG ProcessHeaps;
/*094*/ ULONG GdiSharedHandleTable;
/*098*/ ULONG ProcessStarterHelper;
/*09C*/ ULONG GdiDCAttributeList;
/*0A0*/ KSPIN_LOCK LoaderLock;
/*0A4*/ ULONG OSMajorVersion;
/*0A8*/ ULONG OSMinorVersion;
/*0AC*/ USHORT OSBuildNumber;
/*0AE*/ USHORT OSCSDVersion;
/*0B0*/ ULONG OSPlatformId;
/*0B4*/ ULONG ImageSubsystem;
/*0B8*/ ULONG ImageSubsystemMajorVersion;
/*0BC*/ ULONG ImageSubsystemMinorVersion;
/*0C0*/ ULONG ImageProcessAffinityMask;
/*0C4*/ ULONG GdiHandleBuffer;
/*14C*/ ULONG PostProcessInitRoutine;
/*150*/ ULONG TlsExpansionBitmap;
/*154*/ UCHAR TlsExpansionBitmapBits;
/*1D4*/ ULONG SessionId;
} PEB, *PPEB;
```
PEB结构的偏移`0xc`处保存着另外一个指针ldr,该指针为PEB_LDR_DATA:
## PEB_LDR_DATA
```cpp
typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
```
`PEB_LDR_DATA结构`的后三个成员是指向`LDR_MODULE`链表结构中相应三条双向链表头的指针, 分别是按照加载顺序, 在内存中地址顺序和初始化顺序排列的模块信息结构的指针, 其中`LDR_MODULE结构`就是`_LDR_DATA_TABLE_ENTRY`结构; 而链表的第一个就保存了当前程序的基地址;

## _LDR_DATA_TABLE_ENTRY结构
```cpp
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;// +0x00
    LIST_ENTRY InMemoryOrderLinks;// +0x04
    LIST_ENTRY InInitializationOrderLinks;// +0x08
    PVOID DllBase;// +0x0c
    PVOID EntryPoint;// +0x10
    DWORD SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    DWORD Flags;
    WORD LoadCount;
    WORD TlsIndex;
    LIST_ENTRY HashLinks;
    PVOID SectionPointer;
    DWORD CheckSum;
    DWORD TimeDateStamp;
    PVOID LoadedImports;
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;
```
其中偏移为`0x10`的位置的`EntryPoint`就保存了模块的基地址;
综上所述, 我们可以利用一下这段汇编代码获取程序的基地址:
```c
                mov eax, fs:;                // PEB
                mov ebx, ;        // PEB_LDR_DATA
                mov eax, ;        // InMemoryOrderModuleList
                mov ebx, ;        // ebx = EntryPoint(基址)
```
如果在DLL当中获取程序基地址, 可以使用下面的代码:
```cpp
void Get_addr(DWORD pro_id){
        HANDLE hpro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pro_id);
                if (hpro == 0){
                        printf("无法获取进程句柄");
                }
                printf("进程句柄id: %d\n",hpro);
       
                // 获取每一个模块加载基址
                DWORD pro_base = NULL;
                HMODULE hModule = {0};
          DWORD dwRet = 0;
                int num = 0;
          int bRet = EnumProcessModulesEx(hpro, (HMODULE *)(hModule), sizeof(hModule),&dwRet,NULL);
          if (bRet == 0){
                printf("EnumProcessModules");
          }
                // 总模块个数
                num = dwRet/sizeof(HMODULE);
                printf("总模块个数: %d\n",num);
                // 打印每一个模块加载基址
                char lpBaseName;
                for(int i = 0;i<num;i++){
                        GetModuleBaseNameA(hpro,hModule,lpBaseName,sizeof(lpBaseName));
                        printf("%-2d %-25s基址: 0x%p\n",i,lpBaseName,hModule);
                }
       
          pro_base = (DWORD)hModule;
                printf("程序基址: 0x%p\n",pro_base);
}
```
或者:
```cpp
void Get_addr(){
        HMODULE addr = GetModuleHandle(NULL);
        printf("addr: 0x%p\n", addr);
}
```
# 代码注入
首先这是我们要注入的程序代码:
```cpp

// Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <stdio.h>
#include <windows.h>

void add(int a) {
        printf("a: %d\n", a);
}

int main()
{
        add(8);
        printf("add_addr: 0x%p\n", add); // add函数地址
        while (true)
        {
                printf("Demo....\n");
                getchar();
        }
        return 0;
}
```
!(https://img-blog.csdnimg.cn/20201101134527808.png#pic_center)
此程序有一个`add`函数, 可以接收一个参数, 并且在程序中只调用一次, 我们可以通过代码注入的方式调用这个函数. 注入代码如下:
```cpp
// Win.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include<stdio.h>
#include<Windows.h>
#include<psapi.h>

void injected_code() {
        __asm {
                // 获取基地址
                mov eax, fs:;                // PEB
                mov ebx, ;        // PEB_LDR_DATA
                mov eax, ;        // InMemoryOrderModuleList
                mov ebx, ;        // ebx = 基址

                push        100;                        // add函数参数
                add                ebx, 0x0002964F;// ebx = 基址 + 偏移add函数地址
                call        ebx;                        // 调用add函数
                add                esp, 0x4;
        }
}

void inject_fun(DWORD pid) {

        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
        printf("hProcess: 0x%x\n", hProcess);
        LPVOID call_addr = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        printf("call_addr: 0x%x\n", call_addr);
        int ret = WriteProcessMemory(hProcess, call_addr, injected_code, 0x1000, NULL);
        printf("WriteProcessMemory: 0x%x\n", ret);
        HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)call_addr, NULL, 0, NULL);
        printf("hthread: %x\n", hThread);
        WaitForSingleObject(hThread, 2000);
        CloseHandle(hProcess);
        CloseHandle(hThread);
}

int main()
{
        HWND Prohan = FindWindowA(NULL, "C:\\Users\\cc-sir\\Desktop\\Demo.exe");
        if (Prohan) {
                printf("Prohan: 0x%x\n", Prohan);
                DWORD Pid;
                GetWindowThreadProcessId(Prohan, &Pid);
                printf("Pid: %d\n", Pid);
                // LPCSTR title = "sir";
                // SetWindowText(Prohan, title);
                inject_fun(Pid);
        }
        else {
                printf("FindWindow Error!\n");
        }
        system("pause");
        return 0;
}
```
代码当中重新`push 100`作为`add`函数的参数进行注入:
!(https://img-blog.csdnimg.cn/20201101134947223.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwODI3OTkw,size_1,color_FFFFFF,t_00#pic_center)
# 总结
通过代码注入可以在不修改程序的情况下调用程序当中的代码段, 在实际过程中还是使用工具比较方便....

钞sir 发表于 2020-11-18 11:07

twm74110 发表于 2020-11-18 00:00
我试了intel的编译报错,单独写的话,有些变量要传进去汇编命令里不方便

https://blog.csdn.net/cosmoslife/article/details/9071949可以参考一下如何传变量, 确实没有32位的方便....

twm74110 发表于 2020-11-18 00:00

钞sir 发表于 2020-11-3 13:59
可以改用Intel的编译器或者把汇编部分单独写到一个asm文件里, 然后在其它源文件中引用....

我试了intel的编译报错,单独写的话,有些变量要传进去汇编命令里不方便

bigdawn 发表于 2020-11-3 00:04

收藏学习一下。

终于来了 发表于 2020-11-3 01:59

有点复杂的东西

youkan_pj 发表于 2020-11-3 08:10

记录让生活更美好,不过没基础的人都不怎么了解

你是神 发表于 2020-11-3 09:13

看不明白

ningmengbuku 发表于 2020-11-3 09:36

学习一下

liujieboss 发表于 2020-11-3 10:46

学习一下

twm74110 发表于 2020-11-3 11:32

这内联asm如果要64位编译咋整?

yssun 发表于 2020-11-3 12:04


学习一下

钞sir 发表于 2020-11-3 13:59

twm74110 发表于 2020-11-3 11:32
这内联asm如果要64位编译咋整?

可以改用Intel的编译器或者把汇编部分单独写到一个asm文件里, 然后在其它源文件中引用....
页: [1] 2 3 4
查看完整版本: 代码注入