本文共 6375 字,大约阅读时间需要 21 分钟。
代码注入在分析程序或制作外挂外挂时是非常爱使用的一种手段, 通常用于调用程序功能, 比如调用程序中的call
;其基本原理和的原理一样, 目前已经有很多工具可以直接实现代码注入或DLL注入了, 本文只是学习一下原理…
思路很简单, 基本就两大步:
OpenProcess
打开需要注入的程序, 获取句柄;CreateRemoteThread
函数将我们需要注入的代码以新的线程的方式进行运行, 到达注入的效果;通常我们注入的代码一般都是汇编, 如果注入是很复杂的代码, 我们通常将代码写到DLL
中, 直接用DLL注入
了;
shellcode
; 在fs:[0x30]
地址处保存着一个指针, 指向了PEB结构, 结构基本如下:
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[0x22];/*14C*/ ULONG PostProcessInitRoutine;/*150*/ ULONG TlsExpansionBitmap;/*154*/ UCHAR TlsExpansionBitmapBits[0x80];/*1D4*/ ULONG SessionId;} PEB, *PPEB;
PEB结构的偏移0xc
处保存着另外一个指针ldr,该指针为PEB_LDR_DATA:
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
结构; 而链表的第一个就保存了当前程序的基地址;
typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; // +0x00 LIST_ENTRY InMemoryOrderLinks; // +0x08 LIST_ENTRY InInitializationOrderLinks; // +0x10 PVOID DllBase; // +0x18 PVOID EntryPoint; // +0x1c DWORD SizeOfImage; // +0x20 UNICODE_STRING FullDllName; // +0x24 UNICODE_STRING BaseDllName; // +0x2c 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;
综上所述, 我们可以利用一下这段汇编代码获取程序的基地址:
mov eax, fs:[0x30]; // PEB mov ebx, [eax + 0xc]; // PEB_LDR_DATA mov eax, [ebx + 0x14]; // InMemoryOrderModuleList mov ebx, [eax + 0x10]; // ebx = InInitializationOrderLinks[0]
或者
mov eax, fs:[0x30]; // PEB mov ebx, [eax + 0x8]; // ImageBaseAddress
如果在DLL当中获取程序基地址, 可以使用下面的代码:
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[100] = { 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[100]; for(int i = 0;i
或者:
void Get_addr(){ HMODULE addr = GetModuleHandle(NULL); printf("addr: 0x%p\n", addr);}
首先这是我们要注入的程序代码:
// Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include此程序有一个#include 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;}
add
函数, 可以接收一个参数, 并且在程序中只调用一次, 我们可以通过代码注入的方式调用这个函数. 注入代码如下: // Win.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include#include #include void injected_code() { __asm { // 获取基地址 mov eax, fs:[0x30]; // PEB mov ebx, [eax + 0xc]; // PEB_LDR_DATA mov eax, [ebx + 0x14]; // InMemoryOrderModuleList mov ebx, [eax + 0x10]; // 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
函数的参数进行注入:
通过代码注入可以在不修改程序的情况下调用程序当中的代码段, 在实际过程中还是使用工具比较方便…
转载地址:http://smlhn.baihongyu.com/