是否可以在不使用 LoadLibrary 的情况下将函数“复制”到另一个进程中并在线程中执行它?

Posted

技术标签:

【中文标题】是否可以在不使用 LoadLibrary 的情况下将函数“复制”到另一个进程中并在线程中执行它?【英文标题】:Is it possible to "copy" a function into another process and execute it in a thread without using LoadLibrary? 【发布时间】:2020-05-11 23:48:37 【问题描述】:

我正在尝试编写和执行单个函数函数:

int thread(void) 
    WCHAR boxTitle = L"testing...";
    WCHAR message = L"hello?";
    int (*MessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT);
    MessageBoxW = (LPVOID)0x7FFCA28E2750; // address of MessageBoxW function in user32.dll on my machine
    MessageBoxW(NULL, message, boxTitle, MB_OK);
    return 0;

在另一个进程中使用VirtualAllocExWriteProcessMemoryCreateRemoteThread

int main(void) 

    HANDLE hProc;
    LPVOID baseAddr;
    int funcSize;

    funcSize = (int)main - (int)thread;
    hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 10724); // notepad.exe PID on my machine
    baseAddr = VirtualAllocEx(hProc, NULL, funcSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    WriteProcessMemory(hProc, baseAddr, &thread, funcSize, NULL);
    CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)baseAddr, NULL, 0, NULL);
    CloseHandle(hProc);

    return 0;

问题是消息框没有正确显示我通过函数指针提供给 MessageBoxW 函数的LPCWSTR 参数。出现消息框,但框标题和消息正文为空白。

这是显示此问题的图片的链接:

我感觉这个问题涉及字符编码以及我将函数复制到远程进程的虚拟地址空间的方式,但我真的不知道出了什么问题。

【问题讨论】:

message, boxTitle 指向另一个进程上下文中的什么? 您可以只复制没有导入和重定位的函数,而不是使用指向某些数据的指针 @RbMm 除非您的函数是轻量级叶函数(不使用堆栈,不使用非易失性寄存器,不调用其他函数),否则您还需要通知内核您的函数结构以用于展开目的。您可能还必须将该函数注册为有效的调用目标才能传递 cfg。在 x64 上注入代码比复制代码更复杂。 @RaymondChen - 你错了。不需要注册函数作为有效的调用目标来传递 cfg - 如果我们分配可执行内存 - 所有它都是有效的调用目标。如果我们不使用它,我们不需要有展开的结构。如果需要处理异常,我们可以注册 VEH(在调用 RtlSetProtectedPolicy 之后)。真的,我可以注入原始函数,然后如果需要加载 dll,甚至可以处理所有当前缓解措施的位置 - CFG(带导出抑制、StrictMode)、ACG 等。您的评论之所以受到支持,只是因为人们信任您。但很少有这方面的知识 @RbMm 我不确定 CFG,这就是我说“可能”的原因。如果发生异常,肯定需要注册展开代码。否则,将无法正确展开。现在,这是否“必要”取决于您是从“我想以架构上正确且受支持的方式执行此操作”的角度来看(例如,您正在编写生产代码),还是从角度来看“我是想要执行代码的恶意软件,我不在乎在我被注入后进程是否会变得不可挽回地损坏,因为那不是我的问题” 【参考方案1】:

您无法按照自己的方式计算函数的大小。不保证mainthread会在内存中顺序存储,或者main会在thread之后存储。

您也没有将字符串文字的实际字符复制到远程进程中,您只是将 pointers 复制到字符串文字。您不能跨进程边界共享指针,每个进程都有不同的虚拟地址空间,并且给定进程地址空间内的指针仅在该进程内有效。您需要在远程进程中分配额外的内存来复制字符,然后让远程线程将指向该内存的指针传递给MessageBoxW()

更不用说,您根本没有执行任何清理,因此您正在泄漏在远程进程中分配的内存和系统资源,让它们在远程线程完成运行后闲置。

您将不得不做类似以下的事情(这仅适用于 x86,对于 x64 会更复杂一些。如果我的操作码有一点错误,请原谅我,我的 x86 有点生锈):

static const WCHAR *boxTitle = L"testing...";
static const WCHAR *message = L"hello?";

#pragma pack(push, 1)
struct myThreadOpCodes

    BYTE push_uType;
    DWORD uType;
    BYTE push_lpCaption;
    DWORD lpCaption;
    BYTE push_lpText;
    DWORD lpText;
    BYTE push_hWnd;
    DWORD hWnd;
    BYTE call_MessageBoxW;
    LONG offset_MessageBoxW;
    BYTE xor_EAX[2];
    BYTE ret;
    WORD numBytes;
;
#pragma pack(pop)

int main(void)

    HANDLE hProc, hThread;
    LPVOID baseAddr;
    myThreadOpCodes code;

    hProc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, 10724);
    if (!hProc)
        return 0;

    // TODO: if ASLR is enabled, to get the real address of MessageBoxW within the 
    // target process, you will have to first enumerate the loaded modules in the
    // process looking for the actual base address of kernel32.dll (see
    // http://bytepointer.com/articles/locating_kernel32_in_aslr_memory.htm)
    // then enumerate its exports table looking for the real address of
    // MessageBoxW...
    void *lpMessageBox = GetProcAddress(GetModuleHandle(TEXT("kernel32")), "MessageBoxW");

    code.push_uType = 0x68;
    code.uType = MB_OK;
    code.push_lpCaption = 0x68;
    code.lpCaption = 0;
    code.push_lpText = 0x68;
    code.lpText = 0;
    code.push_hWnd = 0x68;
    code.hWnd = 0;
    code.call_MessageBoxW = 0xE8;
    code.offset_MessageBoxW = 0;
    code.xor_EAX[0] = 0x33;
    code.xor_EAX[1] = 0xC0;
    code.ret = 0xCA;
    code.numBytes = sizeof(LPVOID);

    int TitleLen = (lstrlenW(lpTitle) + 1) * sizeof(WCHAR);
    int TextLen = (lstrlenW(message) + 1) * sizeof(WCHAR);

    baseAddr = VirtualAllocEx(hProc, NULL, sizeof(code) + TitleLen + TextLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (baseAddr)
    
        code.lpCaption = DWORD_PTR(baseAddr) + sizeof(code);
        code.lpText = code.lpCaption + TitleLen;
        code.offset_MessageBoxW = LONG_PTR(lpMessageBox) - (LONG_PTR(baseAddr) + offsetof(myThreadOpCodes, xor_EAX));

        if (WriteProcessMemory(hProc, baseAddr, &code, sizeof(code) + TitleLen + TextLen, NULL))
        
            DWORD oldProtection;
            if (VirtualProtectEx(hProc, baseAddr, sizeof(code), PAGE_EXECUTE, &oldProtection))
            
                FlushInstructionCache(hProc, baseAddr, sizeof(code));

                hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)baseAddr, NULL, 0, NULL);
                if (hThread)
                
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                
            
        

        VirtualFreeEx(hProc, baseAddr, 0, MEM_RELEASE);
    

    CloseHandle(hProc);

    return 0;

话虽如此,将整个函数注入另一个进程的一种更简单的方法是在 DLL 中实现该函数,这样您就可以正常为该函数编写代码,然后将一个对 LoadLibrary() 的简单调用注入目标进程加载DLL,然后DLL可以根据需要调用该函数。但是您似乎想避开这条路线,因为您的问题是关于避开LoadLibrary()

【讨论】:

实际上不得不这样做,我发现在汇编中直接写出来并注入它要简单得多。我的代码看起来不像你的,因为我使用的是一个实际的 .asm 文件,但它的想法是一样的。 同样的想法。但是,您可以将原始 ASM 字节编码到内存缓冲区中,然后将该缓冲区复制到远程进程。但是对于这个讨论来说重要的是在该 ASM 代码中使用指向 MessageBoxW 的字符串参数的指针。 非常感谢您的回复和解释。您提出了一些很好的观点,既然我理解了这些更改,我肯定会尝试实施这些更改。 而是对原始 ASM 字节进行编码(这很不舒服)更好地在 asm 文件中编写 shell 代码。为了计算函数的大小,最好将其放在单独的代码段中,并在段名中使用 $ 来设置段顺序。还有关于 ASLR - 内核尝试在所有进程中加载​​相同地址的 dll。它是随机的,但相同,如果只是没有占用 dll 的内存。在最新的 Windows 内核中,在特殊内存范围内加载 dll,其中在起始地址设置为 0 时未分配虚拟内存。user32 不在位的可能性非常低。这与 ASLR 无关【参考方案2】:

使用这种方法,不需要汇编,但您必须小心地将所有必要的函数、字符串等添加到数据结构中。

(我没有添加任何错误检查和清理。)

#include <windows.h>

#ifndef __MINGW32__
#define CODE_SEG(seg) __declspec(code_seg(seg))
#else
#define CODE_SEG(seg) __attribute__((section(seg)))
#endif

typedef int WINAPI func_MessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT);

struct data

  func_MessageBoxW *fMessageBoxW;
  wchar_t boxTitle[11];
  wchar_t message[7];
;

static CODE_SEG(".text$1") DWORD WINAPI inj(LPVOID para)

  struct data *data = para;
  data->fMessageBoxW(NULL, data->message, data->boxTitle, MB_OK);
  return 0;


CODE_SEG(".text$2") int main(int argc, char **argv)

  HANDLE hProc;
  LPVOID baseAddr;
  size_t funcSize, fullSize;
  int pid;
  struct data data;
  HMODULE user32;

  if (argc < 2)
    return 1;

  user32 = LoadLibrary("user32.dll");
  data.fMessageBoxW = (func_MessageBoxW*)GetProcAddress(user32, "MessageBoxW");
  wcscpy(data.boxTitle, L"testing...");
  wcscpy(data.message, L"hello?");

  pid = atoi(argv[1]);
  funcSize = (size_t)&main - (size_t)&inj;
  fullSize = funcSize + sizeof(data);
  hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
  baseAddr = VirtualAllocEx(hProc, NULL, fullSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  WriteProcessMemory(hProc, baseAddr, &data, sizeof(data), NULL);
  WriteProcessMemory(hProc, (void*)((size_t)baseAddr + sizeof(data)), &inj, funcSize, NULL);
  CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)((size_t)baseAddr + sizeof(data)), baseAddr, 0, NULL);
  CloseHandle(hProc);

  return 0;

【讨论】:

以上是关于是否可以在不使用 LoadLibrary 的情况下将函数“复制”到另一个进程中并在线程中执行它?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在不使用ajax请求的情况下检查用户是否在线?

是否可能 HTML 下载属性可以在不使用 javascript 的情况下下载多个文件?

是否可以在不使用malloc的情况下进行内存泄漏?

是否可以在不渲染 HTML 的情况下使用 React?

是否可以在不删除数据库的情况下加载学说夹具

是否可以在不停止 iPod 音乐的情况下播放声音?