Mostrando entradas con la etiqueta Programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta Programación. Mostrar todas las entradas

martes, 10 de julio de 2012

Hashtables con hsearch_r y hcreate_r, hecho fácil


hsearch_r(), hcreate_r() y hdestroy_r() son funciones POSIX para el manejo de hashtables. No son las más intuitivas, ni tampoco las más portables, pero el hecho de que vengan con cualquier sistema GNU y no tengas que instalarlas las hace muy cómodas.

Dado que trabajar con ellas es poco práctico he creado unas macros que facilitan su uso y hacen el código más legible. Son las siguientes:


#define _GNU_SOURCE
#include <search.h>


#define HASHTABLE_CREATE(name, sz) \
   struct hsearch_data *name = calloc(sz, sizeof(struct htsearch_data*)); \
   hcreate_r(sz, name); 

#define HASHTABLE_DESTROY(name) hdestroy_r(name)

#define HASHTABLE_SET(name, strkey, value) \
{ \
   ENTRY e, *r; \
   e.key = strkey; \
   if(hsearch_r(e, FIND, &r, name)) \
   { \
      r->data = (void*) (value); \
   } \
   else \
   { \
      e.data = (void*) (value); \
      hsearch_r(e, ENTER, &r, name); \
   } \
}


#define HASHTABLE_GET(name, strkey, value) \
{ \
   ENTRY e, *r; \
   e.key = strkey; \
   if(hsearch_r(e, FIND, &r, name)) \
   { \
      value=(int)r->data; \
   } \
   else \
   { \
      value=(int)0; \
   } \
}


Pegando este código en el programa, se pueden usar fácilmente hashtables, como se muestra en el ejemplo:



   HASHTABLE_CREATE(ht, 100);

   HASHTABLE_SET(ht, "e1", 1);
   HASHTABLE_SET(ht, "e2", 2);
   HASHTABLE_SET(ht, "e3", 3);
   HASHTABLE_SET(ht, "e4", 4);
   HASHTABLE_SET(ht, "e5", 5);

   int i;
   HASHTABLE_GET(ht, "e1", i);
   printf("val: %d\n", i);
   HASHTABLE_GET(ht, "e2", i);
   printf("val: %d\n", i);
   HASHTABLE_GET(ht, "e3", i);
   printf("val: %d\n", i);
   HASHTABLE_GET(ht, "e4", i);
   printf("val: %d\n", i);

   HASHTABLE_DESTROY(ht);


Como se ve, el ejemplo es muy sencillo. Primero creamos una estructura con espacio para 100 elementos (caben más, pero habrá colisiones y no será óptimo). Después añadimos elementos, y posteriormente los consultamos. Finalmente, eliminamos la estructura.

A tener en cuenta que HASHTABLE_GET almacena el resultado en "i", lo cual puede confundir un poco al leer el código.

Las macros están hechas para trabajar con valores enteros, pero son fácilmente modificables.






jueves, 26 de enero de 2012

Sockets multiplataforma con GTK+

Recientemente hemos visto como realizar compilación cruzada con GTK+. Esto nos permite escribir aplicaciones que se ejecutan en diferentes plataformas, almenos a nivel gráfico.

Vamos a ver ahora como usar sockets TCP, algo que habitualmente suele estar muy ligado a una plataforma concreta. GTK+ (de hecho es GLIB) nos proporciona una API para acceder a la red. Esta API, al igual que GTK+ es multiplataforma.

Escribiremos un cliente TCP sencillo, que se conectará a un puerto, escribirá un mensaje, y leerá la respuesta.

Primero incluimos las cabeceras habituales en un programa C, junto con las cabeceras de GTK+.

#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>

Necesitamos las siguientes variables;

GError *err = NULL;
GSocket *sock = NULL;
GInetAddress *iaddr = NULL;
GSocketAddress *saddr = NULL;
GCancellable *cancel = NULL;

Usamos las siguientes funciones para inicializar las variables que contienen las direcciones de red. Lògicamente con la IP y puerto que convenga:

g_type_init();
iaddr = g_inet_address_new_from_string("192.168.1.1");
saddr = g_inet_socket_address_new(iaddr, 23);

Abrimos el socket y nos conectamos a el:

sock = g_socket_new(
   G_SOCKET_FAMILY_IPV4,
   G_SOCKET_TYPE_STREAM,
   G_SOCKET_PROTOCOL_TCP,
   &err);

f(!g_socket_connect(sock, saddr, cancel, &err))
{
   printf("Cannot connect\n");
   return -1;
}

Y finalmente, enviamos y recibimos datos:

   char buf[256];
   strncpy(buf, "lst\n", sizeof(buf));
   g_socket_send(sock, buf, strlen(buf), cancel, &err);
   g_socket_receive(sock, buf, sizeof(buf), cancel, &err);

   printf("received:\n%s\n", buf);


Sencillo, ¿no?


miércoles, 25 de enero de 2012

Sockets TCP con Bash

Es muy habitual, cuando se escriben scripts en Bash, recurrir a herramientas como "netcat" para realizar comunicaciones de red. Sin embargo, aunque no es muy conocido, Bash soporta sockets.

Veamos como conectarnos, por ejemplo, a google.
Abrimos el socket y le asociamos el descriptor 3. Recordemos que 0, 1 y 2 se usan habitualmente para la entrada y salida estándard y para la salida de error.

$ exec 3<>/dev/tcp/www.google.com/80

A continuación, enviamos un mensaje por el socket:

$ echo "GET /" >&3

Y finalmente, leemos la respuesta del servidor:

$ cat <&3

HTTP/1.0 302 Found
Location: http://www.google.es/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: PREF=ID=a38d7ed8770f70cb:FF=0:TM=1327418982:LM=1327418982:S=0tgHEV7GxJaC6Wl7; expires=Thu, 23-Jan-2014 15:29:42 GMT; path=/; domain=.google.com
Date: Tue, 24 Jan 2012 15:29:42 GMT
Server: gws
Content-Length: 218
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
... 


martes, 24 de enero de 2012

Compila en Linux y ejecuta en Windows con GTK+

Vamos a ver como realizar una compilación cruzada con las librerías de GTK+. Esto nos permitirá compilar el programa en Linux y ejecutarlo en Windows, por lo que podremos tener programas funcionando en Windows sin tener que usarlo.

Lo primero que tenemos que hacer es instalar el compilador mingw. En mi distribución vale con:

sudo apt-get install mingw32

A continuación tenemos que descargar las librerías necesarias de windows en nuestro Linux. Podemos encontrarlas en

$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/glib/2.28/glib-dev_2.28.8-1_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/atk/1.32/atk-dev_1.32.0-2_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/pango/1.29/pango-dev_1.29.4-1_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/cairo-dev_1.10.2-2_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/libpng-dev_1.4.3-1_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/gettext-runtime-dev_0.18.1.1-2_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.24/gtk+-dev_2.24.8-1_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/fontconfig-dev_2.8.0-2_win32.zip
$ wget http://ftp.gnome.org/pub/gnome/binaries/win32/gdk-pixbuf/2.24/gdk-pixbuf-dev_2.24.0-1_win32.zip

Descomprimiremos los zip dentro del directorio donde se instala mingw. Habitualmente:
/usr/i586-mingw32msvc/

Entramos en dicho directorio y realizamos algunos ajustes:

sed -i 's|^prefix=.*$|prefix=/usr/i586-mingw32msvc|g' lib/pkgconfig/*.pc
cd ./lib
for f in *.lib;
   do mv $f lib${f%%lib}a;
done

En mi caso faltaba un archivo en pkgconfig, lo he corregido con:

$ cp /usr/lib/i386-linux-gnu/pkgconfig/freetype2.pc  pkgconfig/

Pero podría estar ubicado en otro sitio en otras distros.

Llegados a este punto ya deberíamos poder compilar tanto para windows como para linux.

Si compilamos con el siguiente comando:

i586-mingw32msvc-gcc main.c `PKG_CONFIG_LIBDIR=/usr/i586-mingw32msvc/lib/pkgconfig PKG_CONFIG_PATH=/usr/i586-mingw32msvc/lib/pkgconfig pkg-config --cflags --libs gtk+-2.0`

generaremos un ejecutable para Windows. En este caso "a.exe".

Y si compilamos con este comando:

gcc main.c `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0`

Obtendremos un ejecutable para Linux "a.out". Ya tenemos pues, nuestro sistema de compilación cruzada preparado.

Ya podemos trasladar nuestro ejecutable a Windows y probarlo. Para que funcione es necesario haber instalado GTK+ para Windows.

viernes, 4 de septiembre de 2009

ssh en batch sin expect

Ssh y scp no permiten pasar la contraseña como parámetro, lo que complica las cosas a la hora de automatizar tareas. Normalmente se suele recurrir a dos tipos de soluciones: distribuir claves sin password y/o usar expect. La primera no siempre es posible, pues a veces no se tiene el control del servidor. La segunda es más habitual, aunque en ocasiones no se dispone de 'expect', problema con el que me he encontrado recientemente en el proyecto OpenDomo.

 
Como consecuencia he desarrollado la herramienta sshbatch, que permite realizar comandos ssh y scp pasando la passwd como parámetro.

 
Dejo aquí una copia del programa en C y un enlace al grupo de OpenDomo donde expliqué como funciona.


 

lunes, 13 de abril de 2009

Vectores Dinámicos en C

Si queremos usar vectores dinámicos en C necesitamos recurrir a funciones de manejo de memoria de la familia *alloc(). En este post explico cómo hacerlo de forma sencilla y añado algunas macros que facilitan la tarea.


Para empezar necesitaremos declarar el vector. Lo haremos como apuntador a NULL, creando así, un vector vacío. Necesitaremos también una variable que almacene el tamaño del vector, es decir, la cantidad de elementos que contiene.

Como ejemplo crearemos un vector de números enteros.

int *v = NULL;
size_t v_size = 0;



Tenemos un vector vacío, por lo que el siguiente paso no puede ser otro que añadir elementos. Lo haremos mediante realloc(), función que permite reasignar el tamaño reservado en memoria.



v_size++;
v = realloc(v, sizeof(int)*v_size);
v[v_size-1] = 5;


De esta manera podemos añadir tantos elementos como creamos oportuno. El acceso a ellos se realizará mediante la tradicional indexación de vectores v[i], donde i es el índice del vector. Así podremos acceder al contenido de las diferentes posiciones del vector tanto para leerlas como para escribir en ellas.

Finalmente, tendremos que liberar la memoria asignada al vector mediante la función free().


free(v);



A continuación pego un ejemplo completo del uso de vectores dinámicos con las sentencias comentadas anterioremente. Añado tambien un ejemplo de uso de la función qsort() que permite ordenar el vector.

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


int cmp(const void *a, const void *b)
{
   int *ia, *ib;

   ia = (int *) a;
   ib = (int *) b;

   return (*ia - *ib);
}

int main()
{
   /* Declaracion del vector y de su tamaño */
   int *v = NULL;
   size_t v_size = 0;


   /* Añade un elemento al vector con valor 5 */
   v_size++;
   v = realloc(v, sizeof(int)*v_size);
   v[v_size-1] = 5;

   /* Añade un elemento al vector con valor 3 */
   v_size++;
   v = realloc(v, sizeof(int)*v_size);
   v[v_size-1] = 3;

   /* Añade un elemento al vector con valor 1 */
   v_size++;
   v = realloc(v, sizeof(int)*v_size);
   v[v_size-1] = 1;

   /* Mofifica el valor del primer elemento del vector */
   v[0] = 2;

   /* Ordena el contenido del vector */
   qsort(v, v_size, sizeof(int), cmp);

   /* Muestra los elementos del vector */
   int i;
   for(i=0; i<v_size; i++)
      printf("%d ", v[i]);
   printf("\n");

   /* Libera la memoria asignada */
   free(v);

   return 0;
}




La sintaxis usada es un poco engorrosa, principalmente cuando se trata de añadir elementos a un vector. Esto puede solucionarse mediante el uso de macros. A continuación pego un ejemplo usando macros. En el puede verse también cómo pasar un vector como parámetro a una función.


#define VECTOR(v_var,v_size_var,type) \
   type *v_var = NULL; \
   size_t v_size_var = 0;

#define VECTOR_ADD(v_var,v_size_var,type,value) \
   (v_size_var)++; \
   v_var = realloc(v_var, sizeof(type)*(v_size_var)); \
   v_var[v_size_var-1] = value;
   
#define VECTOR_FREE(v_var) if(v_var) free(v_var);


void add_elements(int *v, size_t *v_size)
{
   VECTOR_ADD(v, *v_size, int, 3);
   VECTOR_ADD(v, *v_size, int, 5);
   VECTOR_ADD(v, *v_size, int, 7);
}  

int main()
{
   VECTOR(v, v_size, int);
   VECTOR_ADD(v, v_size, int, 2);
   
   add_elements(v, &v_size);

   int i;
   for(i=0; i<v_size; i++)
      printf("%d ", v[i]);
   printf("\n");
   
   VECTOR_FREE(v);

   return 0;
}




Para finalizar, solo recordar que el punto fuerte de los vectores es el acceso directo a los elementos (solo es necesaria una operación - O(1) ). Esto permite por ejemplo ordenar los elementos del vector a gran velocidad.

Por el contrario son costosas las inserciones de elementos. Pueden añadirse con facilidad al final, pero insertar un elemento en el medio, obliga a recolocar todos los demás elementos. Si se requiere una estructura de datos que permita estas operaciones es mejor recurrir a las listas dinámicas.


martes, 27 de enero de 2009

Bash: printf() en color

El siguiente código permite escribir 'en color' en terminales bash.


#include <stdio.h>

#define COLOR_RED "\e[31m"
#define COLOR_B_RED "\e[31;1m"
#define COLOR_GREEN "\e[32m"
#define COLOR_B_GREEN "\e[32;1m"
#define COLOR_YELLOW "\e[33m"
#define COLOR_B_YELLOW "\e[33;1m"
#define COLOR_BLUE "\e[34m"
#define COLOR_B_BLUE "\e[34;1m"
#define COLOR_MAGENTA "\e[35m"
#define COLOR_BRIGHT "\e[1m"
#define COLOR_CYAN "\e[36m"
#define COLOR_NONE "\e[m"

int main(int argc, char *argv[])
{
    printf(COLOR_RED "hello world\n" COLOR_NONE);
    printf(COLOR_B_RED "hello world\n" COLOR_NONE);
    printf(COLOR_GREEN "hello world\n" COLOR_NONE);
    printf(COLOR_B_GREEN "hello world\n" COLOR_NONE);
    printf(COLOR_YELLOW "hello world\n" COLOR_NONE);
    printf(COLOR_B_YELLOW "hello world\n" COLOR_NONE);
    printf(COLOR_BLUE "hello world\n" COLOR_NONE);
    printf(COLOR_B_BLUE "hello world\n" COLOR_NONE);
    printf(COLOR_CYAN "hello world\n" COLOR_NONE);
    printf(COLOR_MAGENTA "hello world\n" COLOR_NONE);
    printf(COLOR_BRIGHT "hello world\n" COLOR_NONE);
    return 0;
}

domingo, 7 de diciembre de 2008

Cómo crear usuarios en C / Linux

En ocasiones necesitamos crear usuarios desde un programa en C y aunque podría bastar haciendo una llamada a 'useradd' siempre es más elegante usar las llamadas al sistema. En este artículo voy a explicar cómo crear un usuario con su correspondiente registro en /etc/passwd y en /etc/shadow.

Lo primero que necesitamos es una estructura donde guardar los datos que almacenaremos en /etc/passwd, para usar esta estructura 'struct passwd' necesitaremos la cabecera 'pwd.h'.

struct passwd p;

p.pw_name = "myusername";
p.pw_passwd = "x";
p.pw_uid = 1000;
p.pw_gid = 1000;
p.pw_gecos = "Test User";
p.pw_dir = "/home/myusername";
p.pw_shell = "/bin/sh";


A destacar la 'x' en pw_passwd, pues recordemos que estamos usando shadow passwords y por lo tanto la password se guardará en /etc/shadow. Respecto a los demas campos, a elegir.

Aun así, es interesante ver como podemos escoger un id para un usuario para no repetirlo.

struct passwd *pw = NULL;
int min = 1000;
int max = 65000;

f = fopen("/etc/passwd", "r");

/* check user and get valid id */
while ((pw = fgetpwent(f)))
{
if (strcmp(pw->pw_name, p.pw_name) == 0)
{
perror("user_add(): user exists");
exit(0);
}

if ((pw->pw_uid >= p.pw_uid) && (pw->pw_uid <>pw_uid >= min))
{
p.pw_uid = pw->pw_uid + 1;
}
}

fclose(f);



Ya tenemos la entrada de /etc/passwd preparada, así que la añadiremos al fichero de la siguiente manera:


f = fopen("/etc/passwd", "a+");
if(!f)
{
perror("user_add(): cannot open /etc/passwd");
exit(0);
}

if (putpwent(&p, f) == -1)
{
perror("user_add(): putpwent() error");
exit(0);
}

fclose(f);




Vayamos ahora a por las shadow. El proceso es similar al anterior, solo que ahora usaremos una estructura 'struct spwd sp;' para la cual necesitamos la cabecera 'shadow.h'.
Además ha llegado el momento de crear la contraseña, para lo que usaremos la función 'crypt()'. Esta a su vez, necesita una salt, que la crearemos tal y como vemos a continuación.



struct timeval tv;
static char salt[40];

salt[0] = '\0';

gettimeofday (&tv, (struct timezone *) 0);
strcat(salt, l64a (tv.tv_usec));
strcat(salt, l64a (tv.tv_sec + getpid () + clock ()));

if (strlen (salt) > 3 + 8)
salt[11] = '\0';



Esta forma de crear la salt es bastante común, aunque hay otros enfoques válidos. Al final, se trata de genererar diferentes salt cada vez que se crea un usuario.

Vamos ahora a rellenar la estructura shadow:


#define DAY (24L*3600L)
#define WEEK (7*DAY)
#define SCALE DAY
...
struct spwd sp;
sp.sp_namp = p.pw_name;
sp.sp_pwdp = (char*)crypt(passwd, salt);
sp.sp_min = 0;
sp.sp_max = (10000L * DAY) / SCALE;
sp.sp_lstchg = time((time_t *) 0) / SCALE;
sp.sp_warn = -1;
sp.sp_expire = -1;
sp.sp_inact = -1;
sp.sp_flag = -1;



El segundo campo corresponde a la contraseña cifrada en DES, de la forma tradicional y poco segura. Este parámetro se puede mejorar usando otras funciones de cifrado.
Los parámetros siguientes indican los dias de cambios de password, expiración de la cuenta, etc. Ver detalles mediante 'man shadow'.

Finalmente, solo nos queda añadir el registro en /etc/shadow:

f = fopen("/etc/shadow", "a+");
if(!f)
{
perror("user_add(): cannot open /etc/shadow");
exit(0);
}

if (putspent(&sp, f) == -1)
{
perror("user_add(): putspent() error");
exit(0);
}

fclose(f);




Algunos cambios interesantes son usar una función de cifrado más segura, bloquear los archivos /etc/passwd y /etc/shadow antes de abrirlos, y verificar en /etc/shadow que no existe ningun registro que coincida con el que vamos a añadir, tal y como hemos hecho con /etc/passwd. Tambien quedaría realizar una gestión del grupo, como por ejemplo, crearlo si es necesario. En cualquier caso quedan cubiertos los objetivos de este artículo, ver como se hece en leguaje C para crear un usuario de Linux.

A propósito, borrar un usuario consisitiría, simplemente, en eliminar la linea correspondiente de /etc/passwd y /etc/shadow. Así pues, que nadie me pida que explique cómo hacer un programa en c para borrar usuarios ;)

sábado, 22 de noviembre de 2008

Multicast sender/listener

Dejo un ejemplo de como hacer multicast en C/Linux.


sender.c - el envío de mensajes



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>


#define PORT 9999
#define GROUP "225.0.0.37"

int main(int argc, char *argv[])
{
struct sockaddr_in addr;
int fd;
char *message="ieps!";

if((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket()");
exit(EXIT_FAILURE);
}

memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(GROUP);
addr.sin_port=htons(PORT);

while(1)
{
if(sendto(fd, message, strlen(message),
0, (struct sockaddr *) &addr,
sizeof(addr)) < 0)
{
perror("sendto()");
exit(EXIT_FAILURE);
}

sleep(1);
}

return 0;
}



listener.c - recepción de mensajes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>


#define PORT 9999
#define GROUP "225.0.0.37"

int main(int argc, char *argv[])
{
struct sockaddr_in addr;
int fd, nbytes;
unsigned int addrlen;
struct ip_mreq mreq;
char msgbuf[256];
u_int yes=1;

if((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket");
exit(1);
}

if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
&yes, sizeof(yes)) < 0)
{
perror("setsockopt() SO_REUSEADDR");
exit(1);
}

memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=htons(PORT);

if(bind(fd,(struct sockaddr *) &addr,
sizeof(addr)) < 0)
{
perror("bind");
exit(1);
}

mreq.imr_multiaddr.s_addr=inet_addr(GROUP);
mreq.imr_interface.s_addr=htonl(INADDR_ANY);

if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) < 0)
{
perror("setsockopt");
exit(1);
}


while(1)
{
addrlen=sizeof(addr);
if ((nbytes=recvfrom(fd, msgbuf,
sizeof(msgbuf), 0, (struct sockaddr *)
&addr, &addrlen)) < 0)
{
perror("recvfrom");
exit(1);
}

puts(msgbuf);
}

return 0;
}

lunes, 6 de octubre de 2008

¿Qué pasa con atoll()?

Como programador de sistemas UNIX a veces me encuentro con algunas cosas curiosas con las APIs. Hoy, sin ir mas lejos, me he vuelto loco intentando hacer funcionar atoll().

En una parte de un viejo programa, se manejaba un identificador de menos de 32 bits. Ese identificador, que provenía de una máquina Cisco en formato cadena, estaba declarado como 'int' y se transformaba mediante atoi().

Por necesidades ligadas al crecimiento de la empresa, ahora el identificador debía poder almacenar hasta 56 bits.

En pocos segundos tenía el programa funcionando con un identificador declarado como 'long', pero ooops! no funcionaba. De hecho, se comportaba como un 'int'.

El sistema, un CentOS 5.2, el compilador, gcc 4.1.2, sin problemas conocidos. Así que me dediqué a hacer algunas pruebas:

$ cat test1.c

#include<stdio.h>
int main()
{
/* 2^31 = 2147483648 */

char *str="2147483648";
long long i;

i = atoll(str);

printf("%ld\n", i);
}


$ gcc test1.c
$ ./a.out
-2147483648

Overflow, exactamente igual que un 'int'.
Me dirijo al man de atoll() y me encuentro con esto:
"The atol() and atoll() functions behave the same as atoi()"

y efectivamente. La culpa es de la función de conversión, pues con una función my_atoll() personalizada, no existe tal problema:


$ cat test2.c

#includes<stdio.h>
long long my_atoll(char *str)
{
long long res;

res = 0;
for (; *str; str++)
res = 10*res + (*str - '0');

return res;
}


int main()
{
/* 2^31 = 2147483648 */

char *str="2147483648";
long long i;

i =my_atoll(str);


printf("%lld\n", i);
}


$ gcc test2.c
$ ./a.out
2147483648


Sorprendente ...

miércoles, 30 de julio de 2008

Esteganografía: El canal Alpha

En este primer artículo sobre esteganografía veremos como ocultar mensajes dentro de una imagen. Para ello usaremos el lenguaje C y accederemos a imágenes en formato PNG a través de la librería libpng.

Para probar los ejemplos es necesario descargar el programa steg.c que he desarrollado y la imagen de Tux que viene a continuación:

NOTA: Haz clic en la imagen para obtener la original, pues blogspot al redimensionar la imagen se carga su transparencia.




El canal Alpha:

Algunos formatos de imágen como puede ser PNG guardan cada píxel en RGBA. Lo que significa que se guarda información para R (cantidad de color rojo), G (cantidad de color verde), B (cantidad de color azul) y A (opacidad de la imagen).

Representar el color de un píxel mediante un formato RGB es algo ampliamente conocido, así que no entraremos en más detalles. Vamos a centrar nos en la A de RGBA, lo que se conoce como el canal Alpha.

El canal alpha contiene la información referente a la opacidad de la imagen, es decir, cuan transparente es.
En PNG, por ejemplo, se usa un byte en cada píxel para el canal alpha, de manera que, si este tiene valor 0, la imagen es completamente transparente, si tiene valor 255 la imagen no tiene transparencia y para valores entre 0 y 255 el nivel de transparecia cambia gradualmente.

Así pues, si un píxel de cierto color RGB tiene el canal alpha a cero, no se verá. Lo que nos permite ocultar datos en los bytes destinados a R, G y B siempre que sea un píxel transparente.


Acceso a la imagen PNG:

En el programa de ejemplo podemos ver dos funciones "read_png()" y "write_png()". No entraremos en detalle sobre estas dos funciones. Basta decir que la primera carga la imagen en las estructuras de datos de libpng y la segunda escribe el contenido de estas estructuras de datos en una nueva imagen.

Por lo tanto el procedimiento a seguir será leer la imagen, realizar las modificaciones pertinentes y escribir de nuevo la imagen.

Para acceder a los diferentes bytes que componen RGBA lo haremos a traves de las funciones "get_value()" y "set_value()". Mientras que la primera nos permite obtener el valor de R, G, B o A, "set_value()" nos permite modificarlo. Así pues:

- get/set_value(x, y, 0, ...) accede al byte que define R
- get/set_value(x, y, 1, ...) accede al byte que define G
- get/set_value(x, y, 2, ...) accede al byte que define B
- get/set_value(x, y, 3, ...) accede al byte que define A



Ocultando un fichero:

Para ocultar un fichero, el programa tendrá que leer cada byte del fichero a ocultar y escribirlo en un píxel de la imagen PNG donde el canal alpha este a cero. Como ejemplo pego parte de la función "hide_file()":

FILE *f = fopen(msg_file, "r");
if(!f)
error("[hide_file] fopen()");

for(y = 0; y < height; y++)
{
for(x = 0; x < width; x++)
{
if(get_value(x, y, 3)==0)
{
char c1=0;
char c2=0;
char c3=0;

if(!feof(f)) c1 = fgetc(f);
if(!feof(f)) c2 = fgetc(f);
if(!feof(f)) c3  = fgetc(f);

set_value(x, y, 0, c1);
set_value(x, y, 1, c2);
set_value(x, y, 2, c3);
}
}
}

fclose(f);



Extraer el fichero oculto:


De manera similar a cómo hemos ocultado el fichero en el apartado anterior, podemos extraer la información oculta. Veamos un pedazo de "unhide_file()":

FILE *f = fopen(msg_file, "w+");
if(!f)
error("[unhide_file] fopen()");

for(y = 0; y < height; y++)
{
for(x = 0; x < width; x++)
{
if(get_value(x, y, 3)==0)
{
char c1 = get_value(x, y, 0);
char c2 = get_value(x, y, 1);
char c3 = get_value(x, y, 2);

if(c1==0 || c2==0 || c3==0)
{
fclose(f);
return;
}

fprintf(f, "%c%c%c", c1, c2, c3);
set_value(x, y, 0, 0);
set_value(x, y, 1, 0);
set_value(x, y, 2, 0);
}
}
}

fclose(f);






Ejemplo de uso del programa:

Visto el código básico utilizado, podemos empezar a ocultar ficheros. Veamos un ejemplo.

Primero ocultamos un mensaje:

$ gcc steg.c -o steg -lpng
$ echo "Esto es un mensaje de ejemplo" > msg.txt
$ ./steg hide tux.png tux_steg.png msg.txt

Si abrimos la imagen tux_steg.png veremos que nuestros ojos no detectan ninguna modificación.

Ahora obtenemos el mensaje oculto:

$ ./steg unhide tux_steg.png tux_clean.png msg2.txt
$ cat msg2.txt
Esto es un mensaje de ejemplo


Un poco de cifrado:

Actualmente el mensaje esta oculto, pero no es difícil extraerlo, por lo que no estaría mal añadir un poco de cifrado. Lo haremos con la herramienta openssl.

Primero ciframos el archivo:

$ openssl enc -blowfish -in msg.txt -out msg.ciph
enter bf-cbc encryption password:
Verifying - enter bf-cbc encryption password:


A continuación lo codificamos en base64 y lo ocultamos:

$ openssl base64 -in msg.ciph -out msg.cb64
$ ./steg hide tux.png tux_steg.png msg.cb64

En este punto podemos verificar que la imagen con el mensaje oculto no muestra información adicional.


Extraemos el mensaje oculto y lo descodificamos en base64:

$ ./steg unhide tux_steg.png tux_clean.png msg2.cb64
$ openssl base64 -d -in msg2.cb64 -out msg2.ciph


Desciframos el archivo:

$ openssl enc -blowfish -d -in msg2.ciph -out msg2.txt
enter bf-cbc decryption password:

y voilà:

$ cat msg2.txt
Esto es un mensaje de ejemplo


Debilidades del sistema:

Ocultar la información en el canal alpha de la imagen no es precisamente la mejor técnica que se puede usar en esteganografía. De hecho tiene un inconveniente importante: la facilidad con la que se puede saber que hay un mensaje oculto.

Por ejemplo, simplemente poniendo el byte del canal alpha a 255, al visualizar la imagen veríamos las zonas de información con píxeles de diferentes colores.

A continuación dejo un ejemplo de la imagen de Tux, con un fichero oculto y con el canal alpha a 255. En ella vemos la existencia de información oculta en la primera mitad de la imagen.




El mensaje está cifrado, por lo que el atacante no podrá acceder a su contenido. Pero sí podrá detectar el uso de esteganografía, lo que no deja al sistema en muy buen lugar:)


jueves, 24 de julio de 2008

Superado bsgame#1 de Blind Security

El 1 de Julio Blind Security empezaba el I Torneo de Seguridad Informática BlindSec.

Despues de varias intentonas y de encallarme considerablemente en los niveles 9 y 10 he superado el bsgame#1 entre los 10 primeros, lo que me hace ganador de una bonita camiseta ;)

A todo el que le guste romperse el coco con retos informáticos y quiera aprender, le recomiendo encarecidamente que lo intente, dado que el reto continuará abierto.

A mi me ha servido, entre otras cosas, como perfecta excusa para entrar en el terreno de la esteganografía. De hecho, tan pronto como el torneo sea superado (10 finalistas) publicaré un artículo sobre el tema a partir de algunos programas que desarrollé para pasar el nivel 9.





Insisto, si te gusta la seguridad informática y el hacking ético, no lo dudes, pásate por el torneo.

sábado, 31 de mayo de 2008

Sockets en shellscript con xinetd

Existe la posibilidad de crear aplicaciones sencillas "tipo servidor" con scripts de shell. Vamos a ver un ejemplo en el que se configurará el daemon xinetd para abrir un socket y poner un programa a la escucha. En nuestro caso el programa en cuestión será un script, aunque podría utilizarse cualquier otro lenguaje de programación.

Configurando Xinetd:
Generalmente disponcremos de un archivo de configuración de xinetd similar al siguiente:


$ cat /etc/xinetd.conf
#
# Simple configuration file for xinetd
#
# Some defaults, and include /etc/xinetd.d/

defaults
{
instances               = 60
log_type                = SYSLOG authpriv
log_on_success          = HOST PID
log_on_failure          = HOST
cps                     = 25 30
}
includedir /etc/xinetd.d

Este indica la inclusión de los scripts situados en /etc/xinet.d/.


ls -lh /etc/xinetd.d/
total 100K
-rw-r--r--    1 root     root          563 nov 19 17:53 chargen
-rw-r--r--    1 root     root          580 nov 19 17:53 chargen-udp
-rw-r--r--    1 root     root          419 nov 19 17:53 daytime
-rw-r--r--    1 root     root          438 nov 19 17:53 daytime-udp
-rw-r--r--    1 root     root          341 nov 19 17:53 echo
-rw-r--r--    1 root     root          360 nov 19 17:53 echo-udp
-rw-r--r--    1 root     root          318 jun 23  2002 finger
-rw-r--r--    1 root     root          370 sep  1  2002 imap
-rw-r--r--    1 root     root          365 sep  1  2002 imaps
-rw-r--r--    1 root     root          453 sep  1  2002 ipop2
-rw-r--r--    1 root     root          359 sep  1  2002 ipop3
-rw-r--r--    1 root     root          275 jul  5  2002 ntalk
-rw-r--r--    1 root     root          335 sep  1  2002 pop3s
-rw-r--r--    1 root     root          361 jun 24  2002 rexec
-rw-r--r--    1 root     root          378 jun 24  2002 rlogin
-rw-r--r--    1 root     root          431 jun 24  2002 rsh
-rw-r--r--    1 root     root          317 jun 25  2002 rsync
-rw-r--r--    1 root     root          312 nov 19 17:53 servers
-rw-r--r--    1 root     root          314 nov 19 17:53 services
-rw-r--r--    1 root     root          392 ago 11  2002 sgi_fam
-rw-r--r--    1 root     root          263 jul  5  2002 talk
-rw-r--r--    1 root     root          305 jul 23  2002 telnet
-rw-r--r--    1 root     root          497 nov 19 17:53 time
-rw-r--r--    1 root     root          518 nov 19 17:53 time-udp
-rw-r--r--    1 root     root          276 ago 16  2002 vsftpd


Editando cualquiera de estos archivos puede verse la estructura que siguen los scripts. Para nuestro propósito debemos crear uno de estos scritps, por ejemplo el siguiente:


service nuevoservicio
{
port            = 6666
socket_type     = stream
wait            = no
user            = nobody
server          = /usr/local/bin/NuestroServidor.sh
log_on_success  += USERID
log_on_failure  += USERID
disable         = no
}


El puerto escogido para la escucha debe estar especificado en el archivo /etc/services, en nuestro caso, el puerto 6666, no está especificado, por lo tanto añadimos la siguiente línea a /etc/services:


nuevoservicio 6666/tcp   # Nuestro servicio de prueba

Sencillo, ¿no?.

Ahora solo necesitamos crear el script "NuestroServidor.sh" y colocarlo en /usr/local/bin.

Podemos probar con el clásico:


#!/bin/bash

echo "Hello World!"

Para que el servidor funcione correctamente debemos asegurarnos de que dispone de permisos de ejecución para todos los usuarios.

finalmente reiniciamos xinetd:


$ /etc/rc.d/init.d/xinetd restart
Parando xinetd:                                            [  OK  ]
Iniciando xinetd:                                          [  OK  ]


Podemos probar con:


$ telnet localhost 6666
Trying 127.0.0.1...
Connected to hackerbox (127.0.0.1).
Escape character is '^]'.
Hello World!
Connection closed by foreign host.


El programa llamado por xinetd podría haberse desarrollado en cualquier otro lenguaje de programación: C, Perl ... Lo interesante de esta forma de hacerlo es que permite la creación de servicios simples en un tiempo récord. Aunque no se debe olvidar que en estos sistemas la seguridad no es uno de sus fuertes. Con cuidado!

sábado, 23 de febrero de 2008

Publicidad web: Automatizando clics

Aunque la automatización del movimiento del mouse y la realización de clics tiene cientos de aplicaciones lícitas, a nadie se le escapa la posibilidad de usarlo para realizar clics fraudulentos en la los anuncios contextuales de la web.

La primera pregunta que se plantea uno ...

... es qué medidas de seguridad adoptarán las empresas que, como por ejemplo Google (Adsense), ofrecen este servicio.

Es lógico pensar que si todos los clics vienen desde la misma IP no se tendrán en cuenta. Pero esta barrera es fácil saltársela con sistemas de navegación anónima como Tor.

Pero las empresas que se dedican a estas cosas, que no son tontas, no dan esos clics por válidos.

Hasta aqui todo bien, pues sigue la lógica que cabría esperar.

El problema se produce en el momento que estas empresas deciden considerar al propietario de la web que recibe los clics como culpable de los mismos, eliminándole la cuenta.

Este hecho produce una consecuencia terrible: la aparición de un ataque de denegación de servicio.


La segunda pregunta que se plantea uno ...

... es cuanto se puede tardar en desarrollar un programa que mueva el mouse a una posición (x,y) y realice un clic sobre ella.

Después de googlear un poquito y realizar algunas pruebas la conclusión es extremecedora ... En unos 20 minutos el programa esta listo.


Cómo mover el mouse con XLib:

A continuación pego una función que mueve el puntero del mouse a la posición X,Y pasada como parámetro.

void mouse_move(int x, int y)
{
Display *display = XOpenDisplay(0);
Window root = DefaultRootWindow(display);
XWarpPointer(display, None, root, 0, 0, 0, 0, x, y);
XCloseDisplay(display);
}


Cómo hacer clic con XLib:

A continuación pego una función que hace clic sobre la posición actual del mouse. Para realizar clic con el botón derecho se usa button=0.

void mouse_click(int button)
{
Display *display = XOpenDisplay(NULL);
XEvent event;
memset(&event, 0x00, sizeof(event));
event.type = ButtonPress;
event.xbutton.button = button;
event.xbutton.same_screen = True;
XQueryPointer(display, RootWindow(display, DefaultScreen(display)), 
&event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
event.xbutton.subwindow = event.xbutton.window;

while(event.xbutton.subwindow)
{
event.xbutton.window = event.xbutton.subwindow;
XQueryPointer(display, event.xbutton.window, &event.xbutton.root,
&event.xbutton.subwindow, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
}

if(XSendEvent(display, PointerWindow, True, 0xfff, &event)==0)
fprintf(stderr, "XSendEvent()\n");

XFlush(display);
usleep(100000);

event.type = ButtonRelease;
event.xbutton.state = 0x100;

if(XSendEvent(display, PointerWindow, True, 0xfff, &event)==0)
fprintf(stderr, "XSendEvent()\n");

XFlush(display);
XCloseDisplay(display);
}




El programa:

Vistas las funciones anteriores resulta trivial desarrollar un programa:

int main(int argc, char* argv[])
{
if(argc!=3)
{
printf("Usage: %s [x coord] [y coord]\n\n", argv[0]);
return 0;
}

int x = atoi(argv[1]);
int y = atoi(argv[2]);
mouse_move(x, y);
mouse_click(0);
return 0;
}


Solo queda decir que las librerias necesarias de XLib son X11/Xlib.h y X11/Xutil.h
Y que el programa se compila con:

$ gcc click.c -lX11


Conclusiones:

La publicidad web genera mucho dinero, lo que la hace atractiva para todo intento de uso fraudulento. Sin embargo las empresas que se dedican a esto saben lo que hacen y no resulta sencillo tomarles el pelo.
Lo que quiero decir con esto es que el intento de ganar dinero haciendo clics en publicidad propia dificilmente llegará a buen término, por lo que no se lo recomiendo a nadie.

Para finalizar un comentario para las empresas que se dedican a cancelar la cuenta del usuario que recibe clics fraudulentos: mala idea.


sábado, 21 de julio de 2007

Hash mediante C y OpenSSL


OpenSSL es una conocida librería que implementa ciertos algoritmos usados en criptografía. Aunque su nombre pareza indicar lo contrario, no es una librería usada únicamente para desarrollar canales SSL, pero esta es una de sus principales funciones. A continuación veremos como usar OpenSSL para implementar algoritmos de hash.

OpenSSL proporciona también un binario que permite usar criptografía desde la linea de comandos. Como el caso que nos ocupa son los algoritmos de hash, veamos primero como usar openssl para tal propósito.

Primero crearemos un fichero de ejemplo y calcularemos su hash con el algoritmo MD5:


$ echo test > test.txt
$ openssl md5 test.txt
MD5(test.txt)= d8e8fca2dc0f896fd7cb4cb0031ba249



Para usar cualquier otro algoritmo de hash (siempre que sea soportado por OpenSSL) usaremos el mismo método. Por ejemplo, para SHA1:


openssl sha1 test.txt
SHA1(test.txt)= 4e1243bd22c66e76c2ba9eddc1f91394e57f9f83



Para usar las funciones de hash de OpenSSL desde el lenguaje C, necesitaremos incluir la cabecera "openssl/evp.h" en nuestras fuentes y llamar a la función OpenSSL_add_all_digests(). A continuación usaremos EVP_get_digestbyname() para obtener el algoritmo de hash que queremos utilizar: md5, sha1, etc. Finalmente, las funciones EVP_DigestInit(), EVP_DigestUpdate() y EVP_DigestFinal() nos permitirán calcular el hash.

Ejemplo:


unsigned char *simple_digest(char *alg, char *buffer, 
unsigned int len, int *olen)
{
EVP_MD *m;
EVP_MD_CTX ctx;
unsigned char *ret;

OpenSSL_add_all_digests ();
if (!(m = (EVP_MD*) EVP_get_digestbyname(alg)))
return NULL;

if (!(ret = (unsigned char *) malloc(EVP_MAX_MD_SIZE)))
return NULL;

EVP_DigestInit(&ctx, m);
EVP_DigestUpdate(&ctx, buffer, len);
EVP_DigestFinal(&ctx, ret, olen);

return ret;
}




La función simple_digest() permite calcular el hash de una cadena pasada como parámetro "buffer" mediante el algoritmo "alg". Pero en el ejemplo anterior en el que usábamos la herramienta openssl, calculábamos el hash de un fichero entero. Para hacerlo, necesitaremos abrir el fichero, ponerlo en un buffer en memoria y llamar a simple_digest(). A continuación pongo un ejemplo completo:



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>

#define READSIZE 1024

unsigned char *
simple_digest(char *alg, unsigned char *buffer, size_t len, size_t * olen)
{
EVP_MD *m;
EVP_MD_CTX ctx;
unsigned char *ret;

OpenSSL_add_all_digests();
if(!(m = (EVP_MD *) EVP_get_digestbyname(alg)))
return NULL;

if(!(ret = (unsigned char *)malloc(EVP_MAX_MD_SIZE)))
return NULL;

EVP_DigestInit(&ctx, m);
EVP_DigestUpdate(&ctx, buffer, len);
EVP_DigestFinal(&ctx, ret, olen);
return ret;
}

void
print_hex(unsigned char *bs, size_t n)
{
int i;

for(i = 0; i < n; i++)
printf("%02x", bs[i]);
}

unsigned char *
read_file(FILE * f, size_t *len)
{
unsigned char *buffer = NULL, *last = NULL;
unsigned char inbuffer[READSIZE];
int tot, n;

tot = 0;
for(;;)
{
n = fread(inbuffer, sizeof(unsigned char), READSIZE, f);
if(n > 0)
{
last = buffer;
buffer = (unsigned char *)malloc(tot + n);
memcpy(buffer, last, tot);
memcpy(&buffer[tot], inbuffer, n);

if(last)
free(last);
tot += n;

if(feof(f) > 0)
{
*len = tot;
return buffer;
}
}
else
{
if(buffer)
free(buffer);
break;
}
}
return NULL;
}

unsigned char *
process_file(char *algorithm, FILE * f, size_t *olen)
{
size_t filelen;
unsigned char *ret, *contents = read_file(f, &filelen);

if(!contents)
return NULL;

ret = simple_digest(algorithm, contents, filelen, olen);
free(contents);
return ret;
}

int
process_stdin(char *algorithm)
{
size_t olen;
unsigned char *digest = process_file(algorithm, stdin, &olen);

if(!digest)
return 0;

print_hex(digest, olen);
printf("\n");
free(digest);
return 1;
}

void
usage(char *progname)
{

printf("Usage: %s [ md2 | md4 | md5 | sha | sha1 ] [ Files ] \n\n",
progname);
exit(0);
}

int
main(int argc, char *argv[])
{
int i;

/* Sin parametros */
if(argc < 2)
usage(argv[0]);

/* Verificamos los algoritmos */
if((strcmp(argv[1], "md2") != 0) &&
(strcmp(argv[1], "md4") != 0) &&
(strcmp(argv[1], "md5") != 0) &&
(strcmp(argv[1], "sha") != 0) && (strcmp(argv[1], "sha1") != 0))
usage(argv[0]);


/* Un solo parametro, entrada estandar */
if(argc == 2)
{
if(!process_stdin(argv[1]))
perror("stdin");
}

/* Lee y procesa todos los parametros recibidos */
else
{
for(i = 2; i < argc; i++)
{

FILE *file = fopen(argv[i], "rb");
size_t olen;
unsigned char *digest;

if(!file)
{
perror(argv[i]);
return -1;
}

digest = process_file(argv[1], file, &olen);

if(!digest)
{
fclose(file);
return -1;
}

fclose(file);
print_hex(digest, olen);
printf("  %s\n", argv[i]);
free(digest);
}
}
return 1;
}




Finalmente, podemos verificar el correcto funcionamiento de nuestra herramienta comparandola con el binario openssl.


$ gcc -lssl myhash.c -o myhash

$ ./myhash md5 test.txt
d8e8fca2dc0f896fd7cb4cb0031ba249  test.txt

$ openssl md5 test.txt
MD5(test.txt)= d8e8fca2dc0f896fd7cb4cb0031ba249

$ ./myhash sha1 test.txt
4e1243bd22c66e76c2ba9eddc1f91394e57f9f83  test.txt

$ openssl sha1 test.txt
SHA1(test.txt)= 4e1243bd22c66e76c2ba9eddc1f91394e57f9f83



Referencias:
- Network security with OpenSSL, Ed O'Reilly. Viega, Messier, Chandra.

domingo, 15 de julio de 2007

Integer Overflow en Java

En el artículo anterior sobre Integer Overflow, explicaba el concepto y mostraba algunos ejemplos en C. Pues bien, es curioso ver como un lenguaje como Java, considerado muy seguro, también permite este tipo de ataques. Veamos un sencillo ejemplo:


public class ShortOverflow
{
public static void main(String args[])
{
if(args.length<1)         
return;       

short num = Integer.valueOf(args[0]).shortValue();       
System.out.println("num: "+num);       

if(num>100)
System.out.println("num > 100");
else
System.out.println("num < 100");    
}
} 
La versión de máquina virtual usada es la siguiente:
$ java -version
java version "1.5.0_07"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)
Java HotSpot(TM) Client VM (build 1.5.0_07-b03, mixed mode, sharing)
Si ejecutamos el ejemplo vemos como se produce el overflow sin que se produzca ninguna excepción:
$ javac ShortOverflow.java
$ java ShortOverflow 100
num: 100
num < 100

$ java ShortOverflow 101
num: 101
num > 100

$java ShortOverflow 100000
num: -31072
num < 100
Atención! se usa la clase Integer, no la clase Short que sí controla la excepción. Por lo que el problema se produce al realizar el cast de int a short, en la función shortValue(). Lo mismo ocurre si utilizamos el tipo Byte:
public class ByteOverflow
{
public static void main(String args[])
{
if(args.length<1)         
return;       

byte num = Integer.valueOf(args[0]).byteValue();       
System.out.println("num: "+num);       

if(num>100)
System.out.println("num > 100");
else
System.out.println("num < 100");    
}
}
$ javac ByteOverflow.java
$ java ByteOverflow 100
num: 100
num < 100

$ java ByteOverflow 101
num: 101
num > 100

$ java ByteOverflow 1000
num: -24
num < 100

Sin embargo, el desbordamiento no se produce si se usa Integer.valueOf("...").intValue(), Short.valueOf("...").shortValue() o similar:
public class IntegerOverflow
{
public static void main(String args[])
{
if(args.length<1)
return;

int num = Integer.valueOf(args[0]).intValue();

System.out.println("num: "+num);

if(num>100)
System.out.println("num > 100");
else
System.out.println("num < 100");

}
}
$ javac IntegerOverflow.java
$ java IntegerOverflow 10
num: 10
num < 100

$ java IntegerOverflow 100000
num: 100000
num > 100

$ java IntegerOverflow 1000000000000000
Exception in thread "main" java.lang.NumberFormatException: For input string: "1000000000000000"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:459)
at java.lang.Integer.valueOf(Integer.java:553)
at IntegerOverflow.main(IntegerOverflow.java:9)

Krampo en Kriptopolis proporciona una par de enlaces interesantes. En el primero se documenta el comportamiento de shortValue(): http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Number.html#shortValue() En un, muy interesante, segundo enlace se habla del tema: http://mindprod.com/jgloss/overflow.html
Why does Java ignore overflow? Most computer hardware has no ability to automatically generate an interrupt on overflow. And some hardware has no ability to detect it. Java would have to explicitly test for it on every add, subtract and multiply, greatly slowing down production. Further ex-C programmers are very used to this cavalier ignoring of overflow, and commonly write code presuming that high order bits will be quietly dropped. The Pentium has hardware overflow detect but no hardware interrupt. So if Java were to support overflow detect, inside the JVM implementation would need to add a JO *jump on overflow" instruction after every add and subtract, and special code to look at the high order bits of the 32x32->64 bit multiply. 64/32->32 bit division might need special handling.
Para finalizar, veamos un ejemplo ficticio donde se podría explotar este overflow. Supongamos un programa en Java que se utiliza para realizar recargas a teléfonos móviles. Este programa cargará una comisión de un euro en la recarga, y posteriormente, realizará dicha recarga.
public class RecargaMovil
{
public static void main(String args[])
{
if(args.length!=2)
{
System.out.println("Uso: prog [telefono] [importe]");
return;
}

// Aqui se encuentra el error del programador (o vulnerabilidad)
short importe = Integer.valueOf(args[1]).shortValue();
if(importe < = 10)
{
// Quitamos 1 euro de comision;)
importe -= 1;

if(importe<3)
{
System.out.println("Importe demasiado pequeño");
return;
}

System.out.println("Realizando recarga de "+importe+
" euros a "+args[0]);
// Realizar recarga ...
}
else
{
System.out.println("No se permite hacer recargas de mas de 10 euros");
}
}
}
$ java RecargaMovil 93123123 9
Realizando recarga de 8 euros a 93123123

$ java RecargaMovil 93123123 15
No se permite hacer recargas de mas de 10 euros

Si observamos con detenimiento el programa veremos la existencia de un overflow en "importe", al pasar de int a short. Dado que un short permite el almacenamiento de 2^16=65536 valores, o 2^15=32768 por admitir valores con signo, veamos como desbordarlo.
$ java RecargaMovil 98234948 -98302
Importe demasiado pequeño

$ java RecargaMovil 98234948 -98303
Importe demasiado pequeño

$ java RecargaMovil 98234948 -98304
Realizando recarga de 32767 euros a 98234948
Donde "importe" se desborda y nos permite hacer una recarga de 32767 euros:)

sábado, 14 de julio de 2007

Integer Overflow

Un Integer Overflow se produce cuando una operación aritmética intenta almacenar un valor numérico demasiado grande para la variable que lo contiene. De esta forma, si añadimos 1 al valor máximo que una variable puede almacenar, nos encontraremos con resultados curiosos. Veamos que ocurre en un sistema Linux x86.


int main()
{
unsigned short c = 65535;
printf("%d\n", c);
c++;
printf("%d\n", c);
return 0;
}



$ gcc ex1.c
$ ./a.out
65535
0

Como vemos en el ejemplo, al incrementar una variable que ya está en su valor máximo, esta se desborda, empezando de nuevo en el valor cero.
Podemos conocer el valor máximo que se puede almacenar en una variable del lenguaje C usando "sizeof":


int main()
{
printf("size of char: %d bytes\n", sizeof(char));
printf("size of short: %d bytes\n", sizeof(short));
printf("size of int: %d bytes\n", sizeof(int));
printf("size of float: %d bytes\n", sizeof(float));
return 0;
}


$ gcc ex2.c
$ ./a.out
size of char: 1 bytes
size of short: 2 bytes
size of int: 4 bytes
size of float: 4 bytes


Así, por ejemplo, en un char podremos guardar valores menores que 2^8, en un short valores menores que 2^16 y en un int valores menores que 2^32.

En el caso de las variables unsigned, cuando se produce un desbordamiento, nos encontramos con algo similar. Pues si intentamos almacenar un valor negativo en una variable sin signo, se realizará la conversión correspondiente. Por ejemplo, si en una variable "short" podemos almacenar valores menores que 2^16, en una variable "unsigned short" solo podremos almacenar la mitad. Pues se usaran los 2 bytes tanto para almacenar valores positivos como negativos. De esta manera, asignar un valor -1 a un "unsigned short" corresponderá a almacenar el valor 65535, almacenar -2 corresponderá a 65534, y así sucesivamente.


int main()
{
unsigned short c = -1;
printf("%d\n", c);
c++;
printf("%d\n", c);
return 0;
}



$ gcc ex3.c
$ ./a.out
65535
0

No resulta nada complicado hacerse a la idea de como una vulnerabilidad de este tipo puede ser explotada. Por ejemplo, modificando el flujo de ejecución de un programa. En el caso siguiente, se pasa una condición "if" que no debería.


int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: %s num\n", argv[0]);
return -1;
}

size_t num = atoi(argv[1]);

if(num>100)
printf("num > 100\n");
else
printf("num < 100\n);

return 0;
}



$ gcc ex4.c
$ ./a.out 1
num < 100
$ ./a.out 101
num > 101
$ ./a.out -1
num > 100



Para finalizar, veamos un ejemplo de buffer overflow en el que se muestra cómo usar la longitud de una cadena para escribir fuera del buffer. En este caso la variable que permite el overflow es len, dado que existe la posibilidad de que la longitud de la cadena sea zero. Entonces, el valor de len es -1, o lo que viene a ser lo mismo, 255 por ser una variable "unsigned" de 8 bits. Por este motivo, el memcpy copiará 255 bytes a ptr, unos cuantos más de los necesarios. Realmente lo que se desborda es el heap, por lo que se trata, concretamente, de un heap overflow.


int main(int argc, char *argv[])
{
if(argc<=1)      
return -1;   
char *ptr = malloc(strlen(argv[1]));  
int *login_ok = malloc(sizeof(int));  
*login_ok=0;    
printf("size: %d\n", strlen(argv[1]));  
uint8_t len = strlen(argv[1]) -1;   
memcpy(ptr, argv[1], len+1);  
printf("Login: %d\n", *login_ok);   

if(*login_ok)  
{     
printf("Access Accept\n");  
}  
else  
{     
printf("Access Denied\n");  
}  
return 1;
}  
$ gcc ex5.c
$ ./a.out hola
size: 4
Login: 0
Access Denied

$ ./a.out adiossssssssssssssssssssss
size: 26
Login: 0
Access Denied

$ ./a.out ""
size: 0
Login: 3618355
Access Accept 

domingo, 17 de junio de 2007

Static Data Overflow

El Static Data Overflow es un tipo de buffer overflow que se produce con datos estáticos. Estos pueden ser sobreescritos de forma similar a la estudiada en los los buffer overflow o en los heap overflow, es decir, en situaciones en las que el programador no verifica correctamente el tamaño de los datos que copia. Veamos un ejemplo sencillo de Static Data Overflow.
int main(int argc, char *argv[])
{
static int login_ok = 0;
static char user[64];
static char pass[64];

strncpy(user, argv[1], strlen(argv[1]));
strncpy(pass, argv[2], strlen(argv[2]));

printf("User: %s\n", user);
printf("Pass: %s\n", pass);
printf("Login: %d\n", login_ok);

if(login_ok)
{
printf("Access Accept\n");
}
else
{
printf("Access Denied\n");
}

return 0;
}

Como podemos observar en el ejemplo anterior, el programa copia los datos introducidos por el usuario en dos buffers sin verificar correctamente el tamaño de los mismos. Veamos qué ocurre si sobrepasamos los 64 bytes de memoria assignada para las variables user y pass

Esta es la ejecución normal del programa:
$ ./a.out myusername mypass
User: myusername
Pass: mypass
Login: 0
Access Denied

Ahora intentemos sobrepasar el tamaño máximo de la variable user:
$ ./a.out `perl -e 'print "A"x100'` mypass
User: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Pass: mypass
Login: 1094795585
Access Accept


Como vemos el programa nos da acceso sin necesidad de introducir una contraseña correcta. Pero, ¿Qué ha ocurrido? Los datos asignados a las variables user, pass y login_ok están en un espacio contiguo de memoria. Por lo que al sobrepasar el limite de espacio asignado a una de ellas, se sobreescribe el contenido de las demás. Así, si sobrepasamos los 64 bytes de user, sobreescribimos login_ok.

El valor que hemos assignado a login_ok es una cadena de As que ha dado como resultado el valor 1094795585. Aunque cualquier valor diferente de 0 nos hubiese servido igualmente para poder entrar en la sentencia if.

Este tipo de bug está muy relacionado con el heap overflow y/o el buffer overflow, sobre los que ya se ha escrito en este blog. Consultalos para ampliar información.




Heap Overflow

El heap overflow es un tipo de buffer overflow que se produce en un area de memoria denominada heap. La memoria heap se reserva dinamicamente con funciones como por ejemplo malloc() y puede ser sobreescrita de forma similar a la que se produce en los buffer overflow, es decir, en situaciones en las que el programador no verifica correctamente el tamaño de los datos que copia. Veamos un ejemplo sencillo de heap overflow.

int main(int argc, char *argv[])
{
char *user = malloc(64);
char *pass = malloc(64);
int *login_ok = malloc(sizeof(int));
*login_ok=0;

strncpy(user, argv[1], strlen(argv[1]));
strncpy(pass, argv[2], strlen(argv[2]));

printf("User: %s\n", user);
printf("Pass: %s\n", pass);
printf("Login: %d\n", *login_ok);

/* ... check user/pass ... */

if(*login_ok)
{
printf("Access Accept\n");
} 
else
{
printf("Access Denied\n");
}

return 0;
}

Como podemos observar en el ejemplo anterior, el programa copia los datos introducidos por el usuario en dos buffers cuya memoria ha sido reservada con malloc() sin verificar su longitud máxima.
Pero, ¿Qué ocurre si sobrepasamos los 64 bytes de memoria reservada para las variables user y pass? Veamoslo con un ejemplo.

Esta es la ejecución normal del programa:
$ ./a.out myusername mypass
User: myusername
Pass: mypass
Login: 0
Access Denied

Ahora intentemos sobrepasar el tamaño máximo de la variable pass:

./a.out myusername `perl -e 'print "A"x100'`
User: myusername
Pass: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Login: 1094795585
Access Accept

Como vemos el programa nos da acceso sin necesidad de introducir una contraseña correcta. Pero, ¿Qué ha ocurrido? La respuesta es bastantes sencilla. Al reservar memoria con malloc() se nos ha assignado un espacio contiguo de memoria para las variables user, pass y login_ok. Por lo que al sobrepasar el limite de espacio asignado a una de ellas, se sobreescribe el contenido de las demás. Así, si sobrepasamos los 64 bytes de user, sobreescribimos pass y a continuación login_ok. O, como hemos hecho en el ejemplo, si sobrepassamos los 64 bytes de pass, sobreescribimos login_ok.

El valor que hemos assignado a login_ok es una cadena de As que ha dado como resultado el valor 1094795585. Aunque cualquier valor diferente de 0 nos hubiese servido igualmente para poder entrar en la sentencia if.

Existen diferentes maneras de aprovechar un heap overflow para explotar un programa. Estas dependen en gran medida de la imaginación del atacante. Lo más común es sobreescribir directamente una variable como hemos hecho en el ejemplo o modificar un puntero para que salte a cierto código controlado (ver buffer overflow). En cualquier caso, como ya se comentaba en el documento sobre buffer overflow, es necesario verificar correctamente la cantidad de datos que se escribe en los buffers.




sábado, 9 de junio de 2007

Cómo hacer login en UNIX con C

A veces es necesario escribir un programa en C que lea de /etc/passwd y/o /etc/shadow para hacer login. Para este propósito, UNIX ofrece las funciones getpwnam() y getspnam() que permiten acceder a /etc/passwd y /etc/shadow respectivamente. Cada una de estas funciones retorna una estructura con los datos del usuario a partir de su nombre. Las estructuras son las siguientes:


/* Para /etc/passwd usando getpwnam() */

struct passwd
{
char   *pw_name;       /* user name */
char   *pw_passwd;     /* user password */
uid_t   pw_uid;        /* user ID */
gid_t   pw_gid;        /* group ID */
char   *pw_gecos;      /* real name */
char   *pw_dir;        /* home directory */
char   *pw_shell;      /* shell program */
};



/* Para /etc/shadow usando getspnam() */

struct spwd
{
char *sp_namp;         /* Login name */
char *sp_pwdp;         /* Encrypted password */
long  sp_lstchg;       /* Date of last change */
long  sp_min;          /* Min #days between changes */
long  sp_max;          /* Max #days between changes */
long  sp_warn;         /* #days before pwd expires
to warn user to change it */
long  sp_inact;        /* #days after pwd expires
until account is disabled */
long  sp_expire;       /* #days since 1970-01-01
until account is disabled */
unsigned long sp_flag; /* Reserved */
};



A continuación pego unas funciones de ejemplo que permiten hacer login. Con ellas se puede verificar un par user/pass de forma muy simple.

...
if(auth_is_valid_user(user, pass))
login_ok = 1;
...


Para las siguientes funciones es necesario incluir las cabeceras pwd.h, shadow.h y crypt.h. Para compilar, es necesaria la librería crypt (ej: gcc test.c -lcrypt).


int auth_check_pass(const char *clear_pass, const char *encrypted_pass)
{
char *encrypted = crypt(clear_pass, encrypted_pass);

if (strcmp(encrypted, encrypted_pass) == 0)
return 1;
else
return 0;
}

int auth_is_valid_user(const char *user, const char* pass)
{
struct passwd *pw = getpwnam(user);
if(pw==NULL)
return 0;

// password in /etc/shadow
if(strcmp(pw->pw_passwd, "x")==0)
{
/* Solo root puede leer /etc/shadow por lo que esta parte no
funcionara a menos que la ejecute root o el archivo este
setuidado */

struct spwd *sp = getspnam(user);
if(sp==NULL)
return 0;

if(auth_check_pass(pass, sp->sp_pwdp))
return 1;
}

// password in /etc/passwd
else
{
if(auth_check_pass(pass, pw->pw_passwd))
return 1;
}

return 0;
}