常见注入手法第一讲EIP寄存器注入

时间:2023-03-08 15:41:04
常见注入手法第一讲EIP寄存器注入

             常见注入手法第一讲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位计算器做实验 :)  (其他的我也没有 常见注入手法第一讲EIP寄存器注入

)

①.查找窗口获得窗口句柄

 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中是查询不到的,要到定义结构体地方的位置,看注释可以看到.

这里简单看一下,具体怎么组合的,自己详细去看.

常见注入手法第一讲EIP寄存器注入

常见注入手法第一讲EIP寄存器注入

⑥.申请远程内存,一会要写入我们的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版本下)

调用

常见注入手法第一讲EIP寄存器注入

在我们眼中,看着是直接Call,但是F11进去,则会看到一个Jmp

常见注入手法第一讲EIP寄存器注入

JMP

常见注入手法第一讲EIP寄存器注入

所以,对于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中的写法,我下方贴图

常见注入手法第一讲EIP寄存器注入

其实你把Call 和我写的二进制当做汇编看就行,因为2013的汇编,和VC6.0的汇编二进制代码不一样,因为段的问题,不太一样,所以只能写成那样了

首先,我们介绍下这两个函数的作用吧

第一个Call, 这个直接Call 标号2取内容 其实就是把标号2定义的4个字节,当做一个函数地址取运行了.  假设 标号2的地址是

0x00400020 ,那么对它取内容就是第一个定义的00的位置.但是注意,call 后面跟的是一个4个字节的地址.

所以说我们取内容,然后把里面的值我们通过我们的手法把一个函数地址的值给它,那么不就相当于调用了我们的函数了吗.

如果不懂,看图:

常见注入手法第一讲EIP寄存器注入

那么现在经过我讲解,知道为什么我们要定义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

那么后边的地址,我们要通过我们代码,把它修改为标号的位置.

如果不懂,看下图片.

常见注入手法第一讲EIP寄存器注入

那么现在修正了位置,我们就可以写我们的代码了.

代码就两句,其实主要是为了让大家懂原理

 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/

转载请注明出处,谢谢