精华内容
下载资源
问答
  • Windows R3 无模块注入,x86+x64通用,不支持异常处理,编译前关闭GS安全检查,VS2015所写,并明显C++特征,兼容性极高
  • 全能USB3.x/Nvme/Other驱动注入工具是一款无需安装就可以直接使用的驱动注入工具,这款功能可以帮你一键识别USB3.0设备,软件非常实用方便,有需要的用户赶快来下载使用吧!安装驱动平台:Windows7或以上系统功能:1...
  • 隐藏模块无模块注入

    千次阅读 2020-08-12 15:41:39
    这个题目的意思是将程序的基址设置成高地址,将0x400000空出来,然后将游戏主模块拉伸,修复IAT之后,注入进去。这样做的好处主要是简单,因为不需要修复重定位表了。缺点也很明显,很多时候根本没办法在0x400000处...

    模块隐藏那节课要求完成两个作业,都是隐藏模块,本文介绍两种方法分别如何实现。

    方法一:往自己的进程注入游戏主模块

    在这里插入图片描述

    这个题目的意思是将程序的基址设置成高地址,将0x400000空出来,然后将游戏主模块拉伸,修复IAT之后,注入进去。这样做的好处主要是简单,因为不需要修复重定位表了。缺点也很明显,很多时候根本没办法在0x400000处申请足够大的内存容纳游戏程序。,还有就是有些程序跳转到OEP之后会卡住,具体原因我也不知道。我这里用dbgview.exe测试,能够正常启动,这足够说明我的代码没有大的错误。
    在这里插入图片描述
    任务管理器中只会看到控制台的进程,看不到dbgview的进程:
    在这里插入图片描述

    所谓修复IAT,就是指,将游戏PE的IAT表修复成函数地址,做法也很简单,遍历IAT表,loadlibary获取dll句柄,然后遍历函数名或函数序号,一个个getprocaddress,将获取到的函数地址写入到IAT表就算修复好了。

    下面是我的代码,我只给出关键的两个函数,一个是修复IAT表,一个是注入的函数,完整代码放在文章最后。

    注入代码

    // 将游戏模块注入到我的进程
    // 修改ImageBase=0x20000000,使本程序在高地址运行
    // 这种方式非常不推荐,因为游戏程序只要稍微大一些,就很可能无法在0x400000处申请内存了
    void GameInjectMe()
    {	
    	if ((DWORD)GetModuleHandle(NULL) != (DWORD)0x20000000)
    	{
    		printf("当前进程句柄: 0x%X\n", (DWORD)GetModuleHandle(NULL));
    		printf("请修改链接设置,将ImageBase设置为0x20000000\n");
    		return;
    	}
    	// 读取游戏PE,拉伸,修复IAT,写入到其ImageBase处
    	LPVOID pFileBuffer = NULL;
    	//FileToMemory(TEXT("C:\\Documents and Settings\\nixiang\\桌面\\DTDebug(VT-O)专业版V1.0.025\\DTDebug\\DTDebug.exe"), &pFileBuffer);
    	//FileToMemory(TEXT("c:\\program32\\Helloworld.exe"), &pFileBuffer);
    	FileToMemory(TEXT("c:\\program32\\dbgview.exe"), &pFileBuffer);
    	LPVOID pImageBuffer = NULL;
    	DWORD dwImageSize = FileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));		
    	LPVOID pImageBase = VirtualAlloc((LPVOID)pOptionHeader->ImageBase, dwImageSize,MEM_COMMIT, PAGE_READWRITE);	
    	if (pImageBase != (LPVOID)pOptionHeader->ImageBase)
    	{
    		printf("错误码:0x%X 在ImageBase(0x%X)处VirtualAlloc失败\n",GetLastError(),pOptionHeader->ImageBase);
    		return;
    	}
    	// 修复IAT表
    	RepairIAT(pImageBuffer);	
    	memcpy(pImageBase, pImageBuffer, dwImageSize);
    	
    	// 跳转到游戏的入口点
    	DWORD dwOEP = pOptionHeader->AddressOfEntryPoint + pOptionHeader->ImageBase;
    	__asm{
    		jmp dwOEP
    	}	
    }
    

    修复IAT代码

    // 传入一个imagebuffer,修复它的IAT表
    void RepairIAT(LPVOID pImageBuffer)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    	
    // 	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + \
    // 		RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[1].VirtualAddress));
    	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImageBuffer + \
    		pOptionHeader->DataDirectory[1].VirtualAddress);
    	
    	// 严格来说应该是 sizeof(IMAGE_IMPORT_DESCRIPTOR) 个字节为0表示结束
    	while (pImportTable->OriginalFirstThunk || pImportTable->FirstThunk)
    	{
    		// 打印模块名
    		//printf("%s\n", (LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		// 获取模块句柄
    		HMODULE hModule = LoadLibraryA((LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		if (NULL == hModule)
    		{
    			printf("获取模块句柄失败,模块名: %s\n",(LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		}
    		// 修复IAT表
    		//printf("--------------FirstThunkRVA:%x--------------\n", pImportTable->FirstThunk);		
    		PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)((DWORD)pImageBuffer + \
    			pImportTable->FirstThunk);
    		while (*((PDWORD)pThunkData) != 0)
    		{
    			// IMAGE_THUNK_DATA32 是一个4字节数据
    			// 如果最高位是1,那么除去最高位就是导出序号
    			// 如果最高位是0,那么这个值是RVA 指向 IMAGE_IMPORT_BY_NAME
    			if ((*((PDWORD)pThunkData) & 0x80000000) == 0x80000000)
    			{
    				//printf("按序号导入 Ordinal:%04x\n", (*((PDWORD)pThunkData) & 0x7FFFFFFF));
    				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,MAKEINTRESOURCE((*((PDWORD)pThunkData) & 0x7FFFFFFF)));				
    				*((PDWORD)pThunkData) = dwProcAddress;
    			}
    			else
    			{
    				PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)(*((PDWORD)pThunkData) + \
    					(DWORD)pImageBuffer);
    				
    				//printf("按名字导入 Hint:%04x Name:%s\n", pIBN->Hint, pIBN->Name);
    				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,(LPCSTR)pIBN->Name);
    				*((PDWORD)pThunkData) = dwProcAddress;
    			}
    			pThunkData++;
    		}
    		pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));		
    	}	
    }
    

    其实方法一的局限性我在博客开头已经分析过了,不过我想了一下发现,其实问题关键就是0x400000处可能会申请内存失败,实际上我们不需要非得在这个地方申请内存,而是可以在任意地方申请,然后根据重定位表,修复地址即可。而这种方式,在方法二里面用到了。

    以上是第一种方法的做法,下面是第二种做法,往游戏里注入自己,然后跳转到入口函数:

    方法二:往游戏进程注入自己的主模块

    在这里插入图片描述
    这种方式不需要手工修复IAT,因为自身进程启动时操作系统帮我们修复了,我们只需要修复重定位表即可。

    原理上图已经解释的非常明白了,下面给出关键代码:
    重定位表修复代码

    // 修改 ImageBase 并修复重定位表
    // 内存镜像版本
    VOID SetNewImageBase2(LPVOID pImageBuffer, DWORD dwNewImageBase)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    	
    	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pImageBuffer + \
    		pOptionHeader->DataDirectory[5].VirtualAddress);
    	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	
    	
    	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
    	// RVA + ImageBase 这个内存里存储了一个“指针”
    	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
    	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
    	{
    		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
    		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
    		for (size_t i = 0; i < n; i++)
    		{
    			// 高4位等于0011才需要重定位
    			if ((pOffset[i] & 0xF000) == 0x3000)
    			{
    				// 计算需要重定位的数据的RVA地址
    				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);				
    				// 计算在镜像中的地址
    				PDWORD pData = (PDWORD)((DWORD)pImageBuffer + dwRva);
    				// 重定位,即修正写死的地址				
    				*pData += dwImageBaseDelta;
    			}
    		}		
    		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    	}
    	// 修改 ImageBase
    	pOptionHeader->ImageBase = dwNewImageBase;
    }
    

    注入代码

    // 将我的模块注入到游戏进程
    void MeInjectGame()
    {
    	// 获取自己的ImageBase和SizeOfImage
    	HMODULE hModule = GetModuleHandle(NULL);
    	LPVOID pImageBuffer = (LPVOID)hModule;	
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));	
    	// 拷贝到新的缓冲区
    	DWORD dwSizeOfImage = pOptionHeader->SizeOfImage;
    	pImageBuffer = malloc(dwSizeOfImage);
    	memcpy(pImageBuffer, hModule, dwSizeOfImage);
    	// 在游戏进程申请内存
    	HWND hWnd = FindWindowA(NULL, "LittleGame");
        DWORD dwPID = 0;
        GetWindowThreadProcessId(hWnd, &dwPID);
    	printf("进程id: %d\n", dwPID);	
    	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);
    	LPVOID pGameImageBase = VirtualAllocEx(hProcess,NULL,dwSizeOfImage,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    	if (NULL == pGameImageBase)
    	{
    		printf("在游戏进程申请内存失败,错误码: %d\n", GetLastError());
    		return;
    	}
    	// 写入到游戏进程前,先修复重定位表
    	SetNewImageBase2(pImageBuffer, (DWORD)pGameImageBase);
    	WriteProcessMemory(hProcess,pGameImageBase,pImageBuffer,dwSizeOfImage,NULL);
    	// 获取当前进程中,入口函数的地址(希望在注入后运行的那个函数),然后和基址相减得到偏移
    	DWORD dwProcOffset = (DWORD)InjectEntry - (DWORD)hModule + (DWORD)pGameImageBase;
    	// 创建远程线程,执行入口代码
    	CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)dwProcOffset,NULL,NULL,NULL);
    }
    

    入口代码

    DWORD WINAPI InjectEntry(LPVOID param)
    {
    	while (TRUE)
    	{
    		MessageBox(0,0,0,0);
    		Sleep(5000);
    	}
    	return 0;
    }
    

    完整代码

    最后,给出项目完整的代码。
    PE.H

    #ifndef PE_HPP_
    #define PE_HPP_
    
    /********************************************************************************
    时间:2020年7月14日
    作者:hambaga
    说明:重新整理的PE工具函数,仅适用于32位程序
    ********************************************************************************/
    
    #ifndef _CRT_SECURE_NO_WARNINGS
    #define _CRT_SECURE_NO_WARNINGS
    #endif // !_CRT_SECURE_NO_WARNINGS
    
    #include <stdio.h>
    #include <WINDOWS.H>
    #include <STRING.h>
    #include <MALLOC.H>
    
    DWORD FileToMemory(LPCSTR lpszFile, LPVOID *pFileBuffer);
    BOOL MemoryToFile(LPVOID pMemBuffer, DWORD dwSize, LPCSTR lpszFile);
    BOOL Is32PEFile(LPVOID pFileBuffer, DWORD dwSize);
    DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID *pImageBuffer);
    DWORD ImageBufferToFileBuffer(LPVOID pImageBuffer, LPVOID *pFileBuffer);
    DWORD Align(DWORD dwOffset, DWORD dwAlign);
    DWORD RvaToFoa(LPVOID pFileBuffer, DWORD dwRva);
    DWORD FoaToRva(LPVOID pFileBuffer, DWORD dwFoa);
    DWORD MoveNTHeaderAndSectionHeadersToDosStub(LPVOID pFileBuffer);
    VOID SetNewImageBase(LPVOID pFileBuffer, DWORD dwNewImageBase);
    void RepairIAT(LPVOID pImageBuffer);
    
    // 读取文件到内存中,返回读取的字节数;读取失败返回0
    DWORD FileToMemory(LPCSTR lpszFile, LPVOID *pFileBuffer)
    {
    	FILE *pFile = NULL;
    	DWORD dwFileSize = 0;
    	pFile = fopen(lpszFile, "rb");
    	if (pFile == NULL)
    	{
    		printf("打开文件失败\n");
    		return 0;
    	}
    	fseek(pFile, 0, SEEK_END);
    	dwFileSize = ftell(pFile);
    	fseek(pFile, 0, SEEK_SET);
    	*pFileBuffer = malloc(dwFileSize);
    	if (*pFileBuffer == NULL)
    	{
    		printf("分配内存失败\n");
    		fclose(pFile);
    		return 0;
    	}
    	DWORD dwRead = fread(*pFileBuffer, 1, dwFileSize, pFile);
    	fclose(pFile);
    	if (dwRead != dwFileSize)
    	{
    		free(*pFileBuffer);
    		return 0;
    	}
    	return dwRead;
    }
    
    // 验证是否是合法的32位PE文件
    BOOL Is32PEFile(LPVOID pFileBuffer, DWORD dwSize)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    	if (*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
    	{
    		printf("不是有效的MZ标志\n");
    		return FALSE;
    	}
    	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
    	{
    		printf("不是有效的PE标记\n");
    		return FALSE;
    	}
    	return TRUE;
    }
    
    // 将 FileBuffer 拉伸成 ImageBuffer 并写入到新的缓冲区
    // 返回 ImageBuffer 的大小;失败返回0
    DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID *pImageBuffer)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	*pImageBuffer = malloc(pOptionHeader->SizeOfImage);
    	if (*pImageBuffer == NULL)
    	{
    		printf("分配内存失败\n");
    		return 0;
    	}
    	memset(*pImageBuffer, 0, pOptionHeader->SizeOfImage);
    	// 复制DOS头+PE头+可选PE头+节表+文件对齐
    	memcpy(*pImageBuffer, pFileBuffer, pOptionHeader->SizeOfHeaders);
    	// 遍历节表,复制所有节	
    	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
    	{
    		memcpy((LPVOID)((DWORD)(*pImageBuffer) + pSectionHeader[i].VirtualAddress), \
    			(LPVOID)((DWORD)pFileBuffer + pSectionHeader[i].PointerToRawData), \
    			pSectionHeader[i].SizeOfRawData);
    	}
    	return pOptionHeader->SizeOfImage;
    }
    
    // 将 ImageBuffer 变成文件对齐的 FileBuffer 写入新的缓冲区
    // 返回复制的大小,失败返回0
    DWORD ImageBufferToFileBuffer(LPVOID pImageBuffer, LPVOID *pFileBuffer)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	// 最后一个节表
    	PIMAGE_SECTION_HEADER pLastSectionHeader = pSectionHeader + pPEHeader->NumberOfSections - 1;
    	// 计算要复制的字节
    	// 这一步有BUG,当最后一个节后面还有数据时(多见于控制台程序),丢失数据
    	DWORD dwFileBufferSize = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
    	*pFileBuffer = malloc(dwFileBufferSize);
    	if (*pFileBuffer == NULL)
    	{
    		printf("分配内存失败\n");
    		return 0;
    	}
    	memset(*pFileBuffer, 0, dwFileBufferSize);
    	// 复制DOS头+PE头+可选PE头+节表+文件对齐
    	memcpy(*pFileBuffer, pImageBuffer, pOptionHeader->SizeOfHeaders);
    	// 遍历节表,复制文件对齐后的节	
    	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
    	{
    		memcpy((LPVOID)((DWORD)(*pFileBuffer) + pSectionHeader[i].PointerToRawData), \
    			(LPVOID)((DWORD)pImageBuffer + pSectionHeader[i].VirtualAddress), \
    			pSectionHeader[i].SizeOfRawData);
    	}
    	return dwFileBufferSize;
    }
    
    // 内存数据写入文件
    BOOL MemoryToFile(LPVOID pMemBuffer, DWORD dwSize, LPCSTR lpszFile)
    {
    	FILE *fp = NULL;
    	fp = fopen(lpszFile, "wb+");
    	if (fp == NULL)
    	{
    		printf("打开文件失败\n");
    		return FALSE;
    	}
    	DWORD dwWritten = fwrite(pMemBuffer, 1, dwSize, fp);
    	if (dwWritten != dwSize)
    	{
    		printf("写入了 %d 字节,不等于 %d\n", dwWritten, dwSize);
    		fclose(fp);
    		return FALSE;
    	}
    	fclose(fp);
    	return TRUE;
    }
    
    // 计算对齐的函数,如偏移为900,对齐为1000h,返回1000h
    DWORD Align(DWORD dwOffset, DWORD dwAlign)
    {
    	// 如果偏移小于对齐,向上取整
    	if (dwOffset <= dwAlign) return dwAlign;
    	// 如果偏移大于对齐且不能除尽,向上取整
    	if (dwOffset % dwAlign)
    	{
    		return (dwOffset / dwAlign + 1) * dwAlign;
    	}
    	// 如果能除尽,直接返回offset
    	return dwOffset;
    }
    
    // RVA 转 FOA
    DWORD RvaToFoa(LPVOID pFileBuffer, DWORD dwRva)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pFileBuffer + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	// RVA在文件头中或者文件对齐==内存对齐时,RVA==FOA  错!第一句是对的,第二句是错的
    	if (dwRva < pOptionHeader->SizeOfHeaders)
    	{
    		return dwRva;
    	}
    
    	// 遍历节表,确定偏移属于哪一个节	
    	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
    	{
    		if (dwRva >= pSectionHeader[i].VirtualAddress && \
    			dwRva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].Misc.VirtualSize)
    		{
    			int offset = dwRva - pSectionHeader[i].VirtualAddress;
    			return pSectionHeader[i].PointerToRawData + offset;
    		}
    	}
    	printf("找不到RVA %x 对应的 FOA,转换失败\n", dwRva);
    	return 0;
    }
    
    // FOA 转 RVA
    DWORD FoaToRva(LPVOID pFileBuffer, DWORD dwFoa)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pFileBuffer + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	// RVA在文件头中或者文件对齐==内存对齐时,RVA==FOA  错!第一句是对的,第二句是错的
    	if (dwFoa < pOptionHeader->SizeOfHeaders)
    	{
    		return dwFoa;
    	}
    
    	// 遍历节表,确定偏移属于哪一个节	
    	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
    	{
    		if (dwFoa >= pSectionHeader[i].PointerToRawData && \
    			dwFoa < pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData)
    		{
    			int offset = dwFoa - pSectionHeader[i].PointerToRawData;
    			return pSectionHeader[i].VirtualAddress + offset;
    		}
    	}
    	printf("找不到FOA %x 对应的 RVA,转换失败\n", dwFoa);
    	return 0;
    }
    
    // 移动NT头和节表到DOS STUB,该函数在新增节时节表空间不足的情况下调用;返回地址减小值
    DWORD MoveNTHeaderAndSectionHeadersToDosStub(LPVOID pFileBuffer)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	LPVOID pDst = (LPVOID)((DWORD)pDosHeader + sizeof(IMAGE_DOS_HEADER)); // NT头插入点
    	DWORD dwRet = (DWORD)pNTHeader - (DWORD)pDst; // 返回地址减小的值
    	DWORD dwSize = 4 + sizeof(IMAGE_FILE_HEADER) + pPEHeader->SizeOfOptionalHeader + \
    		sizeof(IMAGE_SECTION_HEADER) * pPEHeader->NumberOfSections; // 移动的字节数
    	LPVOID pSrc = malloc(dwSize);
    	if (pSrc == NULL)
    	{
    		printf("分配内存失败\n");
    		return 0;
    	}
    	memcpy(pSrc, (LPVOID)pNTHeader, dwSize); // 保存要复制的数据
    	memset((LPVOID)pNTHeader, 0, dwSize); // 清空原数据
    	memcpy(pDst, pSrc, dwSize); // 移动数据
    	free(pSrc);
    	pDosHeader->e_lfanew = sizeof(IMAGE_DOS_HEADER); // 更新 e_lfanew
    
    	return dwRet;
    }
    
    // 修改 ImageBase 并修复重定位表
    // 文件版本
    VOID SetNewImageBase(LPVOID pFileBuffer, DWORD dwNewImageBase)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
    	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + \
    		RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[5].VirtualAddress));
    	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	
    
    	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
    	// RVA + ImageBase 这个内存里存储了一个“指针”
    	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
    	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
    	{
    		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
    		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
    		for (size_t i = 0; i < n; i++)
    		{
    			// 高4位等于0011才需要重定位
    			if ((pOffset[i] & 0xF000) == 0x3000)
    			{
    				// 计算需要重定位的数据的RVA地址
    				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);
    				// 计算在文件中的偏移
    				DWORD dwFoa = RvaToFoa(pFileBuffer, dwRva);
    				// 计算在文件中的地址
    				PDWORD pData = (PDWORD)((DWORD)pFileBuffer + dwFoa);
    				// 重定位,即修正写死的地址				
    				*pData += dwImageBaseDelta;
    			}
    		}
    
    		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    	}
    	// 修改 ImageBase
    	pOptionHeader->ImageBase = dwNewImageBase;
    }
    
    // 修改 ImageBase 并修复重定位表
    // 内存镜像版本
    VOID SetNewImageBase2(LPVOID pImageBuffer, DWORD dwNewImageBase)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    	
    	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pImageBuffer + \
    		pOptionHeader->DataDirectory[5].VirtualAddress);
    	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	
    	
    	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
    	// RVA + ImageBase 这个内存里存储了一个“指针”
    	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
    	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
    	{
    		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
    		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
    		for (size_t i = 0; i < n; i++)
    		{
    			// 高4位等于0011才需要重定位
    			if ((pOffset[i] & 0xF000) == 0x3000)
    			{
    				// 计算需要重定位的数据的RVA地址
    				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);				
    				// 计算在镜像中的地址
    				PDWORD pData = (PDWORD)((DWORD)pImageBuffer + dwRva);
    				// 重定位,即修正写死的地址				
    				*pData += dwImageBaseDelta;
    			}
    		}		
    		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    	}
    	// 修改 ImageBase
    	pOptionHeader->ImageBase = dwNewImageBase;
    }
    
    
    // 传入一个imagebuffer,修复它的IAT表
    void RepairIAT(LPVOID pImageBuffer)
    {
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
    	PIMAGE_SECTION_HEADER pSectionHeader = \
    		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImageBuffer + \
    		pOptionHeader->DataDirectory[1].VirtualAddress);	
    	// 严格来说应该是 sizeof(IMAGE_IMPORT_DESCRIPTOR) 个字节为0表示结束
    	while (pImportTable->OriginalFirstThunk || pImportTable->FirstThunk)
    	{
    		// 打印模块名
    		//printf("%s\n", (LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		// 获取模块句柄
    		HMODULE hModule = LoadLibraryA((LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		if (NULL == hModule)
    		{
    			printf("获取模块句柄失败,模块名: %s\n",(LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
    		}
    		// 修复IAT表
    		//printf("--------------FirstThunkRVA:%x--------------\n", pImportTable->FirstThunk);		
    		PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)((DWORD)pImageBuffer + \
    			pImportTable->FirstThunk);
    		while (*((PDWORD)pThunkData) != 0)
    		{
    			// IMAGE_THUNK_DATA32 是一个4字节数据
    			// 如果最高位是1,那么除去最高位就是导出序号
    			// 如果最高位是0,那么这个值是RVA 指向 IMAGE_IMPORT_BY_NAME
    			if ((*((PDWORD)pThunkData) & 0x80000000) == 0x80000000)
    			{
    				//printf("按序号导入 Ordinal:%04x\n", (*((PDWORD)pThunkData) & 0x7FFFFFFF));
    				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,MAKEINTRESOURCE((*((PDWORD)pThunkData) & 0x7FFFFFFF)));				
    				*((PDWORD)pThunkData) = dwProcAddress;
    			}
    			else
    			{
    				PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)(*((PDWORD)pThunkData) + \
    					(DWORD)pImageBuffer);
    				
    				//printf("按名字导入 Hint:%04x Name:%s\n", pIBN->Hint, pIBN->Name);
    				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,(LPCSTR)pIBN->Name);
    				*((PDWORD)pThunkData) = dwProcAddress;
    			}
    			pThunkData++;
    		}
    		pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));		
    	}	
    }
    
    
    #endif
    

    main.cpp

    // MemoryInject.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "PE.h"
    #include <MALLOC.H>
    
    void GameInjectMe();
    void MeInjectGame();
    BOOL EnableDebugPrivilege();
    DWORD WINAPI InjectEntry(LPVOID param);
    
    int main(int argc, char* argv[])
    {
    	EnableDebugPrivilege(); 	
    	
    	//GameInjectMe();
    	MeInjectGame();
    	return 0;
    }
    
    
    // 将游戏模块注入到我的进程
    // 修改ImageBase=0x20000000,使本程序在高地址运行
    // 这种方式非常不推荐,因为游戏程序只要稍微大一些,就很可能无法在0x400000处申请内存了
    void GameInjectMe()
    {	
    	if ((DWORD)GetModuleHandle(NULL) != (DWORD)0x20000000)
    	{
    		printf("当前进程句柄: 0x%X\n", (DWORD)GetModuleHandle(NULL));
    		printf("请修改链接设置,将ImageBase设置为0x20000000\n");
    		return;
    	}
    	// 读取游戏PE,拉伸,修复IAT,写入到其ImageBase处
    	LPVOID pFileBuffer = NULL;
    	FileToMemory(TEXT("c:\\program32\\dbgview.exe"), &pFileBuffer);
    	LPVOID pImageBuffer = NULL;
    	DWORD dwImageSize = FileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));		
    	LPVOID pImageBase = VirtualAlloc((LPVOID)pOptionHeader->ImageBase, dwImageSize,MEM_COMMIT, PAGE_READWRITE);	
    	if (pImageBase != (LPVOID)pOptionHeader->ImageBase)
    	{
    		printf("错误码:0x%X 在ImageBase(0x%X)处VirtualAlloc失败\n",GetLastError(),pOptionHeader->ImageBase);
    		return;
    	}
    	// 修复IAT表
    	RepairIAT(pImageBuffer);	
    	memcpy(pImageBase, pImageBuffer, dwImageSize);
    	
    	// 跳转到游戏的入口点
    	DWORD dwOEP = pOptionHeader->AddressOfEntryPoint + pOptionHeader->ImageBase;
    	__asm{
    		jmp dwOEP
    	}	
    }
    
    // 将我的模块注入到游戏进程
    void MeInjectGame()
    {
    	// 获取自己的ImageBase和SizeOfImage
    	HMODULE hModule = GetModuleHandle(NULL);
    	LPVOID pImageBuffer = (LPVOID)hModule;	
    	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
    	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));	
    	// 拷贝到新的缓冲区
    	DWORD dwSizeOfImage = pOptionHeader->SizeOfImage;
    	pImageBuffer = malloc(dwSizeOfImage);
    	memcpy(pImageBuffer, hModule, dwSizeOfImage);
    	// 在游戏进程申请内存
    	HWND hWnd = FindWindowA(NULL, "LittleGame");
        DWORD dwPID = 0;
        GetWindowThreadProcessId(hWnd, &dwPID);
    	printf("进程id: %d\n", dwPID);	
    	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);
    	LPVOID pGameImageBase = VirtualAllocEx(hProcess,NULL,dwSizeOfImage,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    	if (NULL == pGameImageBase)
    	{
    		printf("在游戏进程申请内存失败,错误码: %d\n", GetLastError());
    		return;
    	}
    	// 写入到游戏进程前,先修复重定位表
    	SetNewImageBase2(pImageBuffer, (DWORD)pGameImageBase);
    	WriteProcessMemory(hProcess,pGameImageBase,pImageBuffer,dwSizeOfImage,NULL);
    	// 获取当前进程中,入口函数的地址(希望在注入后运行的那个函数),然后和基址相减得到偏移
    	DWORD dwProcOffset = (DWORD)InjectEntry - (DWORD)hModule + (DWORD)pGameImageBase;
    	// 创建远程线程,执行入口代码
    	CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)dwProcOffset,NULL,NULL,NULL);
    }
    
    DWORD WINAPI InjectEntry(LPVOID param)
    {
    	while (TRUE)
    	{
    		MessageBox(0,0,0,0);
    		Sleep(5000);
    	}
    	return 0;
    }
    
    // 提权函数:提升为DEBUG权限
    BOOL EnableDebugPrivilege()
    {
    	HANDLE hToken;
    	BOOL fOk=FALSE;
    	if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
    	{
    		TOKEN_PRIVILEGES tp;
    		tp.PrivilegeCount=1;
    		LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);
    		
    		tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
    		AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
    		
    		fOk=(GetLastError()==ERROR_SUCCESS);
    		CloseHandle(hToken);
    	}
        return fOk;
    }
    
    
    展开全文
  • Dll注入技术之驱动注入

    万次阅读 2019-01-04 18:56:12
    我这里驱动注入的技术是:采用驱动向目标进程插入APC执行LdrLoadDll函数加载Dll模块。 可以注入64位Dll到64位进程或注入32位Dll到32位进程。暂时没有实现跨位数。 APC(异步过程调用)是一种内核机制,它提供了一种...

    Dll注入技术之驱动注入

    0x0 技术简介

    实现环境

    系统:Windows 7 64bit

    工具:VS+WDK

    驱动注入

    我这里驱动注入的技术是:采用驱动向目标进程插入APC执行LdrLoadDll函数加载Dll模块。

    可以注入64位Dll到64位进程或注入32位Dll到32位进程。暂时没有实现跨位数。

    APC(异步过程调用)是一种内核机制,它提供了一种在特定线程上下文中执行定制例程的方法。一旦被分派,APC将异步转移目标线程的执行流以调用所选的例程。

    apc可分为两大类:

    1. 内核模式`APCs: APC`例程最终将执行内核模式代码。这些被进一步分为特殊的内核模式的apc和普通的内核模式的apc,但是我们不会详细讨论 [它们之间的细微差别]

    2. 用户模式`APCs: APC`例程最终将执行用户模式代码。只有当拥有apc的线程变得可警报时,才会发出用户模式apc。这是我们将在本节其余部分中讨论的APC类型。

    apc主要用于系统级组件,用于执行各种任务(例如促进I/O完成),但也可以用于DLL注入目的。从安全产品的角度来看,内核空间的APC注入提供了一种方便可靠的方法,可以确保特定模块被加载到(几乎)整个系统所需的每个进程中。

    0x1 主要思路

    R3:加载驱动,打开驱动,控制驱动(发送需要注入的进程Pid和要注入的Dll模块路径给驱动)。

    R0:1,通过进程Pid获取EProcess并判断是否为64位进程。

            2,附加到目标进程获取其ntdll.dll模块基址及模块中LdrLoadDll函数地址

            3,初始化ShellCode,赋值其中的参数和函数地址,申请空间拷贝代码

            4,执行ShellCode,查找可用线程插入执行代码APC,插入警醒线程APC

    0x2 主要代码

    整体流程部分

    初始化ShellCode

    查找执行线程

    插入APC代码部分

    0x3 实现效果

    注入x64dll到X64进程

    注入x86dll到wow64进程

    展开全文
  • DLL注入有很多方法,远线程,钩子,输入法,劫持,...现在流行且稳定的注入方法一般都要用驱动来实现了,驱动注入,APC,回调等都可以用驱动注入。 驱动部分代码: #include "apc_inject_test.h" #include "asm_...

    DLL注入有很多方法,远线程,钩子,输入法,劫持,APC等等。
    现在的好多进程都有保护,特别是一些游戏有各种保护如TP TS HS NP CD GPK BE SG3等等保护,这样常规的注入方法就没效果了。

    现在流行且稳定的注入方法一般都要用驱动来实现了,驱动注入,APC,回调等都可以用驱动注入。

    下载地址:https://download.csdn.net/download/qq1289671197/11869225

    驱动部分代码:

    #include "apc_inject_test.h"
    #include "asm_export.h"
    
    #ifdef ALLOC_PRAGMA
    #pragma alloc_text(INIT,DriverEntry)
    #pragma alloc_text(PAGE,DriverUnload)
    #pragma alloc_text(PAGE,ntLoadLibraryA)
    #pragma alloc_text(PAGE,WorkThreAd_Exec)
    #pragma alloc_text(PAGE,uSetTheApc_Exec)
    #pragma alloc_text(PAGE,KernelApcCAllBAck_Exec)
    #pragma alloc_text(PAGE,find_threAd_Exec)
    #endif
    
    BOOLEAN 
    ntLoadLibraryA(
        PCHAR dllPath
        )
    {
        NTSTATUS			status;
        HANDLE				hThreAd = NULL;
    
        if (strlen(dllPath) > 50)
        {
            KdPrint(("dllpath overflow\n"));
            return FALSE;
        }
        status = PsCreateSystemThread(&hThreAd,
            (ACCESS_MASK)0,
            NULL,
            (HANDLE)0,
            NULL,
            WorkThreAd_Exec,
            dllPath
            );
        if (!NT_SUCCESS(status))
        {
            KdPrint(("PsCreateSystemThread err\n"));
            return FALSE;
        }
        return TRUE;
    }
    
    VOID
    WorkThreAd_Exec(
        IN PVOID pContext
        )
    {
        POINTER			process = 0;
        POINTER         threAd = 0;
        POINTER			func_size = 0;
        POINTER			param_size = 0;
        HANDLE          hProcess = NULL;
        PKEVENT			pEvent = NULL;
        PVOID			func_address = NULL;
        PVOID			param_address = NULL;
        KAPC_STATE		ApcStAte = { 0 };
        PCHAR			dllPath = NULL;
        NTSTATUS        status = 0;
    
        dllPath = (PCHAR)pContext;
        //寻找一个进程的eprocess,和可以插入apc的线程的线程对象 
        if (!find_threAd_Exec(&process, &threAd))
        {
            KdPrint(("cAnnot find the right threAd\n"));
            PsTerminateSystemThread(STATUS_SUCCESS);
        }
        //申请一个event,用来提供通知
        pEvent = ExAllocatePool(NonPagedPool, sizeof(KEVENT));
        if (!pEvent)
        {
            KdPrint(("ExAllocatePool(pEvent) fAiled\n"));
            PsTerminateSystemThread(STATUS_SUCCESS);
        }
    
    #ifdef _WIN64
    #ifdef INJECT_WOW64
        func_size = sizeof(shellcode);
    #else
        func_size = (UCHAR*)call_loadlibrary_end - (UCHAR*)call_loadlibrary;
    #endif
    #else
        func_size = (UCHAR*)UserExec_end - (UCHAR*)UserExec;
    #endif
    
        KdPrint(("size: %d\n", func_size));
        param_size = 50;
        status = ObOpenObjectByPointer((PVOID)process,
            OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
            NULL,
            GENERIC_ALL,
            *PsProcessType,
            KernelMode,
            &hProcess
            );
        if (!NT_SUCCESS(status))
        {
            KdPrint(("ObOpenObjectByPointer false :%x\n", status));
            PsTerminateSystemThread(STATUS_SUCCESS);
        }
        //使用mdl在win7上注入系统进程崩溃  mdl没有执行权限  
        //看雪:MDL在NT5上还是可执行的,但是到了NT6上就不可执行了
        //ZwAllocateVirtualMemory内部有attach和detach进程操作
        //这里的内存释放的时机根据具体业务在判断吧
        status = ZwAllocateVirtualMemory(hProcess, &func_address, 0, &func_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!NT_SUCCESS(status))
        {
            KdPrint(("ZwAllocateVirtualMemory false :%x\n", status));
            PsTerminateSystemThread(STATUS_SUCCESS);
        }
        status = ZwAllocateVirtualMemory(hProcess, &param_address, 0, &param_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!NT_SUCCESS(status))
        {
            KdPrint(("ZwAllocateVirtualMemory false :%x\n", status));
            PsTerminateSystemThread(STATUS_SUCCESS);
        }
        //拷贝apc函数体和参数到用户地址空间
        KeStackAttachProcess((PEPROCESS)process, &ApcStAte);
        RtlZeroMemory(func_address, func_size);
    
    #ifdef _WIN64
    #ifdef INJECT_WOW64
        RtlCopyMemory(func_address, shellcode, func_size);
    #else
        RtlCopyMemory(func_address, call_loadlibrary, func_size);
    #endif
    #else
        RtlCopyMemory(func_address, UserExec, func_size);
    #endif
    
        RtlZeroMemory(param_address, param_size);
        RtlCopyMemory(param_address, dllPath, param_size);
        KeUnstackDetachProcess(&ApcStAte);
    
        KeInitializeEvent(pEvent, NotificationEvent, FALSE);
        //插入apc
        status = uSetTheApc_Exec(process, threAd, (POINTER)func_address, pEvent, param_address);
        if (NT_SUCCESS(status))
        {
            KeWaitForSingleObject(pEvent, Executive, KernelMode, FALSE, NULL);
            KdPrint(("apc inject success!\n"));
        }
        else
        {
            KdPrint(("apc inject failed!\n"));
        }
        ExFreePool(pEvent);
        PsTerminateSystemThread(STATUS_SUCCESS);
        KdPrint(("Never be here \n"));
    }
    
    NTSTATUS 
    uSetTheApc_Exec(
        POINTER			process,
        POINTER			threAd,
        POINTER			MAppedAddress,
        PKEVENT			pEvent,
        PCHAR			dllPath
        )
    {
        PKAPC			pkApc;
        BOOLEAN			ret;
        NTSTATUS		dwStAtus = STATUS_SUCCESS;
    
        *((CHAR*)threAd + USERAPCPENDING_OFFSET) = 1;
        pkApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
        if (pkApc == NULL)
        {
            KdPrint(("error:ExAllocAtePool\n"));
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    
    #if defined(_WIN64) && defined(INJECT_WOW64)
        //在64位系统中插入32位用户apc时apc函数地址需要求补后左移两位
        MAppedAddress = (~MAppedAddress + 1) << 2;
    #endif
    
        //初始化一个APC
        KeInitializeApc(
            pkApc,
            (PKTHREAD)threAd,
            OriginalApcEnvironment,
            (PKKERNEL_ROUTINE)KernelApcCAllBAck_Exec,
            NULL,
            (PKNORMAL_ROUTINE)MAppedAddress,//UserApcCAllBAck,
            UserMode,	//用户模式
            (PVOID)dllPath
            );
    
        //插入apc
        ret = KeInsertQueueApc(pkApc, pEvent, 0, 0);
        if (!ret){
            KdPrint(("KeInsertQueueApc err\n"));
            return STATUS_UNSUCCESSFUL;
        }
        return STATUS_SUCCESS;
    }
    
    VOID
    KernelApcCAllBAck_Exec(
        PKAPC Apc,
        PKNORMAL_ROUTINE *NormAlRoutine,
        IN OUT PVOID *NormAlContext,
        IN OUT PVOID *SystemArgument1,
        IN OUT PVOID *SystemArgument2
        )
    {
        PKEVENT		pEvent;
        //回调得到执行的时候应该是在KiDeliverApc内部用户apc的KernelRoutine得到执行的时候,而且其实他在normalRoutine之前也就是我们的apc函数之前执行的
        KdPrint(("NormAlContext: 0x%x\n", (POINTER)*NormAlContext));
        pEvent = (PKEVENT)*SystemArgument1;
        if (pEvent)
        {
            KeSetEvent(pEvent, IO_NO_INCREMENT, FALSE);
        }
        if (Apc)
        {
            ExFreePool(Apc);
        }
    }
    
    //找到合适的插入目标
    BOOLEAN
    find_threAd_Exec(
        OUT	POINTER	*process,
        OUT	POINTER	*threAd
        )
    {
        POINTER			eproc;
        POINTER			begin_proc;
        POINTER			ethreAd;
        POINTER			begin_threAd;
        PLIST_ENTRY		plist_Active_procs;
        PLIST_ENTRY		plist_threAd;
    
        //遍历进程
        eproc = (POINTER)PsGetCurrentProcess();
        if (!eproc)
        {
            return FALSE;
        }
        begin_proc = eproc;
        while (1)
        {
            //OBJECT_TABLE_OFFSET 没有句柄表就是死的进程
            if (0 == _stricmp((CHAR*)(eproc + IMAGEFILENAME_OFFSET), "mir.exe") && (PVOID)(*(POINTER*)((char*)eproc + OBJECT_TABLE_OFFSET)) != NULL)
            {
                break;
            }
            else
            {
                plist_Active_procs = (LIST_ENTRY*)(eproc + ACTIVEPROCESSLINKS_OFFSET);
                eproc = (POINTER)plist_Active_procs->Flink;
                eproc = eproc - ACTIVEPROCESSLINKS_OFFSET;
                if (eproc == begin_proc)
                {
                    return FALSE;
                }
            }
        }
        plist_threAd = (LIST_ENTRY*)(eproc + THREADLISTHEAD_OFFSET);
        ethreAd = (POINTER)plist_threAd->Flink;
        ethreAd = ethreAd - THREADLISTENTRY_OFFSET;
        KdPrint(("threAd: 0x%x\n", ethreAd));
    
        //遍历线程
        begin_threAd = ethreAd;
        while (1){
            KdPrint(("(*(POINTER*)((POINTER)ethreAd+TCB_TEB_OFFSET): 0x%x\n", *(POINTER*)((CHAR*)ethreAd + TCB_TEB_OFFSET)));
            if ((*(POINTER*)((POINTER)ethreAd + TCB_TEB_OFFSET) != 0))
            {
                break;
            }
            else{
                plist_threAd = (LIST_ENTRY*)(ethreAd + THREADLISTENTRY_OFFSET);
                ethreAd = (POINTER)plist_threAd->Flink;
                ethreAd = ethreAd - THREADLISTENTRY_OFFSET;
                KdPrint(("ethreAd: 0x%x\n", ethreAd));
                if (ethreAd == begin_threAd)
                {
                    return FALSE;
                }
            }
        }
        *process = eproc;
        *threAd = ethreAd;
        return TRUE;
    }
    
    VOID 
    DriverUnload(
        IN PDRIVER_OBJECT DriverObject
        )
    {
        if (dllPath)
        {
            ExFreePoolWithTag(dllPath, 'HTAP');
        }
        KdPrint(("DriverUnload\r\n"));
    }
    
    NTSTATUS 
    DriverEntry(
        IN PDRIVER_OBJECT DriverObject, 
        IN PUNICODE_STRING pRegistryString
        )
    {
        DriverObject->DriverUnload = DriverUnload;
        dllPath = ExAllocatePoolWithTag(PagedPool, 50, 'HTAP');
        if (dllPath)
        {
            RtlCopyMemory(dllPath, "C:\\MyHook.dll", 50);
            if (ntLoadLibraryA(dllPath))
            {
                return STATUS_SUCCESS;
            }
        }
        return STATUS_UNSUCCESSFUL;
    }
    
    #ifndef _WIN64
    __declspec(naked)
    UserExec(
        PCHAR	dllPath,
        PVOID	unused1,
        PVOID	unused2
        )
    {
        __asm
        {
            push        ebp
            mov         ebp, esp
            sub         esp, 150h
            push        ebx
            push        esi
            push        edi
            pushad
            pushfd
            lea         ecx, [ebp - 4]
            mov         ebx, dword ptr fs : [0x00000030]
            mov         ebx, dword ptr[ebx + 0x0C]
            mov         ebx, dword ptr[ebx + 0x0C]
            mov         ebx, dword ptr[ebx]
            mov         ebx, dword ptr[ebx]
            mov         eax, dword ptr[ebx + 18h]
            mov         dword ptr[ecx], eax
            popfd
            popad
            mov         eax, dword ptr[ebp - 4]
            mov         dword ptr[ebp - 28h], eax
            mov         eax, dword ptr[ebp - 28h]
            mov         ecx, dword ptr[ebp - 28h]
            add         ecx, dword ptr[eax + 3Ch]
            mov         dword ptr[ebp - 2Ch], ecx
            mov         esi, dword ptr[ebp - 2Ch]
            add         esi, 18h
            mov         ecx, 38h
            lea         edi, [ebp + 0xFFFFFEF4]
            rep movs    dword ptr es : [edi], dword ptr[esi]
            mov         eax, 8
            imul        ecx, eax, 0
            mov         edx, dword ptr[ebp - 4]
            add         edx, dword ptr[ebp + ecx + 0xFFFFFF54]
            mov         dword ptr[ebp + 0xFFFFFEF0], edx
            mov         eax, dword ptr[ebp + 0xFFFFFEF0]
            mov         ecx, dword ptr[ebp - 4]
            add         ecx, dword ptr[eax + 20h]
            mov         dword ptr[ebp - 18h], ecx
            mov         eax, dword ptr[ebp + 0xFFFFFEF0]
            mov         ecx, dword ptr[ebp - 4]
            add         ecx, dword ptr[eax + 24h]
            mov         dword ptr[ebp - 0Ch], ecx
            mov         eax, dword ptr[ebp + 0xFFFFFEF0]
            mov         ecx, dword ptr[ebp - 4]
            add         ecx, dword ptr[eax + 1Ch]
            mov         dword ptr[ebp - 14h], ecx
            mov         eax, dword ptr[ebp + 0xFFFFFEF0]
            mov         ecx, dword ptr[eax + 10h]
            mov         dword ptr[ebp - 1Ch], ecx
            mov         dword ptr[ebp - 20h], 0
            jmp         s1
        s5 :
            mov         eax, dword ptr[ebp - 20h]
            add         eax, 1
            mov         dword ptr[ebp - 20h], eax
        s1 :
            mov         eax, dword ptr[ebp + 0xFFFFFEF0]
            mov         ecx, dword ptr[ebp - 20h]
            cmp         ecx, dword ptr[eax + 14h]
            jae         s2
            mov         eax, dword ptr[ebp - 20h]
            mov         ecx, dword ptr[ebp - 18h]
            mov         edx, dword ptr[ebp - 4]
            add         edx, dword ptr[ecx + eax * 4]
            mov         dword ptr[ebp - 8], edx
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax]
            cmp         ecx, 4Ch
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 1]
            cmp         ecx, 6Fh
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 2]
            cmp         ecx, 61h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 3]
            cmp         ecx, 64h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 4]
            cmp         ecx, 4Ch
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 5]
            cmp         ecx, 69h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 6]
            cmp         ecx, 62h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 7]
            cmp         ecx, 72h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 8]
            cmp         ecx, 61h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 9]
            cmp         ecx, 72h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 0Ah]
            cmp         ecx, 79h
            jne         s3
            mov         eax, dword ptr[ebp - 8]
            movsx       ecx, byte ptr[eax + 0Bh]
            cmp         ecx, 41h
            jne         s3
            mov         eax, dword ptr[ebp - 20h]
            mov         ecx, dword ptr[ebp - 0Ch]
            movzx       edx, word ptr[ecx + eax * 2]
            mov         eax, dword ptr[ebp - 1Ch]
            lea         ecx, [edx + eax - 1]
            mov         dword ptr[ebp - 10h], ecx
            mov         eax, dword ptr[ebp - 10h]
            mov         ecx, dword ptr[ebp - 14h]
            mov         edx, dword ptr[ebp - 4]
            add         edx, dword ptr[ecx + eax * 4]
            mov         dword ptr[ebp - 24h], edx
            mov         eax, dword ptr[ebp + 8]
            push        eax
            call        dword ptr[ebp - 24h]
            mov         al, 1
            jmp         s4
        s3 :
            jmp         s5
        s2 :
            xor         al, al
        s4 :
            pop         edi
            pop         esi
            pop         ebx
            mov         esp, ebp
            pop         ebp
            ret
        }
    }
    __declspec(naked)
    UserExec_end(VOID)
    {
    
    }
    #endif
    
    
    展开全文
  • 驱动级进程保护 隐藏 注入 W7 W8 W10 32 64位都支持 不蓝屏
  • 米锘_驱动注入1.5

    2017-01-26 21:18:12
    米锘_驱动注入DLL (“crossfire.exe”, #DLL) 米锘_驱动注入DLL (“crossfire.exe”, #DLL) 米锘_驱动注入DLL (“crossfire.exe”, #DLL)
  • 内存注入dll 痕迹

    2018-08-22 12:47:45
    内存中 注入dll 需要黑月编译 任何32位进程
  • 驱动级dll注入源码

    2012-06-06 15:28:17
    这个可是非常好的源码 没有使用任何模块 放心下载 绝对不留后门,不修改IE,卸载dll一直有点小问题,希望大家多交流
  • 易语言源码易语言驱动级读写内存模块源码.rar 易语言源码易语言驱动级读写内存模块源码.rar 易语言源码易语言驱动级读写内存模块源码.rar 易语言源码易语言驱动级读写内存模块源码.rar 易语言源码易语言驱动级...
  • 驱动模块-源码

    2021-02-04 16:15:24
    驱动模块 drozer(以前为Mercury)是Android的领先安全测试框架。 drozer允许您通过承担应用程序的角色并与Dalvik VM,其他应用程序的IPC端点和基础操作系统进行交互来搜索应用程序和设备中的安全漏洞。 drozer...
  • 今天一个朋友给我发了一个说是可以过TP的注入驱动,希望我逆一下看看是什么套路。 正文: 接收过来压缩包,看了一下 loadddll32和64分别是用在32和64的驱动,MemOpe和dnf.exe都是测试用例(不过我是用自己写的123....

    前言:

    逆一些东西,有点收获,就写篇随笔记录一下。因为写的随便,就不发看雪了。
    今天一个朋友给我发了一个说是可以过TP的注入驱动,希望我逆一下看看是什么套路。

    正文:

    接收过来压缩包,看了一下
    在这里插入图片描述
    loadddll32和64分别是用在32和64的驱动,MemOpe和dnf.exe都是测试用例(不过我是用自己写的123.exe进行测试)。

    先跑起来,看看线程和模块上有没有什么特别的地方
    在这里插入图片描述
    在这里插入图片描述有一个线程Pchunter标红,而且“模块”那里没有,很可疑,先记下。
    进程模块这里出现了注入的MemOpe.dll,看来不是内存加载。

    为了减小干扰,先试试把“驱动级别的dll注入器.exe”进程强关了,看驱动还有没有效果。
    测试了下仍可以正常注入。说明整个注入过程全由驱动完成,其加载驱动的exe不参与。

    由于是x86sys程序,x64dbg用不上,只好拿IDA
    (顺便吐槽一下,之前尝试用windbg动态调试看看。那用的叫一个难受,单步1步,windbg的反汇编窗口卡1秒。watch窗口也看不了一些[ebp-xx]之类的变量数据,可能是我还不会用)

    代码很干净,函数很少。在这里插入图片描述先看看输入表。在这里插入图片描述
    注意框起来的API,看到这几个api。我想到了ssdt,attach进程,创建线程,设置模块加载回调,申请r3内存空间。
    接下来看看都有什么操作。
    1.和加载驱动的exe通信,获取目标进程名和要注入的dll路径在这里插入图片描述
    2.设置模块加载回调
    在这里插入图片描述
    再看看回调里面有什么东西

    3.判断LoadImage的进程是不是目标进程>>判断加载的模块是不是ntdll.dll>>开一个内核线程
    注意这个ntdll
    在这里插入图片描述
    再来看看内核线程执行的函数里面有什么

    4.Attach到目标进程(之前Pchunter看到的标红线程应该就是这个原因)
    取R3和R0的ZwProtectVirtualMemory,ZwWriteVirtualMemory,ZwReadVirtualMemory,ZwQueryVirtualMemory,LdrLoadDll(无R0),ZwTestAlert(无R0) 地址
    在这里插入图片描述5.往目标进程申请一片full_access内存放shellcode,并对其用到的API地址进行重定位,同时对ntdll!ZwTestAlert下了一个HOOK,用来跳转到自己的shellcode
    (一个进程被Create,ntdll最先加载,再由其加载kernel,user等其他系统DLL。由于ntdll上的线程会经过ntdll!ZwTestAlert这个API,所以对其下Hook来跳转执行自己的shellcode实现DLL加载)
    在这里插入图片描述在这里插入图片描述
    效果图:在这里插入图片描述
    在这里插入图片描述

    总结一下大致流程:

    1.目标进程创建>>
    2.驱动通过Load_Image回调发现目标进程及其Ntdll>>
    3.对其Ntdll线程必经的ntdll!ZwTestAlert下Hook,同时写入shellcode>>
    4.Ntdll线程经过ntdll!ZwTestAlert并跳转到shellcode>>
    5.执行LdrLoadDll加载目标DLL>>
    6.DLL注入完成

    尾声:

    感谢xjun前辈的指点

    (小声bb一句,这全是破绽的驱动注入能过TP,我觉得TP真是放水了。。。)
    说实话,一开始逆的时候。逆到那个Shellcode操作的地方,怎么都看不明白,还以为注入是靠的内核线程attach了目标进程,然后亲自调用了什么API才注入的。这里看不懂,就只能先看其他函数,全部看遍了也没有相关的API和操作。没办法,试试windbg,动态看看对shellcode的操作。。。结果怎么样,前面也吐槽了。
    最后还是配合着看线程环境(看到注入dll的是ntdll模块上的线程),看目标进程里的shellcode才分析出来具体原理。

    虽然很想发到看雪,不过拙文一篇,就不去献丑了。权当是自娱自乐了

    展开全文
  • Java知识体系最强总结(2021版)

    万次阅读 多人点赞 2019-12-18 10:09:56
    其他 Zookeeper 微服务与分布式 Spring Boot Spring Cloud 服务注册发现 服务配置 负载均衡 服务调用 服务限流 熔断降级 网关路由 服务权限 链路追踪 分布式事务 分布式缓存 分布式会话 日志收集 服务监控 消息驱动 ...
  • 在Linux内核中增加新驱动模块

    千次阅读 2018-11-03 09:29:46
    开发环境 开发板:A33-Vstar 开发板系统: Linux/arm 3.4.39 Kernel Ubuntu版本:Ubuntu14.04 ...新增内核驱动,并可以通过make menuconfig配置。 内核完整路径:~/A33-Vstar/dragonboard/linux...
  • 驱动万能注入

    2012-06-06 15:29:33
    这个可是非常好的源码 没有使用任何模块 放心下载 绝对不留后门,不修改IE,卸载dll一直有点小问题,希望大家多交流
  • inject.rar

    2019-10-16 12:46:55
    DLL注入有很多方法,远线程,钩子,输入法,劫持,APC等等。 现在的好多进程都有保护,特别是一些游戏有各种保护如TP TS ...现在流行且稳定的注入方法一般都要用驱动来实现了,驱动注入,APC,回调等都可以用驱动注入
  • 进程_隐藏模块 //隐藏后,连驱动都无法再枚举检测出来 修正BUG命令: -------------------------- 特征码_搜索地址 //修正有时候得到的地址会多加1问题 进程_取窗口句柄 //修复取窗口句柄值不对问题 CPU_取序列号...
  • MyBatis面试题(2020最新版)

    万次阅读 多人点赞 2019-09-24 16:40:33
    为什么需要预编译 定义: SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。 为什么需要预编译 JDBC 中使用对象 PreparedStatement 来...
  • 可以查看进程模块,检测DLL注入,手动清除木马病毒神器。
  • 易语言隐藏进程模块支持WIn7 64位,保护进程 ,防破解,防注入,防Ce,WPE
  • 为老蓝天主板BIOS注入NVME模块

    千次阅读 2020-01-31 21:55:21
    固态现在很便宜,1GB只要1块多,看了看自己的电脑支持MkeyPCIEx4的接口,索性就买了个支持NVMe的固态(以后再买电脑直接就把这个固态放新电脑上用了)。 然而买来之后发现BIOS不支持NVMe…启动时...增加NVMe模块...
  • Java框架总结

    万次阅读 多人点赞 2020-01-17 14:14:13
    体现了拦截器的使用,拦截器是一个一个的小功能模块,用户可以将这些拦截器合并成一个大的拦截器,这个合成的拦截器就像单独的拦截器一样,只要将它配置到一个Action中就可以。 (4)Struts2的缺点: 校验较繁琐,...
  • Java知识体系最强总结(2020版)

    千次阅读 多人点赞 2020-03-07 08:35:51
    服务配置 负载均衡 服务调用 服务限流 熔断降级 网关路由 服务权限 链路追踪 分布式事务 分布式缓存 分布式会话 日志收集 服务监控 消息驱动 数据处理流 自动化测试与部署 第三方支持 分布式协调服务Zookeeper ...
  • 前端面试题

    万次阅读 多人点赞 2019-08-08 11:49:01
    前端面试题汇总 ... 你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么? 21 ... 21 Quirks模式是什么?它和Standards模式有什么区别 21 div+css的布局较table布局有什么优点?...img的alt与title有何异同?...
  • vue面试题

    千次阅读 多人点赞 2019-08-12 17:02:53
    vue面试题 1.Vue和react的相同与不同 相同点: ...数据驱动视图 都有支持native的方案,react native,Vue的weex 都有管理状态,react有redux,vue有自己的VueX 不同点: react严格上只针对MVC的view...
  • spring面试

    千次阅读 2019-06-06 09:25:38
    什么是 Spring 框架? Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。...这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块...
  • vue面试题总汇

    千次阅读 多人点赞 2019-02-22 15:26:53
    能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。 然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受...
  • Springboot

    千次阅读 2018-12-06 11:09:34
    # 0.学习目标 - 了解SpringBoot的作用 - 掌握java配置的方式 - 了解SpringBoot自动配置原理 - 掌握SpringBoot的基本使用 - 了解Thymeleaf的基本使用 # 1....在这一部分,我们主要了解以下3个问题: ...
  • 【数据库学习】数据库总结

    万次阅读 多人点赞 2018-07-26 13:26:41
    自我理解1NF就是重复的列。 如:(X1,X2)→X3,X2→X3 其中x3对x2部分依赖 如:(X1,X2)→X3,X2→X4 其中有非主属性X4部分依赖于候选键{X1,X2},所以这个关系模式不为第二范式;又因为范式之间的关系满足1NF...
  • 驱动保护隐藏.ec

    2019-08-08 14:37:42
    易语言辅助必备驱动保护模块 代码公开 透明 绝暗装之类 ------------------------------ .版本 2 .子程序 关闭保护辅助进程, 逻辑型, 公开, 取消禁止结束并保护程序 .参数 进程ID, 整数型, 可空, 可空,默认...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,618
精华内容 15,847
关键字:

无模块驱动注入