Categorías
Hacking Hacking Python Programacion

Curso de desarrollo de software enfocado a la seguridad informática con Python

En esta ocasión quiero hablaros de un curso que me encuentro preparando y que seguramente será de interés para muchos de vosotros, se trata de un curso de desarrollo de software con Python enfocado a la seguridad informática, al pentesting y al hacking. El curso responde a la necesidad de aquellas personas que quieren aprender a programar desde cero y para los que los libros de “Python para pentesters” y “Hacking con Python” que he escrito hace algunos meses, aun les resultan muy complejos debido a su falta de conocimientos en programación.
Dicho esto, será un curso que partirá desde los conceptos básicos de programación estructurada y orientada a objetos, así como las bases del lenguaje, los principales módulos disponibles, etc. En la medida que el curso va avanzando se explican conceptos básicos de recolección de información, técnicas OSINT y pentesting utilizando Python. En los últimos dos módulos de los seis que se compone el curso, hablaré sobre integración de Python con herramientas de uso común en auditorías de seguridad, tales como Metasploit Framework o Nessus y conceptos básicos sobre patrones de diseño y arquitectura de software.

Como mencionaba antes, el curso se compone de 6 módulos, los cuales se encuentran distribuidos en 10 semanas. Cada semana podréis descargar un fichero comprimido con todos los recursos correspondientes, en el que se incluirán vídeos, documentos y código.

Junto con el curso, se os entregará una copia del libro de “Python para Pentesters”, el cual os servirá como recurso de estudio durante las semanas del curso.
Se encuentra planificado para comenzar el día 1 de Junio y podéis registraros o solicitar información desde ya en el sitio web de “The Security Sentinel” (http://www.thesecuritysentinel.es/1/curso_de_python_para_pentester_676511.html)

Todos los alumnos que os apuntéis, además de los recursos que he mencionado anteriormente (vídeos, documentos, código y el libro de “Python para Pentesters”), también podréis entrar en un espacio privado para poder realizar las practicas, interactuar con otros alumnos que se encuentran apuntados al curso (si os apetece) y contactar conmigo directamente vía chat. En dicho espacio también tendréis disponible una consola Linux en la que podréis practicar con lo aprendido en curso.

Espero veros apuntados y como siempre, ante cualquier duda, comentario o sugerencia podéis contactar conmigo directamente (adastra@thehackerway.com).

Un saludo y Happy Hack!

Categorías
Hacking Networking Programacion Services - Software

Serialización de objetos utilizando la Streaming Library y desarrollo de plugins I2P

En la entrada anterior se ha venido hablado de los beneficios que aporta la Streaming Library si eres un usuario de I2P y quieres automatizar el proceso de crear “peers” descentralizados que, utilizando la red de I2P, puedan comunicarse y transferir información. Esta librería se encuentra escrita en Java y cuenta con todas las características necesarias para poder interactuar con el protocolo I2CP y poder desplegar servicios que utilicen/establezcan puentes de entrada y salida desde la instancia local de I2P. Tal como mencionaba en el artículo anterior, el procedimiento para crear un emisor y un receptor es bastante simple y si el lector alguna vez ha tenido que trabajar con sockets y programación en entornos de red en cualquier lenguaje, rápidamente podrá apreciar que aunque la librería aporta clases nuevas, su funcionamiento es muy similar a los clásicos sockets “servidor” y “cliente”, de hecho, las clases principales de la Streaming library son “wrappers” de las las clases “ServerSocket” y “Socket” de Java, aunque evidentemente implementando toda la lógica necesaria para poder interactuar con I2P.
No obstante, cuando se navega por Internet en busca de información sobre el uso avanzado de está librería normalmente es muy complicado encontrar detalles técnicos que permitan adentrarse en su uso y casi en todos los casos, se implementa el típico ejemplo del servidor “echo”, en el que existe un cliente que actúa como emisor, un servidor que actúa como receptor y el receptor devuelve los mensajes de texto que envía el emisor, funcionando como un servidor “echo” muy simple, no obstante, uno de los principales beneficios con los que cuenta Java, es que tiene un sistema muy potente de serialización de objetos que permite compartir información entre diferentes JVM, algo que potencio enormemente la evolución de tecnologías tales como RMI, CORBA, JINI y posteriormente los tan conocidos EJB en el mundo de J2EE. En este caso, también es posible serializar objetos entre emisores y receptores I2P, lo que supone un abanico de posibilidades enorme a la hora de crear aplicaciones que utilicen la capa de anonimato que aporta I2P a sus usuarios, ya que se pueden crear objetos debidamente preparados para transmitir mucha más información que una cadena de texto. Para los que no lo sepáis, para poder serializar un objeto en Java, dicho objeto, sus atributos y todas las clases padre de las que hereda deben ser serializables también, lo cual se consigue implementando la interfaz “java.io.Serializable”, además, si alguno de los atributos de dicha clase no es serializable, debe utilizarse el modificador de acceso “transient” con el fin de indicarle a la JVM que dicho atributo debe utilizarse únicamente en el contexto de la JVM local y no se debe incluir en el proceso de serialización del objeto.
Dicho esto, queda claro que una de las labores más importantes a la hora de diseñar una aplicación que aproveche los beneficios de la Streaming Library, consiste precisamente en definir una estructura de objetos que contenga todas las relaciones y atributos necesarios para transmitir información entre los túneles de entrada y salida de la instancia de I2P. La estructura de clases en cuestión, además de ser serializable, debe de ser conocida por todos los “peers” que la utilicen, ya que de no ser así, un emisor puede enviar un objeto Java a un receptor, perfectamente serializado, pero dado que el receptor no cuenta el “.class” de la clase, simplemente no sabrá cómo se debe deserializar el objeto. Lo que normalmente se suele hacer, es distribuir un fichero JAR con todos los elementos necesarios en cada uno de los peers, esto quiere decir que aunque la comunicación entre todos los receptores y emisores en I2P sea descentralizada, tienen que haber elementos de comunicación comunes que les permitan comprender los mensajes que se transmiten por sus túneles, exactamente igual que ocurre con las normas que se definen en cualquier protocolo de red con el fin de garantizar la interoperabilidad en las capas de transporte y aplicación. Además de esto, los receptores y emisores deben conocer los “destinations” de las entidades con las que se desean comunicar. Es posible implementar un mecanismo de descubrimiento automático utilizando un servicio oculto en I2P que sea accesible únicamente por aquellas personas que se desean comunicar y en dicho servicio, cada uno podría depositar el “destination” correspondiente a su instancia de I2P con el fin de que otros usuarios puedan enviarse información.

Para el ejemplo de esta entrada, se creará en primer lugar una clase muy simple que funcionará simplemente como un contenedor de información, también conocido en la terminología de Java como un POJO (Plain Old Java Object) el cual simplemente contendrá algunos atributos básicos y una serie de métodos de establecimiento y acceso (setters y getters)

[sourcecode language=»java»]

package net.privanon.common;
public class UserInfo implements java.io.Serializable {
private String username;
private String name;
private String lastname;
private Integer age;
private String privateMessage;
public void setUsername(String username) {
this.username = username;
}

public void setName(String name) {
this.name = name;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public void setAge(Integer age) {
this.age = age;
}
public void setPrivateMessage(String privateMessage) {
this.privateMessage = privateMessage;
}
public String getUsername() {
return this.username;
}
public String getName() {
return this.name;
}
public String getLastname() {
return this.lastname;
}

public Integer getAge() {
return this.age;
}
public String getPrivateMessage() {
return this.privateMessage;
}
}
[/sourcecode]

La clase en si misma no es demasiado interesante, sin embargo representa la piedra angular del proceso de transmisión de datos entre clientes y receptores, ya que contiene los atributos necesarios para obtener/establecer datos y además, implementa la interfaz Serializable, lo que le permitirá “moverse” libremente entre diferentes JVM.
Modificando un poco la estructura del programa receptor visto en el articulo anterior, el resultado final seria el siguiente.

[sourcecode language=»java»]
package testing.i2p;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.util.I2PThread;
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.I2PSessionException;
import net.privanon.common;

public class Server {
public static void main(String[] args) {
I2PSocketManager manager =
I2PSocketManagerFactory.createManager();
I2PServerSocket serverSocket =
manager.getServerSocket();
I2PSession session = manager.getSession();
System.out.println(session.getMyDestination().toBase64());
I2PThread t = new I2PThread(new
ClientHandler(serverSocket));
t.setName("clienthandler1");
t.setDaemon(false);
t.start();
}

private static class ClientHandler implements Runnable {
public ClientHandler(I2PServerSocket socket) {
this.socket = socket;
}

public void run() {
while(true) {
try {
I2PSocket sock = this.socket.accept();
if(sock != null) {
ObjectOutputStream oos = new
ObjectOutputStream(sock.getOutputStream());
ObjectInputStream ois = new
ObjectInputStream(sock.getInputStream());
UserInfo info = null;
while ((info = (UserInfo) ois.readObject()) != null) {
if(info != null && info.getUsername() != null
&& info.getUsername().equals("Adastra")) {
UserInfo response = new UserInfo();
response.setName("unknown");
response.setLastname("unknown");
response.setUsername("unknown");
response.setAge(0);
response.setPrivateMessage("Send the report
and close the operation");
oos.writeObject(response);
}
}
ois.close();
oos.close();
sock.close();
}
} catch (I2PException ex) {
System.out.println("General I2P
exception!");
} catch (ConnectException ex) {
System.out.println("Error
connecting!");
} catch (SocketTimeoutException ex) {
System.out.println("Timeout!");
} catch (IOException ex) {
System.out.println("General
read/write-exception!");
} catch (ClassNotFoundException ex) {
System.out.println("Class Not found
exception!");
}
}
}
private I2PServerSocket socket;
}
}
[/sourcecode]

Hay que notar que las principales diferencias se pueden ver en el uso de la clase “UserInfo” que se ha creado anteriormente y la serialización/deserialización de dicho objeto utilizando las clases ObjectOutputStream y ObjectInputStream. En este caso, se deserializa el objeto recibido por el emisor, se realiza una verficación muy simple sobre el nombre del usuario y a continuación, se responde al emisor con un objeto del mismo tipo.

Por otro lado, el receptor por su parte puede tener una estructura como la siguiente:

[sourcecode language=»java»]
package testing.i2p;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import net.privanon.common;

public class Client {
public static void main(String[] args) {
I2PSocketManager manager =
I2PSocketManagerFactory.createManager();
System.out.println("Please enter a Destination:");
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String destinationString;
try {
destinationString = br.readLine();
} catch (IOException ex) {
System.out.println("Failed to get a
Destination string.");
return;
}
Destination destination;
try {
destination = new Destination(destinationString);
} catch (DataFormatException ex) {
System.out.println("Destination string
incorrectly formatted.");
return;
}
I2PSocket socket;
try {
socket = manager.connect(destination);
} catch (I2PException ex) {
System.out.println("General I2P exception
occurred!");
return;
} catch (ConnectException ex) {
System.out.println("Failed to connect!");
return;
} catch (NoRouteToHostException ex) {
System.out.println("Couldn’t find host!");
return;
} catch (InterruptedIOException ex) {
System.out.println("Sending/receiving was
interrupted!");
return;
}
try {
ObjectOutputStream oos = new
ObjectOutputStream(socket.getOutputStream());
UserInfo info = new UserInfo();
info.setName("Ad");
info.setLastname("Astra");
info.setUsername("Adastra");
info.setAge(30);
info.setPrivateMessage("exposure done. Waiting
for indications");
oos.writeObject(info);
ObjectInputStream ois = new
ObjectInputStream(socket.getInputStream());
UserInfo response = null;
while ((response = (UserInfo) ois.readObject()) != null) {
System.out.println("Name: "+
response.getName());
System.out.println("Last Name: "+
response.getLastname());
System.out.println("Username: "+
response.getUsername());
System.out.println("Age: "+
response.getAge());
System.out.println("Private message: "+
response.getPrivateMessage());
}
ois.close();
oos.close();
socket.close();
} catch (IOException ex) {
System.out.println("Error occurred while
sending/receiving!");
} catch (ClassNotFoundException ex) {
System.out.println("Class Not found
exception!");
}
}
}
[/sourcecode]

Nuevamente, las diferencias más destacables entre el programa emisor del articulo anterior y éste, se centran en la serialización/deserializacion de la clase “UserInfo” por medio de los objetos ObjectInputStream y ObjectOutputStream.

Después de iniciar I2P y de ejecutar tanto el emisor como el receptor, se puede ver el flujo de datos transmitidos entre ambas entidades en la siguiente imagen.

streaming1

 

Aunque ambos programas se ejecutan en la misma máquina y sobre la misma instancia de I2P, el funcionamiento será el mismo en instancias y máquinas separadas.

Otro escenario interesante en el que también se puede utilizar la Streaming Library, es en el desarrollo de plugins que se puedan desplegar directamente desde la consola de administración de I2P. Sobre estas cosas hablaré en un próximo articulo.

Saludos y Happy Hack!
Adastra.

Categorías
Hacking Hacking Python Networking Programacion Services - Software

Uso de Streaming Library para aplicaciones en I2P

Se ha hablado en varias ocasiones sobre los beneficios que aporta I2P al anonimato y en alguna ocasión se ha hablado sobre cómo crear rutinas simples que permitan acceder a I2P utilizando la librería “Streming Library”, la cual se encuentra escrita en Java. Si quieres hacer cualquier tipo de prueba con Python contra I2P, actualmente hay pocas librerías que realmente te lo permitan y que sean estables. Por lo que he podido apreciar en los foros oficiales de desarrolladores, al parecer las personas clave en el desarrollo de plugins, utilidades y el propio core de la red no muestran mucho interés en desarrollar otra librería en un lenguaje distinto para hacer lo que sobradamente ya hace la Streaming Library y francamente tienen toda la razón del mundo, ya que tienen un “roadmap” con cientos de frentes abiertos y es mucho más interesante crear cosas nuevas y mejorar las existentes que reinventar la rueda. Al equipo de I2P le ha costado muchísimo desarrollar la Streaming Library y la cantidad de errores que se reportaban al principio era enorme, es normal que después del número de horas invertidas y el nivel de estabilidad en el que se encuentra ahora, haya sido declarada como la librería de referencia para crear aplicaciones que requieran el uso del protocolo de control de I2P (I2CP) sobre TCP. No obstante, seguimos con los mismos problemas:

1. Aun sigue siendo poca, muy poca la documentación sobre su uso. Si realmente quieres aprender los elementos clave de la librería, tienes que meterte en los JavaDocs y empezar a crear un mapa mental de como están relacionadas las clases y cómo puedes usarlas.

2. Si eres un desarrollador en Python o tienes alguna herramienta para acceder a la web profunda de I2P aunque sea para consultar “Destinations”. Bueno, lo tienes un poco complicado por lo que comentaba antes. Aunque existe el proyecto python-i2cp (https://github.com/majestrate/python-i2cp) para utilizar directamente el protocolo interno de I2P, pero no avanza al ritmo que se esperaría y aun no funciona correctamente a la hora de consultar un destination. Además, utilizar directamente I2CP está desaconsejado por el equipo de I2P. Nos queda utilizar Jython o la JSR de Java correspondiente para ejecutar código de scripting con Python desde la máquina virtual de Java.

Es este artículo intentaré centrarme en el primer punto y explicaré algunas características interesantes en Streaming library para Java y en un próximo articulo, intentaré explicar algunas alternativas a la hora de acceder a I2P desde Python.

No en todos los casos resulta viable utilizar la Streaming Library de I2P, pero por norma general, cuando se habla de aplicaciones punto a punto, como es el caso de descargas de torrents y cosas similares, resulta bastante conveniente y óptimo utilizar esta librería, de hecho, un ejemplo bastante claro lo tenemos con el proyecto I2PSnark, un cliente bittorrent que utiliza la Streaming Library para descargar y compartir torrents utilizando la red de I2P. El código fuente del proyecto se encuentra licenciado con la GNU/GPL y se está disponible en el siguiente repositorio GitHub: https://github.com/i2p/i2p.i2p/tree/master/apps/i2psnark Si se mira con atención las clases y métodos que se incluyen en el proyecto, es mucho más fácil comprender el funcionamiento de la Streaming Library y cómo utilizarla.

En primer lugar, cualquier rutina de código utilizando esta librería puede funcionar en uno de dos modos, o bien como cliente que consulta “destinations” (un destino en I2P es similar a la combinación de una dirección IP y un puerto) o simplemente como servidor. Las principales clases disponibles en la API son “wrappers” de las clases SocketServer y Socket de Java, aunque evidentemente las clases en la Streaming Library adicionan las funcionalidades necesarias para que todas las conexiones se lleven a cabo utilizando la red de I2P.
Por otro lado, en una instancia de I2P, un programa escrito en Java y utilizando la Streaming Library puede actuar como emisor o receptor (recordar que I2P es una red que basada en paquetes y no en circuitos como TOR), con lo cual desde un programa se puede generar el “destination” para que otros usuarios envíen paquetes a la instancia o se pueden buscar (lookup) destinos y enviarles paquetes. El siguiente código sería la clase más simple que se puede crear utilizando la Streaming Library para crear un emisor.

[sourcecode language=»java»]
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;

public class Server {
public static void main(String[] args) {
I2PSocketManager manager = I2PSocketManagerFactory.createManager();
I2PServerSocket serverSocket = manager.getServerSocket();
I2PSession session = manager.getSession();
System.out.println(session.getMyDestination().toBase64());
}
}
[/sourcecode]

Como se puede apreciar, se crea una instancia de la clase “I2PSocketManager” utilizando la factoría “I2PSocketManagerFactory” y posteriormente se crea un objeto “I2PServerSocket”, el cual será utilizado para atender a las peticiones de otros usuarios en la red de I2P. Finalmente, se pinta por pantalla el destination local del proceso recién iniciado. La clase “net.i2p.data.Destination” cuenta con los métodos “toBase64” y “toBase32” que serán utilizados por el cliente para comunicarse con la instancia de I2P.

El código anterior solamente se encarga de obtener una sesión I2P y pintar por pantalla el destination generado, pero una vez hecho esto, el hilo principal del programa termina. Las siguientes instrucciones que se deben añadir, deben permitir que el proceso se quede en estado de escucha para procesar los mensajes que envían los clientes, para ello la clase I2PThread permite crear un hilo de ejecución ininterrumpido que permitirá al emisor actuar como servidor y recibir mensajes.

[sourcecode language=»java»]
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.I2PThread;
public class Server {
public static void main(String[] args) {
I2PSocketManager manager = I2PSocketManagerFactory.createManager();
I2PServerSocket serverSocket = manager.getServerSocket();
I2PSession session = manager.getSession();
System.out.println(session.getMyDestination().toBase64());
I2PThread thread = new I2PThread(new
ClientHandler(serverSocket));
thread.setDaemon(false);
thread.start();
}
private static class ClientHandler implements Runnable {
private I2PServerSocket socket;
public ClientHandler(I2PServerSocket socket) {
this.socket = socket;
}
public void run() {
while(true) {
//Procesar las conexiones de los clientes.
}
}
}
}
[/sourcecode]

En este caso se crea una nueva clase que procesará los mensajes de los clientes de forma indefinida en un bucle “while true”. En dicho bucle se aceptarán las conexiones de los clientes por I2P y posteriormente, se realizará algún tipo de procesamiento, el cual, típicamente consiste en recibir las conexiones por parte de los clientes y responder a los mensajes.

[sourcecode language=»java»]
while(true) {
try {
I2PSocket sock = this.socket.accept();
if(sock != null) {
//Receive
BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
//Send
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
String line = br.readLine();
if(line != null) {
System.out.println(line);
bw.write(line);
bw.flush();
}
sock.close();
}
} catch (I2PException ex) {
System.out.println("General I2P exception!");
} catch (ConnectException ex) {
System.out.println("Error connecting!");
} catch (SocketTimeoutException ex) {
System.out.println("Timeout!");
} catch (IOException ex) {
System.out.println("read/write-exception!");
}
}
[/sourcecode]

Por otro lado, el cliente debe conocer cuál es la dirección (destination) del emisor para poder transmitirle un mensaje y recibir su respuesta. El código necesario en la parte del cliente es mucho más simple, ya que solamente requiere utilizar la clase I2PSocket y conectarse con el destination especificado.

[sourcecode language=»java»]
package testing.i2p;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;

public class Client {
public static void main(String[] args) {
I2PSocketManager manager =
I2PSocketManagerFactory.createManager();
System.out.println("Destination:");
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String destinationString;
try {
destinationString = br.readLine();
} catch (IOException ex) {
System.out.println("Failed to get the Destination.");
return;
}
Destination destination;
try {
destination = new Destination(destinationString);
} catch (DataFormatException ex) {
System.out.println("Destination string incorrectly formatted.");
return;
}
I2PSocket socket;
try {
socket = manager.connect(destination);
} catch (I2PException ex) {
System.out.println("General I2P exception occurred!");
return;
} catch (ConnectException ex) {
System.out.println("Failed to connect!");
return;
} catch (NoRouteToHostException ex) {
System.out.println("Couldn’t find host!");
return;
} catch (InterruptedIOException ex) {
System.out.println("Sending/receiving was interrupted!");
return;
}
try {
//Write to server
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("Hello I2P!\n");
BufferedReader br2 = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
String s = null;
while ((s = br2.readLine()) != null) {
System.out.println("Received from server: " + s);
}
socket.close();
} catch (IOException ex) {
System.out.println("Error occurred while
sending/receiving!");
}
}
}
[/sourcecode]

El código anterior es la base para crear cosas mucho más elaboradas, por ejemplo, en lugar de utilizar las clases BufferedReader y BufferedWriter se podría utilizar clases como ObjectInputStream y ObjectOutputStream que permiten enviar objetos serializados (aquellos que extienden de la interfaz java.io.Serializable) y de esta forma, construir aplicaciones muy robustas al interior de I2P. Por otro lado, también se puede utilizar para crear plugins que se puedan desplegar directamente desde la consola de administración de I2P. Sobre estas cosas hablaré en un próximo articulo.

Saludos y Happy Hack!
Adastra.