XuYangYu233
文章10
标签5
分类0
远程线程注入尝试

远程线程注入尝试

新技术学习

1. 原理

需求

在一些情况中需要在指定的进程空间中添加入自己的代码,而此时由于需要添加的代码较多,使用shellcode不太方便。这时就可以使用远程线程注入的技术一次性添加入较为复杂的代码。

重要函数

  • CreateRemoteThread
HANDLE 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
);

核心函数之一,用于在指定进程中创建新线程,具体用法与CreateThread函数大致相同,多加了一个进程句柄的参数用于确定进程

  • LoadLibraryA
HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);

核心函数之一,用于加载dll到当前进程空间中。其特殊点在于其位于kernel32.dll中,故大多数进程中因为优先加载kernel32的缘故,地址相同。此外,其返回值为模块句柄,创建新线程时线程退出码与句柄的格式相近,可以轻易转换

  • VirtualAllocEx & WriteProcessMemory

这两个函数用于在目标进程中申请一小块区域存放待注入dll的名称,由于CreateRemoteThread时其线程参数需位于目标进程中,故需要将dll名称放入目标进程

  • WaitForSingleObject & GetExitCodeThread

用于等待线程结束并获取线程退出码,以此来判断注入是否成功

  • GetModuleHandle & GetProcAddress

用于获取kernel32和LoadLibraryA的地址,防止出现LoadLibraryA地址错误的情况

2. 实操

待注入的程序代码:

#include <iostream>
#include <Windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter) {
    for (int i = 0; i < 10; i++) {
        Sleep(500);
        printf("Test++\n");
    }

    return 0;
}

int main() {
    HANDLE hThread;
    hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    CloseHandle(hThread);
    getchar();

    return 0;
}

随便写的程序,注意到其中确实有创建线程

注入程序的代码:

#include <iostream>
#include <Windows.h>

BOOL LoadDll(DWORD dwProcessID, char* szDllPathName) {
    BOOL     bRet;
    DWORD     dwLength;
    DWORD     dwLoadAddr;
    DWORD     dwThreadID;
    HANDLE     hProcess;
    HANDLE     hThread;
    LPVOID     lpAllocAddr;
    HMODULE     hModule;

    //获取进程句柄
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
    if (hProcess == NULL) {
        printf("OpenProcess 失败:%d\n", GetLastError());
        return FALSE;
    }

    //计算Dll路径名长度,并且要加上0结尾的长度
    dwLength = strlen(szDllPathName) + 1;

    //在目标进程分配内存
    lpAllocAddr = VirtualAllocEx(hProcess, NULL, dwLength, MEM_COMMIT, PAGE_READWRITE);
    if (lpAllocAddr == NULL) {
        printf("VirtualAllocEx 失败:%d\n", GetLastError());
        CloseHandle(hProcess);
        return FALSE;
    }

    //拷贝DLL路径名到目标进程的内存
    bRet = WriteProcessMemory(hProcess, lpAllocAddr, szDllPathName, dwLength, NULL);
    if (!bRet) {
        printf("WriteProcessMemory 失败:%d\n", GetLastError());
        CloseHandle(hProcess);
        return FALSE;
    }

    WCHAR ker[] = { 'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
    //获取模块地址
    hModule = GetModuleHandle((LPCWSTR)ker);
    if (!hModule) {
        printf("GetModuleHandle 失败:%d\n", GetLastError());
        CloseHandle(hProcess);
        return FALSE;
    }

    //获取LoadLibraryA函数地址 
    dwLoadAddr = (DWORD)GetProcAddress(hModule, "LoadLibraryA");
    if (!dwLoadAddr) {
        printf("GetProcAddress 失败:%d\n", GetLastError());
        CloseHandle(hModule);
        CloseHandle(hProcess);
        return FALSE;
    }

    //创建远程线程,加载DLL
    hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwLoadAddr, lpAllocAddr, 0, NULL);
    if (!hThread) {
        printf("GetProcAddress 失败:%d\n", GetLastError());
        CloseHandle(hModule);
        CloseHandle(hProcess);
        return FALSE;
    }

    //关闭句柄 
    CloseHandle(hProcess);
    return true;

}

int main(int argc, char* argv[]) {
    LoadDll(13300, (char*)"D:\\Study\\C++_work\\VS\\DllTry1\\Debug\\DllTry1.dll");
    printf("success\n");
    getchar();
    return 0;
}

其中main中LoadDll的第一个参数为手动输入,具体可以通过任务管理器查看进程对应pid。第二个参数为自行编写的dll,使用了绝对路径,dll的内容就不给了,随便整个dll其实都行

此外,由于此次为实验性质的代码,故未获取退出码,实验效果通过调试软件进行观察。

效果

首先运行目标程序,使用x32dbg查看其模块:

1.png

x32dbg脱离。然后获取pid后运行注入程序,显示成功后再使用x32dbg附加进行查看:

2.png

可见dll注入成功

思考

代码注入成功了,但是要如何调用呢?目前推测有两种方法:一是dll在加载时有一段可自动执行的代码,通过这个来执行dll内部的代码。二是目标程序中可能存在一些其他的漏洞,各种溢出之类的,通过特定漏洞来使eip转到指定位置进行执行

此外,该方法对于windows现在各种内存防护手段效果如何呢,本次实验中未对相关参数进行设置,在该方面有待进一步探究