本节我们依旧探讨代码注入(CodeInject),相比先前代码注入的篇章,不同的是,这次我们借助OllyDbg的汇编工程,注入的代码为汇编指令字节码的形式写入我们的代码。
汇编代码编写
1 | 004010ED 55 PUSH EBP |
其实代码的完成的功能是上节的ThreadProc
的函数功能,为了方便理解,在po一下:
1 | DWORD WINAPI ThreadProc(LPVOID lParam) |
其中在函数体内部用到的一些字符串以及kernel32的API都是设置好的,并且由结构体lParam提交的。
回到汇编代码的编写,这里对重要的进行说明:
1 | 004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] |
这里是由函数调用决定的,可以知道函数ThreadProc
的参数是通过栈传递的,且只有一个参数,因此通过访问SS:[EBP+8]
就是参数lParam
的地址了。lParam
参数如下:
1 | hMod = GetModuleHandleA("kernel32.dll"); |
1 | 004010F3 68 6C6C0000 PUSH 6C6C |
代码前四行完成的是将构造出字符串user32.dll
,由于是入栈的操作,因此当前的esp指向的是字符串user32.dll
的首地址,此时CALL DWORD PTR DS:[ESI]
此时会完成LoadLibraryA(user32.dll)
此时,Eax存放的是user32.dll
的句柄。
1 | 00401105 68 6F784100 PUSH 41786F |
同样的手法,安排栈中元素,设置esp指向将要使用的字符串MessageBoxA
首地址,再传入先前LoadLibraryA(user32.dll)
获取到的user32.dll
的句柄(存放在EAX中)。随后调用DS:[ESI+4]
,这里取到的是传递给ThreadProc
的参数结构体中的GetProcAddress
,所以上述的汇编代码完成的GetProcAddress(hMod, "MessageBoxA")
获取到user32.MessageBoxA
API的地址。
PS:这里可以好好学习通过push操作,在栈中放置参数的手法。
经过上述的代码后,EAX存放的是user32.MessageBoxA
API的地址。接下来就该调用了。
1 | 00401119 6A 00 PUSH 0 ; - MB_OK (0) |
这里初次看到时感觉设置参数的方式很惊艳,是通过call
指令完成参数入栈的,原理如何?
需要了解call
指令的运作细节,call
指令会将下一条指令的地址进行入栈(十分关键)。然后跑去执行call的主体,随后当call主体进行retn时,将先前入栈的指令地址接着往下的执行流程。
在0040111B
处执行了CALL 0040112C
,此时会将下一个“指令”地址进行入栈,但由于下一条“指令”并非指令,而是设置好的字符串内容,此时的入栈相当于MessageBoxA
参数字符串ReverseCore
入栈了。
随后会跳向call的主体内容的位置0040112C
,然后系统又发现了一个call指令,同样的操作,将MessageBoxA
参数又一字符串www.reversecore.com
入栈了。当然这里观察call的地址会发现猫腻,CALL 00401145
,直接往下就是正常执行了,最后调用CALL EAX(MessageBoxA)
至此,需要的汇编指令编写完毕,只需在调用这段代码前,保证参数lParam
入栈即可。
CodeInject2源码分析
在开始分析CodeInject2源码前,需要做一些准备工作。
编写完上述代码,我们只需要保存他的字节码即可
获取字节码
OD随意载入一个不需要的程序(这里我复制了一份notepad.exe),
敲完ThreadProc的汇编代码之后,选择复制到可执行文件,在接下来的窗口中保存:
保存修改到ThreadProc.exe,在使用OD打开。
在数据窗口跟踪这些指令,然后完整的复制到文件ThreadProc.txt文件中
去除不必要的内容,地址,注释部分,并在每个字节前面加0x,得到字节码
1 | 0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00, |
在保证参数lParam
入栈的情况下,这段代码是可以直接上CPU运行的。
CodeInject2.main
1 | int main(int argc, char *argv[]) |
和先前文章的一致,不做赘述。
关注InjectCode函数
即可:
CodeInject2.InjectCode
1 | BOOL InjectCode(DWORD dwPID) |
各位读者回想一下,关于ThreadProc
的参数部分是一样的,但ThreadProc
代码的主体部分在上节我们是怎么操作的?我们是在开辟出需要的空间(需要计算,这一点依赖于原代码的顺序在编译后顺序保持一致的特性,这带来了诸多不便,使得不通用)之后写入的内容也是依赖于这一点的。
在使用汇编字节码注入中,我们是这样进行的:
1 | if( !WriteProcessMemory(hProcess, pRemoteBuf[1], (LPVOID)&g_InjectionCode, |
其中g_InjectionCode
是字节码部分,在CodeInject2源码中:
1 | BYTE g_InjectionCode[] = |
可以看到,写入的size大小是独立计算的,写入的代码也是独立的。(不需要依赖于编译后代码先后顺序与源代码一致的特性)。
启动CodeInject2.exe并输入notepad.exe的PID,后续都是自动完成的。
回顾
本节关于参数设定,由两个很有趣的点值的学习,一个是通过push入栈,结束了将当前栈顶地址保存出来。
第二个是使用call完成参数的设置。