zoukankan      html  css  js  c++  java
  • 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的地址,虽然很麻烦,特别是反汇编找代码的那部分,但也没办法,这个在后面我会讲

  • 相关阅读:
    STM32概述
    对xlslib库与libxls库的简易封装
    Makefile.am编写规则
    linux下使用autoconf制作Makefile
    python 登录三次禁止登录
    Linux -- gpasswd
    Linux -- userdel
    Linux -- groupmod
    Linux -- groupadd
    Linux -- passwd
  • 原文地址:https://www.cnblogs.com/linyilong3/p/3140694.html
Copyright © 2011-2022 走看看