Obviamente nadie usa la funcion rand() en programas del mundo real, ¿o si?
No se si colocar esto aqui o en criptografia o hacking. El tema trata de atacar una implementacion DEBIL de numeros random.
El ataque no es complejo, para el ejemplo mostrado pero muestra el peligro de las aplicaciones que usan numeros aleatorios debiles o predecibles.
Hace mas de una año en el foro publique un pequeño codigo del Juego Piedra Papel y Tijera: [Aporte] Piedra Pape y Tijera - Mini-Autómata + Ejercicio [1]
En ese ejemplo como originalmente lo hice para un video tutorial use por comodidad la funcion rand() que viene por defecto en las librerias estandar de muchas implementaciones y para no meter a los que se estan iniciando en temas de seguridad complejos, decidi usar esa funcion sabiendo que no es tan segura. Sin embargo no me habia dado cuenta de la gravedad de los numeros arrojados por rand aun inicializando con la semilla del tiempo. En cualquier caso lo numeros que arroja se pueden calcular usando la misma funcion rand()
En un entorno donde no se tiene acceso al codigo fuente de un programa, es posible (dependiendo de lo que el programa realize) tratar de adivinar que procesos internos realiza el programa en especial cuando involucra numeros aleatorios.
Veamos el ejemplo del piedra papel y tijera:
Código
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<time.h> char *cadenas[] = {"Piedra","Papel","Tijera"}; //0 empate //1 Pierde Usuario //2 Gana Usuario //Filas Computadora //Columas Usuario char tabla_resultados[3][3] = { {0,2,1}, {1,0,2}, {2,1,0} }; char *resultado[] = {"Empate","Gana la COMPUTADORA","Gana el USUARIO"}; int main() { char buffer[10]; char *error = NULL; bool entrar = true; bool jugado = false; int usuario = 0; int computadora = 0; do { do { //Entramos en este ciclo hasta que el usuario juege o decida salir para no afectar el valor rand del ejemplo if(error[0] == 's' || error[0] == 'S') { entrar = false; jugado = true; } else{ if(usuario <=3 && usuario >= 1){ usuario--; //Le restamos uno al input para poder usarlo en una tabla de estados con index 0 jugado = true; } else { jugado = false; } } }while(!jugado); }while(entrar); }
Veamos las partes importantes:
Código
Se inicializa el random con la semilla del tiempo como lo haria cual quier persona que esta aprendiendo a programar con numeros aleatorios.
El codigo mostrado lo escribi para compilarlo con GCC pero deberia de funcionar en otros compiladores de C sin muchos cambios.
Código
La computadora usa funcion rand() y le aplica modulo al resultado para obtener valores validos del 0 al 2.
Pues bien para ejemplo es todo lo que necesitamos saber. Bajo la premisa de que el/los programas usan la semilla del tiempo actual en Segundos desde el primero de enero de 1970 [2]
Entonces en otro programa de C podemos buscar todas las semillas de tiempo en un rango determinado y obtener primeras salida de cada una de ellas.
Ejemplo tomemos la fecha de hoy en el formato devuelto por la funcion time(), desde [3] lo podemos obtener para hoy es:
1503838078
Código
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<time.h> int main() { int i = 0; int resultado = 0; while(i < 10) { i++; } }
Salida
Código:
% ./testrand
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
Resultado: 1
Resultado: 1
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
% ./testrand
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
Resultado: 1
Resultado: 1
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
Como vemos la salida del programa es la misma en ambas ocasiones aunque se ejecuta con segundos de diferencia, posiblemente esto le paso a alguien que uso rand sin inicializar el srand, pero no le dio importancia.
¿Ahora que?
El hecho es que podemos reinicializar srand en cualqueir momento del codigo y la salida de rand va a ser la correspondiente a cada semilla ejemplo:
Código
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<time.h> int main() { int i = 0; int resultado = 0; while(i < 5) { i++; } i = 0; while(i < 5) { i++; } i = 0; while(i < 5) { i++; } }
Noten el cambio de srand de 1503838078 -> 1503838079 -> 1503838078 por lo cual la primeras 5 salidas van a ser iguales a las 15 ultimas
Salida
Código:
% ./hackrand
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
Resultado: 1
Resultado: 0
Resultado: 2
Resultado: 0
Resultado: 2
Resultado: 1
Resultado: 2
Resultado: 0
Resultado: 0
Resultado: 1
Resultado: 1
Ya con esta salida sabemos que podemos calcular cualquier salida de rand() en cualquier momento conociendo o calculando la semilla inicial
Pues bien es momento de ganarle al Piedra papel y tijera perdiendo solo los primeros 2 o 3 juegos y en base a estos tratar de calcular cual fue la semilla utilizada.
Hice un programa que que te muestra las salidas de rand ya con X modulo de las semillas +/- 10 al tiempo actual y apartir de ahi elijes cual semilla continuar viendo Y asi sabremos que jugada va a hacer la computadora antes de tiempo y poderle ganar:
Código
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<time.h> int main(int argc, char **argv) { bool entrar,continuar,seleccione; char buffer[10]; char *error = NULL; unsigned int tiempo = 0; unsigned int base = 0; int numero = 0; int resultado = 0,i = 0,j = 0; int divisor = 0; if(argc == 2) { if(error[0] == 0) { base = tiempo -10; j = 0; while( ( base + j) < (tiempo +10)) { i = 0; while(i < 10) { i++; } j++; } do { seleccione = false; if(error[0] == 's' || error[0] == 'S') { entrar = false; } else { if(numero >= 1 && numero <= 20) { continuar = true; numero--; //Restamos para compensar el +1 en el menu i = 0; while(i < 10) { i++; } do { i = 0; while(i < 5) { i++; } switch(buffer[0]) { case 's': case 'S': continuar = false; break; case '+': continuar = true; break; } }while(continuar); } else { seleccione = true; } } }while(seleccione); } } else { } }
Ejecitamos el programa para forcebrutear el srand con el parametro del modulo que estan aplicando:
Código:
% ./crack_rand.exe 3
Valor actual del tiempo 1503839464
Calculando modulos en +/- 10
Opcion 1: 2, 0, 0, 0, 0, 1, 2, 1, 0, 1,
Opcion 2: 2, 0, 0, 1, 1, 1, 0, 2, 1, 0,
Opcion 3: 2, 0, 1, 2, 2, 0, 2, 0, 0, 2,
Opcion 4: 0, 2, 0, 0, 0, 0, 1, 1, 1, 1,
Opcion 5: 0, 2, 0, 1, 1, 0, 0, 1, 1, 2,
Opcion 6: 0, 2, 2, 2, 2, 0, 1, 2, 1, 1,
Opcion 7: 0, 2, 2, 0, 0, 2, 2, 0, 1, 0,
Opcion 8: 1, 1, 1, 1, 0, 2, 1, 1, 1, 2,
Opcion 9: 1, 1, 2, 2, 2, 0, 2, 0, 2, 1,
Opcion 10: 1, 1, 1, 0, 2, 0, 1, 1, 2, 2,
Opcion 11: 1, 0, 1, 1, 0, 2, 2, 2, 2, 1,
Opcion 12: 2, 1, 0, 0, 1, 2, 1, 2, 2, 0,
Opcion 13: 2, 0, 0, 0, 2, 2, 2, 0, 2, 2,
Opcion 14: 2, 2, 0, 1, 0, 1, 1, 1, 2, 0,
Opcion 15: 2, 0, 0, 0, 0, 1, 0, 2, 0, 1,
Opcion 16: 0, 2, 0, 1, 2, 1, 2, 2, 2, 0,
Opcion 17: 0, 2, 2, 1, 2, 2, 0, 0, 0, 2,
Opcion 18: 0, 2, 2, 0, 0, 1, 1, 1, 2, 1,
Opcion 19: 1, 1, 1, 1, 2, 1, 0, 2, 0, 0,
Opcion 20: 1, 1, 2, 1, 2, 1, 1, 1, 2, 2,
Opcion S: Salir
Seleccione una opcion:
Ejecutamos el progrma de priedra papel o tijera y si bien muchas veces no tenemos acceso al codigo fuente, hay cosas que se pueden determinar con un poco de imaginacion y conocimientos de programacion.
Código:
% ./ppt
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:1
Usuario Piedra vs Computadora Piedra
Resultado: Empate
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:1
Usuario Piedra vs Computadora Tijera
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:1
Usuario Piedra vs Computadora Tijera
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:1
Usuario Piedra vs Computadora Piedra
Resultado: Empate
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:2
Usuario Papel vs Computadora Piedra
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:3
Usuario Tijera vs Computadora Papel
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:3
Usuario Tijera vs Computadora Papel
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:3
Usuario Tijera vs Computadora Papel
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:1
Usuario Piedra vs Computadora Tijera
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:3
Usuario Tijera vs Computadora Papel
Resultado: Gana el USUARIO
1) Piedra
2) Papel
3) Tijera
S) Salir
Seleccione una opcion:
Despues de los primeros intentos del programa: podemos darnos cuenta que la secuencia que se esta jugando es la 7 o la 18 del ejemplo
Código:
Opcion 7: 0, 2, 2, 0, 0, 2, 2, 0, 1, 0,
Opcion 18: 0, 2, 2, 0, 0, 1, 1, 1, 2, 1,
Y vemos que en los ultimos juegos Gane todos los piedra papel tijera que jugue.
Si bien es cierto que sin el codigo fuente o binario decompilado no es podible saber la cantidad de veces que se llama a la funcion rand antes de que realmente inicie el programa, si es posible calcular incluso darnos cuenta de que podemos tener una base de datos o programa que calcule los modulos adecuados y tarde o temprano sabremos que semilla se utilizo para el caso de srand
Espero que dejen de usar srand y rand, buscando alguna funcion mas segura, si estan en Linux o un sistema libre, tendran siempre un /dev/random con bastantes numeros aleatorios muy dificiles de predecir.
¿Que alcance tiene esto?
Existen maquinas de juego electronico (bingo,loteria, balckjack) que utilizan funciones aleatorias y podria ser posible que alguna utilize rand() % X para algunos de sus calculos. Digo estas maquinas por que es probable que sus programadores se preocupen menos por la calidad de numeros generado, en maquinas de azar solo importa que los numeros sean uniformes (Todos con la misma probabilidad de salir) esto con el objetivo de calcular tablas de pagos adecuadas para la probabilidad de cada maquina.
Y pues temas se seguridad informatica como la generacion de passwords y otros usos de los numeros PseudoAlearorios, es recomendable buscar fuentes no predecibles de estos.
[1] https://foro.elhacker.net/programacion_cc/aporte_piedra_pape_y_tijera_miniautomata_ejercicio-t454850.0.html
[2] https://www.freebsd.org/cgi/man.cgi?query=time&sektion=3
[3] https://www.epochconverter.com/