注:本实验涉及的代码等仅限于学习目的,请勿用于非法目的,违者后果自负,与本网站无关。
必备API相关知识参考《windows中重要的API略解》
基本原理:
1、程序一定调用了kernel32.dll
2、利用Loadlibrary可以载入其他dll
3、结合GetProcAddress可以获得任意API调用的具体地址
4、使用DLL显式调用API(参考《DLL的两种调用方法》)
基本思路:
1、获得kernel32.dll的地址
2、获得LoadLibrary和GetProcAddress的地址
3、显式调用LoadLibrary和GetProcAddress获得其他API地址
4、显式调用得到的API
关键函数的编写
1、GetKernel32Addr()
用途:获取Kernel32.dll载入到进程中的位置
参数:无参数
输出:返回值为Kernerl32.dll载入的基址
原理:fs寄存器指向进程的TEB块,TEB的0x30位置为指向PEB的指针
PEB的Ldr(0x0c)为指向 _PEB_LDR_DATA结构体的指针
_PEB_LDR_DATA的InMemoryOrderModuleList(0x1c)为一个
双向链表
双向链表Flink指向_LDR_DATA_TABLE_ENTRY机构体的某个成
员(0x08)
_LDR_DATA_TABLE_ENTRY包含FullDllName和DllBase等信息
FullDllName即为模块名,DllBase即为模块载入基址
比较模块名即可找到kernel32.dll的基址
判断尾节点是否等于开始节点可以判断是否查找结束
原理图示意:
代码实现:
DWORD GetKernel32Addr() { WCHAR kernel32[]={'k','e','r','n','e','l','3','2','.','d','l','l','\x00'}; DWORD i=0; PPEB peb=(PPEB)__readfsdword(0x30);//获得PE头 LIST_ENTRY *first=peb->Ldr->InMemoryOrderModuleList.Flink; //模块头指针,指向下一个模块 LIST_ENTRY *pDataTableEntry=first;//载入模块 do{ LDR_DATA_TABLE_ENTRY *dte= (LDR_DATA_TABLE_ENTRY*)((BYTE*)pDataTableEntry-0x8); //dte为当前运行的模块,遍历模块找到kernel32.dll pDataTableEntry=pDataTableEntry->Flink;//下一个模块 const wchar_t *dllname= ((decltype(dte->FullDllName)*)(DWORD*)&(dte->Reserved4))->Buffer; //截取模块名 //下面为自己做的unicode字符串比较方法,可以采用hash方法比较 const wchar_t *dllname_t=dllname; while(*dllname_t){//模块名比较 if(kernel32[i]==(BYTE)*dllname_t)i++; else if((BYTE)*dllname_t<'a') if((BYTE)*dllname_t+0x20==kernel32[i])i++;//统一大小写 dllname_t++; } if(i==12){ return (DWORD)dte->DllBase; break; } } while(pDataTableEntry!=first) return 0; }
2、GetAPIAddress(unsigned char * pDllBase,char * ApiName)
用途:获取API函数地址(主要是Loadlibrary和GetProcAddress,其他在kernel32.dll中的API也可以通过此办法获取)
原理:
获得kernel32.dll的PE头(dos头位置为GetKernel32Addr()返回的地
址)
获得导出表的地址(PE头知识参看《PE文件结构初探》)
遍历查找函数名表获得相应函数的函数名序号
函数名序号(从0开始)等于NumberOfNames时表示未找到并结束
将函数名序号带入函数序号表查找对应的函数序号
Tips:因为导出方式不止有名字导出,所以函数名序号不能直接用于查找
函数地址,应通过函数序号表转化后查找
将函数序号带入函数地址表查找对应的函数地址
函数地址加上基址即为函数的RVA
原理图示意:
导出表结构:
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; //导出函数总数 DWORD NumberOfNames; //通过名称导出的函数总数 DWORD AddressOfFunctions; // 指向导出函数表的RVA DWORD AddressOfNames; // 指向函数名表的RVA DWORD AddressOfNameOrdinals; // 指向函数序号表的RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
代码实现:
DWORD GetAPIAddress(unsigned char * baseAddress,char * ApiName) { unsigned char * DllApiName; BYTE*baseAddress = (BYTE*)Kernel32Addr; PIMAGE_DOS_HEADER dosHeader=(PIMAGE_DOS_HEADER)baseAddress; PIMAGE_NT_HEADERS peHeader = (PIMAGE_NT_HEADERS) (baseAddress+ dosHeader->e_lfanew); //获得PE头 PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)&baseAddress [peHeader->OptionalHeader.DataDirectory[0].VirtualAddress]; //获得导出表 int index=0; while(index<ied->NumberOfNames) { DllApiName=baseAddress+*((DWORD *) &baseAddress[ied->AddressOfNames + (index << 2)]) ; //遍历函数名表 int i=0;//实现字符串对比 char *p=ApiName; while(*p){ if((BYTE)*p!=(BYTE)*DllApiName)break; p++; DllApiName++; i++; } if (i==strlen(ApiName)) break; index ++; } if (index == ied->NumberOfNames) return 0; index = ((WORD *)&baseAddress[ied->AddressOfNameOrdinals])[index]; return ((DWORD *)&baseAddress[ied->AddressOfFunctions])[index] + peHeader->OptionalHeader.ImageBase; }