Пожалуйста, обратите внимание, что пользователь заблокирован
Введение
В этой статье я хотел бы описать процесс анализа патча для CVE-2020-17087. Несмотря на то, что уязвимость не самая свежая, читателям форума может быть интересно то, как Майкрософт исправляет свои ошибки с целью более быстрого анализа последующих исправлений. Может быть даже кто-то захочет создать модель уязвимости, написать ее на IDAPython и найти новые зеродеи. Я же в свою очередь постарался описать детали достаточно подробно, сопроводив листинги комментариями.
Сведения об уязвимости
Уязвимость CVE-2020-17087 находится в драйвере cng.sys, который экспортирует функции, отвечающие за криптографию. Подробнее.
Согласно отчету P0 ошибка содержится в функции
Анализ патча
Для анализа патчей я использую BinDiff. На мой взгляд, это самый быстрый и удобный инструмент для своих нужд, несмотря на его "прожорливость" по отношению к оперативной памяти. В соседней ветке я описал некоторые проблемы, с которыми могут столкнуться новые пользователи. Настоятельно рекомендую ее прочитать.
Патч для уязвимости был добавлен в версии Windows 20H2 19042.630, поэтому для сравнения я взял файлы
Также была добавлена функция
На листинге декомпилятора ниже мы можем видеть исправленную версию функции
Функция
Теперь вернемся к дизассемблерному листингу уязвимой версии драйвера.
В начале функция проверяет регистры
По указателю
После проверки размера буфера в
На этом этапе мы переходим в цикл по смещению
В
После первой итерации цикла мы можем видеть, что в чанк пула происходит копирование из массива шестнадцатиричных значений в формате
Мы добрались до третьей итерации, на которой перезаписываем поля структуры
Анализ крэш-дампа
После загрузки полного ядерного дампа в отладчик и, выполнив
В
Несмотря на то, что есть очевидный примитив на запись, я пока не могу сообразить как его можно использовать т.к. содержимое чанка мы не контролируем. Возможно, это будет в следующей части наряду с разбором PoC и части драйвера.
В этой статье я хотел бы описать процесс анализа патча для CVE-2020-17087. Несмотря на то, что уязвимость не самая свежая, читателям форума может быть интересно то, как Майкрософт исправляет свои ошибки с целью более быстрого анализа последующих исправлений. Может быть даже кто-то захочет создать модель уязвимости, написать ее на IDAPython и найти новые зеродеи. Я же в свою очередь постарался описать детали достаточно подробно, сопроводив листинги комментариями.
Сведения об уязвимости
Уязвимость CVE-2020-17087 находится в драйвере cng.sys, который экспортирует функции, отвечающие за криптографию. Подробнее.
Согласно отчету P0 ошибка содержится в функции
CfgAdtpFormatPropertyBlock при обработке 16-битовых чисел и является целочисленным переполнением, которое приводит к переполнению пула ядра. Т.е. атакующий может контролировать размер буфера из пользовательского режима, обратившись к драйверу по IOCTL 0x390400.Анализ патча
Для анализа патчей я использую BinDiff. На мой взгляд, это самый быстрый и удобный инструмент для своих нужд, несмотря на его "прожорливость" по отношению к оперативной памяти. В соседней ветке я описал некоторые проблемы, с которыми могут столкнуться новые пользователи. Настоятельно рекомендую ее прочитать.
Патч для уязвимости был добавлен в версии Windows 20H2 19042.630, поэтому для сравнения я взял файлы
cng.sys версий 19042.572 и 19042.630 соответственно. На скриншоте ниже мы можем видеть, что изменена была одна функция CfgAdtpFormatPropertyBlock.
Также была добавлена функция
RtlUSortMult(), которая осуществляет проверку 16-битовых чисел перед вызовом функции BCryptAlloc() и предотвращает переполнение, возвращая в этом случае ошибку с кодом STATUS_INTEGER_OVERFLOW.
На листинге декомпилятора ниже мы можем видеть исправленную версию функции
CfgAdtpFormatPropertyBlock.Функция
RtlUShortMult возвращает указатель на результат произведения двух чисел, сохраненного в переменной p_mult_result.
Теперь вернемся к дизассемблерному листингу уязвимой версии драйвера.
В начале функция проверяет регистры
rcx, bp, r8 на равенство нулю и возвращает STATUS_INVALID_PARAMETER, если утвереждение верно (распространенная проверка). Т.е. можем предположить, что аргументы функции хранятся именно в этих регистрах. Нас интересует регистр bp, который имеет размер 16 бит. Его мы и можем контролировать. После подсчета размера входящего буфера происходит выделение неисполняемого невыгружаемого пула ядра с помощью функции BCryptAlloc равного количеству байт, записанных в ecx из di.
BCryptAlloc является лишь оберткой для функции ExAllocatePoolWithTag. В передаваемых аргументах мы можем видеть тег Cngb или 0x62676E43, с которым происходит выделение чанков.
По указателю
rax мы можем видеть выделенный чанк размером 0x10 байт после вызова BCryptAlloc(0x2aac). Судя по всему, Pool Manager не может выделить меньше 16 байт под чанк, т.к. в нашем случае должно было выделиться 8 байт из-за целочисленного переполнения.(0x2aac * 0x6) - (0xffff + 0x1) = 0x8.
Код:
2: kd> ? @rax
Evaluate expression: -140676885522480 = ffff800e`1c35d7d0
2: kd> dt nt!_pool_header (@rax - 0x10)
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y00000000 (0)
+0x002 BlockSize : 0y00000010 (0x2)
+0x002 PoolType : 0y00000010 (0x2)
+0x000 Ulong1 : 0x2020000
+0x004 PoolTag : 0x62676e43
+0x008 ProcessBilled : 0x2ecfaa1a`b58a06e6 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x6e6
+0x00a PoolTagHash : 0xb58a
Код:
2: kd> u
cng!CfgAdtpFormatPropertyBlock+0x6b:
fffff801`59be24c7 663bdd cmp bx,bp
fffff801`59be24ca 7343 jae cng!CfgAdtpFormatPropertyBlock+0xb3 (fffff801`59be250f)
fffff801`59be24cc 4c8bc5 mov r8,rbp
fffff801`59be24cf 4c8d159a590300 lea r10,[cng!`string'+0x18 (fffff801`59c17e70)]
fffff801`59be24d6 410fb606 movzx eax,byte ptr [r14]
fffff801`59be24da 48c1e804 shr rax,4
fffff801`59be24de 420fb60410 movzx eax,byte ptr [rax+r10]
fffff801`59be24e3 668901 mov word ptr [rcx],ax
2: kd> ? @bx
Evaluate expression: 0 = 00000000`00000000
2: kd> ? @bp
Evaluate expression: 10924 = 00000000`00002aac
После проверки размера буфера в
r8 сохраняется длина, а в r10 передается указатель на массив.
Код:
2: kd> db @r10
fffff801`59c17e70 30 31 32 33 34 35 36 37-38 39 61 62 63 64 65 66 0123456789abcdef
fffff801`59c17e80 54 00 4c 00 53 00 5f 00-45 00 43 00 44 00 48 00 T.L.S._.E.C.D.H.
fffff801`59c17e90 45 00 5f 00 45 00 43 00-44 00 53 00 41 00 5f 00 E._.E.C.D.S.A._.
fffff801`59c17ea0 57 00 49 00 54 00 48 00-5f 00 41 00 45 00 53 00 W.I.T.H._.A.E.S.
fffff801`59c17eb0 5f 00 31 00 32 00 38 00-5f 00 43 00 42 00 43 00 _.1.2.8._.C.B.C.
fffff801`59c17ec0 5f 00 53 00 48 00 41 00-32 00 35 00 36 00 5f 00 _.S.H.A.2.5.6._.
fffff801`59c17ed0 50 00 32 00 35 00 36 00-00 00 00 00 00 00 00 00 P.2.5.6.........
fffff801`59c17ee0 54 00 4c 00 53 00 5f 00-45 00 43 00 44 00 48 00 T.L.S._.E.C.D.H.
loc_1C00624D6.
В
r14 находится указатель на большой NonPagedPool размером 0x3ab0 байт.
Код:
2: kd> db @r14
ffff800e`1c708000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
2: kd> !pool @r14
Pool page ffff800e1c708000 region is Nonpaged pool
ffff800e1c707000 doesn't look like a valid small pool allocation, checking to see
if the entire page is actually part of a large page allocation...
*ffff800e1c707000 : large page allocation, tag is Cngb, size is 0x3ab0 bytes
Pooltag Cngb : CNG kmode crypto pool tag, Binary : ksecdd.sys
После первой итерации цикла мы можем видеть, что в чанк пула происходит копирование из массива шестнадцатиричных значений в формате
hex_byte + 0x00 + hex_byte + 0x00 + 0x20 + 0x00. Далее проиходит декремент размера, который мы передавали в полезную нагрузку и цикл повторяется до тех пор, пока все 0x2aac * 0x6 байт не будет записано в чанк.
Код:
2: kd> db @rcx -0x4
ffff800e`1c35d7d0 30 00 30 00 20 00 00 00-00 00 00 00 00 00 00 00 0.0. ...........
ffff800e`1c35d7e0 00 00 02 02 49 6f 43 63-c6 a6 54 b0 1a aa cf 2e ....IoCc..T.....
ffff800e`1c35d7f0 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
Мы добрались до третьей итерации, на которой перезаписываем поля структуры
_POOL_HEADER следующего чанка, в моем случае, с тегом IoCc, что должно вызвать BSOD c ошибкой BAD_POOL_HEADER или PAGE_FAULT_IN_NONPAGED_AREA при записи в невалидную память. Стоит отметить, что PoC работает нестабильно и может вызывать ошибки в других драйверах. Также, я сталкивался с тем, что при использовании Driver Verifier, PoC вообще не срабатывал. Возможно, это как-то связано с особенностями специального пула.
Код:
2: kd> db @rcx - 0x12
ffff800e`1c35d7d0 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffff800e`1c35d7e0 20 00 02 02 49 6f 43 63-c6 a6 54 b0 1a aa cf 2e ...IoCc..T.....
ffff800e`1c35d7f0 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
ffff800e`1c35d800 00 00 02 02 49 6f 43 63-00 00 00 00 00 00 00 00 ....IoCc........
ffff800e`1c35d810 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
ffff800e`1c35d820 00 00 02 02 56 69 30 33-00 00 00 00 00 00 00 00 ....Vi03........
ffff800e`1c35d830 01 00 00 00 ff ff ff ff-00 00 00 00 00 00 00 00 ................
ffff800e`1c35d840 00 00 02 02 44 78 67 4b-00 00 00 00 00 00 00 00 ....DxgK........
Анализ крэш-дампа
После загрузки полного ядерного дампа в отладчик и, выполнив
analyze -v, мы можем видеть полезуню информацию для анализа.
Код:
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffa50ab81f8000, memory referenced.
Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
Arg3: fffff80330cb2503, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
В
Arg1 содержится указатель на память, куда происходит копирование данных. Как уже сказано в описании ошибки, скорее всего это освободившаяся страница памяти, куда мы пытаемся обратиться. Предыдущую валидную страницу, начинающуюся с 0xffffa50ab81f7000 мы практически полностью переписали.
Код:
3: kd> db ffffa50ab81f7000 L100
ffffa50a`b81f7000 98 5d c0 ae 0a a5 ff ff-00 d0 6f b6 0a a5 ff ff .]........o.....
ffffa50a`b81f7010 80 5d c0 ae 0a a5 ff ff-00 00 00 00 00 00 00 00 .]..............
ffffa50a`b81f7020 3a 00 7d 00 11 00 00 00-7a 1d f1 56 0c 01 50 00 :.}.....z..V..P.
ffffa50a`b81f7030 54 55 50 55 51 55 55 14-15 54 55 55 15 55 01 00 TUPUQUU..TUU.U..
ffffa50a`b81f7040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 fc ................
ffffa50a`b81f7050 01 00 a5 ee 83 d0 ff ff-01 f0 a5 ee 83 d0 ff ff ................
ffffa50a`b81f7060 00 00 02 02 4d 6d 52 6c-00 00 00 00 00 00 00 00 ....MmRl........
ffffa50a`b81f7070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa50a`b81f7080 00 00 02 02 43 6e 67 62-00 00 00 00 00 00 00 00 ....Cngb........
ffffa50a`b81f7090 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffffa50a`b81f70a0 20 00 30 00 30 00 20 00-30 00 30 00 20 00 30 00 .0.0. .0.0. .0.
ffffa50a`b81f70b0 30 00 20 00 30 00 30 00-20 00 30 00 30 00 20 00 0. .0.0. .0.0. .
ffffa50a`b81f70c0 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffffa50a`b81f70d0 20 00 30 00 30 00 20 00-30 00 30 00 20 00 30 00 .0.0. .0.0. .0.
Несмотря на то, что есть очевидный примитив на запись, я пока не могу сообразить как его можно использовать т.к. содержимое чанка мы не контролируем. Возможно, это будет в следующей части наряду с разбором PoC и части драйвера.
Последнее редактирование: