博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
代码注入
阅读量:3890 次
发布时间:2019-05-23

本文共 6375 字,大约阅读时间需要 21 分钟。

简介

代码注入在分析程序或制作外挂外挂时是非常爱使用的一种手段, 通常用于调用程序功能, 比如调用程序中的call;其基本原理和的原理一样, 目前已经有很多工具可以直接实现代码注入或DLL注入了, 本文只是学习一下原理…

思路

思路很简单, 基本就两大步:

  1. OpenProcess打开需要注入的程序, 获取句柄;
  2. 通过CreateRemoteThread函数将我们需要注入的代码以新的线程的方式进行运行, 到达注入的效果;

通过PEB获取模块基址

通常我们注入的代码一般都是汇编, 如果注入是很复杂的代码, 我们通常将代码写到DLL中, 直接用DLL注入了;

所以为了使我们的汇编根据有健壮性, 这里说一下如何用汇编获取程序模块的基地址, 注入的代码就相当于是写shellcode;

PEB

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:

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结构; 而链表的第一个就保存了当前程序的基地址;

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

Demo

此程序有一个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函数的参数进行注入:

inject

总结

通过代码注入可以在不修改程序的情况下调用程序当中的代码段, 在实际过程中还是使用工具比较方便…

转载地址:http://smlhn.baihongyu.com/

你可能感兴趣的文章
Leetcode - 11盛最多水的容器
查看>>
Leetcode - 141环形链表
查看>>
Leetcode - 14最长公共前缀
查看>>
Leetcode - 7整数反转
查看>>
PAT---B1022. D进制的A+B (20)
查看>>
PAT---B1037. 在霍格沃茨找零钱(20)
查看>>
PAT---A1019. General Palindromic Number (20)
查看>>
PAT---A1027. Colors in Mars (20)
查看>>
PAT---1058. A+B in Hogwarts (20)
查看>>
PAT---A1001. A+B Format (20)
查看>>
PAT---A1005. Spell It Right (20)
查看>>
PAT---A1035. Password (20)
查看>>
PAT---A1077. Kuchiguse (20)
查看>>
PAT---A1062. Talent and Virtue (25)
查看>>
PAT---A1012. The Best Rank (25)
查看>>
数据库SQL语言语法总结3---查询语句
查看>>
数据库SQL语言语法总结4---数据更新
查看>>
数据库SQL语言语法总结5---视图
查看>>
数据库SQL语言语法总结6---数据控制
查看>>
数据库SQL语言语法总结7---嵌入式SQL
查看>>