-
-
[原创]学习PE文件后的第一次实践项目之DLL反射型注入
-
发表于: 3天前 1585
-
原理
普通注入
先看普通型的注入,如下:
普通的dll注入是在目标进程中开辟一处空间,在空间中写入dll文件的名称,再用LoadLibraryA
函数通过查找名称来加载dll,而想在程序里调用LoadLibraryA
的话就得用到CreateRemoteThread
函数,这个函数传递的参数之一就有函数指针,等到CreateRemoteThread
创建新线程之后,就会在新线程中调用这个过度函数,其二的参数就是传给指针的参数
这里插一句,在我多次用x64dbg调试得到的感悟,CreateRemoteThread
这个函数,相当于开辟了一个不知道在哪的空间,在这个地方引用你要传入的函数
反射型注入
反射型dll注入则是将整个DLL文件传到目标进程的空间中,然后通过CreateRemoteThread
调用一段shellcode,将DLL展开,并运行
源码
Injectmain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | #include <stdio.h> #include <windows.h> #include <tlhelp32.h> DWORD RVAtoFileOffset( DWORD rva, PIMAGE_NT_HEADERS pNtHeaders, PIMAGE_SECTION_HEADER pSec); LPVOID GetRemoteReflectLoad( LPVOID pDll, const char * funcName, unsigned char * pBuf); DWORD ProcesstoPid( wchar_t * Processname); BOOL WINAPI MainInject( DWORD dwTargetPid, char * Dllname); int main() { //先定义需要注入的dll与目标进程的名字 //wchar_t szProcName[MAX_PATH] = L"cs2.exe"; wchar_t szProcName[MAX_PATH] = L "pta.exe" ; char Dllname[MAX_PATH] = "D:\\study\\VStudio\\ReflectDll\\x64\\Debug\\DLLIN.dll" ; //查找获得目标进程的id DWORD dwPid = ProcesstoPid(szProcName); //写入dll文件,这里思考一下,传入的参数都是什么呢? //因为在这个函数里,我们要做的是将dll文件写入,并且用其中的函数 //dll中的API将会作为参数出现,所以传入的是进程id和dll函数目录 DWORD result = MainInject(dwPid, Dllname); } DWORD ProcesstoPid( wchar_t * Processname) //查找指定进程的PID(Process ID) { HANDLE hProcessSnap = NULL; DWORD ProcessId = 0; PROCESSENTRY32 pe32 = { 0 }; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //打开进程快照 if (hProcessSnap == ( HANDLE )-1) { printf ( "\n[-] CreateToolhelp32Snapshot() Error: %d" , GetLastError()); return 0; } pe32.dwSize = sizeof (PROCESSENTRY32); if (Process32First(hProcessSnap, &pe32)) //开始枚举进程 { do { if (!wcscmp(Processname, pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID { ProcessId = pe32.th32ProcessID; break ; } } while (Process32Next(hProcessSnap, &pe32)); //继续枚举进程 } else { printf ( "\n[-] Process32First() Error: %d" , GetLastError()); return 0; } if (!ProcessId) printf ( "no find" ); else printf ( "[+] target id is %d" , ProcessId); CloseHandle(hProcessSnap); //关闭系统进程快照的句柄 return ProcessId; } //用于转换RVA->文件偏移地址 //传参说明:第一个是要转换的相对虚拟地址,第二个是Nt头的位置,第三个是节区表头的位置 DWORD RVAtoFileOffset( DWORD rva, PIMAGE_NT_HEADERS pNtHeaders, PIMAGE_SECTION_HEADER pSec) { // 遍历节区表 for ( int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) { // 检查RVA是否在当前节区的范围内 if (rva >= pSec[i].VirtualAddress && rva < pSec[i].VirtualAddress + pSec[i].SizeOfRawData) { // 转换RVA到文件偏移地址 return pSec[i].PointerToRawData + (rva - pSec[i].VirtualAddress); } } // 如果未找到对应的节区,返回无效值 return 0xFFFFFFFF; } //该函数通过分析PE文件头来尝试获取句柄 LPVOID GetRemoteReflectLoad( LPVOID pDll, const char * funcName, unsigned char * pBuf) { //这里因为dll在别的进程里,所以想要看到可以利用前面的pBuf //定位一些相关文件头 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuf; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)pBuf + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNtHeaders + sizeof (IMAGE_NT_HEADERS)); //获取导出表地址及大小,注意这里是RVA DWORD exportDirRVA = pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress; DWORD exportDirSize = pNtHeaders->OptionalHeader.DataDirectory[0].Size; //定位导出表 //这里遇到一个小问题,得到的偏移地址是RVA,但是咱们的文件现在只是磁盘文件,所以需要转换 DWORD exportDirFileOffset = RVAtoFileOffset(( DWORD )exportDirRVA, pNtHeaders, pSec); //转换之后RVA就变成了文件偏移,然后再定位 PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)pBuf + exportDirFileOffset); //解析导出表,这里同理都是RVA DWORD pRNames = pExportDir->AddressOfNames; DWORD pFNames = RVAtoFileOffset(pRNames, pNtHeaders, pSec); DWORD * pNames = ( DWORD *)(( BYTE *)pBuf + pFNames); DWORD pRFunctions = pExportDir->AddressOfFunctions; DWORD pFFunctions = RVAtoFileOffset(pRFunctions, pNtHeaders, pSec); DWORD * pFunctions = ( DWORD *)(( BYTE *)pBuf + pFFunctions); WORD pRNameOrdinals = pExportDir->AddressOfNameOrdinals; WORD pFNameOrdinals = RVAtoFileOffset(pRNameOrdinals, pNtHeaders, pSec); WORD * pNameOrdinals = ( WORD *)(( BYTE *)pBuf + pFFunctions); // 遍历查找目标函数 DWORD funcRVA = 0; for ( DWORD i = 0; i < pExportDir->NumberOfNames; i++) { DWORD functionNameRVA = pNames[i]; DWORD functionNameFileOffset = RVAtoFileOffset(functionNameRVA, pNtHeaders, pSec); const char * pName = ( char *)(( BYTE *)pBuf + functionNameFileOffset); if ( strcmp (pName, funcName) == 0) { funcRVA = pFunctions[i]; break ; } } if (funcRVA == 0) { printf ( "\n[-] Function %s not found." , funcName); return NULL; } DWORD fileOffset = RVAtoFileOffset(funcRVA, pNtHeaders, pSec);; DWORD * pfileOffset = ( DWORD *)(( BYTE *)pBuf + fileOffset); if (fileOffset == 0) { printf ( "\n[-] Failed to convert RVA to file offset." ); return NULL; } LPVOID remoteFuncAddr = ( LPBYTE )pDll + fileOffset; return remoteFuncAddr; } BOOL WINAPI MainInject( DWORD dwTargetPid, char * Dllname) { //与普通dll注入一样,首先要做的是获取句柄 HANDLE hProc = NULL; hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); if (!hProc) { printf ( "\n[-] OpenProcess Failed." ); DWORD dwError = GetLastError(); printf ( "\n[-] OpenProcess failed. Error code: %d\n" , dwError); return FALSE; } //有了句柄就可以创建空间然后写入了,这里的写入我参考的是PE加载器中 //ReadFileA与创建空间的方法,其中相当于是在目标进程空间中创造一个"类磁盘"空间 HANDLE hFile = CreateFileA(Dllname, GENERIC_READ, //读取权限 FILE_SHARE_READ | FILE_SHARE_WRITE, //允许其他进程读取文件|允许其他进程写入文件 NULL, //不需要特定的安全性 OPEN_EXISTING, //不需要特定的安全性 FILE_ATTRIBUTE_NORMAL, //如果文件存在,则打开文件。如果文件不存在,操作会失败 NULL //普通文件,没有特殊属性。 ); if (hFile == INVALID_HANDLE_VALUE) { printf ( "\n[-] CreateFileA failed." ); return FALSE; } DWORD FileSize = GetFileSize(hFile, NULL); LPDWORD SizeToRead = 0; //本地暂存 unsigned char * pBuf = new unsigned char [FileSize]; ZeroMemory(pBuf, FileSize); int result = ReadFile(hFile, pBuf, FileSize, SizeToRead, NULL); //读取文件放在开辟的空间里,pBuf为空间句柄 if (result == 0) { printf ( "\n[-] 文件读取失败" ); return FALSE; } //对接下来开辟的空间进行计算大小 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(( BYTE *)pBuf + pDos->e_lfanew); PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNt + sizeof (IMAGE_NT_HEADERS)); DWORD ImageSize = pNt->OptionalHeader.SizeOfImage; //开辟目标进程中的"类磁盘"空间,大小为前文的FileSize //这里注意申请的地址权限要是可执行的(PAGE_EXECUTE_READWRITE) //这里创建的时候一下子创两个 //刚开始没发现,才发现,pAlloc应该+的是FileSize哎呀麻烦了我想想 ULONG_PTR TotalSize = ImageSize + ImageSize; printf ( "\n[+] FileSize : %p" , FileSize); printf ( "\n[+] ImageSize: %p" , ImageSize); printf ( "\n[+] TotalSize: %p" , TotalSize); //这里遇到一个问题是,传入的TotaSize大小不够后面节区表的第一个表,好奇怪所以需要修正TotalSize大小 LPVOID pDll = VirtualAllocEx(hProc, NULL, TotalSize + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pDll == NULL) { printf ( "\n[-] 内存分配失败, 错误代码: %d" , GetLastError()); return FALSE; } // 清零目标进程内存 SIZE_T sizeToZero = TotalSize; // 需要清零的字节数 BYTE * zeroBuffer = ( BYTE *) calloc (sizeToZero, 1); // 创建一个全零的缓冲区 if (zeroBuffer == NULL) { printf ( "Failed to allocate zero buffer.\n" ); return -1; } // 写入全零到目标内存 if (!WriteProcessMemory(hProc, pDll, zeroBuffer, sizeToZero, NULL)) { DWORD errorCode = GetLastError(); printf ( "WriteProcessMemory failed with error code: %lu\n" , errorCode); } // 释放缓冲区 free (zeroBuffer); //将dll文件写入进去 if (!WriteProcessMemory(hProc, pDll, pBuf, FileSize, NULL)) { return FALSE; } //接下来的活是找dll文件中相当于loadlibrary函数的自写函数Reflectload //注意前文开辟的"类磁盘"空间中的pDll // 假设 ReflectLoader 是目标函数名 const char * reflectFuncName = "ReflectLoader" ; // 获取 ReflectLoader 在目标进程内存中的地址 LPVOID pReflectLoader = GetRemoteReflectLoad(pDll, reflectFuncName, pBuf); if (!pReflectLoader) { printf ( "[-] Failed to find ReflectLoader.\n" ); VirtualFreeEx(hProc, pDll, 0, MEM_RELEASE); return FALSE; } LPVOID pAlloc = ( LPVOID )(( ULONG_PTR )pDll + ImageSize); PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( ULONG_PTR )pAlloc + pDos->e_lfanew + sizeof (IMAGE_NT_HEADERS)); PIMAGE_SECTION_HEADER pSect = (PIMAGE_SECTION_HEADER)(( ULONG_PTR )pDll + pDos->e_lfanew + sizeof (IMAGE_NT_HEADERS)); PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(( ULONG_PTR )pNt->OptionalHeader.DataDirectory[5].VirtualAddress + ( ULONG_PTR )pAlloc); printf ( "\n[+] Dll磁盘起始地址:%p" , pDll); printf ( "\n[+] Dll磁盘终止地址:%p" , ( ULONG_PTR )pDll + FileSize); printf ( "\n[+] Dll内存起始地址:%p" , pAlloc); printf ( "\n[+] Dll整体结束地址:%p" , ( ULONG_PTR )pAlloc + ImageSize); printf ( "\n[+] 磁盘中Nt表头地址:%p" , ( ULONG_PTR )pDll + pDos->e_lfanew); printf ( "\n[+] 内存中Nt表头地址:%p" , ( ULONG_PTR )pAlloc + pDos->e_lfanew); printf ( "\n[+] 磁盘中Sec表头地址:%p" , pSect); printf ( "\n[+] 内存中Sec表头地址:%p" , pSec); printf ( "\n[+] 重定位表基址:%p" , pBaseReloc); printf ( "\n[+] 重定位表RVA:%p" , ( ULONG_PTR )pNt->OptionalHeader.DataDirectory[5].VirtualAddress); DWORD Relco = RVAtoFileOffset(( DWORD )pNt->OptionalHeader.DataDirectory[5].VirtualAddress, pNt, pSection); printf ( "\n[+] 重定位表文件偏移:%p" , ( ULONG_PTR )pDll + Relco); //DWORD SecNum = pNt->FileHeader.NumberOfSections; //for (int i = 0; i < SecNum; i++) { // if (pSection->SizeOfRawData == 0 || pSection->PointerToRawData == 0) { // pSection++; // continue; // } // char* chSrcMem = (char*)((ULONG_PTR)pDll + (DWORD)(pSection->PointerToRawData)); // char* chDestMem = (char*)((ULONG_PTR)pAlloc + (DWORD)(pSection->PointerToRawData)); // printf("\n[+] 磁盘中第%d个节区表对应文件地址:%p",i, chSrcMem); // printf("\n[+] 内存中第%d个节区表对应文件地址:%p",i, chDestMem); // pSection++; //} printf ( "\n[+] 函数 address:%p" , pReflectLoader); // 调用 ReflectLoader 函数 HANDLE hThread = CreateRemoteThread( hProc, // 目标进程句柄 NULL, // 默认安全属性 0, // 默认堆栈大小 (LPTHREAD_START_ROUTINE)pReflectLoader, // ReflectLoader 地址 pDll, // 参数:DLL 的基址 0, // 默认创建标志 NULL // 不需要线程 ID ); if (!hThread) { printf ( "\n[-] CreateRemoteThread failed: %d" , GetLastError()); VirtualFreeEx(hProc, pDll, 0, MEM_RELEASE); return FALSE; } printf ( "\n[+] ReflectLoader executed successfully." ); // 等待线程执行完成 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); return TRUE; } |
获取目标进程Id
首先查找目标进程的Id,函数的实现不多说了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | DWORD ProcesstoPid( wchar_t * Processname) //查找指定进程的PID(Process ID) { HANDLE hProcessSnap = NULL; DWORD ProcessId = 0; PROCESSENTRY32 pe32 = { 0 }; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //打开进程快照 if (hProcessSnap == ( HANDLE )-1) { printf ( "\n[-] CreateToolhelp32Snapshot() Error: %d" , GetLastError()); return 0; } pe32.dwSize = sizeof (PROCESSENTRY32); if (Process32First(hProcessSnap, &pe32)) //开始枚举进程 { do { if (!wcscmp(Processname, pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID { ProcessId = pe32.th32ProcessID; break ; } } while (Process32Next(hProcessSnap, &pe32)); //继续枚举进程 } else { printf ( "\n[-] Process32First() Error: %d" , GetLastError()); return 0; } if (!ProcessId) printf ( "\nno find" ); else printf ( "[+] target id is %d" , ProcessId); CloseHandle(hProcessSnap); //关闭系统进程快照的句柄 return ProcessId; } |
然后就是注入的程序,思路是先开辟空间,写入DLL文件,然后找到shellcode,最后用CreatERemoteThread函数进入目标进程中
获取目标进程句柄
1 2 3 4 5 6 7 8 9 10 11 | //与普通dll注入一样,首先要做的是获取句柄 HANDLE hProc = NULL; hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); if (!hProc) { printf ( "\n[-] OpenProcess Failed." ); DWORD dwError = GetLastError(); printf ( "\n[-] OpenProcess failed. Error code: %d\n" , dwError); return FALSE; } |
接下来是写入DLL,这里我选择,先在本地读取磁盘文件,然后通过WriteProcessMemory实现远程写入
读取本地磁盘文件在本地进程中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //有了句柄就可以创建空间然后写入了,这里的写入我参考的是PE加载器中 //ReadFileA与创建空间的方法,其中相当于是在目标进程空间中创造一个"类磁盘"空间 HANDLE hFile = CreateFileA(Dllname, GENERIC_READ, //读取权限 FILE_SHARE_READ | FILE_SHARE_WRITE, //允许其他进程读取文件|允许其他进程写入文件 NULL, //不需要特定的安全性 OPEN_EXISTING, //不需要特定的安全性 FILE_ATTRIBUTE_NORMAL, //如果文件存在,则打开文件。如果文件不存在,操作会失败 NULL //普通文件,没有特殊属性。 ); if (hFile == INVALID_HANDLE_VALUE) { printf ( "\n[-] CreateFileA failed." ); return FALSE; } DWORD FileSize = GetFileSize(hFile, NULL); LPDWORD SizeToRead = 0; //本地暂存 unsigned char * pBuf = new unsigned char [FileSize]; ZeroMemory(pBuf, FileSize); int result = ReadFile(hFile, pBuf, FileSize, SizeToRead, NULL); //读取文件放在开辟的空间里,pBuf为空间句柄 if (result == 0) { printf ( "\n[-] 文件读取失败" ); return FALSE; } |
这里是对本地文件的读取,并且这里的pBuf指向的是本地文件读取以后的基地址
注入磁盘dll文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | //对接下来开辟的空间进行计算大小 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(( BYTE *)pBuf + pDos->e_lfanew); PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNt + sizeof (IMAGE_NT_HEADERS)); DWORD ImageSize = pNt->OptionalHeader.SizeOfImage; //开辟目标进程中的"类磁盘"空间,大小为前文的FileSize //这里注意申请的地址权限要是可执行的(PAGE_EXECUTE_READWRITE) //这里创建的时候一下子创两个 //刚开始没发现,才发现,pAlloc应该+的是FileSize哎呀麻烦了我想想 ULONG_PTR TotalSize = ImageSize + ImageSize; printf ( "\n[+] FileSize : %p" , FileSize); printf ( "\n[+] ImageSize: %p" , ImageSize); printf ( "\n[+] TotalSize: %p" , TotalSize); //这里遇到一个问题是,传入的TotaSize大小不够后面节区表的第一个表,好奇怪所以需要修正TotalSize大小 LPVOID = VirtualAllocEx(hProc, NULL, TotalSize+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pDll == NULL) { printf ( "\n[-] 内存分配失败, 错误代码: %d" , GetLastError()); return FALSE; } // 清零目标进程内存 SIZE_T sizeToZero = TotalSize; // 需要清零的字节数 BYTE * zeroBuffer = ( BYTE *) calloc (sizeToZero, 1); // 创建一个全零的缓冲区 if (zeroBuffer == NULL) { printf ( "Failed to allocate zero buffer.\n" ); return -1; } // 写入全零到目标内存 if (!WriteProcessMemory(hProc, pDll, zeroBuffer, sizeToZero, NULL)) { DWORD errorCode = GetLastError(); printf ( "WriteProcessMemory failed with error code: %lu\n" , errorCode); } // 释放缓冲区 free (zeroBuffer); //将dll文件写入进去 if (!WriteProcessMemory(hProc, pDll, pBuf, FileSize, NULL)) { return FALSE; } |
这里做到的便是写入dll但是注意,写入的也是磁盘文件,因为后续要实现对dll文件的展开,映射成内存文件
那就需要两个空间,一个空间是注入原封不动的磁盘文件,另一个是展开需要的空间,但是CreateRemoteThread只可以传入一个参数,那么就不考虑两个空间两个指针了,(其实也可以传入存放给内存空间开辟的地址指针,然后再shellcode里面向回遍历,直到检测到“5D 4A”PE文件头,就获取了磁盘文件的空间指针),一个指针的话,开辟空间的时候就开辟两个ImageSize的空间,然后pDll+ImageSize就是第二个指针了
获取shellcode在文件中的文件偏移地址
1 2 3 4 5 6 7 8 9 10 11 12 | //接下来的活是找dll文件中相当于loadlibrary函数的自写函数Reflectload //注意前文开辟的"类磁盘"空间中的pDll // 假设 ReflectLoader 是目标函数名 const char * reflectFuncName = "ReflectLoader" ; // 获取 ReflectLoader 在目标进程内存中的地址 LPVOID pReflectLoader = GetRemoteReflectLoad(pDll, reflectFuncName, pBuf); if (!pReflectLoader) { printf ( "[-] Failed to find ReflectLoader.\n" ); VirtualFreeEx(hProc, pDll, 0, MEM_RELEASE); return FALSE; } |
注意这里获取的是文件偏移地址,因为此时的dll还没有展开(当然你也可以选择,所有的东西现在本地展开一次,然后写入展开后的程序,这样子就可以正常使用函数RVA,后续只需要修复重定位和导入函数了),所以找的不是RVA,而是文件偏移
GetRemoteReflectLoad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | //该函数通过分析PE文件头来尝试获取句柄 LPVOID GetRemoteReflectLoad( LPVOID pDll, const char * funcName, unsigned char * pBuf) { //这里因为dll在别的进程里,所以想要看到可以利用前面的pBuf //定位一些相关文件头 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuf; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)pBuf + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNtHeaders + sizeof (IMAGE_NT_HEADERS)); //获取导出表地址及大小,注意这里是RVA DWORD exportDirRVA = pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress; DWORD exportDirSize = pNtHeaders->OptionalHeader.DataDirectory[0].Size; //定位导出表 //这里遇到一个小问题,得到的偏移地址是RVA,但是咱们的文件现在只是磁盘文件,所以需要转换 DWORD exportDirFileOffset = RVAtoFileOffset(( DWORD )exportDirRVA, pNtHeaders, pSec); //转换之后RVA就变成了文件偏移,然后再定位 PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)pBuf + exportDirFileOffset); //解析导出表,这里同理都是RVA DWORD pRNames = pExportDir->AddressOfNames; DWORD pFNames = RVAtoFileOffset(pRNames, pNtHeaders, pSec); DWORD * pNames = ( DWORD *)(( BYTE *)pBuf + pFNames); DWORD pRFunctions = pExportDir->AddressOfFunctions; DWORD pFFunctions = RVAtoFileOffset(pRFunctions, pNtHeaders, pSec); DWORD * pFunctions = ( DWORD *)(( BYTE *)pBuf + pFFunctions); WORD pRNameOrdinals = pExportDir->AddressOfNameOrdinals; WORD pFNameOrdinals = RVAtoFileOffset(pRNameOrdinals, pNtHeaders, pSec); WORD * pNameOrdinals = ( WORD *)(( BYTE *)pBuf + pFFunctions); // 遍历查找目标函数 DWORD funcRVA = 0; for ( DWORD i = 0; i < pExportDir->NumberOfNames; i++) { DWORD functionNameRVA = pNames[i]; DWORD functionNameFileOffset = RVAtoFileOffset(functionNameRVA, pNtHeaders, pSec); const char * pName = ( char *)(( BYTE *)pBuf + functionNameFileOffset); if ( strcmp (pName, funcName) == 0) { funcRVA = pFunctions[i]; break ; } } if (funcRVA == 0) { printf ( "\n[-] Function %s not found." , funcName); return NULL; } DWORD fileOffset = RVAtoFileOffset(funcRVA, pNtHeaders, pSec);; DWORD * pfileOffset = ( DWORD *)(( BYTE *)pBuf + fileOffset); if (fileOffset == 0) { printf ( "\n[-] Failed to convert RVA to file offset." ); return NULL; } LPVOID remoteFuncAddr = ( LPBYTE )pDll + fileOffset; return remoteFuncAddr; } |
这个函数的实现,是通过查找dll文件中的导出函数,遍历所有导出函数找到目标函数的RVA,然后需要对RVA转换为文件偏移地址,不过要注意,在文件没有展开之前导出表,以及导出表里的信息都是RVA,需要转换为文件偏移地址,那么写一个RVA-文件偏移就非常方便了
RVAtoFileOffset
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //用于转换RVA->文件偏移地址 //传参说明:第一个是要转换的相对虚拟地址,第二个是Nt头的位置,第三个是节区表头的位置 DWORD RVAtoFileOffset( DWORD rva, PIMAGE_NT_HEADERS pNtHeaders, PIMAGE_SECTION_HEADER pSec) { // 遍历节区表 for ( int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) { // 检查RVA是否在当前节区的范围内 if (rva >= pSec[i].VirtualAddress && rva < pSec[i].VirtualAddress + pSec[i].SizeOfRawData) { // 转换RVA到文件偏移地址 return pSec[i].PointerToRawData + (rva - pSec[i].VirtualAddress); } } // 如果未找到对应的节区,返回无效值 return 0xFFFFFFFF; } |
这个的实现是,先查找处于哪个节区,然后通过节区表的首地址经过RVA-虚拟首地址+节区初始地址的真实地址
CreateRemoteThread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include<stdio.h> #include <stdbool.h> #include <winternl.h> extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll); int CompareDllName( wchar_t * dllName, wchar_t * targetDllName); wchar_t * ExtractDllName( const wchar_t * fullDllName); void my_wctomb( char * dest, const wchar_t * src); int CompareStrings( const char * str1, const char * str2); // DLL入口点函数 BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { // 弹窗代码 MessageBox(NULL, L "哇塞!!你成功啦!!!" , L "注入程序检测中..." , MB_YESNO | MB_ICONASTERISK); char processName[MAX_PATH] = { 0 }; // 存储进程路径的缓冲区 // 获取当前进程的可执行文件路径 DWORD length = GetModuleFileNameA(NULL, processName, MAX_PATH); MessageBoxA(NULL, processName, "当前进程路径: " , MB_YESNO | MB_ICONASTERISK); Sleep(99999999); return TRUE; } // 自定义的宽字符转普通字符的函数 void my_wctomb( char * dest, const wchar_t * src) { while (*src) { if (*src >= L 'A' && *src <= L 'Z' ) { *dest = ( char )(*src + (L 'a' - L 'A' )); // 转换大写字符为小写 } else if (*src >= L 'a' && *src <= L 'z' ) { *dest = ( char )*src; // 保留小写字符 } else if (*src >= L '0' && *src <= L '9' ) { *dest = ( char )*src; // 保留数字字符 } else { *dest = '?' ; // 对于其他字符,可以选择替代字符,例如 '?' } dest++; src++; } *dest = '\0' ; // 确保目标字符串以 null 结尾 } int CompareStrings( const char * str1, const char * str2) { while (*str2 != '\0' ) { if (*str1 != *str2) { return 0; // 返回差值 } str1++; str2++; } return 1; // 两个字符串完全匹配时返回0 } // 比较两个 DLL 名称(大小写不敏感) int CompareDllName( wchar_t * dllName, wchar_t * targetDllName) { size_t i = 0; while (dllName[i] != L '\0' || targetDllName[i] != L '\0' ) { // 将两个字符都转换为小写进行比较 wchar_t ch1 = (dllName[i] >= L 'A' && dllName[i] <= L 'Z' ) ? dllName[i] + (L 'a' - L 'A' ) : dllName[i]; wchar_t ch2 = (targetDllName[i] >= L 'A' && targetDllName[i] <= L 'Z' ) ? targetDllName[i] + (L 'a' - L 'A' ) : targetDllName[i]; // 如果字符不同,则返回比较结果 if (ch1 != ch2) { return 0; } i++; } // 如果字符串都没有结束且匹配到最后,返回 0 表示完全相等 return 1; // 如果两个字符串完全相同,返回 0 } // 提取 DLL 名称的函数 wchar_t * ExtractDllName( const wchar_t * fullDllName) { wchar_t * fileName = NULL; wchar_t * temp = ( wchar_t *)fullDllName; // 遍历并找到最后一个 '\\',获取文件名部分 while (*temp) { if (*temp == L '\\' ) { fileName = temp + 1; // 更新文件名的位置 } temp++; } // 如果没有找到 '\\',则认为整个字符串就是文件名 if (!fileName) { fileName = ( wchar_t *)fullDllName; } return fileName; } extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll) { //获取磁盘文件的DOS头和NT头 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)pDll; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); //赋值NT头 //给内存分配空间,并对pAlloc进行初始化 DWORD ImageSize = pNTheader->OptionalHeader.SizeOfImage; //内存空间大小 PBYTE pAlloc = ( PBYTE )(( ULONG_PTR )pDll + ImageSize); if (pAlloc == NULL) return FALSE; if (pDOSheader->e_magic != IMAGE_DOS_SIGNATURE || pNTheader->Signature != IMAGE_NT_SIGNATURE) return FALSE; // 无效的头,直接退出 // 手动实现 CopyMemory 的功能 BYTE * dst = ( BYTE *)pAlloc; // 目标内存地址 BYTE * src = ( BYTE *)pDll; // 源内存地址 size_t size = pNTheader->OptionalHeader.SizeOfHeaders; // 需要复制的字节数 for ( size_t i = 0; i < size; ++i) { dst[i] = src[i]; // 逐字节复制 } //复制节区表 PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); DWORD SecNum = pNTheader->FileHeader.NumberOfSections; for ( int i = 0; i < SecNum; i++) { if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) { pSec++; continue ; } char * chSrcMem = ( char *)(( ULONG_PTR )pDll + ( DWORD )(pSec->PointerToRawData)); char * chDestMem = ( char *)(( ULONG_PTR )pAlloc + ( DWORD )(pSec->VirtualAddress)); DWORD dwSizeOfRawData = pSec->SizeOfRawData; DWORD dwVirtualSize = pSec->Misc.VirtualSize; for ( DWORD j = 0; j < dwSizeOfRawData; j++) { chDestMem[j] = chSrcMem[j]; } if (dwVirtualSize > dwSizeOfRawData) { char * start = chDestMem + dwSizeOfRawData; char * end = chDestMem + dwVirtualSize; while (start < end) { *start++ = 0; } } pSec++; } //杜哥部分登场 //开始检测并加载重定位表 PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(( ULONG_PTR )pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress + ( ULONG_PTR )pAlloc); //重定位表指针通过NT结构的数据目录表查找到位置 int SizeOfBaseReloc = pNTheader->OptionalHeader.DataDirectory[5].Size; //重定位表的大小也在NT结构中 if (pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress != NULL) { do { PWORD TypeOffset = ( WORD *)(( PBYTE )pBaseReloc + 8); //跳过前两个元素(不过在有的结构声明中TypeOffset不属于该结构 int num = (pBaseReloc->SizeOfBlock - 8) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的 for ( int i = 0; i < num; i++) { WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型) WORD offset = TypeOffset[i] & 0x0FFF; //去掉类型(前四字节) int differ = 0; if (type == 3) { differ = *(( ULONG_PTR *)(offset + pBaseReloc->VirtualAddress + pAlloc)) - pNTheader->OptionalHeader.ImageBase; ULONG_PTR p = ( ULONG_PTR )pAlloc + differ; char * tagetaddr = ( char *)( ULONG_PTR )pAlloc + offset + pBaseReloc->VirtualAddress; char * fromeaddr = ( char *)p; for ( int c = 0;c < 4;c++) { tagetaddr[c] = fromeaddr[c]; } } } SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数 pBaseReloc = (PIMAGE_BASE_RELOCATION)(( PBYTE )pBaseReloc + pBaseReloc->SizeOfBlock); //相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构 } while (SizeOfBaseReloc); } //导入表的处理 //因为导入表还没有修复,所以里面的API大部分都是无法使用的,可避免的我都去避免了 // 但是是在无法避免的如,LoadLibrary函数无法找到,就需要通过 // TEB和PEB的帮助找到了 //GetProcAddress的地址 typedef FARPROC(WINAPI* GETPROCADDR)( HMODULE , LPCSTR ); //LoadLibrary的地址 typedef HMODULE (WINAPI* LOADLIBRARYA)( LPCSTR ); GETPROCADDR pGetProcAddress = NULL; LOADLIBRARYA pLoadLibraryA = NULL; //得到TEB的地址,通过TEB找到PEB PTEB pTEB = (PTEB)__readgsqword(0x30); PPEB pPEB = pTEB->ProcessEnvironmentBlock; // 获取PEB.Ldr PPEB_LDR_DATA pLdr = pPEB->Ldr; // 遍历模块列表,找到 kernel32.dll PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList; PLIST_ENTRY pCurrentEntry = pListHead->Flink; while (pCurrentEntry && pCurrentEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pEntry && pEntry->FullDllName.Buffer) { wchar_t * dllName = pEntry->FullDllName.Buffer; // 提取 DLL 名称 wchar_t * fileName = ExtractDllName(dllName); // 目标 DLL 名称kernel32.dll wchar_t a[50] = { 0 }; a[0] = L 'k' ; a[1] = L 'e' ; a[2] = L 'r' ; a[3] = L 'n' ; a[4] = L 'e' ; a[5] = L 'l' ; a[6] = L '3' ; a[7] = L '2' ; a[8] = L '.' ; a[9] = L 'd' ; a[10] = L 'l' ; a[11] = L 'l' ; a[12] = L '\0' ; if (CompareDllName(fileName, a)) { //分析PE文件找到导出表 HMODULE hKernel32 = ( HMODULE )pEntry->DllBase; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)hKernel32 + ((PIMAGE_DOS_HEADER)hKernel32)->e_lfanew); // 获取导出表的地址 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)hKernel32 + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的各个信息 DWORD * pFunctionNames = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNames); DWORD * pFunctionAddresses = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfFunctions); WORD * pFunctionOrdinals = ( WORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 遍历导出表,查找 LoadLibraryA char targetName1[50] = "LoadLibraryA" ; targetName1[0] = 'L' ; targetName1[1] = 'o' ; targetName1[2] = 'a' ; targetName1[3] = 'd' ; targetName1[4] = 'L' ; targetName1[5] = 'i' ; targetName1[6] = 'b' ; targetName1[7] = 'r' ; targetName1[8] = 'a' ; targetName1[9] = 'r' ; targetName1[10] = 'y' ; targetName1[11] = 'A' ; targetName1[12] = '\0' ; // 遍历导出表,查找 GetProcAddress char targetName2[50] = { 0 }; targetName2[0] = 'G' ; targetName2[1] = 'e' ; targetName2[2] = 't' ; targetName2[3] = 'P' ; targetName2[4] = 'r' ; targetName2[5] = 'o' ; targetName2[6] = 'c' ; targetName2[7] = 'A' ; targetName2[8] = 'd' ; targetName2[9] = 'd' ; targetName2[10] = 'r' ; targetName2[11] = 'e' ; targetName2[12] = 's' ; targetName2[13] = 's' ; targetName2[14] = '\0' ; int isP = 0; for ( DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) { char * functionName = ( char *)(( BYTE *)hKernel32 + pFunctionNames[i]); if (CompareStrings(functionName, targetName1)) { // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pLoadLibraryA = (LOADLIBRARYA)(( BYTE *)hKernel32 + functionRVA); isP++; } if (CompareStrings(functionName, targetName2)) { // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pGetProcAddress = (GETPROCADDR)(( BYTE *)hKernel32 + functionRVA); isP++; } if (isP == 2) { break ; } } break ; // 找到后退出 } } pCurrentEntry = pCurrentEntry->Flink; } if (!pLoadLibraryA && !pGetProcAddress) { char foece[] = "无法获取 LoadLibraryA 地址,请检查目标进程模块" ; return FALSE; } else { char sucsess[] = "sucsess" ; } PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNTheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAlloc); //这个是IID的指针 if (pImport != NULL) { while (pImport->Name != NULL) { char DLLname[50] = { 0 }; // 定义一个存储 DLL 名称的缓冲区 char * pDLLName = ( char *)(pImport->Name + pAlloc); // 获取 DLL 名称的地址 // 手动将名称拷贝到 DLLname 缓冲区 for ( int i = 0; i < sizeof (DLLname) - 1; i++) { if (pDLLName[i] == '\0' ) // 遇到字符串结束符时停止 break ; DLLname[i] = pDLLName[i]; // 拷贝字符 } DLLname[ sizeof (DLLname) - 1] = '\0' ; // 确保缓冲区以 '\0' 结尾 HMODULE hProcess = pLoadLibraryA(DLLname); //通过名称找句柄 if (!hProcess) { return FALSE; } PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(pImport->OriginalFirstThunk + pAlloc); PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(pImport->FirstThunk + pAlloc); while (( ULONG_PTR )(pINT->u1.AddressOfData) != NULL) { PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pAlloc); //找DLL中函数的名字 if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) //判断如果是序号就是第一种处理方式 { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, ( LPCSTR )(pINT->u1.AddressOfData))); //通过序号来获取地址 } else { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址 } pINT++; pIAT++; } pImport++; } } //最后的操作就是修正程序的入口地址 PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pAlloc + pDOSheader->e_lfanew); FARPROC EOP = (FARPROC)(( LPBYTE )pAlloc + pNT->OptionalHeader.AddressOfEntryPoint); EOP(); return TRUE; } |
然后就用CreateRemoteThread创造新线程,然后就可以插入shellcode了
ReflectLoader
我将一个文件的展开过程分为两部分,一部分是映射文件头和节区段,另一部分是修复重定位表和导入表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include<stdio.h> #include <stdbool.h> #include <winternl.h> extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll); int CompareDllName( wchar_t * dllName, wchar_t * targetDllName); wchar_t * ExtractDllName( const wchar_t * fullDllName); void my_wctomb( char * dest, const wchar_t * src); int CompareStrings( const char * str1, const char * str2); // DLL入口点函数 BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { // 弹窗代码 MessageBox(NULL, L "哇塞!!你成功啦!!!" , L "注入程序检测中..." , MB_YESNO | MB_ICONASTERISK); char processName[MAX_PATH] = { 0 }; // 存储进程路径的缓冲区 // 获取当前进程的可执行文件路径 DWORD length = GetModuleFileNameA(NULL, processName, MAX_PATH); MessageBoxA(NULL, processName, "当前进程路径: " , MB_YESNO | MB_ICONASTERISK); Sleep(99999999); return TRUE; } // 自定义的宽字符转普通字符的函数 void my_wctomb( char * dest, const wchar_t * src) { while (*src) { if (*src >= L 'A' && *src <= L 'Z' ) { *dest = ( char )(*src + (L 'a' - L 'A' )); // 转换大写字符为小写 } else if (*src >= L 'a' && *src <= L 'z' ) { *dest = ( char )*src; // 保留小写字符 } else if (*src >= L '0' && *src <= L '9' ) { *dest = ( char )*src; // 保留数字字符 } else { *dest = '?' ; // 对于其他字符,可以选择替代字符,例如 '?' } dest++; src++; } *dest = '\0' ; // 确保目标字符串以 null 结尾 } int CompareStrings( const char * str1, const char * str2) { while (*str2 != '\0' ) { if (*str1 != *str2) { return 0; // 返回差值 } str1++; str2++; } return 1; // 两个字符串完全匹配时返回0 } // 比较两个 DLL 名称(大小写不敏感) int CompareDllName( wchar_t * dllName, wchar_t * targetDllName) { size_t i = 0; while (dllName[i] != L '\0' || targetDllName[i] != L '\0' ) { // 将两个字符都转换为小写进行比较 wchar_t ch1 = (dllName[i] >= L 'A' && dllName[i] <= L 'Z' ) ? dllName[i] + (L 'a' - L 'A' ) : dllName[i]; wchar_t ch2 = (targetDllName[i] >= L 'A' && targetDllName[i] <= L 'Z' ) ? targetDllName[i] + (L 'a' - L 'A' ) : targetDllName[i]; // 如果字符不同,则返回比较结果 if (ch1 != ch2) { return 0; } i++; } // 如果字符串都没有结束且匹配到最后,返回 0 表示完全相等 return 1; // 如果两个字符串完全相同,返回 0 } // 提取 DLL 名称的函数 wchar_t * ExtractDllName( const wchar_t * fullDllName) { wchar_t * fileName = NULL; wchar_t * temp = ( wchar_t *)fullDllName; // 遍历并找到最后一个 '\\',获取文件名部分 while (*temp) { if (*temp == L '\\' ) { fileName = temp + 1; // 更新文件名的位置 } temp++; } // 如果没有找到 '\\',则认为整个字符串就是文件名 if (!fileName) { fileName = ( wchar_t *)fullDllName; } return fileName; } extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll) { //获取磁盘文件的DOS头和NT头 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)pDll; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); //赋值NT头 //给内存分配空间,并对pAlloc进行初始化 DWORD ImageSize = pNTheader->OptionalHeader.SizeOfImage; //内存空间大小 PBYTE pAlloc = ( PBYTE )(( ULONG_PTR )pDll + ImageSize); if (pAlloc == NULL) return FALSE; if (pDOSheader->e_magic != IMAGE_DOS_SIGNATURE || pNTheader->Signature != IMAGE_NT_SIGNATURE) return FALSE; // 无效的头,直接退出 // 手动实现 CopyMemory 的功能 BYTE * dst = ( BYTE *)pAlloc; // 目标内存地址 BYTE * src = ( BYTE *)pDll; // 源内存地址 size_t size = pNTheader->OptionalHeader.SizeOfHeaders; // 需要复制的字节数 for ( size_t i = 0; i < size; ++i) { dst[i] = src[i]; // 逐字节复制 } //复制节区表 PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); DWORD SecNum = pNTheader->FileHeader.NumberOfSections; for ( int i = 0; i < SecNum; i++) { if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) { pSec++; continue ; } char * chSrcMem = ( char *)(( ULONG_PTR )pDll + ( DWORD )(pSec->PointerToRawData)); char * chDestMem = ( char *)(( ULONG_PTR )pAlloc + ( DWORD )(pSec->VirtualAddress)); DWORD dwSizeOfRawData = pSec->SizeOfRawData; DWORD dwVirtualSize = pSec->Misc.VirtualSize; for ( DWORD j = 0; j < dwSizeOfRawData; j++) { chDestMem[j] = chSrcMem[j]; } if (dwVirtualSize > dwSizeOfRawData) { char * start = chDestMem + dwSizeOfRawData; char * end = chDestMem + dwVirtualSize; while (start < end) { *start++ = 0; } } pSec++; } //杜哥部分登场 //开始检测并加载重定位表 PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(( ULONG_PTR )pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress + ( ULONG_PTR )pAlloc); //重定位表指针通过NT结构的数据目录表查找到位置 int SizeOfBaseReloc = pNTheader->OptionalHeader.DataDirectory[5].Size; //重定位表的大小也在NT结构中 if (pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress != NULL) { do { PWORD TypeOffset = ( WORD *)(( PBYTE )pBaseReloc + 8); //跳过前两个元素(不过在有的结构声明中TypeOffset不属于该结构 int num = (pBaseReloc->SizeOfBlock - 8) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的 for ( int i = 0; i < num; i++) { WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型) WORD offset = TypeOffset[i] & 0x0FFF; //去掉类型(前四字节) int differ = 0; if (type == 3) { differ = *(( ULONG_PTR *)(offset + pBaseReloc->VirtualAddress + pAlloc)) - pNTheader->OptionalHeader.ImageBase; ULONG_PTR p = ( ULONG_PTR )pAlloc + differ; char * tagetaddr = ( char *)( ULONG_PTR )pAlloc + offset + pBaseReloc->VirtualAddress; char * fromeaddr = ( char *)p; for ( int c = 0;c < 4;c++) { tagetaddr[c] = fromeaddr[c]; } } } SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数 pBaseReloc = (PIMAGE_BASE_RELOCATION)(( PBYTE )pBaseReloc + pBaseReloc->SizeOfBlock); //相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构 } while (SizeOfBaseReloc); } //导入表的处理 //因为导入表还没有修复,所以里面的API大部分都是无法使用的,可避免的我都去避免了 // 但是是在无法避免的如,LoadLibrary函数无法找到,就需要通过 // TEB和PEB的帮助找到了 //GetProcAddress的地址 typedef FARPROC(WINAPI* GETPROCADDR)( HMODULE , LPCSTR ); //LoadLibrary的地址 typedef HMODULE (WINAPI* LOADLIBRARYA)( LPCSTR ); GETPROCADDR pGetProcAddress = NULL; LOADLIBRARYA pLoadLibraryA = NULL; //得到TEB的地址,通过TEB找到PEB PTEB pTEB = (PTEB)__readgsqword(0x30); PPEB pPEB = pTEB->ProcessEnvironmentBlock; // 获取PEB.Ldr PPEB_LDR_DATA pLdr = pPEB->Ldr; // 遍历模块列表,找到 kernel32.dll PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList; PLIST_ENTRY pCurrentEntry = pListHead->Flink; while (pCurrentEntry && pCurrentEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pEntry && pEntry->FullDllName.Buffer) { wchar_t * dllName = pEntry->FullDllName.Buffer; // 提取 DLL 名称 wchar_t * fileName = ExtractDllName(dllName); // 目标 DLL 名称kernel32.dll wchar_t a[50] = { 0 }; a[0] = L 'k' ; a[1] = L 'e' ; a[2] = L 'r' ; a[3] = L 'n' ; a[4] = L 'e' ; a[5] = L 'l' ; a[6] = L '3' ; a[7] = L '2' ; a[8] = L '.' ; a[9] = L 'd' ; a[10] = L 'l' ; a[11] = L 'l' ; a[12] = L '\0' ; if (CompareDllName(fileName, a)) { //分析PE文件找到导出表 HMODULE hKernel32 = ( HMODULE )pEntry->DllBase; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)hKernel32 + ((PIMAGE_DOS_HEADER)hKernel32)->e_lfanew); // 获取导出表的地址 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)hKernel32 + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的各个信息 DWORD * pFunctionNames = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNames); DWORD * pFunctionAddresses = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfFunctions); WORD * pFunctionOrdinals = ( WORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 遍历导出表,查找 LoadLibraryA char targetName1[50] = "LoadLibraryA" ; targetName1[0] = 'L' ; targetName1[1] = 'o' ; targetName1[2] = 'a' ; targetName1[3] = 'd' ; targetName1[4] = 'L' ; targetName1[5] = 'i' ; targetName1[6] = 'b' ; targetName1[7] = 'r' ; targetName1[8] = 'a' ; targetName1[9] = 'r' ; targetName1[10] = 'y' ; targetName1[11] = 'A' ; targetName1[12] = '\0' ; // 遍历导出表,查找 GetProcAddress char targetName2[50] = { 0 }; targetName2[0] = 'G' ; targetName2[1] = 'e' ; targetName2[2] = 't' ; targetName2[3] = 'P' ; targetName2[4] = 'r' ; targetName2[5] = 'o' ; targetName2[6] = 'c' ; targetName2[7] = 'A' ; targetName2[8] = 'd' ; targetName2[9] = 'd' ; targetName2[10] = 'r' ; targetName2[11] = 'e' ; targetName2[12] = 's' ; targetName2[13] = 's' ; targetName2[14] = '\0' ; int isP = 0; for ( DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) { char * functionName = ( char *)(( BYTE *)hKernel32 + pFunctionNames[i]); if (CompareStrings(functionName, targetName1)) { // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pLoadLibraryA = (LOADLIBRARYA)(( BYTE *)hKernel32 + functionRVA); isP++; } if (CompareStrings(functionName, targetName2)) { // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pGetProcAddress = (GETPROCADDR)(( BYTE *)hKernel32 + functionRVA); isP++; } if (isP == 2) { break ; } } break ; // 找到后退出 } } pCurrentEntry = pCurrentEntry->Flink; } if (!pLoadLibraryA && !pGetProcAddress) { char foece[] = "无法获取 LoadLibraryA 地址,请检查目标进程模块" ; return FALSE; } else { char sucsess[] = "sucsess" ; } PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNTheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAlloc); //这个是IID的指针 if (pImport != NULL) { while (pImport->Name != NULL) { char DLLname[50] = { 0 }; // 定义一个存储 DLL 名称的缓冲区 char * pDLLName = ( char *)(pImport->Name + pAlloc); // 获取 DLL 名称的地址 // 手动将名称拷贝到 DLLname 缓冲区 for ( int i = 0; i < sizeof (DLLname) - 1; i++) { if (pDLLName[i] == '\0' ) // 遇到字符串结束符时停止 break ; DLLname[i] = pDLLName[i]; // 拷贝字符 } DLLname[ sizeof (DLLname) - 1] = '\0' ; // 确保缓冲区以 '\0' 结尾 HMODULE hProcess = pLoadLibraryA(DLLname); //通过名称找句柄 if (!hProcess) { return FALSE; } PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(pImport->OriginalFirstThunk + pAlloc); PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(pImport->FirstThunk + pAlloc); while (( ULONG_PTR )(pINT->u1.AddressOfData) != NULL) { PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pAlloc); //找DLL中函数的名字 if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) //判断如果是序号就是第一种处理方式 { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, ( LPCSTR )(pINT->u1.AddressOfData))); //通过序号来获取地址 } else { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址 } pINT++; pIAT++; } pImport++; } } //最后的操作就是修正程序的入口地址 PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pAlloc + pDOSheader->e_lfanew); FARPROC EOP = (FARPROC)(( LPBYTE )pAlloc + pNT->OptionalHeader.AddressOfEntryPoint); EOP(); return TRUE; } |
映射对应部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //获取磁盘文件的DOS头和NT头 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)pDll; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); //赋值NT头 //给内存分配空间,并对pAlloc进行初始化 DWORD ImageSize = pNTheader->OptionalHeader.SizeOfImage; //内存空间大小 PBYTE pAlloc = ( PBYTE )(( ULONG_PTR )pDll + ImageSize); if (pAlloc == NULL) return FALSE; if (pDOSheader->e_magic != IMAGE_DOS_SIGNATURE || pNTheader->Signature != IMAGE_NT_SIGNATURE) return FALSE; // 无效的头,直接退出 // 手动实现 CopyMemory 的功能 BYTE * dst = ( BYTE *)pAlloc; // 目标内存地址 BYTE * src = ( BYTE *)pDll; // 源内存地址 size_t size = pNTheader->OptionalHeader.SizeOfHeaders; // 需要复制的字节数 for ( size_t i = 0; i < size; ++i) { dst[i] = src[i]; // 逐字节复制 } //复制节区表 PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); DWORD SecNum = pNTheader->FileHeader.NumberOfSections; for ( int i = 0; i < SecNum; i++) { if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) { pSec++; continue ; } char * chSrcMem = ( char *)(( ULONG_PTR )pDll + ( DWORD )(pSec->PointerToRawData)); char * chDestMem = ( char *)(( ULONG_PTR )pAlloc + ( DWORD )(pSec->VirtualAddress)); DWORD dwSizeOfRawData = pSec->SizeOfRawData; DWORD dwVirtualSize = pSec->Misc.VirtualSize; for ( DWORD j = 0; j < dwSizeOfRawData; j++) { chDestMem[j] = chSrcMem[j]; } if (dwVirtualSize > dwSizeOfRawData) { char * start = chDestMem + dwSizeOfRawData; char * end = chDestMem + dwVirtualSize; while (start < end) { *start++ = 0; } } pSec++; } |
这部分相对简单,而且这个部分在里面和外面都可以实现,赋值文件头,然后根据节区头里面的文件偏移和虚拟地址偏移来映射就好
注意这里结束以后,节区表就修复完成了,所以可以使用自己封装的函数了
重定位表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //开始检测并加载重定位表 PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(( ULONG_PTR )pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress + ( ULONG_PTR )pAlloc); //重定位表指针通过NT结构的数据目录表查找到位置 int SizeOfBaseReloc = pNTheader->OptionalHeader.DataDirectory[5].Size; //重定位表的大小也在NT结构中 if (pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress != NULL) { do { PWORD TypeOffset = ( WORD *)(( PBYTE )pBaseReloc + 8); //跳过前两个元素(不过在有的结构声明中TypeOffset不属于该结构 int num = (pBaseReloc->SizeOfBlock - 8) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的 for ( int i = 0; i < num; i++) { WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型) WORD offset = TypeOffset[i] & 0x0FFF; //去掉类型(前四字节) int differ = 0; if (type == 3) { differ = *(( ULONG_PTR *)(offset + pBaseReloc->VirtualAddress + pAlloc)) - pNTheader->OptionalHeader.ImageBase; ULONG_PTR p = ( ULONG_PTR )pAlloc + differ; char * tagetaddr = ( char *)( ULONG_PTR )pAlloc + offset + pBaseReloc->VirtualAddress; char * fromeaddr = ( char *)p; for ( int c = 0;c < 4;c++) { tagetaddr[c] = fromeaddr[c]; } } } SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数 pBaseReloc = (PIMAGE_BASE_RELOCATION)(( PBYTE )pBaseReloc + pBaseReloc->SizeOfBlock); //相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构 } while (SizeOfBaseReloc); } |
根据数据目录表定位重定位表,再根据重定位表结构来进行修复
导入表修复
导入表的修复是比较困难的,再导入表修复前是不可以使用API的,但是在修复导入表这个过程中,不可避免的要用到LoadLibraryA与GetProcAddress,所以就需要一个新东西先找到这两个函数
TEB和PEB
这里我没有过多的去了解,仅仅知道TEB是线程环境块,其中存储着PEB的指针,PEB是一个进程环境块,其中有一个叫PEB.Ldr的结构体,里面存储着已经加载的DLL文件指针
也就是说可以通过查找PEB.Ldr来找到kernel32.dll模块的句柄,然后找到其中的API,PEB.Ldr的结构如下
1 2 3 4 5 6 7 8 | typedef struct _PEB_LDR_DATA { ULONG Length; // 结构体的大小 BOOLEAN Initialized; // 是否初始化 PVOID SsHandle; // 保留字段,通常为 NULL LIST_ENTRY InLoadOrderModuleList; // 模块按加载顺序排列的双向链表 LIST_ENTRY InMemoryOrderModuleList; // 模块按内存顺序排列的双向链表 LIST_ENTRY InInitializationOrderModuleList; // 模块按初始化顺序排列的双向链表 } PEB_LDR_DATA, *PPEB_LDR_DATA; |
定位PEB.Ldr
理论可行,实践开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //导入表的处理 //因为导入表还没有修复,所以里面的API大部分都是无法使用的,可避免的我都去避免了 // 但是是在无法避免的如,LoadLibrary函数无法找到,就需要通过 // TEB和PEB的帮助找到了 //GetProcAddress的地址 typedef FARPROC(WINAPI* GETPROCADDR)( HMODULE , LPCSTR ); //LoadLibrary的地址 typedef HMODULE (WINAPI* LOADLIBRARYA)( LPCSTR ); GETPROCADDR pGetProcAddress = NULL; LOADLIBRARYA pLoadLibraryA = NULL; //得到TEB的地址,通过TEB找到PEB PTEB pTEB = (PTEB)__readgsqword(0x30); PPEB pPEB = pTEB->ProcessEnvironmentBlock; // 获取PEB.Ldr PPEB_LDR_DATA pLdr = pPEB->Ldr; // 遍历模块列表,找到 kernel32.dll PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList; PLIST_ENTRY pCurrentEntry = pListHead->Flink; while (pCurrentEntry && pCurrentEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); |
直到这一步是在定位到载入的模块的句柄,后续需要的是遍历载入的模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | if (pEntry && pEntry->FullDllName.Buffer) { wchar_t * dllName = pEntry->FullDllName.Buffer; // 提取 DLL 名称 wchar_t * fileName = ExtractDllName(dllName); // 目标 DLL 名称kernel32.dll wchar_t a[50] = { 0 }; a[0] = L 'k' ; a[1] = L 'e' ; a[2] = L 'r' ; a[3] = L 'n' ; a[4] = L 'e' ; a[5] = L 'l' ; a[6] = L '3' ; a[7] = L '2' ; a[8] = L '.' ; a[9] = L 'd' ; a[10] = L 'l' ; a[11] = L 'l' ; a[12] = L '\0' ; if (CompareDllName(fileName, a)) { |
1 | 这里我定义的时候,直接用kernel32.dll定义是不可以的,这个指针会指向 00 ,所以我用了一个一个赋值 |
ExtractDllName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 提取 DLL 名称的函数 wchar_t * ExtractDllName( const wchar_t * fullDllName) { wchar_t * fileName = NULL; wchar_t * temp = ( wchar_t *)fullDllName; // 遍历并找到最后一个 '\\',获取文件名部分 while (*temp) { if (*temp == L '\\' ) { fileName = temp + 1; // 更新文件名的位置 } temp++; } // 如果没有找到 '\\',则认为整个字符串就是文件名 if (!fileName) { fileName = ( wchar_t *)fullDllName; } return fileName; } /* 000001ED62E25 43 00 3A 00 5C 00 57 00 69 00 6E 00 64 00 6F 00 C.:.\.W.i.n.d.o. 000001ED62E25 77 00 73 00 5C 00 53 00 59 00 53 00 54 00 45 00 w.s.\.S.Y.S.T.E. 000001ED62E25 4D 00 33 00 32 00 5C 00 6E 00 74 00 64 00 6C 00 M.3.2.\.n.t.d.l. 000001ED62E25 6C 00 2E 00 64 00 6C 00 6C 00 00 00 AB AB AB AB l...d.l.l...«««« */ |
1 | 这个函数的作用是获得dll文件名,因为这里加载的时候是绝对路径,所以需要去点前面的路径,只留下最后一个部分 - - 模块名,例如下面注释的内容,就是其中加载过的一个例子,这个函数就是把前面的windows、system32去掉,而且一个\在字符中表示是`L '\\' `,所以在这个函数之后获得的就是dll名称 |
CompareDllName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 比较两个 DLL 名称(大小写不敏感) int CompareDllName( wchar_t * dllName, wchar_t * targetDllName) { size_t i = 0; while (dllName[i] != L '\0' || targetDllName[i] != L '\0' ) { // 将两个字符都转换为小写进行比较 wchar_t ch1 = (dllName[i] >= L 'A' && dllName[i] <= L 'Z' ) ? dllName[i] + (L 'a' - L 'A' ) : dllName[i]; wchar_t ch2 = (targetDllName[i] >= L 'A' && targetDllName[i] <= L 'Z' ) ? targetDllName[i] + (L 'a' - L 'A' ) : targetDllName[i]; // 如果字符不同,则返回比较结果 if (ch1 != ch2) { return 0; } i++; } // 如果字符串都没有结束且匹配到最后,返回 0 表示完全相等 return 1; // 如果两个字符串完全相同,返回 0 } |
1 | 比较函数,先把他们都换成小写字母,这样就不用考虑大小写了 |
定位函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | if (CompareDllName(fileName, a)) { //分析PE文件找到导出表 HMODULE hKernel32 = ( HMODULE )pEntry->DllBase; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)hKernel32 + ((PIMAGE_DOS_HEADER)hKernel32)->e_lfanew); // 获取导出表的地址 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)hKernel32 + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的各个信息 DWORD * pFunctionNames = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNames); DWORD * pFunctionAddresses = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfFunctions); WORD * pFunctionOrdinals = ( WORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 遍历导出表,查找 LoadLibraryA char targetName1[50] = "LoadLibraryA" ; targetName1[0] = 'L' ; targetName1[1] = 'o' ; targetName1[2] = 'a' ; targetName1[3] = 'd' ; targetName1[4] = 'L' ; targetName1[5] = 'i' ; targetName1[6] = 'b' ; targetName1[7] = 'r' ; targetName1[8] = 'a' ; targetName1[9] = 'r' ; targetName1[10] = 'y' ; targetName1[11] = 'A' ; targetName1[12] = '\0' ; // 遍历导出表,查找 GetProcAddress char targetName2[50] = { 0 }; targetName2[0] = 'G' ; targetName2[1] = 'e' ; targetName2[2] = 't' ; targetName2[3] = 'P' ; targetName2[4] = 'r' ; targetName2[5] = 'o' ; targetName2[6] = 'c' ; targetName2[7] = 'A' ; targetName2[8] = 'd' ; targetName2[9] = 'd' ; targetName2[10] = 'r' ; targetName2[11] = 'e' ; targetName2[12] = 's' ; targetName2[13] = 's' ; targetName2[14] = '\0' ; int isP=0; for ( DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) { char * functionName = ( char *)(( BYTE *)hKernel32 + pFunctionNames[i]); if (CompareStrings(functionName, targetName1) ){ // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pLoadLibraryA = (LOADLIBRARYA)(( BYTE *)hKernel32 + functionRVA); isP++; } if (CompareStrings(functionName, targetName2)) { // 找到函数名,获取其地址 DWORD functionRVA = pFunctionAddresses[pFunctionOrdinals[i]]; pGetProcAddress = (GETPROCADDR)(( BYTE *)hKernel32 + functionRVA); isP++; } if (isP == 2) { break ; } } break ; // 找到后退出 } |
从模块基地址中提取模块句柄,然后分析PE文件导出表获取函数名,对比获得函数在当前进程中的地址
真正的修复导入表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNTheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAlloc); //这个是IID的指针 if (pImport != NULL) { while (pImport->Name != NULL) { char DLLname[50] = { 0 }; // 定义一个存储 DLL 名称的缓冲区 char * pDLLName = ( char *)(pImport->Name + pAlloc); // 获取 DLL 名称的地址 // 手动将名称拷贝到 DLLname 缓冲区 for ( int i = 0; i < sizeof (DLLname) - 1; i++) { if (pDLLName[i] == '\0' ) // 遇到字符串结束符时停止 break ; DLLname[i] = pDLLName[i]; // 拷贝字符 } DLLname[ sizeof (DLLname) - 1] = '\0' ; // 确保缓冲区以 '\0' 结尾 HMODULE hProcess = pLoadLibraryA(DLLname); //通过名称找句柄 if (!hProcess) { return FALSE; } PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(pImport->OriginalFirstThunk + pAlloc); PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(pImport->FirstThunk + pAlloc); while (( ULONG_PTR )(pINT->u1.AddressOfData) != NULL) { PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pAlloc); //找DLL中函数的名字 if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) //判断如果是序号就是第一种处理方式 { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, ( LPCSTR )(pINT->u1.AddressOfData))); //通过序号来获取地址 } else { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址 } pINT++; pIAT++; } pImport++; } } |
通过LoadLibrarA和GetProcAddress函数修复导入表
运行
1 2 3 4 | //最后的操作就是修正程序的入口地址 PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pAlloc + pDOSheader->e_lfanew); FARPROC EOP = (FARPROC)(( LPBYTE )pAlloc + pNT->OptionalHeader.AddressOfEntryPoint); EOP(); |
结束
源码2
没啥区别,先扩展到节区表,再用函数,再创建线程,这样做的好处就是不需要再算文件偏移,并且开辟空间的时候更小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | #include <stdio.h> #include <windows.h> #include <tlhelp32.h> DWORD RVAtoFileOffset( DWORD rva, PIMAGE_NT_HEADERS pNtHeaders, PIMAGE_SECTION_HEADER pSec); LPVOID GetRemoteReflectLoad( LPVOID pDll, const char * funcName, unsigned char * pBuf); DWORD ProcesstoPid( wchar_t * Processname); BOOL WINAPI MainInject( DWORD dwTargetPid, char * Dllname); PBYTE fexPE(unsigned char * pDll); int main() { //先定义需要注入的dll与目标进程的名字 //wchar_t szProcName[MAX_PATH] = L"cs2.exe"; wchar_t szProcName[MAX_PATH] = L "pta.exe" ; char Dllname[MAX_PATH] = "D:\\study\\VStudio\\craft\\x64\\Debug\\Dll1.dll" ; //查找获得目标进程的id DWORD dwPid = ProcesstoPid(szProcName); //写入dll文件,这里思考一下,传入的参数都是什么呢? //因为在这个函数里,我们要做的是将dll文件写入,并且用其中的函数 //dll中的API将会作为参数出现,所以传入的是进程id和dll函数目录 DWORD result = MainInject(dwPid, Dllname); } DWORD ProcesstoPid( wchar_t * Processname) //查找指定进程的PID(Process ID) { HANDLE hProcessSnap = NULL; DWORD ProcessId = 0; PROCESSENTRY32 pe32 = { 0 }; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //打开进程快照 if (hProcessSnap == ( HANDLE )-1) { printf ( "\n[-] CreateToolhelp32Snapshot() Error: %d" , GetLastError()); return 0; } pe32.dwSize = sizeof (PROCESSENTRY32); if (Process32First(hProcessSnap, &pe32)) //开始枚举进程 { do { if (!wcscmp(Processname, pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID { ProcessId = pe32.th32ProcessID; break ; } } while (Process32Next(hProcessSnap, &pe32)); //继续枚举进程 } else { printf ( "\n[-] Process32First() Error: %d" , GetLastError()); return 0; } if (!ProcessId) printf ( "no find" ); else printf ( "[+] target id is %d" , ProcessId); CloseHandle(hProcessSnap); //关闭系统进程快照的句柄 return ProcessId; } //该函数通过分析PE文件头来尝试获取句柄 LPVOID GetRemoteReflectLoad( LPVOID pDll, const char * funcName, unsigned char * pBuf) { PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuf; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)pBuf + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNtHeaders + sizeof (IMAGE_NT_HEADERS)); DWORD exportDirRVA = pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress; DWORD exportDirSize = pNtHeaders->OptionalHeader.DataDirectory[0].Size; PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)pBuf + exportDirRVA); //解析导出表,这里同理都是RVA DWORD * pRNames = ( DWORD *)(pBuf + pExportDir->AddressOfNames); DWORD * pRFunctions = ( DWORD *)(pBuf + pExportDir->AddressOfFunctions); WORD * pRNameOrdinals = ( WORD *)(pBuf + pExportDir->AddressOfNameOrdinals); // 遍历查找目标函数 DWORD funcRVA = 0; for ( DWORD i = 0; i < pExportDir->NumberOfNames; i++) { DWORD functionNameRVA = pRNames[i]; const char * pName = ( char *)(( BYTE *)pBuf + functionNameRVA); if ( strcmp (pName, funcName) == 0) { funcRVA = pRFunctions[i]; break ; } } if (funcRVA == 0) { printf ( "\n[-] Function %s not found." , funcName); return NULL; } LPVOID remoteFuncAddr = ( LPBYTE )pDll + funcRVA; return remoteFuncAddr; } PBYTE fexPE(unsigned char * pDll) { //获取磁盘文件的DOS头和NT头 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)pDll; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); //赋值NT头 PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); DWORD ImageSize = pNTheader->OptionalHeader.SizeOfImage; PBYTE PALLOC = ( PBYTE )VirtualAlloc(NULL, ImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (PALLOC == NULL) return NULL; if (pDOSheader->e_magic != IMAGE_DOS_SIGNATURE || pNTheader->Signature != IMAGE_NT_SIGNATURE) return NULL; // 无效的头,直接退出 ZeroMemory(PALLOC, ImageSize); CopyMemory(PALLOC, pDll, pNTheader->OptionalHeader.SizeOfHeaders); //复制节区表 PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); DWORD SecNum = pNTheader->FileHeader.NumberOfSections; for ( int i = 0; i < SecNum; i++) { if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) { pSec++; continue ; } char * chSrcMem = ( char *)(( uintptr_t )pDll + pSec->PointerToRawData); //chSrcMem =节区的文件偏移地址 char * chDestMem = ( char *)(( uintptr_t )PALLOC + pSec->VirtualAddress); //chDestMem=节区的RVA地址 DWORD dwSizeOfRawData = pSec->SizeOfRawData; //文件大小 RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData); //复制过程 pSec++; } return PALLOC; } BOOL WINAPI MainInject( DWORD dwTargetPid, char * Dllname) { //与普通dll注入一样,首先要做的是获取句柄 HANDLE hProc = NULL; hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); if (!hProc) { printf ( "\n[-] OpenProcess Failed." ); DWORD dwError = GetLastError(); printf ( "\n[-] OpenProcess failed. Error code: %d\n" , dwError); return FALSE; } //有了句柄就可以创建空间然后写入了,这里的写入我参考的是PE加载器中 //ReadFileA与创建空间的方法,其中相当于是在目标进程空间中创造一个"类磁盘"空间 HANDLE hFile = CreateFileA(Dllname, GENERIC_READ, //读取权限 FILE_SHARE_READ | FILE_SHARE_WRITE, //允许其他进程读取文件|允许其他进程写入文件 NULL, //不需要特定的安全性 OPEN_EXISTING, //不需要特定的安全性 FILE_ATTRIBUTE_NORMAL, //如果文件存在,则打开文件。如果文件不存在,操作会失败 NULL //普通文件,没有特殊属性。 ); if (hFile == INVALID_HANDLE_VALUE) { printf ( "\n[-] CreateFileA failed." ); return FALSE; } DWORD FileSize = GetFileSize(hFile, NULL); LPDWORD SizeToRead = 0; //本地暂存 unsigned char * pBuf = new unsigned char [FileSize]; ZeroMemory(pBuf, FileSize); int result = ReadFile(hFile, pBuf, FileSize, SizeToRead, NULL); //读取文件放在开辟的空间里,pBuf为空间句柄 if (result == 0) { printf ( "\n[-] 文件读取失败" ); return FALSE; } PBYTE PAlloc = fexPE(pBuf); //完成节区赋值 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)PAlloc; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(PAlloc + pDOSheader->e_lfanew); //赋值NT头 DWORD ImageSize = pNTheader->OptionalHeader.SizeOfImage; PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(( LPBYTE )pNTheader + sizeof (IMAGE_NT_HEADERS)); LPVOID pDll = VirtualAllocEx(hProc, NULL, ImageSize + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pDll == NULL) { printf ( "\n[-] 内存分配失败, 错误代码: %d" , GetLastError()); return FALSE; } // 清零目标进程内存 SIZE_T sizeToZero = ImageSize; // 需要清零的字节数 BYTE * zeroBuffer = ( BYTE *) calloc (sizeToZero, 1); // 创建一个全零的缓冲区 if (zeroBuffer == NULL) { printf ( "Failed to allocate zero buffer.\n" ); return -1; } // 写入全零到目标内存 if (!WriteProcessMemory(hProc, pDll, zeroBuffer, sizeToZero, NULL)) { DWORD errorCode = GetLastError(); printf ( "WriteProcessMemory failed with error code: %lu\n" , errorCode); } // 释放缓冲区 free (zeroBuffer); //将dll文件写入进去 if (!WriteProcessMemory(hProc, pDll, PAlloc, ImageSize, NULL)) { return FALSE; } //接下来的活是找dll文件中相当于loadlibrary函数的自写函数Reflectload //注意前文开辟的"类磁盘"空间中的pDll // 假设 ReflectLoader 是目标函数名 const char * reflectFuncName = "ReflectLoader" ; // 获取 ReflectLoader 在目标进程内存中的地址 LPVOID pReflectLoader = GetRemoteReflectLoad(pDll, reflectFuncName, PAlloc); if (!pReflectLoader) { printf ( "[-] Failed to find ReflectLoader.\n" ); VirtualFreeEx(hProc, pDll, 0, MEM_RELEASE); return FALSE; } printf ( "\n[+] 函数 address:%p" , pReflectLoader); // 调用 ReflectLoader 函数 HANDLE hThread = CreateRemoteThread( hProc, // 目标进程句柄 NULL, // 默认安全属性 0, // 默认堆栈大小 (LPTHREAD_START_ROUTINE)pReflectLoader, // ReflectLoader 地址 pDll, // 参数:DLL 的基址 0, // 默认创建标志 NULL // 不需要线程 ID ); if (!hThread) { printf ( "\n[-] CreateRemoteThread failed: %d" , GetLastError()); VirtualFreeEx(hProc, pDll, 0, MEM_RELEASE); return FALSE; } printf ( "\n[+] ReflectLoader executed successfully." ); // 等待线程执行完成 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); return TRUE; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include<stdio.h> #include <stdbool.h> #include <winternl.h> extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll); int CompareDllName( wchar_t * dllName, wchar_t * targetDllName); wchar_t * ExtractDllName( const wchar_t * fullDllName); void my_wctomb( char * dest, const wchar_t * src); int CompareStrings( const char * str1, const char * str2); // DLL入口点函数 BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { // 弹窗代码 MessageBox(NULL, L "哇塞!!你成功啦!!!" , L "注入程序检测中..." , MB_YESNO | MB_ICONASTERISK); char processName[MAX_PATH] = { 0 }; // 存储进程路径的缓冲区 // 获取当前进程的可执行文件路径 DWORD length = GetModuleFileNameA(NULL, processName, MAX_PATH); MessageBoxA(NULL, processName, "当前进程路径: " , MB_YESNO | MB_ICONASTERISK); Sleep(99999999); return TRUE; } // 自定义的宽字符转普通字符的函数 void my_wctomb( char * dest, const wchar_t * src) { while (*src) { if (*src >= L 'A' && *src <= L 'Z' ) { *dest = ( char )(*src + (L 'a' - L 'A' )); // 转换大写字符为小写 } else if (*src >= L 'a' && *src <= L 'z' ) { *dest = ( char )*src; // 保留小写字符 } else if (*src >= L '0' && *src <= L '9' ) { *dest = ( char )*src; // 保留数字字符 } else { *dest = '?' ; // 对于其他字符,可以选择替代字符,例如 '?' } dest++; src++; } *dest = '\0' ; // 确保目标字符串以 null 结尾 } int CompareStrings( const char * str1, const char * str2) { while (*str2 != '\0' ) { if (*str1 != *str2) { return 0; // 返回差值 } str1++; str2++; } return 1; // 两个字符串完全匹配时返回0 } // 比较两个 DLL 名称(大小写不敏感) int CompareDllName( wchar_t * dllName, wchar_t * targetDllName){ size_t i = 0; while (dllName[i] != L '\0' || targetDllName[i] != L '\0' ) { // 将两个字符都转换为小写进行比较 wchar_t ch1 = (dllName[i] >= L 'A' && dllName[i] <= L 'Z' ) ? dllName[i] + (L 'a' - L 'A' ) : dllName[i]; wchar_t ch2 = (targetDllName[i] >= L 'A' && targetDllName[i] <= L 'Z' ) ? targetDllName[i] + (L 'a' - L 'A' ) : targetDllName[i]; // 如果字符不同,则返回比较结果 if (ch1 != ch2) { return 0; } i++; } // 如果字符串都没有结束且匹配到最后,返回 0 表示完全相等 return 1; // 如果两个字符串完全相同,返回 0 } // 提取 DLL 名称的函数 wchar_t * ExtractDllName( const wchar_t * fullDllName) { wchar_t * fileName = NULL; wchar_t * temp = ( wchar_t *)fullDllName; // 遍历并找到最后一个 '\\',获取文件名部分 while (*temp) { if (*temp == L '\\' ) { fileName = temp + 1; // 更新文件名的位置 } temp++; } // 如果没有找到 '\\',则认为整个字符串就是文件名 if (!fileName) { fileName = ( wchar_t *)fullDllName; } return fileName; } extern "C" __declspec ( dllexport ) BOOL ReflectLoader( char * pDll) { //开始检测并加载重定位表 PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)pDll; //赋值DOS头 PIMAGE_NT_HEADERS pNTheader = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); //赋值NT头 PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(( ULONG_PTR )pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress + ( ULONG_PTR )pDll); //重定位表指针通过NT结构的数据目录表查找到位置 int SizeOfBaseReloc = pNTheader->OptionalHeader.DataDirectory[5].Size; //重定位表的大小也在NT结构中 if (pNTheader->OptionalHeader.DataDirectory[5].VirtualAddress != NULL) { do { PWORD TypeOffset = ( WORD *)(( PBYTE )pBaseReloc + 8); //跳过前两个元素(不过在有的结构声明中TypeOffset不属于该结构 int num = (pBaseReloc->SizeOfBlock - 8) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的 for ( int i = 0; i < num; i++) { WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型) WORD offset = TypeOffset[i] & 0x0FFF; //去掉类型(前四字节) int differ = 0; if (type == 3) { differ = *(( ULONG_PTR *)(offset + pBaseReloc->VirtualAddress + pDll)) - pNTheader->OptionalHeader.ImageBase; ULONG_PTR p = ( ULONG_PTR )pDll + differ; char * tagetaddr = ( char *)( ULONG_PTR )pDll + offset + pBaseReloc->VirtualAddress; char * fromeaddr = ( char *)p; for ( int c = 0;c < 4;c++) { tagetaddr[c] = fromeaddr[c]; } } } SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数 pBaseReloc = (PIMAGE_BASE_RELOCATION)(( PBYTE )pBaseReloc + pBaseReloc->SizeOfBlock); //相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构 } while (SizeOfBaseReloc); } //导入表的处理 //因为导入表还没有修复,所以里面的API大部分都是无法使用的,可避免的我都去避免了 // 但是是在无法避免的如,LoadLibrary函数无法找到,就需要通过 // TEB和PEB的帮助找到了 //GetProcAddress的地址 typedef FARPROC(WINAPI* GETPROCADDR)( HMODULE , LPCSTR ); //LoadLibrary的地址 typedef HMODULE (WINAPI* LOADLIBRARYA)( LPCSTR ); GETPROCADDR pGetProcAddress = NULL; LOADLIBRARYA pLoadLibraryA = NULL; //得到TEB的地址,通过TEB找到PEB PTEB pTEB = (PTEB)__readgsqword(0x30); PPEB pPEB = pTEB->ProcessEnvironmentBlock; // 获取PEB.Ldr PPEB_LDR_DATA pLdr = pPEB->Ldr; // 遍历模块列表,找到 kernel32.dll PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList; PLIST_ENTRY pCurrentEntry = pListHead->Flink; while (pCurrentEntry && pCurrentEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pEntry && pEntry->FullDllName.Buffer) { wchar_t * dllName = pEntry->FullDllName.Buffer; // 提取 DLL 名称 wchar_t * fileName = ExtractDllName(dllName); // 目标 DLL 名称kernel32.dll wchar_t a[50] = { 0 }; a[0] = L 'k' ; a[1] = L 'e' ; a[2] = L 'r' ; a[3] = L 'n' ; a[4] = L 'e' ; a[5] = L 'l' ; a[6] = L '3' ; a[7] = L '2' ; a[8] = L '.' ; a[9] = L 'd' ; a[10] = L 'l' ; a[11] = L 'l' ; a[12] = L '\0' ; if (CompareDllName(fileName, a)) { //分析PE文件找到导出表 HMODULE hKernel32 = ( HMODULE )pEntry->DllBase; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(( BYTE *)hKernel32 + ((PIMAGE_DOS_HEADER)hKernel32)->e_lfanew); // 获取导出表的地址 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(( BYTE *)hKernel32 + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的各个信息 DWORD * pFunctionNames = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNames); DWORD * pFunctionAddresses = ( DWORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfFunctions); WORD * pFunctionOrdinals = ( WORD *)(( BYTE *)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 遍历导出表,查找 LoadLibraryA char targetName1[50] = "LoadLibraryA" ; targetName1[0] = 'L' ; targetName1[1] = 'o' ; targetName1[2] = 'a' ; targetName1[3] = 'd' ; targetName1[4] = 'L' ; targetName1[5] = 'i' ; targetName1[6] = 'b' ; targetName1[7] = 'r' ; targetName1[8] = 'a' ; targetName1[9] = 'r' ; targetName1[10] = 'y' ; targetName1[11] = 'A' ; targetName1[12] = '\0' ; // 遍历导出表,查找 GetProcAddress char targetName2[50] = { 0 }; targetName2[0] = 'G' ; targetName2[1] = 'e' ; targetName2[2] = 't' ; targetName2[3] = 'P' ; targetName2[4] = 'r' ; targetName2[5] = 'o' ; targetName2[6] = 'c' ; targetName2[7] = 'A' ; targetName2[8] = 'd' ; targetName2[9] = 'd' ; targetName2[10] = 'r' ; targetName2[11] = 'e' ; targetName2[12] = 's' ; targetName2[13] = 's' ; targetName2[14] = '\0' ; int isP=0; for ( DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) { char * functionName = ( char *)(( BYTE *)hKernel32 + pFunctionNames[i]); if (CompareStrings(functionName, targetName1) ){ // 找到函数名,获取其地址 pLoadLibraryA = (LOADLIBRARYA)(( BYTE *)hKernel32 + pFunctionAddresses[pFunctionOrdinals[i]]); isP++; } if (CompareStrings(functionName, targetName2)) { // 找到函数名,获取其地址 pGetProcAddress = (GETPROCADDR)(( BYTE *)hKernel32 + pFunctionAddresses[pFunctionOrdinals[i]]); isP++; } if (isP == 2) { break ; } } break ; // 找到后退出 } } pCurrentEntry = pCurrentEntry->Flink; } if (!pLoadLibraryA && !pGetProcAddress) { char foece[] = "无法获取 LoadLibraryA 地址,请检查目标进程模块" ; return FALSE; } else { char sucsess[] = "sucsess" ; } PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNTheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pDll); //这个是IID的指针 if (pImport != NULL) { while (pImport->Name != NULL) { char DLLname[50] = { 0 }; // 定义一个存储 DLL 名称的缓冲区 char * pDLLName = ( char *)(pImport->Name + pDll); // 获取 DLL 名称的地址 // 手动将名称拷贝到 DLLname 缓冲区 for ( int i = 0; i < sizeof (DLLname) - 1; i++) { if (pDLLName[i] == '\0' ) // 遇到字符串结束符时停止 break ; DLLname[i] = pDLLName[i]; // 拷贝字符 } DLLname[ sizeof (DLLname) - 1] = '\0' ; // 确保缓冲区以 '\0' 结尾 HMODULE hProcess = pLoadLibraryA(DLLname); //通过名称找句柄 if (!hProcess) { return FALSE; } PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(pImport->OriginalFirstThunk + pDll); PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(pImport->FirstThunk + pDll); while (( ULONG_PTR )(pINT->u1.AddressOfData) != NULL) { PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pDll); //找DLL中函数的名字 if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) //判断如果是序号就是第一种处理方式 { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, ( LPCSTR )(pINT->u1.AddressOfData))); //通过序号来获取地址 } else { pIAT->u1.AddressOfData = ( ULONG_PTR )(pGetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址 } pINT++; pIAT++; } pImport++; } } //最后的操作就是修正程序的入口地址 PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pDll + pDOSheader->e_lfanew); FARPROC EOP = (FARPROC)(( LPBYTE )pDll + pNT->OptionalHeader.AddressOfEntryPoint); EOP(); return TRUE; } |
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课