Mostrando entradas con la etiqueta C. Mostrar todas las entradas
Mostrando entradas con la etiqueta C. Mostrar todas las entradas

10 febrero 2008

El efecto 2038 y como probarlo desde C

Ya se habló de sobra del efecto 2000 hace tiempo: los programas que empleasen dos cifras para las fechas podrían considerar que el año 00 no era el 2000, sino el 1900, y calcular incorrectamente cualquier diferencia entre tiempos, con los problemas que podría conllevar.

Pero también existe el efecto 2038: muchos sistemas actuales están programados en lenguaje C (o alguno de sus "derivados") y en estos sistemas la forma "habitual" de contar fechas es como segundos transcurridos desde el 1 de enero de 1970.

¿El problema? En un sistema de 32 bits, como son la mayoría de los actuales, el tipo de datos "time_t" usado para guardar fechas equivale a un "long", un entero largo... y el número de segundos que se puede contar acaba en 2038, en la madrugada del 19 de enero. Un ejemplo para probarlo desde C sería éste


#include
#include

int main ()
{
time_t momento;
int i;

momento = 2147483641;
for (i = 0; i < 10; i++) {
momento ++;
printf ( "El instante %ld se convierte en %s\n",
momento, ctime ( &momento ) );
}

return 0;
}


Se supone que es C estándar, debería funcionar tanto desde Windows como desde Linux, y su resultado debería ser algo como:

El instante 2147483642 se convierte en Tue Jan 19 04:14:02 2038
El instante 2147483643 se convierte en Tue Jan 19 04:14:03 2038
El instante 2147483644 se convierte en Tue Jan 19 04:14:04 2038
El instante 2147483645 se convierte en Tue Jan 19 04:14:05 2038
El instante 2147483646 se convierte en Tue Jan 19 04:14:06 2038
El instante 2147483647 se convierte en Tue Jan 19 04:14:07 2038
El instante -2147483648 se convierte en (null)
El instante -2147483647 se convierte en (null)
El instante -2147483646 se convierte en (null)
El instante -2147483645 se convierte en (null)


¿La solución? Debería venir "sóla". En sistemas de 64 bits, y si se usa realmente el tipo "time_t", esto debería ocultar los detalles del hardware, y debería bastar con recompilar el fuente para tener una vida muuuucho más larga. Esperemos que en el 2038 no queden muchos equipos con software de 32 bits y/o que no lo hayan previsto...

Más detalles en la Wikipedia: Efecto 2038
Y en algún que otro sitio más, como El módem

02 mayo 2007

Operaciones frecuentes con arrays

Hay una serie de operaciones que son muy frecuentes cuando uno tiene una serie de datos almacenados en un array. En ocasiones se trata de "operaciones reales", y otras veces son ejercicios casi "puramente académicos", destinados a ayudar a afianzar conocimientos a la gente que comienza a programar.

En cualquier caso, es una duda habitual de programadores que comienzan, por lo que vamos a ver las operaciones que más se repiten. En los casos supondré que tenemos un array que no está totalmente lleno de datos, y que los datos existentes están al comienzo del array.

Definir un array: reservamos espacio y "anotamos" que todavía no hay ningún dato.

#define MAXDATOS 100

float datos[MAXDATOS];
int cantidadDatos = 0;


Añadir un dato al final: si queda espacio, lo guardamos en la última posición y aumentamos el contador.

if (cantidadDatos < MAXDATOS) {
datos[cantidadDatos] = valor;
cantidadDatos++;
}


Ver si existe un cierto valor: si el array no está ordenado, habrá que recorrerlo de principio a fin hasta encontrar el valor o ver si el array termina.

encontrado = 0;
for (i=0; i < cantidadDatos; i++)
if (datos[i] == valorBuscado) {
encontrado = 1;
break;
}



Hallar el valor máximo (en una lista no ordenada): podemos comenzar asumiendo que el máximo es el primer valor de la lista y entonces comparar con todos los demás para ver si alguno es mayor que él (estamos dando por sentado que hay al menos un dato en la lista).
maximo = datos[0];
for (i=1; i < cantidadDatos; i++)
if (datos[i] > maximo)
maximo = datos[i];


Hallar el valor mínimo: similar al anterior.


Eliminar una posición de la lista: habrá que mover "hacia atrás" todos los datos que existían a continuación del que eliminamos, y entonces "anotar" que hay un dato menos (ni siquiera haría falta "vaciar" el último dato).

for (i=posicionABorrar; i < cantidadDatos-1; i++)
datos[i] = datos[i+1];
cantidadDatos--;


Insertar un valor en una posición: habrá que mover "hacia adelante" todos los datos a partir de esa posición (deberemos empezar desde el final), y finalmente guardar el dato.

if (cantidadDatos < MAXDATOS) {
for (i=cantidadDatos; i >= posicionInsertar;i--)
datos[i+1] = datos[i];
datos[posicionInsertar] = nuevoValor;
cantidadDatos++;
}



Ordenar los datos: hay muchas formas de ordenar. Normalmente las más sencillas son también las menos eficientes, pero como no buscamos eficiencia, sino entender una forma simple de ordenar, podríamos utilizar el método de "la burbuja": en la primera pasada se compara el primer número con el segundo, y se intercambian si están en orden incorrecto; luego se compara el segundo con el tercero, el tercero con el cuarto, y así sucesivamente. De esta forma, al final de la primera pasada ya tenemos el último número colocado al final. Luego repetimos con los n-1 restantes, para obtener otro número, y así hasta terminar. Se trata de dos bucles "for":


for (i=0; i < cantidadDatos-1; i++)
for (j=i+1; j < cantidadDatos; j++)
if (datos[i] > datos[j]) {
temporal = datos[i];
datos[i] = datos[j];
datos[j] = temporal;
}

08 abril 2007

Crackeando ficheros ZIP

A raíz del último post, me he preguntado cuanto tiempo se tardaría en descubrir claves usando "fuerza bruta" en el caso de que hubiera además que acceder a un fichero que contenga la información encriptada. Un caso típico sería el de intentar descubrir la contraseña de un fichero Zip.

Como era de esperar, los resultados en un caso "normal" no son tan favorecedores para el "atacante" como los que aparecían en el Blog original. En mi caso, un Zip con una contraseña de 3 letras ha tardado más de 10 minutos en encontrarla.

Ha sido un ataque "poco eficiente". En concreto, he usado un programa muy simple y que accedía continuamente a un descompresor externo. En estas circunstancias, ha tardado 11 minutos y 42 segundos en probar 10.159 contraseñas de tres letras. Eso quiere decir que era capaz de probar "sólo" 14,5 contraseñas por segundo (como curiosidad, esos son tiempos medidos en Windows; este mismo fuente compilado bajo Linux, y funcionando en circunstancias similares era capaz de probar 125,3 contraseñas por segundo, casi 9 veces más rápido).

Entonces, saquemos cuentas para claves formadas sólo por letras minúsculas (26) o por cualquier carácter ASCII imprimible de 7 bits (95 símbolos, aunque muchos compresores permiten también usar contraseñas con caracteres extendidos).

Longitud de la contraseña
Cualquier ASCII imprimible
Sólo minúsculas
1 carácter
2 caracteres
3 caracteres
4 caracteres
5 caracteres
6,55 segundos
10,37 minutos
16,42 horas
65,01 días
16,92 años
1,79 segundos
46,62 segundos
20,20 minutos
8,75 horas
9,48 días

Como se ve, en ataques tan simples, una contraseña de 5 símbolos no alfabéticos para un fichero ZIP ya daría una seguridad razonable. Pero sólo contra ataques así de simples.

¿Mejoras posibles? Innumerables, claro. Pero se puede hacer que sea MUCHO más rápido apenas con tres cambios:
  • No usar un descompresor externo, sino una rutina de descompresión que sea parte de nuestro fuente, con lo que se evita la carga debida a la llamada a un programa externo.
  • Si tenemos los datos en memoria, en vez de leer el fichero para cada prueba, también se ganaría mucha velocidad.
  • No escribir en pantalla cada clave que se prueba, y menos todavía usando el modo de consola de Windows.

Queda como reto para quien se atreva con ello... ;-)


Por cierto, el fuente de prueba, para los curiosos, era simplemente así (C estándar, usando un descompresor que usa el parámetro -s para indicar la contraseña y -o para sobreescribir resultados en vez de pedir confirmación si el fichero ya existe):


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

int main() {
int resultado;
long contador = 0;
char clave[4]="aaa";
char orden[200];
char a,b,c;

for (a='a'; a<='z'; a++)
for (b='a'; b<='z'; b++)
for (c='a'; c<='z'; c++) {
clave[0] = a;
clave[1] = b;
clave[2] = c;
printf("Probando: %s...\n", clave);
sprintf(orden, "unzip -s%s -o 1.zip", clave);
resultado=system(orden);
contador++;
if (resultado == 0) {
printf("\n\nEncontrada. Es: %s\n", clave);
printf("Claves intentadas: %ld\n", contador);
return 0;
}
}
printf("Clave no encontrada!");
return 1;
}


05 diciembre 2006

PVM desde Ubuntu 6.10

PVM son las siglas de Parallel Virtual Machine. Es una de las herramientas software disponibles para hacer que varios ordenadores "normales" se puedan comportar como un sistema paralelo.

En Internet es relativamente fácil encontrar recopilaciones de instrucciones que enseñan a instalarlo y ponerlo en marcha. El problema es que la mayoría de ellas están en inglés, y alguna que otra está en español pero ya no se corresponde con lo que actualmente podría resultar la opción más cómoda.

¿Cual sería esa "opción más cómoda"? Posiblemente, emplear un gestor de paquetes como Synaptic (o el que incluya nuestra distribución de Linux) para descargar la herramienta con todas sus dependencias e instalarla.

Pero si usamos esa instalación "automática", quizá las instrucciones que encontremos por Internet ya no nos sean tan útiles. Ese ha sido mi caso, y por ello comentaré lo que he tenido que hacer hasta conseguir que PVM funcionara en mi sistema, por si alguien más se encuentra con el mismo problema.

En mi caso, he usado la distribución de Gnu/Linux conocida como Ubuntu 6.10.

Mi primera opción a la hora de plantearme "jugar" con PVM ha sido ver si aparecía en la lista de paquetes accesibles mediante el gestor de paquetes Synaptic. Efectivamente, en la sección "Desarrollo" del repositorio "universe" se encontraba la versión 3.4.5. He marcado para descarga el paquete "pvm", pero también el "pvm-dev", para poder crear programas para mi "ordenador paralelo".

Hasta aquí sin problemas. Desde un terminal, tecleo "pvm" y puedo entrar, empezamos bien. De momento, volvamos a salir con "halt".

Vamos a comprobar que podemos compilar programas sencillos. Busco el "hello.c" y el "hello_other.c" que deberían estar entre los ejemplos de PVM. Primer problema: la mayoría de instrucciones que había encontrado por Internet decían que PVM suele estar instalado en /usr/share/pvm3/ ... pero no es mi caso. Pido ayuda al bendito "whereis": tecleo "whereis pvm" y me cuenta que ha ido a parar a/usr/lib/pvm3/

Ya sabemos donde buscar. Entro a /usr/lib/pvm3/ y resulta que hay muy poca cosa: una carpeta "bin", casi vacía, otra "conf" con dos ficheros llamados LINUX.def y LINUX.m4 (al menos ya sé el nombre de mi arquitectura), y otra carpeta "lib", en la que se encuentran los supuestos ejecutables "pvm", "pvmd", etc (que realmente son enlaces a ficheros existentes en /usr/bin/).

Pero no hay ejemplos. Bueno, no es grave. Los busco en otra distribución "empaquetada" de PVM, y los copio a mi carpeta de trabajo. Por si alguien no los encuentra, esta es una versión "ligeramente traducida" de hello.c:

----------------


static char rcsid[] =
"$Id: hello.c,v 1.2 1997/07/09 13:24:44 pvmsrc Exp $";

/*
* PVM version 3.4: Parallel Virtual Machine System
* University of Tennessee, Knoxville TN.
* Oak Ridge National Laboratory, Oak Ridge TN.
* Emory University, Atlanta GA.
* Authors: J. J. Dongarra, G. E. Fagg, M. Fischer
* G. A. Geist, J. A. Kohl, R. J. Manchek, P. Mucci,
* P. M. Papadopoulos, S. L. Scott, and V. S. Sunderam
* (C) 1997 All Rights Reserved
*
* NOTICE
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby granted
* provided that the above copyright notice appear in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation.
*
* Neither the Institutions (Emory University, Oak Ridge National
* Laboratory, and University of Tennessee) nor the Authors make any
* representations about the suitability of this software for any
* purpose. This software is provided ``as is'' without express or
* implied warranty.
*
* PVM version 3 was funded in part by the U.S. Department of Energy,
* the National Science Foundation and the State of Tennessee.
*/

#include <stdio.h>
#include "pvm3.h"

main()
{
int cc, tid;
char buf[100];

printf("Soy la tarea t%x\n", pvm_mytid());

cc = pvm_spawn("hello_other", (char**)0, 0, "", 1, &tid);

if (cc == 1) {
cc = pvm_recv(-1, -1);
pvm_bufinfo(cc, (int*)0, (int*)0, &tid);
pvm_upkstr(buf);
printf(" respuesta de la tarea t%x: %s\n", tid, buf);

} else
printf("No se ha podido lanzar hello_other\n");

pvm_exit();
}

----------------

y esta sería la equivalente versión "ligeramente traducida" de hello_other.c:

----------------


static char rcsid[] =
"$Id: hello_other.c,v 1.2 1997/07/09 13:24:45 pvmsrc Exp $";

/*
* PVM version 3.4: Parallel Virtual Machine System
* University of Tennessee, Knoxville TN.
* Oak Ridge National Laboratory, Oak Ridge TN.
* Emory University, Atlanta GA.
* Authors: J. J. Dongarra, G. E. Fagg, M. Fischer
* G. A. Geist, J. A. Kohl, R. J. Manchek, P. Mucci,
* P. M. Papadopoulos, S. L. Scott, and V. S. Sunderam
* (C) 1997 All Rights Reserved
*
* NOTICE
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby granted
* provided that the above copyright notice appear in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation.
*
* Neither the Institutions (Emory University, Oak Ridge National
* Laboratory, and University of Tennessee) nor the Authors make any
* representations about the suitability of this software for any
* purpose. This software is provided ``as is'' without express or
* implied warranty.
*
* PVM version 3 was funded in part by the U.S. Department of Energy,
* the National Science Foundation and the State of Tennessee.
*/

#include "pvm3.h"
#include <stdio.h>

#include
<string.h>

main()
{
int ptid;
char buf[100];

ptid = pvm_parent();

strcpy(buf, "hello, world from ");
gethostname(buf + strlen(buf), 64);

pvm_initsend(PvmDataDefault);
pvm_pkstr(buf);
pvm_send(ptid, 1);

pvm_exit();
exit(0);
}

----------------

Para compilar cada uno de ellos podríamos usar "aimk" o simplemente una línea de comandos como ésta:

cc hello.c -o hello -lpvm3

y lo mismo para el otro fuente:

cc hello_other.c -o hello_other -lpvm3


Los dos ejecutables están preparados. Sería mucha casualidad que todo funcionara ya, pero podemos probarlo. Entramos a "pvm" y tecleamos

spawn -> hello

Con esa orden, debería poner en marcha el programa llamado "hello" y mostrar el resultado en la consola. Pero lo habitual será que no funcione. Deberíamos preparar antes un par de variables de entorno, que le digan dónde está pvm, que arquitectura usamos, etc.

En el caso más favorable, puede que sólo haga falta la variable de entorno que indica dónde están los ejecutables. Salimos de pvm ("halt") y tecleamos:

PVM_PATH=/home/nacho/
export PVM_PATH

(obviamente, en vez de /home/nacho sería la carpeta en que realmente estén nuestros ejecutables)

Ya podemos volver a entrar a "pvm" y volver a intentar el "spawn->hello"

Si no funciona así, quizá debamos "dar todos los pasos": Editamos el fichero ".bashrc" de nuestra carpeta home y le añadimos al final

PVM_ROOT=/usr/lib/pvm3
PVM_ARCH="LINUX"
PATH=$PVM_ROOT/bin:$PATH
export PVM_ROOT PVM_ARCH

Hay por ahí quien dice que es interesante añadirlas también en el /etc/.profile, para que esté accesible a todos los usuarios. También hay quien recomienda añadir la línea

PVM_RSH=/usr/bin/ssh

(y exportarla), para que sea "ssh" el encargado de las conexiones a otros equipos, en vez de rsh.

Pero en teoría sólo con lo anterior debería "casi funcionar". ¿Por qué casi? Porque en principio nuestros ficheros ejecutables se buscarán en PVM_ROOT/bin/PVM_ARCH (en mi caso: /usr/lib/pvm3/bin/LINUX/). Esa carpeta puede no existir, así que deberíamos crearla, y posteriormente copiar los ejecutables en ella. ¿Y si nuestros programas usan ficheros o algún otro dato local? La configuración por defecto es que se guarden en nuestra carpeta "home" (para mí: /home/nacho/)

Una vez que están copiados nuestros ejecutables a la carpeta correcta (o reconfigurado el sistema), si tecleamos "spawn -> hello" deberiamos obtener una respuesta parecida a ésta:

-------
spawn -> hello
[1]
1 successful
t40002
pvm> [1:t40003] EOF
[1:t40002] Soy la tarea t40002
[1:t40002] respuesta de la tarea t40003: hello, world from nacho-laptop
[1:t40002] EOF
[1] finished
-------

Prueba superada. Ahora "sólo" queda comenzar a crear nuestros programas.

Una cosa antes de terminar: se supone que la ventaja de usar PVM es tener una "máquina paralela virtual", así que lo suyo es añadir algún otro equipo a nuestra "máquina paralela", de modo que sí se puedan realizar realmente varias cosas a la vez.

La forma de conseguirlo sería "añadiendo" (add) otro ordenador al nuestro desde pvm, con

add equipo2

El único requisito es que ese otro ordenador nos "dé permiso", para lo que debe contener un fichero "/home/nacho/.rhosts", (en la carpeta "home" que corresponda, claro) que es el que le indica qué equipos pueden conectar mediante "rsh", y qué usuarios pueden conectar desde esos equipos, algo como:

equipo1 nacho
equipo3 nacho
equipo4 juan

Podemos comprobar desde dentro de "pvm" cuantos equipos hay conectados con la orden

conf

¿Más información? Algo hay por ahí...
  • Para las órdenes durante el manejo de PVM, como "spawn", se puede pedir ayuda al de siempre: "man pvm"
  • Para variables de entorno, como PVM_ROOT y sus valores por defecto, "man pvm_intro" debería resumirlas todas.
  • Para aprender a usar las funciones de comunicación incluidas en PVM, como pvm_spawn y pvm_recv, habrá que buscar algún tutorial. Hay pocos, y casi todos en inglés... pero esa es otra historia, y deberá ser contada en otro momento... ;-)

¡¡¡Valor y... a por ello!!!

29 noviembre 2006

DJGPP sigue vivo

DJGPP es una adaptación al sistema operativo MsDos del compilador de C de GNU, realizada por DJ Delorie a partir de 1989.

La primera versión se basaba en el compilador "gcc" versión 1.35, y la más reciente (a fecha noviembre de 2006), casi 20 años después, se apoya en la 4.10.

Está originalmente diseñado para MsDos, pero las versiones recientes del entorno funcionan muy bien bajo Windows, permiten nombres de ficheros largos (incluso con espacios, aunque no es recomendable para los fuentes) y manejo de ratón... en modo texto.

La apariencia del entorno, que recuerda mucho a los compiladores de Borland, es ésta:




Sigue siendo un compilador ideal para quien no tiene un ordenador muy potente, o quien necesita aprender programación en un centro de enseñanza que utiliza la platforma Linux y busca una alternativa que le permita practicar en casa sin necesidad de instalar un Linux en su equipo.

Su web oficial es www.delorie.com/djgpp/, pero en ella toda la información que encontrarás está en inglés. Si no te aclaras con el inglés y quieres saltar directamente a la página de descargas, la tienes en www.delorie.com/djgpp/zip-picker.html. En ella puedes elegir qué compiladores quieres (sólo C, o bien incluir otros lenguajes como C++, que es razonable hoy en día, o como Objective C, menos usado). También puedes incluir en la descarga algunos extras recomendables, como el entorno integrado RHIDE (el que aparece en la imagen anterior), o la librería "curses" para acceder a la pantalla siguiendo el estándar de Unix (Linux), o incluso la biblioteca gráfica Allegro, muy usada para crear juegos y programas que tengan que mostrar gráficos en pantalla. Los más atrevidos pueden incluso descargar el código fuente de todas las herramientas.

Eso sí, todo esto nos puede suponer descargar cerca de 30 ficheros ZIP, que habría que descomprimir a mano en sus carpetas correctas, y posteriormente se debería crear una variable de entorno y actualizar el PATH del sistema. Todo ello está explicado (en inglés) en la página oficial, y no debería suponer ningún problema para quien ya ha manejado MsDos o el intérprete de comandos de Linux. Pero si este no es tu caso, todavía no está todo perdido...

Si te apetece probarlo desde Windows, pero no te atreves con la instalación "normal", aquí tienes una opción alternativa: puedes descargar este fichero djgpp_instalado.zip (de unos 23 Mb de tamaño), descomprimirlo en la carpeta raíz de tu disco C (debería bastar con hacer doble clic en el fichero para ver su contenido, elegir "copiar" la carpeta llamada "djgpp", desplazarte al disco C y elegir "pegar"), entrar a la nueva carpeta "djgpp" y hacer doble clic en "start". Si lo vas a usar con una cierta frecuencia, te será más cómodo tener un acceso directo en el escritorio (botón derecho sobre el fichero Start, escoger la opción "Enviar a" y la subopción "Escritorio, crear acceso directo".

Este fichero "start" se encarga de crear momentáneamente la variable de entorno, actualizar el PATH y poner en marcha el entorno RHIDE, para que no tengas preocuparte de la parte incómoda.

¡Suerte!