常见注入手法第一讲EIP寄存器注入
博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/
转载请注明出处,谢谢
鉴于注入手法太多,所以这里自己整理一下,每个注入单独一片博客。方便大家简单理解。
但是有的注入可能需要需要注入方法的相结合,什么意思,也就是说以前我们写的汇编代码注入,原理就是通过远程线程注入得来的
所以前提你就要理解远程线程注入
今天我们讲一下EIP寄存器注入。我们上一讲是 异常处理(SEH)第一讲,但是中间岔开了,也是为了整理一下注入手法。所以异常第二讲明天继续。此篇文章主要讲解注入。和异常没有任何关系,如果你是奔着异常处理而来,那么你可以直接去看异常处理。
废话不多说,开始讲解。
我们昨天,也就是异常第一讲的时候,我们知道了我们可以设置寄存器的值,或者获取寄存器的值,微软也帮我们提供了API
但是现在这个API正是我们要用的时候了。
博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/
转载请注明出处,谢谢
一丶寄存器注入,之写入代码注入
什么是写代码注入,简而言之就是你把代码写进了对方进程进行执行,全程没有任何DLL,而且杀毒不会报毒,属于很强大的手法,因为我们挂起线程,然后写内容进去执行,比如你的软件,你会不会往内存写内容。所以杀毒不能报毒,这个属于很正常的操作。
我们开始吧
昨天简单说了下思路
/*
思路:
1.查找窗口,获得窗口句柄
2.获得线程ID进程PID
3.获得线程句柄,同时也要获得进程的句柄
4.挂起线程
5.获得寄存器的值
6.修改EIP的值
7.申请远程内存
8.写入远程内存,把EIP也要写进去,这样远程执行完毕之后会切换回来继续执行
9.恢复线程
10.关闭线程句柄
*/
一看上面,我们发现我们要写的很多,其实一点也不多,主要上面是思路,体现在代码上很少。
那么从第一步开始写吧
今天我们还是拿我们可爱的32位计算器做实验 :) (其他的我也没有
)
①.查找窗口获得窗口句柄
HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("计算器"));
if (NULL == hWnd)
{
MessageBox(NULL, TEXT("对不起,找不到窗口"), TEXT("错误"), MB_OK);
return ;
}
这一步不多讲了,如果想学习注入,API的知识必不可少,所以不会API,请自己查询MSDN,或者Google一下API的意思,在这里我认为大家都已经会了API
②.获得线程的ID
/*2.获得线程的PID和进程的PID*/
DWORD dwTid = 0;
DWORD dwPid = 0;
dwTid = GetWindowThreadProcessId(hWnd, &dwPid);
③.获得进程和线程的句柄
HANDLE hThrHandle = NULL;
HANDLE hProHandle = NULL;
hThrHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid);
if (NULL == hThrHandle)
{
MessageBox(NULL, TEXT("对不起,获取线程句柄失败"), TEXT("Title"), MB_OK);
return ;
}
hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hProHandle)
{
MessageBox(NULL, TEXT("对不起,获取进程句柄失败"), TEXT("Title"), MB_OK);
return ;
}
④.挂起线程
SuspendThread(hThrHandle); //给个线程的句柄,挂起这个线程
⑤.获取寄存器的值
获取寄存器的值,主要是为了我们要获取当前的EIP的值.然后还回去的时候也需要.
CONTEXT context = { };
context.ContextFlags = CONTEXT_FULL; //比如初始化标志
BOOL bRet = GetThreadContext(hThrHandle, &context);
if (!bRet)
{
MessageBox(NULL, TEXT("对不起,获取寄存器信息失败"), TEXT("Title"), MB_OK);
return ;
}
这里需要注意一下,我们初始化的标志,这个在MSDN中是查询不到的,要到定义结构体地方的位置,看注释可以看到.
这里简单看一下,具体怎么组合的,自己详细去看.
⑥.申请远程内存,一会要写入我们的InjectCOde,(也就是把二进制写进去)
LPVOID lpCode = NULL;
lpCode = VirtualAllocEx(hProHandle, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NULL == lpCode)
{
MessageBox(NULL, TEXT("对不起,申请远程内存失败"), TEXT("Title"), MB_OK);
return ;
}
我们最好输出一下,因为一会使用OD调试的时候,要看下内存是否被申请了.
printf("%p \r\n", lpCode);
⑦.注意Release版本和Debug版本的区别
Release版本,调用函数的时候是直接调用
Debug的版本,调用函数的时候,默认会有一层Jmp跳转
看一下图片,我们要调用任何一个函数(Debug版本下)
调用
在我们眼中,看着是直接Call,但是F11进去,则会看到一个Jmp
JMP
所以,对于Debug版本,我们要取出Jmp的地址,这个地址才是真正的函数地址.
而Release版本,则没有,需要用OD调试,大家自己去看看即可.这里不做演示.
而Release版本,则不用怎么麻烦了,直接写函数地址就行(这里为了下方往我们申请的内存中写函数里面的内容准备的,所以如果是Release版本,直接填上函数名即可.)
Debug版本的获取函数地址.
void *lpAddr = (void *)((long)InjectCode + *(long *)((char *)InjectCode +)+);
注意: inject和MyAdd都是一个函数,刚才举例子是用的MyAdd,那个函数没有,纯粹是举例子的.至于InjectCode
下方会仔细讲解.这里简单知道就行
至于上面为什么那样写,我们可以暂时知道这样写能获取函数地址即可.因为重点不在这里.在下方EIP注入的地址重定位问题.鉴于时间关系,大家如果想知道的,自己去OD看下就明白了,或者自己单步拆开来看.
⑧.把InjectCode函数,当做代码,写入到我们申请的空间
WriteProcessMemory(hProHandle, lpCode, lpAddr, , NULL);//写入100个字节
如果是Release版本,则不用计算Debug那种公式了.我们直接写成下方代码即可
WriteProcessMemory(hProHandle, lpCode, InjectCode, , NULL);//写入100个字节
其实到这里就是简单调用API,往远程写了一块内存而已.现在我们中间省略几步,先把框架写出来,
也就是说这个框架不会变动的.至于中间的这几步,因为很重要,为了防止大家不太明白,所以框架先写出来.
下面具体讲解这几步怎么写.当然,最后我会贴出完整代码.
⑨.执行我们的核心代码...
⑩.修改EIP的值,修改为我们的InjectCode的位置,让EIP跳转到InjectCode的位置执行代码
context.Eip = (DWORD)lpCode;
SetThreadContext(hThrHandle, &context);
注意,LPcode是我们申请的远程的内存的首地址,现在是让EIP指向这个地方,
当做代码运行.
核心代码,一定要懂.
11.释放资源
ResumeThread(hThrHandle);
CloseHandle(hProHandle);
CloseHandle(hThrHandle); 不重要,知道就好
现在我们的框架已经写出来了,现在我们要知道
我们让EIP把我们申请的内存的位置当做代码跑,而我们申请的内存,写入的是我们的INJECTCODE的代码,也就是说这个函数中的所有二进制都当做代码去跑了.
那么我们就可以做点我们的事情了.
博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/
转载请注明出处,谢谢
二丶注入代码要写入什么
①.Call的讲解,和InjectCode的代码
我们上面说了很多InjectCode,那么这个函数到底是写入的什么__declspec(naked) void InjectCode()
{
__asm
{
NOP
NOP //对其一下以后使用
pushad
pushf push
push
push
push _emit 0ffh //offh 和 15h相当于Call
_emit 015h
_emit 0x01
_emit 0x02 //这段二进制其实是随便Call 一个地址. 总结出来汇编代码就是 Call [地址]
_emit 0x03
_emit 0x04
popf
popad _emit 0ffh //前两个相当于JMP 下面是地址,总结出来是 Call [地址]
_emit 025h _emit 0x00 //跳转的位置,随机写入
_emit 0x00
_emit 0x00
_emit 0x00 label1:
_emit 0x1
_emit 0x2
_emit 0x3
_emit 0x4
label2:
_emit 0x2
_emit 0x3
_emit 0x4
_emit 0x5
}
}
好,看到上方代码是不是不想往下看了,但是其实很简单,为啥看上面的代码
我们不直接写汇编代码
这是因为,我用的是2013 (我的天终于换成了2013),但是为什么这样写,因为我被坑了,不这样写不能操作.
在VC++6.0中的写法,我下方贴图
其实你把Call 和我写的二进制当做汇编看就行,因为2013的汇编,和VC6.0的汇编二进制代码不一样,因为段的问题,不太一样,所以只能写成那样了
首先,我们介绍下这两个函数的作用吧
第一个Call, 这个直接Call 标号2取内容 其实就是把标号2定义的4个字节,当做一个函数地址取运行了. 假设 标号2的地址是
0x00400020 ,那么对它取内容就是第一个定义的00的位置.但是注意,call 后面跟的是一个4个字节的地址.
所以说我们取内容,然后把里面的值我们通过我们的手法把一个函数地址的值给它,那么不就相当于调用了我们的函数了吗.
如果不懂,看图:
那么现在经过我讲解,知道为什么我们要定义4个 _emit了吗,因为这个要通过我们的手法,写入一个函数的地址,然后让CALL去调用.
那么现在我们介绍下Jmp的作用
②.Jmp的作用
Jmp的作用和上面一样,就是JMP标号,其实就是JMP 对标号取内容的值当做地址去执行
为什么这样做,因为我们写完我们的代码要让它回到以前执行的代码位置处
而正好我们定义了4个_emit 这4个字节可以通过我们上面框架的时候,通过获取寄存器信息的EIP的值,获得的EIP,然后写到这4个字节中
什么意思? 就是我们上面获得了EIP的值了,那么把这个EIP的值,写入到这4个字节中,那么JMP的时候,就JMP这4个字节,不就实现了还原EIP的位置了吗.
看了怎么多的概念,晕了,那么我们现在讲我们的核心代码
博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/
转载请注明出处,谢谢
三丶核心代码的编写
我们上面预留出了第九个步骤,为什么,因为这个步骤要知道的知识太多,虽然代码很少.
我们知道,上面的InjectCode,我们要当做代码执行,而我们总共预留出了8个字节的空间,也就是标号1和标号2
那么我们现在要把一个函数地址,写到这个标号中,还有把获取到的EIP的值,也写到这里面,那么当我们第十步的时候
EIP的值会切换到我们写入的这块内存,而我们写入的就是INJECTCODE,也就是说变相的等于EIP切换到我们写的函数
那么现在就回遇到一个问题,执行我们的代码的时候,如果我们给了函数的地址,那么则会执行这个函数,
如果我们还原了,那么则会注入完成之后还原.
有的人可能会想,很简单,我用WriteprocessMemory把这两个值写入到这里不就完了.
那么现在可以写入,也是没问题的,
但是会出现两个问题.(其实也都算一个问题)
Call的时候我们要Call的标号是不是正确的?
给标号的位置写入内存的时候是不是正确的?
好,告诉你们吧,不正确,因为在自己进程中Call一个标号,相当于Call一个常量.
那么在别人进程中也是Call一个常量.但是位置就不一样了
现在我们要解决这个地址重定位问题.
一丶解决Call的时候的问题
我们都知道,Call的时候,是这样的
Call dword ptr[00400000] 二进制代码则是 ff 15 00 00 40 00
那么第一步,我们要算出偏移来
Call的地址位置 - InjectCode的地址位置 = call和injectcode位置的偏移
然后这个偏移 +2 的位置则是我们要修改的地址.
为什么要修改,因为你Call的时候不能这样去Call 我们要保留Call 也就是二进制的 ff 15
那么后边的地址,我们要通过我们代码,把它修改为标号的位置.
如果不懂,看下图片.
那么现在修正了位置,我们就可以写我们的代码了.
代码就两句,其实主要是为了让大家懂原理
long DestValue = (long)(char *)lpCode + 0x1C; //定位到标号位置,转为整数
WriteProcessMemory(hProHandle, (char *)lpCode + 0xF,//call的位置后面 +1修改
&DestValue, sizeof(DestValue),
NULL);
看到没,为什么+1c 为什么 + F,就是上面的内容
+1C 就是首地址 + 1C的偏移,您能找到标号的位置.
+F 也就是首地址 + 偏移,找到Call后面的4个字节地址的位置
现在用 WriteprocessMemory则把Call的地址修改为了标号的位置
Call dwptr[正确的标号] 标号:
那么我们的EIP切还的时候,代码正常执行,遇到这段代码,则会去Call标号里面的内容去调用了,是不是.
但是现在它里面额内容我们应该写成函数指针,这样才会调用函数,现在这是让它正确的知道去哪里Call了
而修改标号的内容,也是算偏移
找到标号的位置.把你想要修改的值写上
看代码
DestValue = (long)GetProcAddress((GetModuleHandle("user32.dll")), "MessageBoxA");
WriteProcessMemory(hProHandle, (char *)lpCode + 0x1C, &DestValue, sizeof(DestValue),NULL);
知道为啥+ 1c了吧,首地址 + 偏移等于标号位置,标号位置修改为函数地址,当Call的时候则会call这个函数了
那么我们要换回去也是一样的
找到jmp 后面地址的位置, 首地址 + 偏移 + 2 = jmp 后面地址的位置
然后找到另一个标号位置,把这个标号位置,写入到jmp后面,那么就把jmp的地址修改了.
而标号中的内容,我们可以写成以前EIP的位置,那么不就注入完成之后返回了.
完整代码:
#include <stdio.h>
#include <windows.h> int MyAdd(int n1, int n2)
{
return n1 + n2;
}
__declspec(naked) void InjectCode()
{
__asm
{
NOP
NOP //对其一下以后使用
pushad
pushf push
push
push
push _emit 0ffh
_emit 015h
_emit 0x01
_emit 0x02 //这段二进制其实是随便Call 一个地址.
_emit 0x03
_emit 0x04
popf
popad _emit 0ffh
_emit 025h _emit 0x00 //跳转的位置,随机写入
_emit 0x00
_emit 0x00
_emit 0x00 label1:
_emit 0x1
_emit 0x2
_emit 0x3 ;写入EIP返回的地址
_emit 0x4
label2:
_emit 0x2
_emit 0x3
_emit 0x4 ;存放我们要写入的值,可以写入函数地址,也可以写入EIP返回的地址
_emit 0x5
}
}
int main(_In_ int _Argc, _In_reads_(_Argc) _Pre_z_ char ** _Argv, _In_z_ char ** _Env)
{
/*1.获取窗口句柄*/
__asm
{
NOP
}
//InjectCode();
int r = MyAdd(, );
HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("计算器"));
if (NULL == hWnd)
{
MessageBox(NULL, TEXT("对不起,找不到窗口"), TEXT("错误"), MB_OK);
return ;
}
/*2.获得线程的PID和进程的PID*/
DWORD dwTid = ;
DWORD dwPid = ;
dwTid = GetWindowThreadProcessId(hWnd, &dwPid); /*3.获得进程和线程的句柄*/
HANDLE hThrHandle = NULL;
HANDLE hProHandle = NULL;
hThrHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid);
if (NULL == hThrHandle)
{
MessageBox(NULL, TEXT("对不起,获取线程句柄失败"), TEXT("Title"), MB_OK);
return ;
}
hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hProHandle)
{
MessageBox(NULL, TEXT("对不起,获取进程句柄失败"), TEXT("Title"), MB_OK);
return ;
}
/*4.挂起线程*/
SuspendThread(hThrHandle); /*5.获取寄存器的值*/
CONTEXT context = { };
context.ContextFlags = CONTEXT_FULL; //比如初始化标志
BOOL bRet = GetThreadContext(hThrHandle, &context);
if (!bRet)
{
MessageBox(NULL, TEXT("对不起,获取寄存器信息失败"), TEXT("Title"), MB_OK);
return ;
}
/*6.申请内存*/
LPVOID lpCode = NULL;
lpCode = VirtualAllocEx(hProHandle, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NULL == lpCode)
{
MessageBox(NULL, TEXT("对不起,申请远程内存失败"), TEXT("Title"), MB_OK);
return ;
}
printf("%p \r\n", lpCode);
/*因为是Debug版本,所以计算一下JMP跳的位置*/
char * ch1 = ((char *)InjectCode + );
long ch2 = *(long *)ch1;
void *lpAddr = (void *)((long)InjectCode + *(long *)((char *)InjectCode +)+);
// InjectCode();
/*7.写入内存*/
WriteProcessMemory(hProHandle, lpCode, lpAddr, , NULL);//写入100个字节
/*释放资源*/
/*8.解决重定位的问题*/
//找到标号的位置,然后找到jmp的位置,在jmp的2个字节后面,写入标号的位置
//标号的位置 标号 - 首地址 = 偏移 + 指令大小 首地址 + 偏移 = 标号位置
long DestValue = (long)(char *)lpCode + 0x1C; //定位到标号位置,转为整数
WriteProcessMemory(hProHandle, (char *)lpCode + 0xF,//call的位置后面 +1修改
&DestValue, sizeof(DestValue),
NULL); DestValue = (long)GetProcAddress((GetModuleHandle("user32.dll")), "MessageBoxA");
WriteProcessMemory(hProHandle, (char *)lpCode + 0x1C, &DestValue, sizeof(DestValue),NULL); DestValue = (long)(char *)lpCode + 0x20;//找到标号位置
//写入EIP以前的值,然后JMP跳转到地方. 20 标号位置
WriteProcessMemory(hProHandle, (char *)lpCode + 0x18, &DestValue, sizeof(DestValue), NULL);//找到EIP的位置
/*9.修改EIP的值,让其跳转*/
DestValue = (long)context.Eip;
WriteProcessMemory(hProHandle, (char *)lpCode + 0x20, &DestValue, sizeof(DestValue), NULL);
context.Eip = (DWORD)lpCode;
SetThreadContext(hThrHandle, &context); ResumeThread(hThrHandle);
CloseHandle(hProHandle);
CloseHandle(hThrHandle); return ;
}
如果不懂,请私信留言.关于地址重定位问题,当然不止这一个办法,比如上次我们写的汇编代码注入,也是解决了地址重定位问题
当然这个还可以写成汇编版本,留作作业,也可以把Messagebox变成Loadlibrary,那么则会执行一个Dll,具体功能你自己在Dll里面编写即可.
这些我会在星期六星期天放到作业当中,自己做一下
下几节课讲解APC注入,以及异常.
课堂资料:
链接:http://pan.baidu.com/s/1hr4ukdA 密码:rlju
原创不易,请爱心点赞评论,转发.如果不会,请下方留言.
博客园IBinary原创 博客连接:http://www.cnblogs.com/iBinary/
转载请注明出处,谢谢