今天和同事聊天时他说到以前写外挂时用到了远程注入dll的技术,他当时是这样做的:首先通过CreateProcess创建子进程,需要指定CREATE_SUSPENDED,防止主线程启动。然后在子进程分配内存,写入自己加载dll的代码。之后修改子进程入口点的代码,使子进程启动时先跳到加载dll的代码,随后再恢复入口点。这种方法和hook类似,将入口点hook住,来运行自己的代码。
我当时就想到了是否可以修改主线程的EIP来达到这种功能呢。思路如下:获取主线程的CONTEXT,保存CONTEXT中原来的EIP值,修改CONTEXT中的EIP为自己写的代码,然后在自己的代码运行完后跳到保存的EIP处。
代码如下:
1. 创建子进程
CreateProcess(argv[1], NULL, NULL, NULL, FALSE, [B]CREATE_SUSPENDED[/B], NULL, NULL, &SI, &PI)
2. 获取主线程的CONTEXT
Context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(PI.hThread, &Context);
3. 在子进程中分配空间
Buffer = VirtualAllocEx(PI.hProcess, NULL, sizeof(SHELL_CODE),
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
SHELL_CODE是我定义的结构体,szPath是dll的路径,szInstruction是我加载dll的代码
typedef struct _SHELL_CODE
{
char szPath[MAX_PATH];
char szInstruction[0x20];
} SHELL_CODE, *PSHELL_CODE;
4. 定义shellcode
CHAR szDllName[] = "injection.dll";
CHAR szShellCode[] =
"\x60\x68\x12\x34\x56\x78\xb8\x12\x34\x56\x78\xff\xd0\x61\xe9\x12\x34\x56\x78";
szShellCode转化成汇编代码如下所示:
pushad
push 0x78563412
mov eax, 0x78563412
call eax
popad
jmp 0x78563412
0x78563412是我预先填的地址,随后填为真正的地址。
5. 修改shellcode
*(DWORD*)(szShellCode + 2) = (DWORD)Buffer;
*(DWORD*)(szShellCode + 7) = (DWORD)LoadLibraryA;
*(DWORD*)(szShellCode + 15) = Context.Eip -
(DWORD)((PUCHAR)Buffer + FIELD_OFFSET(SHELL_CODE, szInstruction) + sizeof(szShellCode) - 1);
第一个是填写字符串的地址,第二个是填写LoadLibraryA的地址,第三个是填写jmp到原来的EIP的地址。
6. 将shellcode写到为子进程分配的内存中
SHELL_CODE ShellCode;
CopyMemory(((PSHELL_CODE)&ShellCode)->szPath, szDllName, sizeof(szDllName));
CopyMemory(((PSHELL_CODE)&ShellCode)->szInstruction, szShellCode, sizeof(szShellCode));
WriteProcessMemory(PI.hProcess, Buffer, &ShellCode, sizeof(SHELL_CODE), &NumberOfBytesWritten)
7. 设置主线程CONTEXT的EIP并启动
Context.Eip = (DWORD)(((PSHELL_CODE)Buffer)->szInstruction);
SetThreadContext(PI.hThread, &Context);
ResumeThread(PI.hThread);
整个过程结束,当主线程启动时,会加载我们的dll,随后跳到原来的入口点执行代码。思路很简单,大牛勿喷。如有雷同,应该是我井底之蛙了
放个代码:
https://github.com/yorath/DllInjection
Github已更新,改为使用EAX来指向我的shellcode
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)