本帖最后由 52HB 于 2024-06-09 16:54 编辑
Z0-导入: 当你尝试破解一个程序时,是否会关注其WindowsAPI调用?例如常见的CreateThread,OpenProcess,MessageBox等。
显然,如果可以定位到程序调用的API,可以极大降低我们的破解难度。反之,如果无法定位到程序调用的API,甚至是不知道调用了哪些API,岂不是难上加难? Z1-SysCall(系统调用)流程: Windows系统提供了两种处理器访问模式:用户模式(user mode)和内核模式(kernel mode)。通常情况下,应用程序运行在用户层,具有一段相对独立的虚拟内存,因此无法访问其他空间的内存;可是内核中包含了大量操作系统的内部数据结构,应用程序难免需要访问这些数据结构或调用内部Windows例程以执行特权操作,此时必须先从用户模式切换到内核模式,这里就涉及到SysCall(系统调用)。 举个简单的例子,OpenProcess调用流程:call OpenProcess-->kernel32.dll.OpenProcess-->ntoskrnl.exe.NtOpenProcess-->SysCall 当在程序代码段Call OpenProcess时,先跳转到kernel32.dll.OpenProcess,执行以下代码mov edi,edi push ebp mov ebp,esp sub esp,24 mov eax,dword ptr ss:[ebp+10] xor ecx,ecx mov dword ptr ss:[ebp-C],eax mov eax,dword ptr ss:[ebp+C] neg eax mov dword ptr ss:[ebp-8],ecx mov dword ptr ss:[ebp-24],18 sbb eax,eax mov dword ptr ss:[ebp-20],ecx and eax,2 mov dword ptr ss:[ebp-1C],ecx mov dword ptr ss:[ebp-18],eax lea eax,dword ptr ss:[ebp-C] push eax lea eax,dword ptr ss:[ebp-24] mov dword ptr ss:[ebp-14],ecx push eax push dword ptr ss:[ebp+8] lea eax,dword ptr ss:[ebp-4] mov dword ptr ss:[ebp-10],ecx push eax call dword ptr ds:[<&NtOpenProcess>]
执行完毕后,会通过call dword ptr ds:[<&NtOpenProcess>]跳转到ntoskrnl.exe.NtOpenProcess,执行SysCall mov eax,26 mov edx,ntdll.77938F70 syscall edx ret 10
需要注意的是mov eax,26这条指令,其代表着需要调用的“函数标号”,不同函数具有不同的标号,只有为26时,才是调用NtOpenProcess 显而易见的是,API的调用流程十分繁琐与明显,导致极易定位乃至hook函数的调用,这也让逆向分析有了可乘之机。那么我们如何解决这个问题呢? 方法1:程序内复写WindowsAPI,不调用任何dll 弊端:呃?勇气可嘉,祝福你早日写完! 方法2:利用GetProcessAddress获取函数地址进行调用 弊端:GetProcessAddress过于敏感,下断即可拦截信息
看来以上方法不太行得通,难道没有解决方法了吗? 诶,似乎就算调用WindowsAPI,其底层也是通过SysCall实现的啊。那么,如果我们直接通过对应的”函数标号“进行SysCall,岂不妙哉?
Z2-C++模拟SysCall流程: P1:动态获取所需dll的基址,此处以ntdll为例,如果你需要用到其他的dll,方法大相径庭。 //获取ntdll基址[/font][/color] [color=#acbac7][font=system-ui, -apple-system, BlinkMacSystemFont, "]void* GetNtDllBase() { //通过4次偏移从gs寄存器中取得ntdll基址 //用户模式时,gs指向TEB寄存器 ULONG64 peb = __readgsqword(0x60);//从TEB中获取PEB ULONG64 ldr = *(ULONG64*)(peb + 0x18);//从PEB中获取LDR PLIST_ENTRY modList = *(PLIST_ENTRY*)(ldr + 0x10);//从LDR中获取保存模块信息的链表 return *(void**)((ULONG64)modList->Flink+0x30);//从modList中获取ntdll基址 }
P2:实现自己的hash算法,你也可以用其他的算法。 这里用到hash算法的原因是,我们需要先将需要调用的API名称转为hash值,再通过hash值匹配对应的函数。这样可以避免出现明文字符串 //国际知名算法---djb2,你也可以用其他的 DWORD64 djb2(PBYTE str) { DWORD64 dwHash = 0x52194628;//随意设定 int c; while (c = *str++) dwHash = ((dwHash << 0x5) + dwHash) + c; return dwHash;//返回hash }
P3:获取SysCall系统调用号,也就是先前的所称的”函数标号“ //利用hash匹配系统调用号 int GetSystemCallIndex(DWORD64 hash) { //依旧是多次偏移获取目标 BYTE* ntdllBase = (BYTE*)GetNtDllBase();//获取ntdll基址 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ntdllBase;//获取Dos头,直接强转就行了 PIMAGE_FILE_HEADER pFile = (PIMAGE_FILE_HEADER)(ntdllBase + pDos->e_lfanew + 4);//e_lfanew是Dos头末尾,+4跳过5045 PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((BYTE*)pFile + IMAGE_SIZEOF_FILE_HEADER);//获取程序可选头 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(ntdllBase + pOptional->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);//获取导出表 DWORD numberOfFunc = pExport->NumberOfFunctions;//获取函数个数 DWORD numberOfName = pExport->NumberOfNames;//获取函数名个数 DWORD* pEAT = (DWORD*)(ntdllBase + pExport->AddressOfFunctions);//获取导出地址表 DWORD* pENT = (DWORD*)(ntdllBase + pExport->AddressOfNames);//导出名称表 WORD* pEIT = (WORD*)(ntdllBase + pExport->AddressOfNameOrdinals);//导出序号表 //遍历ntdll内函数 for (size_t i = 0; i < numberOfFunc; i++) { for (size_t j = 0; j < numberOfName; j++) { if (i == pEIT[j]) { BYTE* fnName = (BYTE*)(ntdllBase + pENT[j]); if (hash == djb2(fnName))//如果有函数名称的hash和传入的目标API名称的hash相同 { return *(DWORD*)(ntdllBase + pEAT + 4);//返回该函数的系统调用号 } } } } return -1;//如果未找到,返回-1 }
P4:ASM实现SysCall .data ;全局变量 存放需要模拟的API的系统调用号 SysCallId dword 0h .code ;将传入的Id赋值给SysCallId MySysCallWrapper proc mov SysCallId,0 mov SysCallId,ecx ret MySysCallWrapper endp ;模拟SysCall流程 MySysCall proc mov r10,rcx mov eax,SysCallId syscall ret MySysCall endp end
在cpp文件中声明两个函数 extern "C" VOID MySysCallWrapper(DWORD id); extern "C" VOID MySysCall(...);
P5:调用测试,这里用的是NtCreateThreadEx函数,你可以换成任何函数 void MyThread() { cout << "OK!" << endl; } int main() { //此处以NtCreateThreadEx为例 //cout << hex << djb2((BYTE*)"NtCreateThreadEx") << endl; //对应的hash为0xe4856a46f7313853 DWORD id = GetSystemCallIndex(0xe4856a46f7313853);//通过hash匹配对应函数标号 MySysCallWrapper(id);//将获取到的id传入 //调用SysCall HANDLE hThread; MySysCall(&hThread, PROCESS_ALL_ACCESS, 0, GetCurrentProcess(), MyThread,0,0,0,0,0,0); system("pause"); return 0; }
Z3-测试: 运行程序后,可以观察到线程已经成功创建,并且输出OK! 拖入调试工具中可以发现,函数堆栈调用中并没有关于NtCreateThreadEx的相关信息 导入表内也找不到NtCreateThreadEx,甚至没有ntdll的调用 这样,NtCreateThreadEx的调用就被我们成功模拟了。现在,无论是API断点还是APIHook,均无法定位并拦截我们的调用,极大的增添了破解者的分析难度与分析成本! 下方隐藏内容为本帖所有文件或源码下载链接:
游客你好,如果您要查看本帖隐藏链接需要登录才能查看,
请先登录
|