代码注入

简介

代码注入在分析程序或制作外挂外挂时是非常爱使用的一种手段, 通常用于调用程序功能, 比如调用程序中的call;其基本原理和DLL注入的原理一样, 目前已经有很多工具可以直接实现代码注入或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<num;i++){
			GetModuleBaseNameA(hpro,hModule[i],lpBaseName,sizeof(lpBaseName));
			printf("%-2d %-25s基址: 0x%p\n",i,lpBaseName,hModule[i]);
		}
	
	    pro_base = (DWORD)hModule[0];
		printf("程序基址: 0x%p\n",pro_base);
}

或者:

void Get_addr(){
	HMODULE addr = GetModuleHandle(NULL);
	printf("addr: 0x%p\n", addr);
}

代码注入

首先这是我们要注入的程序代码:


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

Demo
此程序有一个add函数, 可以接收一个参数, 并且在程序中只调用一次, 我们可以通过代码注入的方式调用这个函数. 注入代码如下:

// Win.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

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

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

总结

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CTF(Capture The Flag)竞赛中,常见的一个攻击技术是PHP代码注入(PHP Code Injection)。这种攻击利用了应用程序对用户输入的不充分验证和过滤,使得攻击者能够将恶意的PHP代码注入到应用程序中,从而执行任意代码或实施其他攻击。 以下是一些常见的PHP代码注入漏洞场景和防范措施: 1. 用户输入的直接执行:如果应用程序直接将用户输入作为PHP代码执行,而没有进行充分的验证和过滤,攻击者可以通过提交恶意代码来执行任意操作。防范措施是使用合适的输入验证和过滤,例如使用白名单来限制允许的操作或使用安全的函数来处理用户输入。 2. 变量覆盖:如果应用程序在解析用户输入时,没有正确处理变量覆盖的情况,攻击者可以通过构造特殊的输入来覆盖应用程序中的变量,并执行恶意操作。防范措施是在处理用户输入之前,将其赋值给新的变量,并确保不会被覆盖已有的变量。 3. 文件包含漏洞:如果应用程序在包含文件时没有进行充分的验证和过滤,攻击者可以通过构造特殊的文件路径来包含恶意的PHP代码文件。防范措施是使用白名单来限制允许包含的文件路径,并对用户输入进行适当的过滤和验证。 4. 数据库查询注入:虽然不是直接的PHP代码注入,但数据库查询注入漏洞可能导致执行恶意的SQL语句,从而进一步执行PHP代码。防范措施是使用参数化查询或预处理语句,避免直接将用户输入拼接到SQL查询中。 总之,要防范PHP代码注入漏洞,开发者应该始终进行充分的输入验证和过滤,使用安全的函数和方法处理用户输入,避免直接执行或拼接用户输入作为代码执行。同时,定期更新和修复应用程序中使用的框架、库和组件,以确保使用的是最新的安全版本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值