-[ 0x10 ]-------------------------------------------------------------------- -[ Un ejemplo de codigo evolutivo ]------------------------------------------ -[ by Cemendil ]-----------------------------------------------------SET-30-- UN EJEMPLO DE CODIGO EVOLUTIVO Contenidos: 1.0. Introduccion. 2.0. Implementando una version de Tierra. 2.1. Introduccion al ensamblador Tierra. 2.2. Una descripcion detallada del ensamblador Tierra. 2.3. Un ejemplo de replicante. 3.0. Echando las cosas a correr. 3.1. Creando tus replicantes. Ensamblado e inoculacion. 3.2. El programa portador. 3.3. El incubador. 4.0. Ejemplos de evolucion de programas. 4.1. El portador de la generacion 76. 4.2. El portador de la generacion 3000. 5.0. Para que sirve todo esto. 6.0. Referencias. Apendice : Una aplicacion al codigo polimorfico mutable. 7.0. Codigo fuente. 1.0. Introduccion. Wood is highly ecological, since trees are a renewable resource. If you cut down a tree, another will grow in its place. And if you cut down the new tree, still another will grow. And if you cut down that tree, yet another will grow, only this one will be a mutation with long, poisonous tentacles and revenge in its heart, and it will sit there in the forest, cackling and making elaborate plans for when you come back. En este articulo construiremos un programa capaz de evolucionar y replicarse por si mismo, preservando una alta estabilidad frente a las mutaciones. Como veremos, para ello es esencial contar con el apoyo de un ensamblador/emulador incorporado en el mismo ejecutable; en otro caso la estabilidad frente a muta- ciones es absolutamente imposible. Veremos como es posible, en pocas generaciones, desarrollar programas por evolucion que resuelven problemas de manera diferente a la de sus predecesores. Se incluye codigo fuente en C para, en un sistema UNIX, ensamblar/desensamblar, inocular/extraer, e incubar diferentes cepas de programas autorreplicantes. El codigo empleado es sencillo, y los programas en C son cortos, de manera que pue- de resultarte facil programar tus propias cepas, extender el lenguaje, o incubar tus creaciones. Nada de lo contenido en este articulo es infeccioso o destructivo, aunque dada la naturaleza de los programas que se modifican a si mismos, es convenien- te ejecutar los programas como usuario sin privilegios. Lo mas serio que ha lle- gado a pasarme fue una cascada de core-dumps con una de las versiones beta del incubador. Dado que particularmente el incubador lanza dos procesos en paralelo, la carga sobre el sistema es notable; el incubador es un buen programa test para la potencia del sistema. Volviendo a la necesidad de un lenguaje evolutivo propio, es facil darse cuenta de las causas de la inestabilidad del ensamblador ix86 frente a mutacio- nes. Principalmente: a) El ensamblador tiene un sistema de control de flujo basado en saltos absolutos y relativos. P. ej., jmp 0xbb5a. Si una mutacion cae sobre la magni- tud del salto, el programa queda completamente desfigurado. b) El numero de instrucciones de ensamblador es extremadamente grande. Esto implica que cada mutacion puntual produce un rango excesivamente grande de variaciones del codigo, lo cual aumenta desmesuradamente el impacto de una sola mutacion. Fue Tom Ray [1] el primero en darse cuenta de que existe una solucion muy elegante al problema a). Concretamente, se trata de sustituir los saltos con referencia numerica por saltos con referencia a plantillas ('templates', en el articulo de Ray). La lectura del articulo de Ray, asi como la experimentacion con su programa Tierra [2], son muy recomendables. En esencia, las referencias por plantillas consisten en lo siguiente: se definen dos instrucciones mnemonicas de tipo no-operation, nop0 y nop1. Se defi- ne una plantilla como una sucesion de varias instrucciones tipo nop, por ejemplo ... codigo ensamblador ... nop0 nop1 nop1 nop0 ... codigo ensamblador ... Es interesante observar que cada plantilla se puede asociar a un numero binario, y de esa manera la plantilla anterior seria 0110. Entonces, podemos definir su plantilla complementaria como la que corresponde al NOT de la misma, en nuestro ejemplo seria: nop1 nop0 nop0 nop1 Pues bien, la idea de Tom Ray consiste en definir los saltos desde una plantilla hasta su complementaria. Es decir, ... codigo ensamblador ... jmp -----------------+ | nop0 | cesion nop1 | de nop1 | control nop0 | | ... codigo ensamblador ... | | nop1 | nop0 | nop0 | nop1 <-----------------+ ... codigo ensamblador ... que la instruccion de salto jmp reconoce la plantilla siguiente (en caso de existir), y busca la complementaria en el segmento de codigo, procediendo a ceder el cotrol. Naturalmente, puede no encontrarse la complementaria (debido a una mutacion), o incluso una mutacion puede hacer que se encuentre la incorrec- ta, pero la probabilidad de error catastrofico es mucho menor que en las referencias absolutas. En particular, es posible intercalar tantas instrucciones como se deseen entre las dos plantillas sin que por ello el mecanismo de salto falle (asumiendo que no se intercala una plantilla complementaria). Haciendo uso de este mecanismo de saltos, y de un conjunto de 32 instruccio- nes en ensamblador, Ray [2] logro desarrollar programas replicantes con capaci- dad para evolucionar por seleccion natural, apareciendo casos de parasitismo, hiperparasitismo y dependencia simbiotica en pocas generaciones. En su caso, Ray construyo una maquina virtual masivamente paralela; nosotros apuntaremos a un interprete embebido capaz de ejecutarse en tiempo real como un programa. En cuanto a la condicion b), podemos expresarla de la manera siguiente: si las mutaciones caen aleatoriamente sobre cualquier parte del codigo, con la misma probabilidad en cada caso, entonces cuanto mas grande sea una instruccion, mayor sera la probabilidad de que le caiga una mutacion que la desfigure. Por ejemplo, la instruccion 'ret' (en hex 0xC9), recibira una mutacion cuatro veces menos que una instruccion tipo 'lea' que ocupe 4 bytes. Esto sugiere que seria buena idea que en el codigo mutable todas las instrucciones fueran igual de largas, para no privilegiar ninguna instruccion. De la misma manera, si el nume- ro de instrucciones es relativamente reducido (digamos 32 instrucciones), enton- ces una mutacion al azar tendra muchas mas probabilidades de ser inocua que en el caso de haber varios miles de instrucciones (esto puede calcularse rigurosa- mente, pero el formato ASCII no es muy adecuado para ello). En lo sucesivo llamaremos 'Tierra' al lenguaje ensamblador que construiremos usando los metodos de Ray. Desecharemos definitivamente el ensamblador x86 como codigo mutable. En vez de ello, haremos recaer las mutaciones sobre el ensambla- dor Tierra, e interpretaremos ese ensamblador Tierra mediante un interprete em- bebido en el programa que ejecutemos. 2.0. Implementando una version de Tierra. Those who can, do. Those who can't, simulate. -- UNIX fortune cookie. 2.1. Introduccion al ensamblador Tierra. Como ya hemos indicado, vamos a tener que inventar nuestro propio conjunto de instrucciones en ensamblador, compatible con el mecanismo de salto por plan- tillas. Desarrollar un ensamblador implica desarrollar una CPU virtual que sea emulada por el programa a ejecutar (en principio podriamos usar un subconjunto de las instrucciones x86, pero esto haria que el emulador fuera muy complicado). En vez de ello, crearemos una CPU virtual con su ensamblador, lo mas sencilla posible. Nuestra CPU tendra 8 registros, cada uno de ellos de 8 bits. Estos registros estan organizados como una pila (de manera analoga a la FPU del x86). Las ins- trucciones en ensamblador no operan sobre registros concretos, si no sobre los registros de pila disponibles en ese momento. Por ejemplo, la instruccion de suma, 'add', lo que hace es sumar al elemento en el TOS (top-of-stack) el elemento inmediatamente superior en la pila. Mnemonico: add Operacion: st(0) = st(0) + st(1) esta instruccion puede fallar en caso de que no haya suficientes elementos en la pila (un buffer underflow, es decir, no existe st(1)). En ese caso, la instruc- cion no hace nada, pero la CPU anota que se ha producido un fallo (si el progra- ma falla demasiado, lo eliminaremos por defectuoso). Salvo por eso, el programa continua como si no hubiera habido fallo. En general, todas las instrucciones en ensamblador funcionan de manera pare- cida, operando en la pila de registros. Muchas de ellas son sensibles a overflow o underflow, y por ello generan fallos que permiten seleccionar a los programas mas competentes. Veamos el conjunto de instrucciones en ensamblador seleccionado. Tiene en total 16 instrucciones: nop0 -- no hace nada ; indica plantillas. nop1 -- no hace nada ; indica plantillas. ld1 -- mete 1 en la pila. neg -- complemento a 2 del elemento en el TOS. add -- suma al elemento del TOS el del TOS+1. drop -- elimina un elemento de la pila. Es un 'pop'. swap -- intercambia elementos en TOS y TOS+1. dup -- mete el elemento en el TOS en la pila (lo duplica). ld -- mete en la pila un elemento desde buffer de entrada. st -- escribe desde la pila un elemento en el buffer de salida. rot -- intercambia ciclicamente los elementos en TOS, TOS+1 y TOS+2 ifz -- ejecuta la siguiente instruccion solo si el elemento en el TOS es igual a cero. En otro caso la ignora. jmp -- salto al complemento de la plantilla siguiente. scasf -- busca el complemento de la plantilla siguiente hacia adelante y mete su ip en la pila. scasb -- busca el complemento de la plantilla siguiente hacia atras y mete su ip en la pila. end -- cierra los buffers y termina el programa. con tan solo estas 16 instrucciones es posible desarrollar programas replicantes bastante complejos. Antes de entrar en descripciones detalladas de las instruc- ciones, veamos algunos ejemplos. Al inicializarse, la CPU siempre tiene el numero 0 el el TOS. Imaginemos que queremos calcular el numero 5. Esto se podria hacer con el codigo: # Inicialmente la pila es: 0 ld1 # ...ahora la pila es: 1:0 swap # 0:1 add # 1:1 add # 2:1 add # 3:1 add # 4:1 add # 5:1 swap # 1:5 drop # 5 existen muchas otras maneras de hacer lo mismo, algunas mejores, otras peores. Veamos un ejemplo de rot, que hace un ciclo de los tres ultimos elementos de la pila: # Si la pila es inicialmente a:b, queremos hallar b:a:b # Esta operacion se conoce en FORTH como 'over'. # Inicialmente la pila es: a:b swap # ... ahora es: b:a dup # b:b:a rot # a:b:b rot # b:a:b la versatilidad de las instrucciones de manipulacion de pila es muy grande. Aunque la resta no es una de las instrucciones de la CPU, es muy facil res- tar dos cantidades: # Si tenemos en la pila a:b, calculemos a-b: # Inicialmente la pila es: a:b swap # ... ahora es: b:a neg # -b:a add # a-b:a swap # a:a-b drop # a-b de la misma manera puede calcularse b-a, y otras cantidades. Por ejemplo, es muy facil hacer un contador que se decrementa de uno en uno: # Inicialmente la pila es: c ld1 # ... ahora es: 1:c neg # -1:c add # c-1:c swap # c:c-1 drop # c-1 luego veremos un ejemplo de como implementar bucles usando este tipo de conta- dores. 2.2. Una descripcion detallada del ensamblador Tierra. Nuestros programas en Tierra estaran embebidos en el segmento de datos esta- ticos de un ejecutable normal de UNIX. Ese ejecutable lanzara un interprete de Tierra que leera los datos y los interpretara. El interprete permite que el pro- grama en Tierra lea su propio codigo, y que lo copie en un programa 'descendien- te', que es otro ejecutable UNIX. Sin embargo, el programa Tierra puede cometer errores al copiarse (mutaciones), e incluso sus fallos pueden ser tan criticos que el interprete aborte la ejecucion y anule la copia. Esto es lo peor que le puede ocurrir a nuestra celula: ser incapaz de reproducirse. Veamos entonces cual es la estructura del interprete. Como hemos dicho, la CPU tiene 8 registros organizados en una pila, y 16 instrucciones. Ademas, la CPU tiene una memoria de 256 bytes (lo maximo que se puede direccionar con un registro de 8 bits), en la que puede leer de manera arbitraria con la instruc- cion 'ld' (todas las instrucciones seran detalladas mas abajo). La CPU ejecuta su propio codigo desde la memoria, que es de solo-lectura. Ademas, con la ins- truccion 'st' puede escribir en los datos de su descendiente. Los datos de su descendiente son de solo-escritura. Una analogia: cuando una celula se divide, esta lee su ADN (que es de solo lectura aunque ejecutable), y lo copia en el ADN de su descendiente (que no lee ni ejecuta). La copia se produce, idealmente, en bloques bien definidos de bases nitrogenadas. Nuestra CPU se comporta exactamente igual, solo que la copia es byte a byte. He aqui un esquema de nuestra celula-CPU: +---------------+ Ejecucion /-----\ Escritura +---------------+ | Memoria / ADN | ---------------> | CPU | ----------------> | Memoria / ADN | | (256 bytes) | Lectura \-----/ | hijo (256b) | +---------------+ || +---------------+ +------+ | Pila | +------+ la tarea de nuestra celula es escribir una copia de si misma en la memoria de su hijo, despues de lo cual terminara la ejecucion (con la instruccion 'end'). La memoria de la celula es un array de bytes ordenado de 0 a 255. La memoria de la hija es solo accesible como un buffer de escritura a la que se le van en- viando bytes, hasta terminar el proceso con 'end'. Este buffer de escritura no puede ser leido, ni el puntero al buffer puede ser modificado (salvo en una uni- dad al escribir). Veamos entonces las instrucciones ensamblador con todo detalle (para conocer su funcionamiento exacto, consultar 'muta.c', subrutina 'ejecuta'). nop0 : Esta instruccion no hace nada, pero sirve de plantilla para jmp, scasf, scasb. Esta instruccion no falla nunca. nop1 : Esta instruccion no hace nada, pero sirve de plantilla para jmp, scasf, scasb. Esta instruccion no falla nunca. ld1 : Decrementa una unidad el TOS y mete 1 en pila[TOS]. Si no es posible decrementar el TOS (overflow), da error y no hace nada. neg : Calcula el complemento a dos de pila[TOS] y lo almacena en pila[TOS]. Esta instruccion no falla nunca. add : Suma pila[TOS] y pila[TOS+1] y lo almacena en pila[TOS]. Si pila[TOS+1] no corresponde a ningun registro (underflow) da error y no hace nada. drop : Hace aumentar en una unidad el TOS. Si pila[TOS+1] no existe (underflow) esta instruccion da error y no hace nada. swap : Intercambia pila[TOS] y pila[TOS+1]. Si pila[TOS+1] no existe (underflow) esta instruccion da error y no hace nada. dup : Hace descender una unidad el TOS. Entonces almacena en pila[TOS] el valor de pila[TOS+1]. Si el TOS no se puede decrementar en una unidad (overflow) la instruccion da error y no hace nada. ld : Lee la posicion de memoria indicada por pila[TOS]. Sustituye pila[TOS] por la cantidad leida. Esta instruccion no falla nunca. st : Envia el byte en pila[TOS] al buffer de escritura. Entonces hace aumentar en una unidad el TOS. Si pila[TOS+1] no existe (underflow), la instruccion no hace nada. Si a base de repetir instrucciones st la CPU envia mas de 256 bytes, las siguientes instrucciones st sobreescriben el buffer desde el principio. rot : Hace la transformacion: pila[TOS+2] -> temp pila[TOS+1] -> pila[TOS+2] pila[TOS] -> pila[TOS+1] temp -> pila[TOS] esto es, una rotacion a la derecha de los tres elementos inferiores de la pila. Esta instruccion falla, y no hace nada, si no existen pila[TOS+1] o pila[TOS+2] (underflow). ifz : Si pila[TOS] es cero, ejecuta la siguiente instruccion. En otro caso salta esa instruccion y pasa a la siguiente. Esta instruccion no falla nunca. jmp : Si a continuancion de esta instruccion hay una plantilla (de nop0's y nop1's), entonces se busca una complementaria y se continua la ejecucion del programa a partir de esas plantillas. Si no se en- cuentra complementaria, el programa continua su ejecucion normal- mente (sin fallo). Si despues del jmp no hay plantilla, la instruc- cion falla y no hace nada. NOTA: la busqueda de la plantilla se hace primero hacia adelante, y en caso de no encontrar la plantilla, se hace luego hacia atras. Se salta siempre a la primera plantilla encontrada. scasf : Si a continuacion de esta instruccion hay una plantilla, entonces se busca una complementaria hacia adelante. Si se encuentra, se hace descender una unidad el TOS y se almacena en la pila la posicion de memoria del _final_ de esa plantilla. Si no se encuentra, se almacena el valor 0 en la pila. Esta instruccion falla si no hay plantilla a continuacion, o si no es posible hacer descender el TOS (overflow). scasb : Si a continuacion de esta instruccion hay una plantilla, entonces se busca una complementaria hacia atras. Si se encuentra, se hace descender una unidad el TOS y se almacena en la pila la posicion de memoria del _inicio_ de esa plantilla. Si no se encuntra, se almacena el valor 0 en la pila. Esta instruccion falla si no hay plantilla a continuacon, o si no es posible hacer descender el TOS (overflow). end : Esta instruccion termina el programa Tierra y devuelve el control al emulador para que cierre los ficheros y termine la ejecucion. Al llegar a esta instruccion se escribe el programa hijo en el disco duro. Esta instruccion no falla nunca. A partir de estas descripciones es sencillo comprender cualquier programa escrito en Tierra. Observa que todas las instrucciones son tremendamente tole- rantes con los fallos: si una instruccion no puede cumplir su funcion, indica el error al interprete (que cuenta los fallos), y simplemente no hace nada. Esto es una gran diferencia con el ensamblador tipico, que ante el mas minimo error pro- duce algun tipo de excepcion. Por poner un ejemplo, si aqui tuviesemos una ope- racion de division, en caso de dividir por cero no abortariamos el programa, si no que simplemente indicariamos el error y continuariamos como si nada. Los se- res vivos son flexibles. Cuando la CPU comete un error, el interprete lo cuenta. Si el numero de errores supera un cierto limite (definido como TOLERANCIA en muta.c), el inter- prete aborta el programa y evita la replicacion del programa. Esto es ventajoso para establecer un criterio de seleccion natural de programas. Por poner una analogia biologica, existen ciertos procesos en la naturaleza que requieren mas energia que otros: la manera natural de funcionar es a traves de caminos de mi- nima energia. Si se buscan atajos, se consume demasiada energia. Aqui los fallos hacen de energia: si consumes demasiados, te agotas y abortas la reproduccion. 2.3. Un ejemplo de replicante. Veamos un ejemplo de un programa completamente funcional. El interprete siempre comienza a ejecutar el codigo en la direccion de memoria cero. La pila siempre comienza con el valor cero en pila[TOS]. Recuerda que solo hay 8 posi- ciones en la pila. Salirse de esos limites lleva a un overflow o underflow. En el codigo que sigue hemos llamado 'final' a la direccion en memoria del final del codigo, y 'inicio' a la direccion de inicio. Hemos llamado 'tam' al tamano total del programa en bytes (cada instruccion ocupa un byte). Ten en cuenta que final - inicio = tam - 1. Hemos llamado ADN(x) al byte que esta en la posicion x de la memoria de la celula. Aqui esta el programa: ####################################################### # Inicio del programa. La primera instruccion va en la zona de memoria 0. # La pila empieza con el 0 cargado en el TOS. nop0 # Plantilla de inicio. Pila: 0 nop0 nop0 nop0 nop0 scasf # Buscamos el final del codigo. Pila: final:0 nop0 # Complemento de plantilla de final. nop1 nop0 nop1 nop0 swap # Eliminamos el cero de la pila. Pila: 0:final drop # Pila: final scasb # Buscamos el inicio del codigo. Pila: inicio:final nop1 # Complemento de plantilla de inicio. nop1 nop1 nop1 nop1 # Calculemos el tamano del codigo. # Pila: dup # inicio : inicio : final rot # final : inicio : inicio rot # inicio : final : inicio neg # -inicio : final : inicio add # tam-1 : final : inicio ld1 # 1 : tam-1 : final : inicio add # tam : tam-1 : final : inicio swap # tam-1 : tam : final : inicio drop # tam : final : inicio # Ahora eliminamos 'final' de la pila. swap # final : tam : inicio drop # tam : inicio # Con esto estamos listos para la copia. # 'tam' actuara como contador de los bytes a copiar. # 'inicio' actuara como puntero al ADN de la celula. nop1 # Plantilla de copia. nop1 nop0 nop0 nop1 swap # inicio : tam dup # inicio : inicio : tam ld # ADN(inicio) : inicio : tam Lee byte de su ADN. st # inicio : tam Almacena byte en ADN del hijo. ld1 # 1 : inicio : tam add # inicio+1 : inicio : tam swap # inicio : inicio+1 : tam drop # inicio+1 : tam swap # tam : inicio+1 ld1 # 1 : tam : inicio+1 neg # -1 : tam : inicio+1 add # tam-1 : tam : inicio+1 swap # tam : tam-1 : inicio+1 drop # tam-1 : inicio+1 ifz # Hemos llegado al final ? jmp nop1 # Comp. Plantilla de salida. nop1 nop0 nop0 nop0 jmp nop0 # Comp. plantilla de copia. nop0 nop1 nop1 nop0 swap # Para separar una plantilla de otra. nop0 # Plantilla de salida. nop0 nop1 nop1 nop1 end # Cierra buffer y acaba. nop1 # Plantilla del final. nop0 nop1 nop0 nop1 # ###################################################### Este programa es manifiestamente mejorable, aunque en principio no este del todo mal. Un hecho sorprendente es que incubando este codigo, en apenas 76 generacio- nes obtuve una mutacion mucho mas simple y perfectamente funcional. En general, el programa es facil de entender una vez que se ha dominado el ensamblador y su manera de operar. Simplemente se calcula la posicion inicial del codigo en memoria (que de hecho es cero, pero se calcula con plantillas). Luego se calcula la posicion final. Se restan y se suma 1, obteniendo el tamano. Haciendo un bucle con el tamano, se van escribiendo uno a uno todos los bytes de codigo, desde el primero al ultimo. Entonces el programa termina. Este programa no comete ni un solo fallo de overflow o underflow, y se eje- cuta en una cantidad relativamente reducida de ciclos. 3.0 Echando las cosas a correr. 'Twas midnight, and the UNIX hacks Did gyre and gimble in their cave All mimsy was the CS-VAX And Cory raths outgrabe. "Beware the software rot, my son! The faults that bite, the jobs that thrash! Beware the broken pipe, and shun The frumious system crash!" Hasta ahora hemos definido un ensamblador muy reducido con una CPU minima. Veamos ahora como ensamblar, inocular e incubar programas replicantes. Para ello contamos con los programas incluidos con el articulo, que son: basico : Un programa ensamblador autorreplicante. muta.c : Codigo fuente del portador/interprete. asm.c : Ensamblador/desensamblador de Tierra. inocula.c : Inoculador/extractor de programas. incuba.c : Incubador. opcodes.h : Fichero de cabecera con info. del ensamblador. Crea un directorio y mete todos los programas alli. Luego compila los programas en C de manera normal: demeter# gcc -o muta muta.c demeter# gcc -o inocula inocula.c demeter# gcc -o asm asm.c demeter# gcc -o incuba incuba.c Ahora veamos como proceder para hacer una experimentacion con este codigo. 1) En primer lugar, el fichero 'basico' es un fichero ascii con el codigo ensamblador, comentarios, etc. Compilalo a forma binaria usando: demeter# ./asm e basico basico.bin el ensamblador respondera: ............................................ Bytes: 74 demeter# lo que quiere decir que basico.bin tiene 74 bytes una vez compilado. 2) Ahora inocula basico.bin en el programa 'muta', para poderlo ejecutar. Esto se logra haciendo: demeter# ./inocula i basico.bin muta a lo que respondera el inoculador: Inoculacion terminada. Ahora basico.bin esta cargado en la zona de datos estaticos de 'muta', de manera que al ejecutar ese programa se interpretara el codigo que acabas de inocular. Recuerda que una vez que has inoculado a 'muta', si quieres inocular otra cosa debes recompilar 'muta.c' para limpiar la zona de datos estaticos! 3) Si ahora ejecutas el programa 'muta', obtendras un programa de salida, que por defecto se llama 'salit'. Este programa es el hijo de 'muta' :) demeter# ./muta demeter# ls basico basico.bin incuba incuba.c inocula inocula.c opcodes.h salit asm asm.c Este fichero 'salit' es tambien ejecutable, y da lugar a otro fichero, tambien llamado 'salit' con el nieto de 'muta'. Y asi sucesivamente. Ten en cuenta que es posible que, debido a mutaciones u otros fallos, el programa hijo no pueda aparecer. El programa 'muta' introduce fallos aleatorios (la probabilidad controlada por la definicion RADIO_FALLOS en 'muta.c') en el funcionamiento del programa. Algunos de estos fallos pueden ser lo bastante graves como para que no haya reproduccion. Si esto ocurre, ejecuta 'muta' hasta obtener salit. Si no lo logras tras varios intentos, probablemente hay un bug en el codigo del programa ensamblador ('basico' funciona perfectamente). Si quieres seguir ejecutando 'salit', mejor renombralo antes de ejecu- tarlo, para que no se sobreescriba al reproducirse. La incubadora se encargara de estas tareas automaticamente. 4) Ahora vamos a extraer y desensamblar el codigo contenido en 'salit'. Esto es bastante util para debuggear programas, y para analizar muta- ciones. Esto se logra haciendo lo inverso que en 1) y 2): demeter# ./inocula e salit code.bin Extraccion terminada. Ahora el codigo de 'salit' esta en 'code.bin'. Este fichero es un vol- cado de toda la memoria de 'salit' (256 bytes), dado que no podemos saber que partes de la memoria se ejecutan o no. Ahora desensamblemos code.bin: demeter# ./asm d code.bin code Ahora 'code' contiene un desensamblado del codigo de 'salit'. Puedes editar este fichero ascii y analizar los opcodes para ver que ha variado. 5) Dado que es muy incomodo andar ejecutando a mano los programas, renombrandolos y seleccionandolos, puedes usar la incubadora para ello. La incubadora toma un 'ancestro', lo renombra y lo ejecuta, ejecutando luego a sus descendientes, y asi sucesivamente. A la incubadora la analizaremos con mas detalle en una seccion posterior. Suponiendo que tienes un codigo inoculado en 'muta', para incubar ese codigo ejecuta: demeter# ./incuba muta La incubadora renombrara 'muta' como 'itm.00' (item numero 00), y lo ejecutara. Si no tiene descendencia, abortara (estas cosas pasan aveces, debido a mutaciones catastroficas). Si hay descendencia, re- nombrara al descendiente como 'itm.??' (con ?? cualquier numero entre 00 y 99, tomado al azar). Esto termina la primera generacion. Luego el proceso se repite para todos los items en el directorio, con lo que el crecimiento se vuelve exponencial (1 celula, 2, 4, 8, 16...) Como hay un limite de 100 celulas, pronto empiezan a sobreescribirse unas a otras (en caso de que logren reproducirse). Aqui es donde aparece la seleccion natural: solo las celulas que se reproducen son capaces de sobrevivir. Puedes detener la incubacion en cualquier momento, y analizar los ficheros 'itm.??', que son programas como 'muta', pero inoculados con descendientes suyos. Algunos son autenticas mutaciones, otros son callejones sin salida. El analisis de estos bichejos da algunas sorpresas. 3.1. Creando tus replicantes. Ensamblado e inoculacion. El programa 'muta', recien compilado con 'gcc -o muta muta.c', esta en blan- co. No tiene ninguna capacidad de replicacion (prueba a ejecutarlo). Asi que hay que meterle algun codigo replicante para que marche. Para ello debes en primer lugar crear tu programa ensamblador, en formato ascii. Puedes usar comentarios '#' y espacios libremente en los archivos ensam- blador, pero ten en cuenta que los tabuladores no se reconocen, y que 'asm' dis- tingue mayusculas y minusculas. Tienes un ejemplo en 'basico', que es directa- mente ensamblable. Las opciones de 'asm' son: asm e entrada salida : ensambla 'entrada' en el binario 'salida'. asm d entrada salida : desensambla 'entrada' en el ascii 'salida'. Una vez que tienes tu codigo binario del replicante, hay que meterlo en la zona de datos de 'muta', para ser leido e interpretado. En la zona de datos de 'muta' hay un numero magico: 0xdeadbeef que el inoculador detecta. Entonces es- cribe el codigo binario a partir del numero magico, donde el interprete esta programado para buscarlo. Las opciones de 'inocula' son: inocula i ent sal : inocula el binario 'ent' en el portador 'sal'. inocula e ent sal : extrae del portador 'ent' el binario 'sal'. El ensamblador y el inoculador funcionan perfectamente juntos, y son la base del analisis de las mutaciones que van apareciendo. 3.2. El programa portador. Veamos algunos detalles del portador, 'muta.c'. El portador es un diminuto interprete de la CPU que ejecuta las instrucciones desde su zona de datos esta- tica. El funcionamiento del portador es el siguiente: 1) El portador se copia a si mismo en el fichero 'salit'. 2) El portador sobreescribe 'salit', eliminando su zona de datos. 3) El portador interpreta el codigo inoculado. Puede ocurrir entonces: 3a. El codigo inoculado se copia (con o sin mutaciones) en 'salit'. 3b. El codigo inoculado consume demasidos ciclos del interprete, y es por ello eliminado. Se borra el fichero 'salit'. La replicacion ha fallado. 3c. El codigo inoculado comete demasiados errores al ejecutarse, y es por ello eliminado. Se borra el fichero 'salit'. La replicacion ha fallado. 4) El portador cierra los bufferes y termina. Los casos 3b y 3c provocan el fallo de la replicacion, y su tolerancia esta con- trolada por las definiciones MAX_CICLOS y TOLERANCIA en 'muta.c'. Una tarea fundamental del portador es introducir fallos al azar en el fun- cionamiento del programa inoculado. Esto se logra con la funcion 'flaw_bit' de 'muta.c', que introduce errores de calculo y copia en practicamente todas las instrucciones. La probabilidad de error esta determinada por la definicion RADIO_FALLOS de 'muta.c'. Observa que no solo se producen fallos de copia, si no tambien fallos aritmeticos, que pueden alterar el funcionamiento de un programa aparentemente normal. Pese a todo, manteniendo los parametros dentro de limites razonables los sistemas incubados sobreviven con facilidad. Si te pasas con las mutaciones, en vez de programas con ocho ojos y nueve brazos obtienes un rapido exterminio, por lo general. O una simplificacion exagerada de los supervivien- tes; las estructuras complejas no sobreviven con facilidad a los vapuleos. Para testar los programas y su capacidad de replicacion, es interesante eje- cutar el portador sin que este introduzca fallos. Esto se logra invocandolo con un argumento (cualquiera): demeter# ./muta c <---- Portador sin fallos ni mutaciones. Esto es interesante para comprobar si un programa es un replicador en condicio- nes ideales. Ningun fallo estorbara el funcionamiento del programa. 3.3. El incubador. El incubador es un programa que podria haberse hecho con un script o bien en PERL. Su funcion principal es gestionar la descendencia de un determinado porta- dor. El algoritmo fundamental se basa en generaciones, y es el siguiente: A.0. Toma el portador inicial y renombralo como 'itm.00'. A.1. Haz un listado de los portadores en el directorio. Solo se tienen en cuenta los portadores del tipo 'itm.??', donde ?? es un numero entre 00 y 99. A.2. Ejecuta sucesivamente todos los portadores listados en A.1. A.2.1. Si el portador ejecutado tiene descendencia, toma una cantidad ?? al azar entre 00 y 99, y renombra al descendiente como 'itm.??' Esto probablemente sobreescribira algun otro portador, incluso uno que todavia no se ha ejecutado. A.2.2 Si el portador ejecutado no tiene descendencia, exterminalo. Esto favorece la supervivencia de los portadores estables. A.3. Escribe algunos datos (numero de portadores, numero de portadores fallidos, numero de generacion), y vuelve a A.1. Este metodo es algo brutal, pero bastante rapido, y favorece a los portadores con capacidad de reproduccion y codigo robusto. Es posible gestionar mas de 100 portadores de una sola vez (o menos), pero con 100 ya se carga bastante al sis- tema. Salvo que tengas un superordenador, 100 portadores son mas que suficien- tes. He dejado la incubadora funcionando mas de 3000 generaciones sin que hubie- ra un solo fallo en mi sistema (FreeBSD 5.0 - Release). Esto supone mas de dos horas y media de funcionamiento sin fallos, con cerca de 300000 portadores eje- cutados. 4.0. Ejemplos de evolucion de programas. To those accustomed to the precise, structured methods of conventional system development, exploratory development techniques may seem messy, inelegant, and unsatisfying. But it's a question of congruence: precision and flexibility may be just as disfunctional in novel, uncertain situations as sloppiness and vacillation are in familiar, well-defined ones. Those who admire the massive, rigid bone structures of dinosaurs should remember that jellyfish still enjoy their very secure ecological niche. -- Beau Sheil, "Power Tools for Programmers" Partiendo del codigo ensamblador escrito en 2.3., he desarrollado la incuba- dora primero 76 generaciones, y luego 3000 generaciones. He tomado dos ejemplos de replicantes en cada una de las dos muestras. He aqui los resultados, despie- zados y comentados. 4.1. El portador de la generacion 76. Los comentarios sobre este especimen van al final: ########### # Engendro de la generacion 76. Replicador perfecto. # # Comentarios: Pila: swap 0: swap # Esto provoca 2 fallos de ejecucion. 0: nop0 # Esta plantilla es inutil. 0: nop0 nop0 scasf # Busca 101 -- Falla. 0:0 nop0 nop1 nop0 scasb # Busca 00000 -- Falla. 0:0:0 nop1 nop1 nop1 nop1 nop1 # Las operaciones que vienen a continuacion son # innecesariamente complejas, y su funcion es # meter en la pila 0:0, para inicializar el bucle # de copia. dup # 0:0:0:0 ifz rot neg # 0:0:0:0 add add ld1 # 1:0:0:0:0 neg # ff:0:0:0:0 swap # 0:ff:0:0:0 drop drop drop # 0:0 # Ahora esta inicializada la pila para el bucle. Dado # que esta parte sera llamada varias veces, llamaremos # a:b a las posiciones de la pila. La primera vez, a=b=0. nop1 # Plantilla del bucle. a:b nop1 nop0 nop0 nop1 nop1 swap # b:a dup # b:b:a ld # ADN(b):b:a st # b:a ld1 # 1:b:a add # b+1:b:a swap # b:b+1:a drop # b+1:a swap # a:b+1 ld1 # 1:a:b+1 add # a+1:a:b+1 swap # a:a+1:b+1 drop # a+1:b+1 ifz # Comprueba si a+1 es cero, es decir, si # el registro que contiene 'a' se ha desbordado!!! jmp # Esto lleva eventualmente a un end. nop1 # Complemento de plantilla del final. nop0 # jmp nop0 # Complemento de plantilla del bucle (parcial). nop0 # Plantilla del final mezclada con la anterior. nop1 nop1 nop0 swap # Instrucciones inutiles antes del final. nop0 # Esta plantilla no vale de nada. nop0 nop1 nop1 nop1 end # Termina. nop1 end # Redundante. # ################################# Lo interesante de este programa es que, aunque tiene bastante en comun con el portador inicial, aparecen tres elementos distintivos: 1) Las plantillas de salto se han modificado. Esto prueba la estabilidad del mecanismo de salto por plantillas frente a mutaciones. 2) Simplificacion: Los mecanismos de medicion del tamano del programa, que habia en el primer portador, han desaparecido, dejando un conjunto de instrucciones inutiles; parecido a un organo atrofiado. 3) Innovacion: Este nuevo replicador no necesita medir el tamano de su codi- go porque lo que hace es copiar la memoria al completo (256 bytes). Para ello inicializa un contador a cero y lo va incrementando 1 a 1. Cuando el contador desborda, se han copiado 256 bytes. Este programa ha sido capaz de simplificar e innovar el funcionamiento en un to- tal de 76 generaciones. Es mucho mejor de lo que esperaba al programar esto. Ob- serva que el paradigma de copia ha cambiado totalmente. 4.2. El portador de la generacion 3000. Analizar una muestra de 100 portadores con 3000 generaciones de historia no es cosa facil. Pero tomando uno al azar ('itm.99'), comprobe de inmediato que era un replicador perfecto (en condiciones ideales). Sin embargo, con muy pocas diferencias, es un pariente cercano del de la generacion 76; emplea el mismo me- todo de desbordamiento. Veamos el codigo tal y como aparece tras desensamblar, con algunos comentarios por enmedio: NOTA: los numeros intercalados en los comentarios los escribe el desensamblador. Son la version decimal del volcado binario del codigo. Simplemente ignora- los. ########################################################## # Replicador de la generacion 3000. Desensamblado. # # add # 4 Estas instrucciones fallan todas. add # 4 add # 4 add # 4 drop # 5 add # 4 add # 4 # Hasta aqui la pila es cero y hay 7 fallos. ld1 # 2 ld1 # 2 drop # 5 dup # 7 # Esto da 1:1:0 add # 4 add # 4 drop # 5 drop # 5 # La pila vuelve a ser 0. Todo lo anterior es inutil. swap # 6 # Ocho fallos. drop # 5 # Nueve. dup # 7 dup # 7 # Ahora la pila es 0:0:0 # Dado que actua como contador, le llamaremos a:b:c para ver # como varian los parametros en el bucle. nop0 # 0 # Tremenda plantilla. Una parte vale para el bucle. nop0 # 0 nop0 # 0 nop1 # 1 nop1 # 1 nop1 # 1 nop0 # 0 nop1 # 1 nop0 # 0 nop0 # 0 swap # 6 b:a:c dup # 7 b:b:a:c ld # 8 adn(b):b:a:c st # 9 b:a:c ld1 # 2 1:b:a:c add # 4 b+1:b:a:c swap # 6 drop # 5 b+1:a:c swap # 6 a:b+1:c ld1 # 2 1:a:b+1:c add # 4 a+1:a:b+1:c swap # 6 drop # 5 a+1:b+1:c ifz # 11 Test de overflow. jmp # 12 nop0 # 0 Esto va a parar al end adelante. jmp # 12 nop1 # 1 Esto va a parar a la tremenda plantilla del bucle. nop1 # 1 end # 15 Final del programa. # Aqui viene un monton de instrucciones aparentemente # aleatorias, que no se ejecutan si no hay fallo. # # ..... # ##################################### Como dijimos, este es un pariente cercano del especimen de la generacion 76. Usa el mismo metodo de overflow de registros para copiar todo el codigo. Sin embargo hay al menos dos aspectos interesantes, relativos a las etiquetas: 1) El salto final a 'end' depende de una plantilla de un solo nop. Esta cla- ro que el programa no quiere fallar en este salto. Esto parece una defen- sa contra los fallos aleatorios de replicacion. 2) La plantilla de entrada al bucle es monstruosa. Una vez mas, parece que el codigo quiere asegurarse de que cualquier etiqueta de mas de un nop va a parar hasta el bucle. En resumen, este ejemplar de la generacion 3000 parece el de la 76, pero estabi- lizado frente a fallos de transcripcion. Haciendo que la etiqueta de salida sea supersimple, y la de entrada al bucle practicamente universal, es dificil que los descendientes de este codigo vayan a cometer errores en los saltos, aunque tengan ligeras mutaciones. Por otro lado, los mecanismos de copia son identicos byte a byte al de la generacion 76. Parece que el metodo de desbordamiento es preferible al de cuen- ta exacta, quizas por ser mas simple y estar menos sujeto a fallos aritmeticos. 5.0 Para que sirve todo esto. The man who sets out to carry a cat by its tail learns something that will always be useful and which never will grow dim or doubtful. -- Mark Twain. Puede dudarse, bastante razonablemente, que este concepto de codigo mutable pueda valer para algo. Sin embargo, veamos que se puede hacer llevando las cosas un poco al limite. Antes que nada, ten en cuenta que el metodo aqui expuesto de hecho permite crear programas ejecutables con capacidad para evolucionar. Es muy cierto que no evoluciona _todo_ el programa, pero tal como se indico en la in- troduccion, existen poderosos motivos para suponer que la evolucion de _todo_ un programa en un ix86 es simplemente imposible. La cuestion es como sacar partido de las reducidas capacidades evolutivas de un programa realizado para un ordena- dor convencional. En primer lugar, nuestro codigo Tierra tiene tan solo 16 instrucciones, en tanto que el de Tom Ray tiene 32. Nuestro codigo es simplemente un mecanismo de experimentacion, una manera de comprobar que la evolucion del codigo es posible. Si incluimos otras 16 instrucciones en nuestro interprete, podemos aumentar mu- cho su funcionalidad: desde un mecanismo para manipular el buffer de salida, extensiones aritmeticas o mejoras a la gestion de la pila, hasta mecanismos pa- ra interactuar varios programas, compartir recursos u otras opciones (Ray ha trabajado en esa direccion dentro de su emulador). Pero sin ir tan lejos, imaginemos que extendemos levemente las instruccio- nes que hemos empleado, para que tengan mejores capacidades logicas (AND, XOR, etc.) y mejor manipulacion del buffer de salida. Ademas, reprogramamos el porta- dor en ensamblador, para que sea mas compacto. Entonces podriamos introducir el portador en un programa tal como un virus, donde se podria encargar de la ruti- na de encriptacion. Una rutina de encriptacion mutable y dotada de cierta esta- bilidad podria ser una adicion desconcertante a un buen virus. Teniendo en cuen- ta que en tan solo 76 generaciones este tipo de codigos es capaz de variar sus estrategias de calculo, es divertido imaginar que pasaria con las mutaciones en los pasos iniciales (exponenciales) de una infeccion virica. Para detalles con- cretos acerca de virus mutables, consulta el apendice. Potencialmente el codigo mutable puede tener otras aplicaciones, pero quizas ninguna tan divertida como la anterior. 6.0. Referencias. fortune: cpu time/usefulness ratio too high -- core dumped. -- Fortune file. [1] Ray, Tom. "Zen and the art of creating life" (Accesible en Internet). [2] Ray, Tom. Tierra v 4.0. (Accesible en Internet). [3] The UNIX Fortune File, April 19, 1994. ------------------ ------------------ Apendice : Una aplicacion al codigo polimorfico mutable. Y asimismo absorbia todo un microcosmos de criaturas vivientes..., bacterias y virus que, sobre otros planetas, habian evolucionado de mil mortales linajes. Aun cuando tan solo muy pocos podian sobrevivir en aquella atmosfera y temperatura, eran suficientes. -- Arthur C. Clarke, "Antes del Eden". He aqui una idea para implementar un mecanismo capaz de producir polimorfismo evolutivo. Antes que nada, hay que incluir en el interprete de Tierra un ensam- blador que traduzca opcodes de Tierra al ensamblador x86. Esto es sencillo, dado que las instrucciones de salto y busqueda (jmp, scasf, scasb) no deben ser en- sambladas (ver NOTA mas adelante). No es complicado traducir la mayor parte de las instrucciones, sobre todo te- niendo en cuenta que el x86 cuenta con una pila propia. Por ejemplo, sin romper- se la cabeza, podemos incluso traducir el codigo a mano: ld1 ----> movb $1, %al ; push %al dup ----> popb %al ; pushb %al ; pushb %al drop ----> popb %al neg ----> popb %al ; negb %al ; pushb %al add ----> popb %al ; popb %ah ; addb %ah, %al ; pushb %ah ; pushb %al o : movb 1(%esp), %al ; addb %al, (%esp) etc... Una vez que has metido en el interprete la capacidad de ensamblar, extiende Tie- rra para incluir los comandos: asm : Inicia el ensamblado del codigo polimorfico. El interprete reconoce esta instruccion y comienza a ensamblar cada instruccion Tierra ejecutada, hasta llegar a dasm. split : Esta instruccion debe ir entre asm y dasm. Indica donde acaba la rutina de encriptacion y donde comienza la rutina de desencriptacion. dasm : Termina el ensamblado de codigo polimorfico. La idea es la siguiente: entre asm y dasm se introduce una serie de instruc- ciones Tierra: asm # Comienza asm. Supongamos que Pila: a ld1 # 1:a add # a+1:a neg # -a-1:a swap # a:-a-1 drop # -a-1 ld1 # 1:-a-1 swap # -a-1:1 add # -a:1 neg # a:1 swap # 1:a drop # a dasm # Termina instruccion asm. La intencion es clara: mete entre 'asm' y 'dasm' una serie de transformacio- nes que dejen invariante el elemento del TOS. Es muy facil programar al inter- prete para que compruebe que el codigo entre asm y dasm deja invariante cual- quier byte 'a' de entrada (256 comprobaciones, a fin de cuentas). Naturalmente, si tienes una cierta transformacion compuesta: a = a[0] --> a[1] --> a[2] --> a[3] --> ... --> a[n-1] --> a[n] = a tal que inicia y termina en el mismo elemento, entonces si divides esa transfor- macion en un determinado punto, tienes dos transformaciones inversas. a' = f(a) definida: a = a[0] --> a[1] --> ... --> a[j] = a' a = g(a') definida: a' = a[j] --> ... --> a[n-1] --> a[n] = a Siendo: g(f(a)) = a para todo a . Aqui es donde aparece la instruccion 'split'. Su intencion es partir en dos el codigo entre 'asm' y 'dasm'. Notemos que esta division no se puede hacer en cualquier punto, si no en aquellos en los que todos los elementos en la pila se conozcan en tiempo de compilacion. Veamos el codigo anterior con un 'split' bien colocado: asm # Comienza asm. Supongamos que Pila: a ld1 # 1:a add # a+1:a neg # -a-1:a swap # a:-a-1 drop # -a-1 split # Estado de pila correcto. ld1 # 1:-a-1 swap # -a-1:1 add # -a:1 neg # a:1 swap # 1:a drop # a dasm # Termina instruccion asm. Claro, no hay que ser Einstein para darse cuenta de que esto es precisamente una rutina de encriptacion basada en: a --> -1+neg(a) --> a NOTA: naturalmente, no debemos permitir instrucciones de salto y busqueda entre del par 'asm' y 'dasm', dado que dependen de datos exteriores a la zona a ensam- blar. Esto es facil de lograr; basta definir un bit en la CPU que, si esta acti- vo, trate a jmp,scasf,scasb como no-operations. Entonces, ese bit es activado por 'asm' y desactivado por 'dasm'. Lo interesante es que este mecanismo puede encajarse perfectamente en el interprete de Tierra, a un coste relativamente bajo. Es sencillo incluir variaciones aleatorias de mecanismos de encriptacion, por ejemplo creando una instruccion 'rand' que meta un numero aleatorio en la pila. Este numero se supone conocido a la hora de traducir a ensamblador, y vale como 'clave' de cifrado. Por ejemplo: asm # Inicio de asm. Sea la pila: a rand # r:a swap # a:r xor # r^a:r split # Ok. xor # a:r swap # r:a drop # a dasm # Fin de asm. Puesto que el valor de 'rand' en la pila se supone conocido en tiempo de tradu- cir a ensamblador (el interprete genera el valor aleatorio y lo pasa al ensam- blador como una constante), el split esta bien colocado. Este procedimiento es el clasico enmascaramiento xor. Con todo esto el interprete Tierra puede ahora generar diminutos pares de funciones inversas, y hacerlos crecer por seleccion natural. Es el propio inter- prete el que se encarga de ver si las mutaciones siguen dando funciones inver- as. Solo si el codigo funciona, el interprete genera un diminuto par de fragmen- tos de ensamblador, que pueden ser usados dentro de un bucle como rutinas de encriptado/desencriptado del virus. Por resumir todo lo expuesto hasta ahora: aparte de la capacidad de reprodu- cirse, imponemos a las celulas de Tierra la necesidad de generar un par de fun- ciones inversas. Si son capaces de hacerlo, les permitimos duplicarse y ademas extraemos las funciones, traducidas a ensamblador. Usamos la encriptadora para codificar la totalidad del virus. Entonces metemos la desencriptadora en la ru- tina de desencriptacion del virus, y lo pegamos todo. Asi tenemos un virus poli- morfico autenticamente mutante. Asi que, aunque el x86 no es mutable, si que lo es cuando se logra embeber dentro del codigo Tierra. El ensamblador Tierra es muy facil de traducir a x86, precisamente porque deriva de FORTH, uno de los lenguajes de programacion para los que es mas sencillo desarrollar un compilador. Este mecanismo polimorfico, asi como los detalles concretos de las instruc- ciones 'asm', 'dasm', 'split', bien podrian merecer un articulo completo para ellas solas. De todos modos, creo que con esta breve explicacion se ha cubierto lo esencial: como generar pares de rutinas de encriptacion de una manera evolu- tiva. Pasar a detalles mas concretos requeriria una implementacion dependiente de un determinado sistema operativo, algo que hare si este mecanismo despierta el suficiente interes. --------------- --------------- 7.0. Codigo fuente. Todos los ficheros de codigo fuente necesarios para este articulo estan en los mensajes PGP que siguen. Para extraerlos sencillamente pasa el articulo por PGP. Los mensajes estan codificados sin clave y seran automaticamente extraidos. Si no sabes usar PGP (o gpg) no deberias estar leyendo esto!!! -----BEGIN PGP MESSAGE----- Version: 2.6.3ia owGVVt1u2zYU3s0upqc4dZBZsg3FTrFiiOsG2JYCBYL1Zr2yjYGmKJuYJAqilKRo /Uh7iL5Zz+GPRGdJt/HCoMjv/H/n0H//8GX3PdNlyr/DdTGBCCaA66bSrNwVLFPN RSa0GD4hE/CHFE3DUsQ6+G9Kg6q5VJXQVyDA42eQQSBOEhdRdCYrXnSo57VuM6nS w5vhaIRqFIqkh1EURbplreTAD6yBQlaCrX9ebJd4cadkBp1WMW2S6FMEUHetjkcf tLoCDAjW5EULmhVbWGf9fpQsESseZBvPcXv0ugoh/jQW4rfvbm9ggnirFiRKSljB nAStKxxdwP39QRYC4he5UHlMAgmdGiFaMo9jjoJ70XJ7DS9WMN5U48QFI6fTLQL4 0ouIQgvYNYL9ZY6OxozDbq0PRzqKIizVr6qsWcMgw+RzlomK6RmwYq8gk7pFtxXV SrcNL+sUhkUloKC4lY9NSBOsld3sXNw+vIWJKgwL4njCYIXuJPD5M8STnf1IoBFt 11QuVR6MWIwb9frrRX/NptMl7PDHHBxNOTCym0KWsmIgdM2QVBqrB0ifipuIZCXx kHYmM+lJYKaYddfsWUCN0xoGpRtSi3cPlxiPJGcIQXmrP1rEDH70yJAzntYDZWZg t8g0a9kYXroN6iAfZtCy0jhjXAEyZ/x5xCSfcRTzGYKApwSzrgK4iIdvyrt1eW6C G2/myDquKuRFJ5bPoc7+A2j+MGf/jrp5/zYEOVSO8yOWqzkssRyvYfGKNtNp4q57 jjmFnqGNuBONFpj+ma15YgkXwENRLz6dUqLfwOVPrxL45AbEMNmQQSXTknYFa/Yq xdnQTwY4PlKI0jyWMxoiyfLR3V5hr2m5H9LhV/DpFR77mjVIijwevTcTj+Yk5ktx if4g302ccJ6lm2pkou6t9sMrMiavBg601MHxOB0brDHkjWyqXz62NJzPM6MQMxNS ORjS32BzV6HFSmTDFIyemYK+HCfzL+TKgHZT4YS63JDjCVrkLh50aQajcw2bFs58 TJ4mHGnCwyI9JqsvRfSU0uvra0Cd/6wK5u7ywRjiLr/HXoWdqCWTVUwbpBP38xT3 d+stJRCxQW4praTGoPKuotfTZpQyQBowCS8T88rZxtb3Eits7u7Wiy122kmqGb4d YzG+Cjjn1Pavl1vuhTkRzJ4RXDwvmImcdUUbyjlnHfno0V1h09eisk5fYmVGjX2D KUoDWMHvH25vhw59V9ZKyx2Siu0aibmR/CAaM/AR37CMYaeCa9WFaVVKDitObb0k W/fTwZhB/A9jiJdP2zIE9hkyz2DfPKa0fkaY5zxsreAWr3NeKC1sa/iP/vLkL8o3 11c= =epkN -----END PGP MESSAGE----- -----BEGIN PGP MESSAGE----- Version: 2.6.3ia owGVVkFu2zAQzKkI+AoCOaRBEEO6+lI0boGeinxhJVIOixVJiBKC5k95WT9RipJs kebajg4EBM3MzpK7S33c/qu+VOBUbW78o40tePrc8RcE3StE4EJypVWtzIaN2HRh rgbXnAo8D/5DaxyXyBulAb0Q8toItfdKjAi7M61F2UrdmzGwXbsIKsFEeYhfLibe wGbkfqJqPWu2UcsuqHo5qxA2THTGTglU5xOYNiDKgOeeO/791XTAe6lloOol2NZ/ nVW2UyZhD8pP7sHqJMp0yTqifO4A6wHlnF8PLeg4PyYGmyMekkiy6Uyfg09Hv01p FDwWPcK13OfgTxQehMjhfZ5P5SkaRZlDj0iCQevTHKJGF/TETThjhZJxEuz5kpTH VrgPxPuoFSh3S4wp4jWuLrjZGc2l86Xtl+AGlX9z3II36e3UxirYkM0RzaUZG02E eSxQ+RwKxge/vsRHMGZOfNzdH7+/TriHlOH6LCNRJUsvwhEFN2EeT+CXsl/xwkbk DzRB0QW8UrzQTGsk1dQZ6IV+TtBnjS6cA5qu5hjHVPOeM/ErTFFEuQdhOCzX3TfG /rT27JTfxOXsAJXI1nNx1CpILZtvjSK+J+jL8mXsQCfHRuz4oCEWNH0H9L1N5VGk lxSTOnuSOyU7H74amkZ2/C+HGqorxwCu/gyK9PfgPw== =y87E -----END PGP MESSAGE----- -----BEGIN PGP MESSAGE----- Version: 2.6.3ia owGVV91v2zYQ3+Pglz3u9aqhi/wR2e6KdY2bAkObFgGKtEg7YEBqDDRFO0wkUqAo J2mQP2n/4+5ISpbcuF2EIJbveN+/O57//ennxY9S8WrBEv4DPuNBDwYA8MHolWE5 g1SA53OpVYLMca/3C1KyCjkvyptybG8KUSbnL9tkm0rdJVVKIrVLS6URym6LGqlW XZowRjl1vd4YnXstllJJ8gftwsMe535KCgSc/nny9j3SphPHQtVHGagqF0ZDzq5l ril6LrIqYyWwkAcDmKJd2kGUXu3A/U+CjUOysWawkEIlwQsXCbO6hFWmFyx7aCyk o7TMSg78HL1SOl8YUZ5NJ5P52bP5LIR04skUSaFLuUA7dUgJ6Xhz/O4IBlgGowvJ ZiHFRxeCV5ZBpQgBVphcKpYyWAtTEg6+59hayxSEVxI79wZcFKzfu3W5k8pCIdMR UABVOXNEdyzLEHRnz+ct0uCc8cuzJ0hyNPdtMsecksZZMErFK61GeYEuM7Na0xmh QGRQGM1FqV24jYYpaTj569070uA1yyXEMbqFjKU2l3G/Dy9g0odbKBCB2sQRkaP+ DMS1tPEUX+42kk7wEM87ym2DER+Rc3gya6gIc85s7JgjiJIxqm0zVYtLcY7gWX9W R/qBGVZn18BeMqYTey7xtQpxLfg69pIUb0t7CMbVY9YS8CE5Qgjr6lxmAuIrhqxf fan68OiQSocH73rgwXIqlogwziBDMHOtrFAy1Q5ylQJqcm61wYFwP1YIDCaoiF8f n8IgLQJQMBEVt+DnBNKlKZyDJCJ9QumrEVdSpciNUdKTgusxieBBI1jNdxFQ4V2h mjpRCV3e8yKOpM2TaER2i/2X6T+K5WIET50k4oFClKoSTfLqTydf3MR1K8rhcL6t 5XefYp/h5uC8AYiPx1ZGxdIl2eX4Fcs4tizls6yW8kIDy4B9wep7iBumVhomk/3n z3e2oxOsuxG/1M1YqVKulEh9t/HQZhzdiTusPqyE5XE9KgJUUJH3/ekfMISYP55O AuchGqb3aiDOk5AXSgSVPWcSM4Mv2ON8FCaE6/d5CChAaPZdAI0u3LtTQaZ+w2It JQ2QXNAE7bWTk2m1QveVMO4mxJMsQ7zTwAzoIYdoAkzdxKhsGUfHKpXcVU2UheAy x2o190hyzyRxI6jOj5tDhVBxNE7FelxhkVOdR6PIRIjiwxaK3cSpbeZhzgNbGGmg I7uxCB2TKTUJ2aImiZKH6Ef0tVo8IsUdE4jeYwoorbigw5tM4F9WZyPVht17q4Sh VqlMqkvfmZNJ1J+1jqCFjxIYZ6Um06UVybYK/O/kHVCmWOeNopb2wKUR4rzGHYO1 LpDuLoTXqWkBont9B6837E1zh8k07Y4fj6ZwqtXwYR1p7yHcyjX71rLQugekm31h trrhWDOw6tLfV3DrC3ui8aa42bbzqItSP/Lr9aDEmpZ4CqtpBZBUs1r8H+/wOoX4 4nCCQLnAq1bS53C4QVx7z6qXiXpkXsw3GGh5hCnzLiRdU/SEAUijrw2fWsUnjeuu V19P12ZlqB2up8P2dQ7NjV4fCRBrX+v3HXLO9HraGnbQcZYK5CFfskzaaDOaXGvu Y3uR06fCeex7p+tsJ33O+JW0/Dx223R/m/nVaXqwpQQcnbw/Ovl0sEnUG8IqQdKI IsP55n4ZbBlvJ81Bezic7eCH5tsqrNvGJa2dVNB7wms9KMcuZ73dERz9ffyxHcFe ncs90LwqcPok39IfPGwKsCuSlcYNlEo5e7ij+JuEVZk92CFZb6DkSLTTgc4S13ru eru+iKwUzXD1SKubo1WBPcfZC13daqu75m0z6qjSNbXAH3N2GUdvhTqAx+ln68FT hi+v0Lx//awQ4V9fryOQm2iWy6wqz3FLS3VlN5tqvfrjDxv0NeNMfWF4PY0oZwhT vGFWFcuSxu0lR8ViewtxxGaFvOv9Bw== =u/82 -----END PGP MESSAGE----- -----BEGIN PGP MESSAGE----- Version: 2.6.3ia owGtVVFr2zAQ3tuYf8Utg9VOvLRp1z3EyWC0GZTBBt3KHkIoiiw7orZUJLstdP0V +x37jztJtuKUhA5WPYSLdPedvvvu5D8vfy9fcSFpXZAhfYFrvw8B9AHgzO2mUkHK gMqU53KIR+70VGqQ15RLwfQYODQYMTBgd5UizLjuB8EbLmhRI8BEVymXw9XHzlZG RVWYrSC4kTyFWsvQGFFwHwBc15UOexdajlt0mHNgogJNigXMmbd7UYL+7I5X4QGa Dy1eExZy9EPfGLgLcPi10DwXLIVCihxKkou6RJcYuYpKyQKmcJB0/eiKKKBJgHu3 K14wCEcR2gbLLJ5BqBhJQ8wQgyUC/QjetsjvI5hM4SiCJXpdJZ0o5wFTTHiXIsKS sSyCXFYSr21vgzIk0EQUmrErl+TdUQzfZ7MvlycX57YGD75uX+uSKWlocSpBdJHQ hIzTlTlHGRCJp2TYKeIIzWDtP97N2BG2tV0TpjGMIstmC9dbxSv2uEQu4vXU/Da+ Zt03XD6TorBXZZpieK3sbf1dkXUnQaPeYAAf4fD42APed4Ab2HUNmGVK0CyJ5qZG uSIiZU1V2uWr4/4+2Ir7kjcTY4YCKqZKLhCy121INxr/2Y9PtuFWUZ6rCzea0OZ4 liZsBHiqCzOpwsQXZIICfwD/fzDo1mFnZ7YUdndisqnrzKhGt8tqFCwJF1ZTonKM t/r00b6ZL4yyiOMFx2xewqwWBhNJBk4AE46cUBvzEEZWYX3LK7qyZzfz0WJ+sOhy pEQz2ON7406PNrBtt7SrkXsjkO0IHO0OTFlG6qLqxjWXbUpm3uQpfhqYcHc+XMTw 7fL89KfrDcPTuEzM49DO91l5LTVfYiuTpeJqS1/0Ing871jKjURHPhH8QuPkfPbp h09pnP81pXsPt2Q0QL5I9nVrPzBGWzPHtndxNNpBX+8b0Qup7RbiOdsfrT9dgN0Q /AU= =FtqU -----END PGP MESSAGE----- -----BEGIN PGP MESSAGE----- Version: 2.6.3ia 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 VnOB5QxAnUeeCD5lIKXH8mSAMPnf =xZkO -----END PGP MESSAGE----- -----BEGIN PGP MESSAGE----- Version: 2.6.3ia owGFkFFPwjAUhY1v7lfczDdCgIogxviADowGgQDGROShW4vUQLu0QxONj/4c/6O3 6+zwRZvs3OTrOac3+9r/jA9UmijGTW21h6degYEwGQXGobiAlGoKfA18s11TprS9 mwmuNa1BpR4Eh4wvheQwHI0bYI9TwK4eVhlgCj/rEYlQEhsxBrsxkvuJj0mF/phL MFxDQjexoIya/LWf1CAq7EdlUe/KoaZH3Shy6NijaDIa56jl0fS+61C7dN05Aic7 LzoCnTI4K9CpR5ORY6Th0XX/wSHi0c2tqyfl9tPL7rRvUfMXurCo3L43dFuQVhDg v81EAsmKatD8hWvD56S9mHcWcA7voVRpI6zaQXCsmVXJn1ApY6hMqzSswmMAf57Q vNLU2rdpXoNiMhStrIrlG3bA/y3PGxs3CTXLYsY4uWThx1nwDQ== =pnvD -----END PGP MESSAGE-----