• 常见注入手法第一讲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/

    转载请注明出处,谢谢

  • 相关阅读:
    windwos8.1英文版安装SQL2008 R2中断停止的解决方案
    indwows8.1 英文版64位安装数据库时出现The ENU localization is not supported by this SQL Server media
    Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds
    SQL数据附加问题
    eclipse,myeclipse中集合svn的方法
    JAVA SSH 框架介绍
    SSH框架-相关知识点
    SuperMapRealSpace Heading Tilt Roll的理解
    SuperMap iserver manage不能访问本地目的(IE9)
    Myeclipse中js文件中的乱码处理
  • 原文地址:https://www.cnblogs.com/iBinary/p/7572595.html
Copyright © 2020-2023  润新知