吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 657|回复: 7

[原创逆向图文] 反逆向分析技术之模拟SysCall流程

[复制链接]
吃麻花麻花疼 发表于 2024-5-8 13:02 | 显示全部楼层 |阅读模式

本帖最后由 吃麻花麻花疼 于 2024-5-8 20:12 编辑

反逆向分析技术之模拟SysCall流程

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,执行以下代码
[Asm] 纯文本查看 复制代码
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
[Asm] 纯文本查看 复制代码
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,方法大相径庭。
[C++] 纯文本查看 复制代码
//获取ntdll基址[/font][/color]
[color=#acbac7][font=system-ui, -apple-system, BlinkMacSystemFont, &quot;]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值匹配对应的函数。这样可以避免出现明文字符串
[Java] 纯文本查看 复制代码
//国际知名算法---djb2,你也可以用其他的
DWORD64 djb2(PBYTE str)
{
        DWORD64 dwHash = 0x52194628;//随意设定

        int c;
        while (c = *str++)
                dwHash = ((dwHash << 0x5) + dwHash) + c;

        return dwHash;//返回hash
}

P3:获取SysCall系统调用号,也就是先前的所称的”函数标号
[C++] 纯文本查看 复制代码
//利用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
[Asm] 纯文本查看 复制代码
.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文件中声明两个函数
[C++] 纯文本查看 复制代码
extern "C" VOID MySysCallWrapper(DWORD id);
extern "C" VOID MySysCall(...);

P5:调用测试,这里用的是NtCreateThreadEx函数,你可以换成任何函数
[C++] 纯文本查看 复制代码
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的相关信息
forum.png
导入表内也找不到NtCreateThreadEx,甚至没有ntdll的调用
forum.png
这样,NtCreateThreadEx的调用就被我们成功模拟了。现在,无论是API断点还是APIHook,均无法定位并拦截我们的调用,极大的增添了破解者的分析难度分析成本

Z4-结语:
模拟SysCall流程的方法有许多,各种解决方案也非常精彩,本文采用的是Hell'sGate地狱之门技术。学习该技术期间,浏览了许多大神的帖子与视频,本想在结尾加上链接,但恐有引流之嫌。在此由衷感谢各位!初次发帖,难免漏洞百出,各位请多包涵!附上完整项目链接



评分

参与人数 4威望 +1 HB +26 THX +4 收起 理由
诸葛村夫 + 2 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
龙龙飞凤舞 + 2 + 1
Shark恒 + 1 + 20 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
boot + 2 + 1

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| 吃麻花麻花疼 发表于 2024-5-8 13:26 | 显示全部楼层

因为不太熟悉发帖格式,导致发帖后多次修改,麻烦审核了!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
lies 发表于 2024-5-10 16:12 | 显示全部楼层

谢谢分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
boot 发表于 2024-5-12 20:39 | 显示全部楼层

吃麻花麻花疼 发表于 2024-5-8 13:26
因为不太熟悉发帖格式,导致发帖后多次修改,麻烦审核了!

此源码只支持64位编译;是否添加支持32位的编译?
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| 吃麻花麻花疼 发表于 2024-5-12 21:56 | 显示全部楼层

boot 发表于 2024-5-12 20:39
此源码只支持64位编译;是否添加支持32位的编译?

32位只需要更改获取ntdll部分的基址偏移与syscall时使用的寄存器就OK了,我写的属于简化版的Hell'sGate,省略了很多处理,只算是入门,更深入的了解看原论文与源码会更好
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
boot 发表于 2024-5-12 22:25 | 显示全部楼层

吃麻花麻花疼 发表于 2024-5-12 21:56
32位只需要更改获取ntdll部分的基址偏移与syscall时使用的寄存器就OK了,我写的属于简化版的Hell'sGate, ...

是的。对于32位,获取ntdll的基址,可以通过声明裸函数,然后写内联汇编解决。我还差的是mysyscall32.asm的实现。我用xDbg对比了64位和32位的调用,发现32位的不一样:虽然获取到了ntdll的基址,但是获取NtCreateThreadEx的序列号失败。要不你花时间试试?Hell'sGate的原项目也是缺少32位的实现。我查了资料,64位的是syscall,32位的貌似是int 80h……
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| 吃麻花麻花疼 发表于 2024-5-12 23:07 | 显示全部楼层

boot 发表于 2024-5-12 22:25
是的。对于32位,获取ntdll的基址,可以通过声明裸函数,然后写内联汇编解决。我还差的是mysyscall32.asm ...

我的问题,确实说错了,windows对于32位的程序,是通过中断机制(INT2E)实现的系统调用,并不支持syscall,但是仍然可以通过将系统调用号存入EAX中匹配对应API进行调用。地狱之门的原生设计似乎不支持32位,我不确定简单的复制32位下的系统调用汇编代码,进行替换是否可行,等我研究研究具体实现。
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
诸葛村夫 发表于 2024-5-13 13:20 | 显示全部楼层

emmmm,等我把你这文章研究明白了我去请教一下恒,问问遇到这种情况又有何种逆向思路
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表