• WINDOWS黑客基础(3):注入代码


    有使用过外挂的朋友应该知道,我们在玩游戏的时候,有很多辅助功能给你使用,比如吃药,使用物品等功能,这个时候我们就是使用注入代码的技术,简单的来将就是我们让另外一个进程去执行我们想让它执行的代码,这中间的关键函数是CreateRemoteThread

    HANDLE WINAPI CreateRemoteThread(
      _In_   HANDLE hProcess,
      _In_   LPSECURITY_ATTRIBUTES lpThreadAttributes,
      _In_   SIZE_T dwStackSize,
      _In_   LPTHREAD_START_ROUTINE lpStartAddress,
      _In_   LPVOID lpParameter,
      _In_   DWORD dwCreationFlags,
      _Out_  LPDWORD lpThreadId
    );

    CreateRemoteThread的参数跟CreateThread的参数差不多,多出来的hProcess是我们要对其操作的进程HANDLE,在早期的WINDOWS版本CreateThread确实是使用CreateRemoteThread实现的,就是把hProcess传入我们自己的进程HANDLE


    CreateRemoteThread的功能就是在指定的进程创建一个线程,这个线程运行我们指定的函数,看起来很简单,但是有一个问题,就是虚拟内存导致的问题

    大家都知道,在WINDOWS下是使用虚拟内存来进行数据管理的,每个进程都有自己独立的地址空间,假设进程A准备向进程B注入一段代码,他要让进程B执行他进程空间的函数InjectionCode(),这个函数在进程A的地址空间地址为0X3000

    现在我们开始进行代码注入,利用CreateRemoteThread我们告诉B进程,请执行虚拟内存地址为0X3000的代码,这个时候B进程该干什么呢??B进程收到这个命令后,他很听话地创建了线程,然后乖乖得CALL了0X3000的内容,请注意,现在B进程CALL的是它自己内存空间内0X3000的代码而不是A进程的,那么现在B进程的0X3000是什么内容??没人知道,运气好的话说不定真的有段代码给你执行,运气不好你自己也不知道会发生什么事情,这就跟你进错了学生公寓一样,同样号码的房间,运气好是校花的房间,运气不好就是如花的房间

    那么我们怎么才能让进程去执行我们对应的代码呢??我们只要在B进程内开辟一块内存,然后把我们的代码或者数据复制进去,再执行对应的代码就可以了,我们需要用到这几个函数:

    LPVOID WINAPI VirtualAllocEx(
      _In_      HANDLE hProcess,
      _In_opt_  LPVOID lpAddress,
      _In_      SIZE_T dwSize,
      _In_      DWORD flAllocationType,
      _In_      DWORD flProtect
    );
    
    BOOL WINAPI WriteProcessMemory(
      _In_   HANDLE hProcess,
      _In_   LPVOID lpBaseAddress,
      _In_   LPCVOID lpBuffer,
      _In_   SIZE_T nSize,
      _Out_  SIZE_T *lpNumberOfBytesWritten
    );

    这两个函数跟我们平常用的函数都差不多,只是多了个进程的选项,大概步骤如下图:

    现在我们将实际操作一下:

    下面是我们要注入的程序,在这之前,我们最好把基地址固定掉,这样我们不会每次重新运行程序的时候函数的地址都会改变,在VS2008中,项目属性->链接器->高级,把随机基址和固定基址选择默认值

    void PrintMsg(const char *msg)
    {
        printf("ThreadI D:%d Msg:%s
    ",GetCurrentThreadId(),msg);
    }
    int main(void)
    {
        printf("%d
    ",GetCurrentThreadId());
        printf("Print Msg Function Address:%X
    ",PrintMsg);
        system("pause");
    }

    假设我们的PrintMsg的地址是0x401000,现在我们需要往这个进程里面注入一段代码,让她可以自动调用PrintMsg这个函数

     1 static const char *msg = "INJECTION CODE SUCESS
    ";
     2 static const unsigned int PARAM_SIZE = 100;
     3 static const unsigned int EXE_SIZE = 500;
     4 
     5 
     6 void InjectionCode(const char *msg)
     7 {
     8     __asm
     9     {
    10         push eax
    11         push msg
    12         mov eax,0x401000
    13         call eax            //因为在我们要注入的进程中,PrintMsg位于0x401000这个位置
    14         pop eax
    15         pop eax
    16     }
    17 }
    18 int main(void)
    19 {
    20     HANDLE hProcess = OpenProcessByProcessNmae("main.exe"); //这个函数在上一章
    21     
    22     if (hProcess == INVALID_HANDLE_VALUE)
    23     {
    24         printf("error open process %d
    ",GetLastError());
    25         return 1;
    26     }
    27     //一定要把函数的代码和msg写入要注入的进程,否则会发生位置错误(一般是崩溃)
    28     LPVOID RemoteExe = VirtualAllocEx(hProcess,NULL,EXE_SIZE,MEM_COMMIT,PAGE_EXECUTE);
    29     LPVOID RemoteParam = VirtualAllocEx(hProcess,NULL,PARAM_SIZE,MEM_COMMIT,PAGE_READWRITE);
    30 
    31     SIZE_T WriteCount = 0;
    32     int ret = 0;
    33     ret = WriteProcessMemory(hProcess,RemoteParam,msg,PARAM_SIZE,&WriteCount);
    34     ret = WriteProcessMemory(hProcess,RemoteExe,InjectionCode,0x13,&WriteCount);
    35 
    36     HANDLE hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)RemoteExe,RemoteParam,0,NULL);
    37     WaitForSingleObject(hThread,INFINITE);
    38 }

    运行上面的程序,我们就可以在另外一个进程中创建一个线程,并且这个线程将会输出该线程的ID以及我们要输出的消息

    上面的程序还有几个要注意的:

    1.资源竞争

    由于是创建线程执行相应代码所以肯定会有资源竞争的问题,以后要写代码一定要注意,在本例中我忽略了这个问题

    2. 关于代码的长度问题

    在本例中,我们的代码长度是0X13,但是要知道,汇编代码的长度随便懂一下就可能更改,可能因为一个指令,也可能因为一个参数,所以我们需要时刻注意这点,关于代码长度怎么测量,我是看了反汇编的代码后计算的,这个方法比较准确,也可以大概估计下,只要能把代码复制完整就可以,超出也没关系,只要不超出申请的内存大小就可以

    3.记得备份我们使用的寄存器

    这个十分重要,一旦你更改了寄存器,如果没有后面没有恢复,可能会导致一系列错误,特别是ESP,EBP等重要的寄存器

    3.注入代码多次调用系统DLL中的函数

    <<WINDOWS核心编程>>里面说,系统的DLL都会加载到一个固定的地址,比如VirtualAllocEx,一般我们在A进程和B进程的时候,call或者jmp的地址都是一样的,所以一般我们如果调用的是系统函数,一般我们不需要担心,但是,昨天我想到了一个问题,比如我们进程A要命令进程B调用CreateToolhelp32Snapshot这个系统API,现在我们假设CreateToolhelp32Snapshot这个API在单独的TLHELP32.DLL里面(实际上这个在KERNEL32.DLL里面,所有进程都会加载这个DLL,所以不需要担心下面的问题,这个只是举例),操作系统在加载DLL的时候,会统一把这个API的地址映射到虚拟内存的0XFF40100的地址,按照我们原来的想法进程B会自己跑去call 0XFF40100这个地址。但问题在于,如果我们的进程根本就没有加载TLHELP32.DLL这个DLL,那么进程call 0XFF40100会怎么样??这个就要看你这个地方是什么代码了,有人说操作系统会帮你加载这个DLL,但我觉得是错的,因为操作系统要帮你加载的DLL都在PE头里面的导入表里面,要嘛就是我们要显示地去加载,否则操作系统不会知道我们的API在哪个DLL里面

    4.注入代码多次调用我们自己编写的函数

    比如我们有IntejectionCode,里面调用了IntejectionCode1,这个时候我们需要把IntejectionCode1也写入对方进程里面,不能只写入IntejectionCode,并且,我们需要更改IntejectionCode里面call IntejectionCode1跳转指令,让其跳转到正确的位置。总之,别人的地盘别人做主,对方进程想把代码放哪里就放哪里,我们无法管理(实际上virtualAllocEx是可以指定位置的,但是一般我们都尽量让操作系统去指定),我们只能入乡随俗,人家让我们去哪里调用我们就要去哪里调用,不然很容易导致进程崩溃

    5.关于代码的基地址

    在本例中PrintMsg的基地址是固定的,是我们人为去固定基地址的,我们在开发的时候很少人会去把基地址固定掉,所以在进程运行的时候,PrintMsg这个函数的地址是会改变的,当然我们也可以算出来这段代码在运行的时候会放在哪里,因为PrintMsg这段代码以二进制放在EXE文件的时候,也有一个文件偏移量,当操作系统把EXE文件加载进内存后,会根据基地址和文件偏移量,来算出PrintMsg在虚拟内存中的位置,所以我们只要能拿到进程运行时候的基地址,并且把EXE反汇编查看这段代码的文件偏移量,也能算出每次运行的时候PrintMsg的地址,虽然很麻烦,特别是反汇编找代码的那部分,但也没办法,这个在后面我会讲

  • 相关阅读:
    OutputCache 缓存key的创建 CreateOutputCachedItemKey
    Asp.net Web Api源码调试
    asp.net mvc源码分析DefaultModelBinder 自定义的普通数据类型的绑定和验证
    Asp.net web Api源码分析HttpParameterBinding
    Asp.net web Api源码分析HttpRequestMessage的创建
    asp.net mvc源码分析ActionResult篇 RazorView.RenderView
    Asp.Net MVC 项目预编译 View
    Asp.net Web.config文件读取路径你真的清楚吗?
    asp.net 动态创建TextBox控件 如何加载状态信息
    asp.net mvc源码分析BeginForm方法 和ClientValidationEnabled 属性
  • 原文地址:https://www.cnblogs.com/linyilong3/p/3140694.html
Copyright © 2020-2023  润新知