zoukankan      html  css  js  c++  java
  • 常见注入手法第一讲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位计算器做实验 :)  (其他的我也没有

    )

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

     HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("计算器"));
      if (NULL == hWnd)
      {
        MessageBox(NULL, TEXT("对不起,找不到窗口"), TEXT("错误"), MB_OK);
        return 0;
      }

    这一步不多讲了,如果想学习注入,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 0;
      }
      hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
      if (NULL == hProHandle)
      {
        MessageBox(NULL, TEXT("对不起,获取进程句柄失败"), TEXT("Title"), MB_OK);
        return 0;
      }

    ④.挂起线程

     SuspendThread(hThrHandle);  //给个线程的句柄,挂起这个线程

    ⑤.获取寄存器的值

    获取寄存器的值,主要是为了我们要获取当前的EIP的值.然后还回去的时候也需要.

    CONTEXT context = { 0 };
    context.ContextFlags = CONTEXT_FULL;  //比如初始化标志
    BOOL bRet = GetThreadContext(hThrHandle, &context);
    if (!bRet)
      {
        MessageBox(NULL, TEXT("对不起,获取寄存器信息失败"), TEXT("Title"), MB_OK);
        return 0;
      }

    这里需要注意一下,我们初始化的标志,这个在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 0;
      }

    我们最好输出一下,因为一会使用OD调试的时候,要看下内存是否被申请了.

    printf("%p 
    ", 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 +1)+5);

    注意: inject和MyAdd都是一个函数,刚才举例子是用的MyAdd,那个函数没有,纯粹是举例子的.至于InjectCode

    下方会仔细讲解.这里简单知道就行

    至于上面为什么那样写,我们可以暂时知道这样写能获取函数地址即可.因为重点不在这里.在下方EIP注入的地址重定位问题.鉴于时间关系,大家如果想知道的,自己去OD看下就明白了,或者自己单步拆开来看.

    ⑧.把InjectCode函数,当做代码,写入到我们申请的空间

    WriteProcessMemory(hProHandle, lpCode, lpAddr, 100, NULL);//写入100个字节
    如果是Release版本,则不用计算Debug那种公式了.我们直接写成下方代码即可
    WriteProcessMemory(hProHandle, lpCode, InjectCode, 100, 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 0
          push 0
          push 0
          push 0
    
          _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[正确的标号]
    
    标号:
        00 00 00 00

    那么我们的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 0
          push 0
          push 0
          push 0
    
          _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(1, 2);
      HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("计算器"));
      if (NULL == hWnd)
      {
        MessageBox(NULL, TEXT("对不起,找不到窗口"), TEXT("错误"), MB_OK);
        return 0;
      }
      /*2.获得线程的PID和进程的PID*/
      DWORD dwTid = 0;
      DWORD dwPid = 0;
      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 0;
      }
      hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
      if (NULL == hProHandle)
      {
        MessageBox(NULL, TEXT("对不起,获取进程句柄失败"), TEXT("Title"), MB_OK);
        return 0;
      }
      /*4.挂起线程*/
      SuspendThread(hThrHandle);   
    
      /*5.获取寄存器的值*/
      CONTEXT context = { 0 };
      context.ContextFlags = CONTEXT_FULL;  //比如初始化标志
      BOOL bRet = GetThreadContext(hThrHandle, &context);
      if (!bRet)
      {
        MessageBox(NULL, TEXT("对不起,获取寄存器信息失败"), TEXT("Title"), MB_OK);
        return 0;
      }
      /*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 0;
      }
      printf("%p 
    ", lpCode);
      /*因为是Debug版本,所以计算一下JMP跳的位置*/
      char * ch1 = ((char *)InjectCode + 1);
      long ch2 = *(long *)ch1;
      void *lpAddr = (void *)((long)InjectCode + *(long *)((char *)InjectCode +1)+5);
      // InjectCode();
      /*7.写入内存*/
      WriteProcessMemory(hProHandle, lpCode, lpAddr, 100, 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 0; }

    如果不懂,请私信留言.关于地址重定位问题,当然不止这一个办法,比如上次我们写的汇编代码注入,也是解决了地址重定位问题

    当然这个还可以写成汇编版本,留作作业,也可以把Messagebox变成Loadlibrary,那么则会执行一个Dll,具体功能你自己在Dll里面编写即可.

    这些我会在星期六星期天放到作业当中,自己做一下

    下几节课讲解APC注入,以及异常.

    课堂资料:

    链接:http://pan.baidu.com/s/1hr4ukdA 密码:rlju

    原创不易,请爱心点赞评论,转发.如果不会,请下方留言.

    博客园IBinary原创  博客连接:http://www.cnblogs.com/iBinary/

    转载请注明出处,谢谢

  • 相关阅读:
    2019.8.16
    一种抠环的办法
    [HAOI2015]树上染色
    有关树形背包
    2019.7.27
    有关矩阵快速幂
    2019.7.25
    欧拉函数(转载)
    2019.7.22
    phpstudy集成环境安装redis扩展
  • 原文地址:https://www.cnblogs.com/iBinary/p/7572595.html
Copyright © 2011-2022 走看看