G A C E T A   D E   L I N U X
...haciendo a Linux un poco más divertido!
Seguimiento de procesos usando ptrace, Parte III
Por Sandeep S
Traducción al español por Rafa Pereira
el día 20 de mayo 2003, para La Gaceta de Linux

 
Las características básicas de ptrace fueron explicadas en la Parte I. En la Parte II vimos un pequeño programa que accedía a los registros de un proceso y los modificaba para cambiar la salida de ese proceso, insertando código extra. En esta ocasión vamos a acceder a la memoria de un proceso. El propósito de este artículo es introducir un método para infectar programas en tiempo de ejecución. Son muchas las áreas potenciales de utilización de esta técnica.


1. Introducción.

Estamos familiarizados con ptrace y conocemos las técnicas para vincular un proceso, para hacerle un seguimiento (tracing) y, finalmente, para liberarlo. También tenemos una idea de la estructura del formato binario de Linux: ELF.

Nuestro plan es acceder/modificar un programa en ejecución. Así que tenemos que localizar los símbolos del interior del programa. Ahí necesitamos link_map. link_map es la estructura interna del enlazador dinámico en la cual éste mantiene información sobre las librerías cargadas y sobre los símbolos incluídos en ellas.

El formato de link_map es (de /usr/include/link.h)

struct link_map
  {
    ElfW(Addr) l_addr;      /* Base address shared object is loaded at.  */
    char *l_name;           /* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;        /* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
  };

Una breve explicación de los campos:

link_map es una lista enlazada, cada elemento de la cual tiene un apuntador a una librería cargada. Para seguir esta cadena, lo que tenemos que hacer es recorrer todas las librerías y buscar nuestro símbolo en cada una de ellas. Ahora tenemos una pregunta. ¿Dónde podemos encontrar este link_map?

Para cada fichero objeto hay una tabla de desplazamiento global (GOT: Global Offset Table) que contiene muchos detalles del fichero binario. La segunda entrada de la GOT está dedicada al link_map. Así que obtenemos la dirección del link_map de GOT[1] y procedemos a la búsqueda de nuestro símbolo.

2. Directos al código.

Ya hemos reunido la información básica necesaria para acceder a la memoria. Empecemos. En primer lugar vinculamos el proceso 'pid' para su seguimiento (tracing). A continuación obtenemos el link_map. Encontrarás funciones read_data, read_str, etc. Estas son funciones auxiliares para facilitar el trabajo con ptrace. Las funciones auxiliares son auto-explicativas.

La función para localizar el link_map es:

struct link_map *locate_linkmap(int pid)
{
    Elf32_Ehdr *ehdr = malloc(sizeof(Elf32_Ehdr));
    Elf32_Phdr *phdr = malloc(sizeof(Elf32_Phdr));
    Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
    Elf32_Word got;
    struct link_map *l = malloc(sizeof(struct link_map));
    unsigned long phdr_addr, dyn_addr, map_addr;
    
     read_data(pid, 0x08048000, ehdr, sizeof(Elf32_Ehdr));
    phdr_addr = 0x08048000 + ehdr->e_phoff;
    printf("program header at %p\n", phdr_addr);
    read_data(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));

    while (phdr->p_type != PT_DYNAMIC) {
        read_data(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,
                             sizeof(Elf32_Phdr));
    }
    
    read_data(pid, phdr->p_vaddr, dyn, sizeof(Elf32_Dyn));
    dyn_addr = phdr->p_vaddr;

    while (dyn->d_tag != DT_PLTGOT) {
        read_data(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
    }

    got = (Elf32_Word) dyn->d_un.d_ptr;
    got += 4;           /* second GOT entry, remember? */

    read_data(pid, (unsigned long) got, &map_addr, 4);
    read_data(pid, map_addr, l, sizeof(struct link_map));
    free(phdr);
    free(ehdr);
    free(dyn);
    return l;
}

Empezamos desde la posición 0x08048000 para obtener la cabecera elf del proceso cuyo seguimiento vamos a realizar. Obtenemos la cabecera elf y, a través de sus campos, accedemos a la cabecera de programa. (Los campos de las cabeceras fueron discutidos en la Parte II.) Una vez que hemos obtenido la cabecera de programa, procedemos a localizar la cabecera con la información de enlace dinámico. De la cabecera/estructura con la información de enlace dinámico obtenemos la ubicación de la información. Seguimos buscando hasta obtener la dirección base de la tabla de desplazamiento global (GOT).

Ahora que tenemos la dirección de la GOT tomamos su segunda entrada (ahí tenemos el link_map). De ahí cogemos la dirección del link_map y regresamos.

Tenemos la estructura link_map. Lo que necesitamos obtener es symtab y strtab. Para ello, vamos al campo l_ld de link_map y recorremos las secciones dinámicas hasta encontrar DT_SYMTAB y DT_STRTAB y, finalmente, poder buscar nuestro símbolo desde DT_SYMTAB. DT_SYMTAB y DT_STRTAB son las direcciones de la tabla de símbolos y de la tabla de cadenas de caracteres respectivamente.

La función resolv_tables es:

void resolv_tables(int pid, struct link_map *map)
{
    Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
    unsigned long addr;
    addr = (unsigned long) map->l_ld;
    read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
    while (dyn->d_tag) {
        switch (dyn->d_tag) {
        case DT_HASH:
            read_data(pid, dyn->d_un.d_ptr + map->l_addr + 4, 
                       &nchains, sizeof(nchains));
            break;
        case DT_STRTAB:
            strtab = dyn->d_un.d_ptr;
            break;
        case DT_SYMTAB:
            symtab = dyn->d_un.d_ptr;
            break;
        default:
            break;
        }
        addr += sizeof(Elf32_Dyn);
        read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
    }
    free(dyn);
}

Aquí, lo que hacemos es leer las secciones dinámicas una por una y comprobar si la etiqueta (tag) es DT_STRTAB o DT_SYMTAB. Si lo es, guardamos sus apuntadores respectivos en strtab y symtab. Una vez que se han terminado las secciones dinámicas, podemos parar.

Nuestro siguiente paso es extraer de la tabla de símbolos el símbolo buscado. Para ello, tomamos cada entrada de la tabla de símbolos y comprobamos si se trata del nombre de una función. (Estamos interesados en encontrar el valor de una función de librería). Si lo es, lo comparamos con el nombre de la función que nos han dado. Si coincide, devolvemos el valor del símbolo.

Ya tenemos el valor del símbolo que estábamos buscando. ¿Qué ayuda nos va a proporcionar ese valor? La respuesta depende del lector. Como ya ha quedado establecido, podemos utilizarlo con propósitos nobles o malvados.

Quizá el lector esté pensando que aquí termina todo. Pero hemos olvidado un paso que no deberíamos olvidar: desvincular el proceso del que hemos hecho el seguimiento. Este olvido puede dejar al proceso en un estado de parada indefinida, con las consecuencias ya discutidas en la Parte I. Así que nuestro último paso es desvincular el proceso al que hemos hecho seguimiento.

El programa puede obtenerse en Ptrace.c. Casi todo el código es auto-explicativo.

Lo compilamos con:

#cc Ptrace.c -o symtrace

Ahora queremos probar el programa. Para ello, ejecutamos y dejamos en ejecución algún proceso en otra consola, volvemos a ésta y tecleamos lo siguiente (aquí el programa al que hacemos seguimiento es emacs y el símbolo buscado esstrcpy). Se puede hacer seguimiento de cualquier programa 'traceable' en lugar de emacs y buscar cualquier símbolo:

#./symtrace `ps ax | grep 'emacs' | cut -f 2 -d " "` strcpy
Y observa lo que ocurre.

3. Conclusión.

De esta forma llegamos al final de una serie de tres artículos que ha hecho un recorrido por la programación básica con ptrace. Una vez que se ha entendido el concepto básico no es difícil dar más pasos por uno mismo. En www.phrack.org hay disponibles más detalles sobre ptrace y elf. Una cosa más a comentar es que hemos llegado hasta aquí sin mencionar siquiera un tema importante. Una de las características importantes de ptrace es su interacción con las llamadas al sistema. En User Mode Linux (Linux en Modo Usuario), esta característica se usa a gran escala. Estoy ocupado con las clases y con el proyecto de último curso, pero prometo, si dispongo de tiempo, que continuaremos esta serie y, a continuación, daremos un vistazo a esas características de ptrace.

Son bienvenidas todas las sugerencias, críticas, contribuciones, etc. Puedes contactar conmigo en busybox@sancharnet.in


Copyright © 2002, Sandeep S. Licencia de Copia http://www.linuxgazette.com/copying.html
Publicado en el número 85 de Linux Gazette, Diciembre 2002