1、代码注入
代码注入是一种向目标进程插入独立运行代码并使之运行的技术,它一般调用CreateRemoteThread() API以远程线程形式运行插入的代码,所以也被称为线程注入。原理如图所示:
首先向目标进程插入代码于=与数据,在此过程中,代码以线程过程(Thread Procedure)形式插入,二代码中使用的数据则以线程参数的形式传入。也就是说,代码与数据是分别注入的。
使用代码注入的原因:
1、占用内存少
如果要注入的代码与数据较少,那么就不需要讲它们做成DLL的形式注入。采用直接代码注入的方式同样能够获得与DLL注入相同的效果,且占用的内存会更少。
2、难以查找痕迹
采用DLL注入方式会在目标进程的内存中留下相关的痕迹,采用代码注入方式几乎不会留下任何痕迹。
DLL注入技术主要用在代码量大且复杂的时候,而代码注入技术则适合用于代码量较少的情况。
2、注入示例
1、运行notepad.exe
首先运行notepad.exe,然后查看进程的PID,如图所示:
2、运行CodeInjection.exe
在命令行窗口输入命令与参数(notepad.exe的PID),回车运行:
3、弹出消息框
notepad.exe进程中弹出一个消息框,如图所示:
3、代码
1、main()函数
首先看一下main()函数。
1 | int main(int argc, char *argv[]) |
main()函数用来调用InjectCode()函数,传入的函数参数为目标进程的PID。
2、ThreadProc()函数
该函数为注入目标进程的代码(线程函数)。
1 | DWORD WINAPI ThreadProc(LPVOID lParam) |
上述代码看起来比较复杂,其实等同于
1 | hMod = LoadLibraryA("user32.dll"); |
从上述代码中的ThreadProc()函数可以看到,函数中并未直接调用相关API,也未直接定义用字符串,它们都是通过THREAD_PARAM结构体以线程参数的形式传递使用。原因在于编译的过程中,编译器将会把调用API的地址写死,但是在目标进程中对应的地址并不一定存在该函数,导致代码不能正常工作。因此我们通过重定义API函数,使其不依赖动态库的调用。
3、InjectCode()函数
InjectCode()是代码注入的核心部分,以下是代码:
1 | BOOL InjectCode(DWORD dwPID) |
InjectCode()函数的set THREAD_PARAM部分用来设置THREAD_PARAM结构体变量,它们会注入目标进程,并且以参数形式传递给ThreadProc()线程函数。
其核心API函数整理如下:
1 | OpenProcess() |
上述代码主要用来在目标进程中分别为data和code分配内存,并将它们注入进程。最后调用CreteRemoteThread() API,执行远程线程。
3、调试
代码注入是一种向目标进程创建新线程的技术,在OD的Debugging options中将Events设置为Break on new thread。从现在开始,每当notepad.exe进程中生成新线程,调试器就暂停在线程函数开始的代码位置。