浅析syscall

admin 2022年2月11日20:32:34安全文章 程序逆向评论163 views10600字阅读35分20秒阅读模式

    最近在面试一些人的免杀问题时总会谈到syscall,但对于一些检测、细节、绕过检测反而没有说的很清楚,本文简单总结一些syscall的方式,来帮你唬过面试官。


简介


   目前syscall已经成为了绕过AV/EDR所使用的主流方式,可以用它绕过一些敏感函数的调用监控(R3)。主流的AV/EDR都会对敏感函数进行HOOK,而syscall则可以用来绕过该类检测。

 

浅析syscall


    Hook一般放置在kernel32.dll、kernelbase.dl、ntdll.dll之中,在EDR环境中,如果调用的函数被 HOOK,则跳转到EDR的dll之中,该dll一般在进程启动时被加载。这里拿Bitdefender Antivirus 进行测 试。

    

    这是一个简单的测试代码:


#include <Windows.h> #include <iostream>int main() {    STARTUPINFO            sinfo;      PROCESS_INFORMATION    pinfo;      memset(&sinfo, 0, sizeof(STARTUPINFO));      memset(&pinfo, 0, sizeof(PROCESS_INFORMATION));      sinfo.dwFlags = STARTF_USESHOWWINDOW;    s  info.wShowWindow = SW_SHOWMAXIMIZED;      BOOL bSucess = CreateProcess(L"C:\Windows\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &sinfo, &pinfo);       std::cout << "Hello World!n";    }


当进程启动时会加载Bitdefender的dll,即atcuf64.dll 


浅析syscall


如果进行调试则可以看到atcuf64的调用:


浅析syscall


    EDR一般会对多个函数进行HOOK,常见的Hook手段有JMP、EAT、GPA,Bitdefender的HOOK列表 可以在这里找到:https://github.com/Mr-Un1k0d3r/EDRs/blob/main/bitdefender.txt,而针对性的 Unhook可以在此处找到:https://github.com/plackyhacker/Unhook-BitDefender 除了这种手动检测 之外可以编写代码进行批量测试,主要测试逻辑如下


 if (correct_bytes[0] == assemblyBytes[0] && correct_bytes[1] == assemblyBytes[1] && correct_bytes[2] == assemblyBytes[2] && correct_bytes[3] == assemblyBytes[3])            {                printf"t[*]%s has NOT been hooked!n", szExportedFunctionName );                nClean++;            }            else            {                printf("t[*] %s HAS been hooked!n", szExportedFunctionName);                printf("tt");             }


效果


浅析syscall


windows下函数的调用流程是


OpenProcess() [Kernel32] -> OpenProcess() [Kernelbase] -> NtOpenProcess() [Ntdll] -> Direct syscall to the kernel -> | Kernel Mode |


在执行流程中以Nt和Zw开头的Ntdll函数进行执行,他们后的代码都在内核中运行。

 

浅析syscall


    所以直接系统调用是R3执行的后一步,将函数执行转发给R0。整个过程 唯一的区别就是EAX中的数字,也就是syscall number,不同操作系统版本之间syscall number不同。可以参考https://j00ru.vexillium.org/syscalls/nt/64/ 


浅析syscall


即下面这种形式:


0x4c 0x8b 0xd1 0xb8 0xZZ 0xZZ 0x00 0x00

浅析syscall


所以为了绕过HOOK,我们可以使用Syscall。使用前提为: 


  • 不使用GetModuleHandle找到ntdll 的基址 

  • 解析DLL的导出表 

  • 查找syscall number 

  • 执行syscall


查找DLL地址 


  此类操作我们需要用到PEB_LDR_DATA中的InMemoryOrderModuleList,说白了还是PEB、TEB的使 用。

 

typedef struct _LIST_ENTRY {   struct _LIST_ENTRY *Flink;   struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;


    PEB_LDR_DATA的InMemoryOrderModuleList.Flink指向第一个模块加载的InMemoryOrderLinks, InMemoryOrderLinks在LDR_DATA_TABLE_ENTRY结构体之中,而模块加载信息都在 _LDR_DATA_TABLE_ENTRY中。

 

typedef struct _LDR_DATA_TABLE_ENTRY {    PVOID Reserved1[2];    LIST_ENTRY InMemoryOrderLinks;    PVOID Reserved2[2];    PVOID DllBase; // Base address of the module in memory     PVOID EntryPoint;    PVOID Reserved3;    UNICODE_STRING FullDllName; // Full path + name of the dll    BYTE Reserved4[8];    PVOID Reserved5[3];    union {        ULONG CheckSum;        PVOID Reserved6;    };    ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;


如果我们想浏览每个模块,我们只需要跳转到每个InMemoryOrderLinks.Flink。在 _LDR_DATA_TABLE_ENTRY结构中,我们可以检索到模块的基本地址和它的名称等。更详细的信息可以 查看:https://mohamed-fakroud.gitbook.io/red-teamings-dojo/shellcoding/leveraging-from-pe-pa rsing-technique-to-write-x86-shellcode


浅析syscall


解析导出地址表 (EAT) 


 一旦我们检索到 Dll 基地址,我们需要找到目标函数的地址。为此,我们必须解析 DLL 的导出部分以 找到Export Address Table(EAT)。包含 DLL的EAT所有函数地址。这个工作可以交给_IMAGE_EXPORT_DIRECTORY,其架构体如下:

 

typedef struct _IMAGE_EXPORT_DIRECTORY {                  DWORD   Characteristics;                DWORD   TimeDateStamp;                    WORD    MajorVersion;                 WORD    MinorVersion;                 DWORD   Name;                   // The name of the Dll    DWORD   Base;                   // Number to add to the values found in AddressOfNameOrdinals to retrieve the "real" Ordinal number of the function (by real I mean used to call it by ordinals).    DWORD   NumberOfFunctions;      // Number of all exported functions          DWORD   NumberOfNames;          // Number of functions exported by name          DWORD   AddressOfFunctions;     // Export Address Table. Address of the functions addresses array.       DWORD   AddressOfNames;         // Export Name table. Address of the functions names array.            DWORD   AddressOfNameOrdinals;  // Export sequence number table.  Address of the Ordinals (minus the value of Base) array.             } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;


浅析syscall


调用Syscall 


    有了上面的内容,下面就是进行动态查找syscall number和调用syscall ,下面是一些常见的Syscall技 术

(https://github.com/jthuraisamy/SysWhispers不在其中)。


Hell’s Gate:地狱之门 

 地址之门,项目地址为:https://github.com/am0nsec/HellsGate、项目简介地址:

https://vxug.fak edoma.in/papers/VXUG/Exclusive/HellsGate.pdf 利用代码动态查找0x4c 0x8b 0xd1 0xb8 0xZZ 0xZZ 0x00 0x00: 


浅析syscall


也就是函数从 RCX 寄存器移入 R10 寄存器,然后将系统调用移入 EAX,与自带的asm文件对应:

 

浅析syscall


 测试结果,未能绕过Bitdefender (shellcode为手写的shellcode不存在被杀的问题):


浅析syscall


Halo’s Gate:光环之门 


     光环之门主要是为了防止当mov r10,rcx被HOOK时地狱之门失效的问题。项目地址为:https://blog. sektor7.net/#!res/2021/halosgate.md 成熟的项目为:https://github.com/boku7/AsmHalosGate 其 思路为syscall stub中的 syscall ID 是彼此递增的!这意味着,例如,如果你被钩住了,但下面的下一个 函数没有,你只需要检索它的系统调用并减去 1 即可获得当前函数的系统调用。 


浅析syscall


代码如下:


浅析syscall

浅析syscall


Tartarus Gate:塔尔塔罗斯之门 


 在光环之门的基础上增加了一些asm混淆。从: 


浅析syscall


变成了:

 

浅析syscall


项目地址为:https://github.com/trickster0/TartarusGate 


FreshyCalls 


 mdsec新出的一种syscall的方式,项目地址:https://github.com/crummie5/FreshyCalls 文章地 址:https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-syst em-calls-for-red-teams/ 主要思路为从函数地址中获取Syscall ID。在调试器中按照地址排序,ID号递增


浅析syscall


浅析syscall


所以我们可以将Nt函数的地址进行排序,便可以在不解析syscall stub 的情况下获取syscall id。实现 代码如下:


for (size_t i = 0; i < export_dir->NumberOfNames; i++) {    function_name = reinterpret_cast<const char *>(ntdll_base + names_table[i]);    // If the name of the function start with "Nt" and don't start with "Ntdll"    // we retrieve the info    if (function_name.rfind("Nt", 0) == 0 && function_name.rfind("Ntdll", 0) == std::string::npos) {      stub_ordinal = names_ordinals_table[i];      stub_address = ntdll_base + functions_table[stub_ordinal];            // We put the RVA as a key and the function name as the value.      // This is a sorted map.      // The elements are automatically sorted using the key value.      // This means that when all the Nt function will be loaded,       // the first element will be the Nt function with the lowest address       // and the last the one with the biggest address.      stub_map.insert({stub_address, function_name});    }  }  // `stub_map` is ordered from lowest to highest using the stub address. Syscalls numbers are  // assigned using this ordering too. The lowest stub address will be the stub with the lowest  // syscall number (0 in this case). We just need to iterate `stub_map` and iterate the syscall  // number on every iteration.  static inline void ExtractSyscallsNumbers() noexcept {    uint32_t syscall_no = 0;    // The stub_map filled previously in the code presented above    for (const auto &pair: stub_map)  {      //Creation of a map associating function name and syscall identifier.      syscall_map.insert({pair.second, syscall_no});      syscall_no++;    }  };


Syswhispers2 


 与FreshyCalls基本相同,项目地址如下:https://github.com/jthuraisamy/SysWhispers2 只是把Nt 换成了zw,nt与zw的区别参考:https://docs.microsoft.com/en-us/windows-hardware/drivers/kern el/using-nt-and-zw-versions-of-the-native-system-services-routines


实现代码如下:


  DWORD i = 0;  /*  //For info. It's in the header file.  typedef struct _SW2_SYSCALL_ENTRY  {    DWORD Hash;    DWORD Address;  } SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;    #define SW2_MAX_ENTRIES 500  */      PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;    do    {        // Retrieve function name from the Dll.        PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
// Check if the function name starts with "Zw" if (*(USHORT*)FunctionName == 'wZ') { // If yes, hash the name (for AV/EDR/Malware Analyst evasion reasons) and put it in an Entries element Entries[i].Hash = SW2_HashSyscall(FunctionName); // Put also the address of the function Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
i++; if (i == SW2_MAX_ENTRIES) break; } } while (--NumberOfNames);
// Save total number of system calls found. SW2_SyscallList.Count = i;
// Sort the list by address in ascending order. for (i = 0; i < SW2_SyscallList.Count - 1; i++) { for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++) { if (Entries[j].Address > Entries[j + 1].Address) { // Swap entries. SW2_SYSCALL_ENTRY TempEntry;
TempEntry.Hash = Entries[j].Hash; TempEntry.Address = Entries[j].Address;
Entries[j].Hash = Entries[j + 1].Hash; Entries[j].Address = Entries[j + 1].Address;
Entries[j + 1].Hash = TempEntry.Hash; Entries[j + 1].Address = TempEntry.Address; } } }
return TRUE;}


ParallelSyscalls 


 也是一种由mdsec提出的方式,项目地址:https://github.com/mdsecactivebreach/ParallelSyscalls 文章地址:https://www.mdsec.co.uk/2022/01/edr-parallel-asis-through-analysis/ 主要思路为利用 LdrpThunkSignature 恢复系统调用。实现代码如下


BOOL InitSyscallsFromLdrpThunkSignature(){    PPEB Peb = (PPEB)__readgsqword(0x60);    PPEB_LDR_DATA Ldr = Peb->Ldr;    PLDR_DATA_TABLE_ENTRY NtdllLdrEntry = NULL;
for (PLDR_DATA_TABLE_ENTRY LdrEntry = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink; LdrEntry->DllBase != NULL; LdrEntry = (PLDR_DATA_TABLE_ENTRY)LdrEntry->InLoadOrderLinks.Flink) { if (_wcsnicmp(LdrEntry->BaseDllName.Buffer, L"ntdll.dll", 9) == 0) { // got ntdll NtdllLdrEntry = LdrEntry; break; } }
if (NtdllLdrEntry == NULL) { return FALSE; }
PIMAGE_NT_HEADERS ImageNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)NtdllLdrEntry->DllBase + ((PIMAGE_DOS_HEADER)NtdllLdrEntry->DllBase)->e_lfanew); PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&ImageNtHeaders->OptionalHeader + ImageNtHeaders->FileHeader.SizeOfOptionalHeader);
ULONG_PTR DataSectionAddress = NULL; DWORD DataSectionSize;
for (WORD i = 0; i < ImageNtHeaders->FileHeader.NumberOfSections; i++) { if (!strcmp((char*)SectionHeader[i].Name, ".data")) { DataSectionAddress = (ULONG_PTR)NtdllLdrEntry->DllBase + SectionHeader[i].VirtualAddress; DataSectionSize = SectionHeader[i].Misc.VirtualSize; break; } }
DWORD dwSyscallNo_NtOpenFile = 0, dwSyscallNo_NtCreateSection = 0, dwSyscallNo_NtMapViewOfSection = 0;
if (!DataSectionAddress || DataSectionSize < 16 * 5) { return FALSE; }
for (UINT uiOffset = 0; uiOffset < DataSectionSize - (16 * 5); uiOffset++) { if (*(DWORD*)(DataSectionAddress + uiOffset) == 0xb8d18b4c && *(DWORD*)(DataSectionAddress + uiOffset + 16) == 0xb8d18b4c && *(DWORD*)(DataSectionAddress + uiOffset + 32) == 0xb8d18b4c && *(DWORD*)(DataSectionAddress + uiOffset + 48) == 0xb8d18b4c && *(DWORD*)(DataSectionAddress + uiOffset + 64) == 0xb8d18b4c) { dwSyscallNo_NtOpenFile = *(DWORD*)(DataSectionAddress + uiOffset + 4); dwSyscallNo_NtCreateSection = *(DWORD*)(DataSectionAddress + uiOffset + 16 + 4); dwSyscallNo_NtMapViewOfSection = *(DWORD*)(DataSectionAddress + uiOffset + 64 + 4); break; } }
if (!dwSyscallNo_NtOpenFile) { return FALSE; }
ULONG_PTR SyscallRegion = (ULONG_PTR)VirtualAlloc(NULL, 3 * MAX_SYSCALL_STUB_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!SyscallRegion) { return FALSE; }
NtOpenFile = (FUNC_NTOPENFILE)BuildSyscallStub(SyscallRegion, dwSyscallNo_NtOpenFile); NtCreateSection = (FUNC_NTCREATESECTION)BuildSyscallStub(SyscallRegion + MAX_SYSCALL_STUB_SIZE, dwSyscallNo_NtCreateSection); NtMapViewOfSection = (FUNC_NTMAPVIEWOFSECTION)BuildSyscallStub(SyscallRegion + (2* MAX_SYSCALL_STUB_SIZE), dwSyscallNo_NtMapViewOfSection);
return TRUE;}


缺点是会在加载处显示两个ntdll。


浅析syscall


绕过syscall检测 


 下面是一些针对syscall检测的绕过方法。


int2Eh法 

 来源:https://captmeelo.com/redteam/maldev/2021/11/18/av-evasion-syswhisper.html 就是把 syscall关键字换成了int 2eh


浅析syscall


Egg Hunting 


因为调用syscall的过程基本都是固定的,所以我们可以更改其行为逻辑。

 

浅析syscall


在汇编中我们可以使用DB进行字节插入,比如“Hello”,我们便可以:


DB 77h ; 'H' DB 0h  ; 'e' DB 0h  ; 'l' DB 74h ; 'l' DB 0h  ; 'o'

使用这个技巧,我们可以放置一系列已知字节(egg)作为syscall指令的占位符,并在运行时替换 它。比如这样:

浅析syscall


结果 


  增加syscall混淆后,成功绕过bitdefender


浅析syscall







     ▼
更多精彩推荐,请关注我们


请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!


浅析syscall



原文始发于微信公众号(鸿鹄实验室):浅析syscall

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月11日20:32:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  浅析syscall http://cn-sec.com/archives/774714.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: