Contents

[从零开始的免杀生活]06.销声匿迹·下

0x00.序

时隔两个月又滚来更新。。没人看就当自己记录吧 上篇介绍了Srdi,通过使用ldrLoad

0x01.什么是系统调用

在win下要从R3(用户模式) 进 R0需要通过调用中断来实现 在x86下win使用sysenter 或者int 编号进r0 x64下通过syscall实现

0x02.sysCall 的作用

在R3创建进程时,我们调用的API函数都是在NTDLL里面统一进行系统调用的,所以edr只要hook了ntdll里面的所有函数就可以进行拦截和检测我们调用了哪些api函数,为了避免在用户层被EDR hook的敏感函数检测到敏感行为,利用从ntdll中读取到的系统调用号进行系统直接调用来绕过敏感API函数的hook。

0x03.syscall的编写

以x64的ntdll为例 我们使用ida https://z1.ax1x.com/2023/11/08/pi1qYWj.jpg 进入NtCreateThreadEx 看到反汇编中的代码 如下图 https://z1.ax1x.com/2023/11/08/pi1q0mV.jpg 可以看到函数进0环的调用形式,高版本win的ntdll会先进行判断一下是否支持syscall如果不支持就使用int 2E中断调用,这边就直接默认他支持syscall(现在应该没有不支持的cpu了吧

mov     r10, rcx        
mov     eax, 调用号
syscall     
retn

所以我们编写时只需要知道当前系统的函数调用号就可以模拟ntdll进行系统调用,下面用VS给大家演示

Setp1.新建一个asm文件

https://z1.ax1x.com/2023/11/08/pi1qdO0.jpg

.code

NtCreateThreadEx proc
   mov r10,rcx
   mov eax,0C7h
   syscall
   ret
NtCreateThreadEx endp

end

Setp2.添加依赖项

生成依赖项 -> 生成自定义 -> 勾选masm https://z1.ax1x.com/2023/11/08/pi1qayq.png

https://z1.ax1x.com/2023/11/08/pi1qUln.jpg

Setp3.添加函数声明

EXTERN_C NTSTATUS NtCreateThreadEx
(
    OUT PHANDLE hThread,
    IN ACCESS_MASK DesiredAccess,
    IN PVOID ObjectAttributes,
    IN HANDLE ProcessHandle,
    IN PVOID lpStartAddress,
    IN PVOID lpParameter,
    IN ULONG Flags,
    IN SIZE_T StackZeroBits,
    IN SIZE_T SizeOfStackCommit,
    IN SIZE_T SizeOfStackReserve,
    OUT PVOID lpBytesBuffer
);

Setp4.进行函数调用

这里就用一个随便的函数地址给他让他创建线程

void test() {
    MessageBoxA(0, "syscall调用!", "syscall", 0);
}
HANDLE pHandle = GetCurrentProcess();
HANDLE tHandle = NULL;
//进行创建线程 
//ps:如果pHandle是别的进程的句柄就相当于注入进程了
//==CreateRemoteThread
NtCreateThreadEx(&tHandle, 0x1FFFFF, NULL, pHandle, test, NULL, FALSE,
NULL, NULL, NULL, NULL);
WaitForSingleObject(tHandle, -1);

然后我们可以借助 NtAllocateVirtualMemory,NtCreateThreadEx ,CopyMemory 进行申请内存,写入shellcode,执行shellcode了。内容就和前面的一样了。

第一次编译可能会出现下图的错误 我们只需再次编译即可! https://z1.ax1x.com/2023/11/08/pi1qNSs.jpg

运行程序

https://z1.ax1x.com/2023/11/08/pi1qyY4.jpg

0x04.思考

既然syscall这么简单 那我们直接用不就好了

调用确实是简单,但是微软就喜欢整花活,每个小版本的windows的每个函数调用号都不一样(可能有一样的?),可能我们自己电脑系统的调用号是0x1a 到别人电脑上已运行 哦吼,XXX.exe已停止运行。

这样肯定是不行的,所以我们得动态获取运行程序电脑上的调用号。有几个获取的思路

1.通过PEB获取NTDLL的基址然后通过EAT(导出表)找到函数地址 然后偏移到调用号的地方获取

2.还是获取NTDLL导出表 然后获取所有Zw函数进行排序,排序后的顺序就行调用号(SysWhipers2)

3.我自己的一个想法,大部分和1一样只是不是用比对和偏移进行获取而是将硬编码的数相加然后再减去前面固定的硬编码的合值就可以得到最后的结果。

其中PEB获取dll导出表的方法我前面写过一篇文章 手动编写&获取ShellCode http://tupler.top/posts/%E5%85%8D%E6%9D%80%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%89%8B%E5%8A%A8%E7%BC%96%E5%86%99%E8%8E%B7%E5%8F%96shellcode/

讲的很清楚 有兴趣可以去看看

第一种方法可以参考Github项目HellsGate 第二个就是SysWhipers2

0x05.魔改HellsGate

将HellsGate 去了特征 之后进行VT扫描 发现可以做到0查杀
https://z1.ax1x.com/2023/11/08/pi1q5TO.png 然而因为反沙箱搞的不是很好第二天就直接被杀穿 毕竟用的原生cs的特征还是很明显的,内存一dump出来特征直接明摆着。。 其中在做全绿VT时发现一个特别的AV : BkAv Pro 这个杀软的AI检测 我发现只要改了PE可选头里面的MajorImageVersion 随便改个值只要不是0就可以bypass 也是很神奇的检测点了。。

0x06.总结

syscall可以绕过edr的hook 也可以绕过一些静态的检测。syscall这个技术出来挺久了的,近年来的一些edr也对此做了一些加强,但还是万变不离其中,本身安全就是对抗的关系,你查杀这里我绕过这里 你检测我加密混淆。