声明:本内容只作为个人学习研究使用,请勿用作其他用途。
PEB
进程环境信息块,是一个从内核中分配给每个进程的用户模式结构,每一个进程都会有从ring0分配给该进程的进程环境块。
GS段寄存器指向当前的TEB结构,可以看到PEB在TEB的0x30偏移处
微软并没有定义PEB结构,因此需要我们自定义
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN Spare;
HANDLE Mutant;
PVOID ImageBase;
PPEB_LDR_DATA LoaderData;
PVOID ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PVOID FastPebLock;
PVOID FastPebLockRoutine;
PVOID FastPebUnlockRoutine;
ULONG EnvironmentUpdateCount;
PVOID* KernelCallbackTable;
PVOID EventLogSection;
PVOID EventLog;
PVOID FreeList;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[0x2];
PVOID ReadOnlySharedMemoryBase;
PVOID ReadOnlySharedMemoryHeap;
PVOID* ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
ULONG NumberOfProcessors;
ULONG NtGlobalFlag;
BYTE Spare2[0x4];
LARGE_INTEGER CriticalSectionTimeout;
ULONG HeapSegmentReserve;
ULONG HeapSegmentCommit;
ULONG HeapDeCommitTotalFreeThreshold;
ULONG HeapDeCommitFreeBlockThreshold;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
PVOID** ProcessHeaps;
PVOID GdiSharedHandleTable;
PVOID ProcessStarterHelper;
PVOID GdiDCAttributeList;
PVOID LoaderLock;
ULONG OSMajorVersion;
ULONG OSMinorVersion;
ULONG OSBuildNumber;
ULONG OSPlatformId;
ULONG ImageSubSystem;
ULONG ImageSubSystemMajorVersion;
ULONG ImageSubSystemMinorVersion;
ULONG GdiHandleBuffer[0x22];
ULONG PostProcessInitRoutine;
ULONG TlsExpansionBitmap;
BYTE TlsExpansionBitmapBits[0x80];
ULONG SessionId;
} PEB, * PPEB;
在PEB中的0x0c处为一指针,指向PEB_LDR_DATA结构,该结构体包含有关为进程加载的模块的信息(存储着该进程所有模块数据的链表)。
在PEB_LDR_DATA的0x0c,0x14,0x1c中为三个双向链表LIST_ENTRY,在struct _LDR_MODULE的0x00,0x08和0x10处是三个对应的同名称的LIST_ENTRY, PEB_LDR_DATA和struct _LDR_MODULE就是通过这三个LIST_ENTRY对应连接起来的。
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, * PLDR_MODULE;
三个双向链表分别代表模块加载顺序,模块在内存中的加载顺序以及模块初始化装载的顺序
每个双向链表都是指向进程装载的模块,结构中的每个指针,指向了一个LDR_DATA_TABLE_ENTRY的结构(如图)
这个结构很重要,提供了内存模块的基址和dll名称
syscall
基础知识
x86 windows 使用 sysenter 实现系统调用
x64 windows 使用 syscall 实现系统调用
目前syscall已经成为了绕过AV/EDR所使用的主流方式,可以用它绕过一些敏感函数的调用监控(R3)。主流的AV/EDR都会对敏感函数进行HOOK,而syscall则可以用来绕过该类检测。
-
使用syscall的步骤
- 使用GetModuleHandle找到ntdll的基址
- 解析DLL的导出表
- 查找syscall number
- 执行syscall
ntdll
在win10中,除了minimal和pico进程外,所有用户态的进程默认情况下都隐式链接到ntdll.dll
一般情况下,ntdll.dll在内存中的第二个模块,kernel32.dll是第三个模块。然而有些杀软会改变内存中的模块顺序列表,因此我们需要先确定指向的内存模块是ntdll.dll。如果访问了错误的模块,很显然不会加载任何功能。
- ntCreateThread
- ntCreateProcess
- ntAllocateVirtualMemory
差别就在传入 eax 寄存器的值不同,这里存储的是系统调用号,基于 eax 所存储的值的不同,syscall 进入内核调用的内核函数也不同。
EDR检测与绕过原理
在创建R3进程的时候,EDR会hook用户层的相关windows API调用,从而完成对进程动态行为的监控。比如,hook VirtualAlloc,监控内存分配。hook CreateProcess,监控进程创建。可以在用户层完成hook,也可以在内核层hook。用户层hook的好处是对性能的影响较小,相对于内核层hook更稳定,不容易导致系统蓝屏,所以很多EDR会选择在用户层hook,同时在内核层使用回调监控重要的内核api调用。一个进程分配了RWX属性的内存,或者修改了内存属性,将RW的内存修改为了RWX,由于RWX内存属性是shellcode或反射型DLL加载所用的内存属性,因此EDR会对申请的内存进行扫描,匹配到恶意软件的yara规则后,将会杀死恶意进程,并向控制中心发送告警。
为了避免在用户层被EDR hook的敏感函数检测到敏感行为,利用从ntdll中读取到的系统调用号进行系统直接调用来绕过敏感API函数的hook。主要来应对 EDR 对 Ring3 API 的 HOOK,不同版本的 Windows Ntxxx 函数的系统调用号不同,且调用时需要逆向各 API 的结构方便调用。
SysWhispers2
https://github.com/jthuraisamy/SysWhispers2
代码原理是从内存当中的ntdll中根据导出函数的地址顺序,确定系统调用号。
SW2_PopulateSyscallList函数其具体含义是先解析 Ntdll.dll 的 导出地址表 EAT,定位所有以 “Zw” 开头的函数,最后按地址从小到大进行排序。代码太长这里就不贴了,项目里有 另一个函数是 SW2_GetSyscallNumber,这个函数循环遍历 SW2_PopulateSyscallList 的数组,如果 Hash 相等就返回 循环的值 作为 SyscallNumber。
(遇到hook ntdll系统调用的EDR就不行了)
用python脚本生成需要的文件,通过包含头文件就可以syscall。使用时只需要把
.h,.c,.std.asm三个文件拷贝过来,然后根据github上的教程做就行。
Hell's Gate地狱之门
https://github.com/am0nsec/HellsGate/
是另一种syscall动态调用方案,对内存中已经加载的ntdll.dll模块遍历导出表,根据函数哈希找到函数地址,将这个函数读取出来后动态获取对应的系统调用号。
这种方式的好处是可以准确的获取系统调用号,缺点是需要一个干净的内存ntdll模块,否则将无法完成syscall调用号的获取(遇到hook ntdll系统调用的EDR就不行了)。
Halo's Gate光环之门
不能直接用
是地狱之门的加强。光环之门可以应对native api被hook的情况
EDR不可能HOOK全部的Nt*函数,总有一些不敏感的函数没有被HOOK。根据syscall的特征字节码4C 8B D1 B8,在内存中原本native api在的位置向上向下每32个字节进行搜索。找到没有被HOOK的存根后获取其系统调用号再减去移动的步数,就是所要搜索的系统调用号。
https://github.com/boku7/AsmHalosGate
Tartarus’ Gate
这个项目的作者声称是对光环之门的加强,只是检测第一个字节和第四个字节是否是0xe9(jmp),来判断函数是否被hook。 新增的代码判断没有意义。
https://github.com/trickster0/TartarusGate
DripLoader
https://github.com/xuanxuan0/DripLoader
搜索内存中,找到内存块属性为free的内存
寻找合适的内存基址,cVmResv即shellode长度/内存块大小+1,即一共需要多少块内存。当确定的基址连续cVmResv块的内存都free,返回这个基址
ParallelSyscalls(unhook)
该项目的亮点是使用syscall从磁盘读取ntdll.dll,最后一步利用 LdrpThunkSignature 恢复系统调用。
https://github.com/mdsecactivebreach/ParallelSyscalls
unhook的几种技术
https://xz.aliyun.com/t/11532