[HACK] return-into-libc

Jose Carlos Luna Duran luna at aditel.org
Tue Sep 21 18:16:23 CEST 2004


En Mon Sep 20, 2004 at 09:13:32PM -0700, Rafael San Miguel Carrasco <rsmc at soluciones-seguras.com> escribio:
> 
> Hola,
> 
> Tengo un par de dudas acerca de la técnica return-into-libc para 
> explotar desbordamientos.
> En los tutoriales que he leido siempre hablan de una estructura que debe 
> encontrarse en memoria antes de la llamada a system ():
> 
>    <dirección system()><dirección retorno><dirección argumento>

Esta simplemente simulando el estado en el que queda la pila al hacer un call.
Veamoslo paso por paso. En el caso que presentas, despues de haber
realizado un bof o lo que sea, el EIP apunta a una dirección de memoria
la cual contiene la orden "ret". Tu has transformado, gracias al bof , la
pila para que en ese momento tenga este aspecto:

[direccion de system()][direccion de retorno][arg de system][...][Inicio de pila]

Despues de ese ret la pila queda:

  [direccion de retorno][arg de system][...][Inicio de pila]
  ^
  |-ESP
  EIP=&system()

Ahora contrastemos con el estado de la pila al tener un trozo de codigo que hace
un system("tralala"):

$ cat test.c
int main() {
        system("tralala");
}

Una llamada a funcion en C acaba siendo compilado en x86 en:

push &argXXX
....
push &arg2
push &arg1
call direccion
YYYY
....

El call tiene el efecto de meter en pila la direccion de memoria
de la siguiente instruccion que le sucede, y pasar el control a la direccion
de memoria pasada como parametro, algo asi como push &YYYY, jmp direccion
Por lo tanto la estructura que queda es justo la que tu has puesto
en el ejemplo.

Y lo podemos comprobar rapidamente:
$ gdb ./test
(gdb) break  system
Function "system" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (system) pending.
(gdb) r
Starting program: /home/luna/tt1 
Breakpoint 2 at 0x4006f8b0
Pending breakpoint "system" resolved

Breakpoint 2, 0x4006f8b0 in system () from /lib/tls/libc.so.6
(gdb) x/5x $esp
0xbffff85c:     0x080483a0      0x080484c4      0x08048410      0xbffff8f4
0xbffff86c:     0x400447f8
(gdb) disas main
Dump of assembler code for function main:
0x08048384 <main+0>:    push   %ebp
0x08048385 <main+1>:    mov    %esp,%ebp
0x08048387 <main+3>:    sub    $0x8,%esp
0x0804838a <main+6>:    and    $0xfffffff0,%esp
0x0804838d <main+9>:    mov    $0x0,%eax
0x08048392 <main+14>:   sub    %eax,%esp
0x08048394 <main+16>:   movl   $0x80484c4,(%esp)
0x0804839b <main+23>:   call   0x80482a0 <_init+40>
0x080483a0 <main+28>:   leave  
0x080483a1 <main+29>:   ret    

Como vemos en el tope de la pila tenemos 0x080483a0, que es justo la direccion
siguiente al call, y como segundo parametro tenemos 0x080484c4:
(gdb) x/1s 0x080484c4
0x80484c4 <_IO_stdin_used+4>:    "tralala"

Que es el parametro de system.

> 
> Mi duda es por qué esta estructura funciona. En principio, el sistema 
> operativo lee la dirección de retorno y la carga en el eip, pero
> nunca había visto casos en los que se añadan estos dos parámetros 
> adicionales. En el código ensamblador que se genera para un
> programa tampoco aparece esta ordenación en memoria, al menos lo que yo 
> he visto con el ddd.
> 
> La segunda duda es en relación con la dirección de system (). 
> Dependiendo del procedimiento que utilice, aparece una dirección
> 0x80X o una dirección 0x40X. Entiendo que la primera es la de la 
> librería libc y la segunda la llamada a sistema del kernel, pero
> no he encontrado ninguna forma de demostrarlo.

La primera no es la de la libc, la primera es una direccion que
apunta a una entrada de la PLT (Procedure Linkage Table) del ELF cargado. 
La PLT es utilizada en los ficheros ELF para el linkado dinamico de
funciones. Para mas informacion al respecto leer la seccion apropiada
de la especificacion ELF [1]. La segunda si que pertenece a la direccion
de system de la libc segun ha sido esta mapeada en memoria:

(gdb) p system
$1 = {<text variable, no debug info>} 0x4006f8b0 <system>     (*)

$ ldd ./test
     libc.so.6 => /lib/tls/libc.so.6 (0x4002f000) <--------\
  /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)    |
                                                           |
$  objdump -T  /lib/tls/libc.so.6 |grep -i system          |
000408b0  w   DF .text  00000087  GLIBC_2.0   system       |
  ^-------------------------------------------\            |
                                              |            |
                                   /----------+------------| 
                                   |          |
$ perl -le 'print sprintf "%x",0x4002f000+0x000408b0'
4006f8b0  <--------- (*)

Las llamadas al kernel en Linux/x86 se hacen mediante
la interrupcion 0x80, pasandose los parametros de la misma
por registro: 
    movl   $0xb,%eax  (La syscall 11 es execve)
    movl   &"/bin/sh", %ebx  (1er parametro)
    movl   &argv[], %ecx   
    movl   &envp[], %edx
    int 0x80        (Consultar man execve, para ver el significado
                     de estos parametros)

Una llamada a system, se traducira finalmente
en un fork y una llamada a la syscall execve:
$ strace -f ./test
...
[pid 14252] execve("/bin/sh", ["sh", "-c", "tralala"], [/* 33 vars */]) = 0
....

[1] http://x86.ddj.com/ftp/manuals/tools/elf.pdf

> 
> Muchas gracias. Un saludo.
> 
> 
> 

Te recomiendo que antes de meterte en return-into-libc, leas el
"smashing the stack for fun and profit" de aleph1, del
cual hay traducciones al castellano en la red.

Saludos,

-- 
dreyer / Jose Carlos Luna Duran  @ UJI
luna at aditel.org / Jose.Carlos.Luna at cern.ch




More information about the hacking mailing list