|
Dímelo con flores ;-)
May 2008 - Artículos
-
 En los dos primeros artículos pudimos ver los objetos del API de Windows que íbamos a usar para poder acceder a la caché de los archivos temporales de Internet, cómo persistir estos datos en formato XML, y finalmente cómo permitir que temporalmente nuestra aplicacación se ejecute con las credenciales de otro usuario, para poder realizar una serie de acciones que de otro modo no podría efectuar por falta de privilegios. Siguiendo con el objetivo de este ejercicio hoy vamos a encapsular la lógica de negocio de nuestra aplicación en forma de servicio de Windows, y éste va a encargarse de realizar una consulta a la caché de Internet de nuestro ordenador cada X tiempo (configurable mediante un fichero XML). Lo primero de todo va a ser construir la base de nuestro servicio mediante un proyecto de tipo Windows Service. Esta plantilla nos genera un proyecto con un componente de tipo ServiceBase, que servirá para ir construyendo nuestro pequeño juguetito. A continuación vamos a cambiar el nombre al servicio, en nuestro caso lo llamaremos "IECacheQueryService". Y posteriormente estableceremos a True los valores de las propiedades CanPauseAndContinue y CanShutdown, para poder pausar y detener nuestro servicio en tiempo de ejecución. Importante: Comprobar que el nombre sel servicio se ha cambiado correctamente en el punto de entrada de la aplicación (si no fuese así tendremos que modificarlo manualmente): static class Program { static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new IECacheQueryService() }; ServiceBase.Run(ServicesToRun); } }
Implementando funcionalidad al servicio:
De momento el servicio será capaz de iniciarse, pero no realizará nada. Ahora debemos dotarlo de funcionalidad, y para ello vamos a crear un temporizador que se encargue de realizar una tarea cada X tiempo (por defecto una hora). Éste lo crearemos como un miembro dentro de nuestra clase:
public partial class IECacheQueryService : ServiceBase { private Timer clock = new Timer();
Y lo usaremos desde el constructor de la misma, así como en los eventos OnStart y OnStop. Observar que en el constructor lo inicializamos con un valor que proviene del fichero de configuración del proyecto (propiedades del proyecto/settings) llamado TimerInterval y se ha establecido a un valor de 3.600.000 (1 hora en milisegundos).
public IECacheQueryService() { InitializeComponent(); clock = new Timer(Properties.Settings.Default.TimerInterval); clock.Elapsed += new ElapsedEventHandler(clock_Elapsed); clock.Enabled = true; //... } protected override void OnStart(string[] args) { ExecuteQuery(); clock.Start(); //... } protected override void OnStop() { clock.Stop(); }
Del mismo modo también especificamos el manejador de evento asociado que se ejecutará cada vez que se cumpla este intervalo de tiempo (clock_Elapsed). Éste será el corazón de nuestro servicio y será el encargado de invocar la consulta a la caché de IE:
void clock_Elapsed(object sender, ElapsedEventArgs e) { try { ExecuteQuery(); } catch (Exception ex) { appEventLog.WriteEntry("Exception: " + ex.Message, EventLogEntryType.Error); } }
Simple verdad? Ahora vamos a construir este método ExecuteQuery a apartir de las piezas que vimos en anteriores entradas de la misma serie. No os preocupeis que en el último post estará un enlace al código completo del ejemplo (incluido el instalable).
Que hace exactamente este método?
- Primero de todo crea un objeto de tipo StopWatch para medir con precisión el tiempo que transcurre en la ejecución del mismo.
- Llama al método getResults que se encarga de efectuar la consulta y devolver un objeto que es una lista genérica de entradas de la caché.
- Construye el nombre de fichero en el que guardaremos los resultados y llama al método saveResults, encargado de realizar la suplantación de identidad y serializar los resultados en el fichero antes mencionado.
private void ExecuteQuery() { try { Stopwatch watch = new Stopwatch(); watch.Start(); List<IECacheEntry> results = getResults( Properties.Settings.Default.SearchPattern); string filename = string.Format("{0}{1}_{2}.xml", Properties.Settings.Default.TargetPathLocation, Environment.MachineName, DateTime.Now.ToString("ddMMyyyy_hhmm")); watch.Stop(); TimeSpan ts = watch.Elapsed; saveResults(results, filename); appEventLog.WriteEntry(string.Format( "Query executed successfully at {0} in '{1}' by user '{2}'. Elapsed time (ms): {3}", DateTime.Now.ToString(), Environment.MachineName, WindowsIdentity.GetCurrent().Name,ts.Milliseconds)); } catch (Exception ex) { appEventLog.WriteEntry("Exception: " + ex.Message, EventLogEntryType.Error); } }
Está el servicio terminado? Bueno, si asumimos que el código está completo y funciona sin errores, sí, lo está :-P
Y ahora cómo lo probamos? Pues... bueno, para probarlo antes tenemos que instalarlo.
Agregando los elementos necesarios para instalar el servicio:
No he encontrado demasiada documentación al respecto, de modo que intentaré mostrar los pasos a seguir mediante capturas de pantalla.
Vamos a agregar un elemento que nos permita distribuir el servicio:
Le agregaremos dos componentes serviceProcessInstaller y serviceInstaller (es posible que no aparezcan en la barra de herramientas, así que tendremos que agregarlos mediante la opción "Choose Items..."
A continuación los configuraremos del siguiente modo:
| Control |
Propiedad |
Valor |
| serviceProcessInstaller1 |
Account |
LocalSystem (*) |
| serviceProcessInstaller1 |
Parent |
Installer1 |
| serviceInstaller1 |
Description |
Internet Explorer Query Cache |
| serviceInstaller1 |
Display Name |
Internet Explorer Query Cache |
| serviceInstaller1 |
Parent |
Installer1 |
| serviceInstaller1 |
ServiceName |
IEQueryCache |
| serviceInstaller1 |
StartType |
Automatic |
(*) Habitualmente se utilizará siempre la cuenta con menos nivel de privilegios (LocalService), pero en nuestro caso necesitamos acceder a la caché de IE, de modo que es posible que incluso tengamos que configurar la cuenta de un usuario de windows con suficientes permisos.
Cruzamos los dedos, compilamos el proyecto y vamos a agegar un proyecto de instalación a la solución.
Creando el proyecto de instalación:
Agregaremos un proyecto de tipo Setup and deployment, que será el encargado de instalar nuestro servicio en las estaciones Windows que deseamos monitorizar.
Agregamos la salida del proyecto anterior al nuevo proyecto de instalación:
Ahora mostraremos la ventana de "custom actions" para agregar como acción al instalador la salida de nuestro proyecto de servicio de Windows.
Una vez realizado esto ya podemos compilar el proyecto de instalación e instalarlo en las estaciones cliente. Si quereis instalarlo en vuestra estación para probarlo y configurarlo adecuadamente, la opción más sencilla es seleccionar la opción Install del menú contextual del proyecto de instalación, en el explorador de proyectos.
Una vez instalado, aparecerá en el administrador de servicios, para que podamos configurarlo del modo deseado. En este caso particular, si deseamos monitorizar la caché de un usuario en particular, deberemos usar las credenciales de éste usuario, ya que si no el S.O. no será capaz de montar el fichero Index.dat correspondiente a la caché de Internet Explorer.
Bueno, hasta aquí ha llegado este ejemplo. En el próximo post haremos un resumen de lo que se ha visto hasta ahora y publicaré un enlace con el código completo del proyecto para que sirva de ejemplo (o de mal ejemplo :-P).
Saludos desde andorra,
** crossposting desde el blog de Lluís Franco en geeks.ms **
|
-
Como las buenas costumbres no hay que perderlas, he decidido retomar los hilos "por fin es viernes..." que hasta ahora publicaba en mi blog personal, y publicarlos también aquí, en Geeks. Que un poco de humos también va bien de vez en cuando. Gracias a todos los que me mandáis colaboraciones, sin vosotros no habría "PFEV". Unos chistes cortos By Cesar: Restaurante de lujo: - ¿Que tomarán los señores....? - A mi me pone una langosta Thermidor y un cava Juve & Camps reserva de familia. - ¡Excelente decisión! ¿Y a su esposa....? - Póngale un fax y dígale que me lo estoy pasando de *** madre..... -------------------------------------------------------------------------------- Dos caballeros que se movían muy deprisa en el interior de un Hipermercado con sus carritos de compras se chocan. Uno le dice al otro: - Perdóneme Usted; es que busco a mi señora. - ¡Qué coincidencia, yo también! Estoy ya desesperado. - Bueno tal vez le pueda ayudar. ¿Cómo es su señora? - Es alta, de pelo castaño claro, piernas bien torneadas, pechos firmes, un culo precioso, en fin, muy bonita... ¿Y la suya?. - Olvídese de la mía, vamos a buscar la suya... -------------------------------------------------------------------------------- Un catalán que esta arrancando el papel pintado de su casa es visitado por un amigo.... - ¿Qué, redecorando la casa?. - No, de mudanza. -------------------------------------------------------------------------------- Un tío esta haciendo un crucigrama. - Oye, a ver si tu sabes esta: 'Órgano sexual femenino', con cuatro letras, y la segunda es una 'O'. - ¿Horizontal o vertical? - Horizontal. - ¡Ah! pues entonces 'boca'. -------------------------------------------------------------------------------- Esto es una pareja que se conoce en una fiesta y la misma noche acaban en la cama. Al acabar, va la chica y dice: - Oye, tú no tendrás el SIDA, ¿verdad? - ¡No! - Menos mal, ya sería mala suerte cogerlo dos veces en la misma semana... -------------------------------------------------------------------------------- Un hombre dice a su novia: - Mari, ahora mismo te la voy a meter hasta el fondo. - ¡Joder!, podrías decir algo mas romántico- dice ella. - Está bien, Maria, a la luz de la luna te la voy a meter hasta el fondo. -------------------------------------------------------------------------------- - Mamá, mamá ¿cuánto cuesta casarse? - No tengo ni idea, hijo; todavía no he acabado de pagar las consecuencias. -------------------------------------------------------------------------------- Un borracho llega a su casa cantando y haciendo barullo, en eso se asoma un vecino y le dice: - ¡¡Psss!!, ¡no haga bulla que su mujer se va a despertar! - ¡No se preocupe!, cuando llego así mi mujer y yo jugamos al exorcista! - ¿Ah, si? y ¿cómo es eso? - Bueno, ella me sermonea y yo vomito! -------------------------------------------------------------------------------- Una pareja de ancianos discuten y el le dice a ella: - Cuando te mueras voy a comprar una losa que diga: 'Aquí yace mi mujer, tan fría como siempre'. - Y yo voy a poner: 'Aquí yace mi marido, ¡AL FIN RIGIDO!'. -------------------------------------------------------------------------------- - Mi marido es impotente al 100% - Eso no es nada, el mío lo es al 200% - ¡Pero eso es imposible!. ¿Como puede ser? - Es que se ha mordido la lengua esta mañana. -------------------------------------------------------------------------------- Una pareja que esta en la cama, son las 4 de la mañana, los dos muy relajados por haber echado un par de polvos buenísimos. De pronto él pregunta: -¿Quieres que te dé por culo?. Ella se para, piensa y dice: - Bueno, ya que estamos, ¡adelante! Y él le contesta: - Pues levántate y hazme una tortilla de patatas que estoy muerto de hambre. -------------------------------------------------------------------------------- Le dice la madre a la hija: - Hija, dicen las vecinas que te estás acostando con tu novio. - ¡Ay, mami! la gente es muy chismosa: una se acuesta con cualquiera y ya dicen que es el novio. -------------------------------------------------------------------------------- Dos amigos que se encuentran... - Hombre Luis, ¿qué es de tu vida? - Pues mira, me he colocado de funcionario. - ¡Que bien!, así por las tardes no trabajas. - No. Por las tardes no voy. Cuando no trabajo es por las mañanas. -------------------------------------------------------------------------------- Un hombre entra en un restaurante y ve a una mujer muy bonita sola en una mesa. Se aproxima y pregunta: - Disculpe señorita, he visto que está usted sola, ¿puedo sentarme y hacerle compañía? La mujer escandalizada, se pone de pie y responde gritando: - ¿Usted está loco?, pero ¿qué se piensa que soy? Todo el restaurante lo escucha y el hombre sin saber que cara poner contesta: - Disculpe yo sólo quería hacerle compañía. A lo que la mujer responde dándole una bofetada al hombre: - Y encima insiste!!!! Atrevido!! El hombre completamente abochornado se va a la otra punta del restaurante y decide sentarse allí. A los pocos minutos la mujer se levanta y se acerca a la mesa de él. - Disculpe por la forma que lo traté antes, pero soy psicóloga y estoy estudiando el comportamiento de las personas ante situaciones inusitadas. El hombre se levanta y contesta gritando: - ¿¿100.000 pesetas??? Estás loca!! Ninguna *** vale eso!! -------------------------------------------------------------------------------- - Papá, papá.. ¿Por qué os casasteis tú y mamá? - Por tu culpa, cabrón! -------------------------------------------------------------------------------- En una prueba de alcoholemia el Guardia Civil le dice al conductor: - Mire... ¿No le da vergüenza? (Enseñándole el alcoholímetro que marcaba 3,45) - ¡Joder! ¡Las cuatro menos cuarto! ¡Mi mujer me mata! -------------------------------------------------------------------------------- - Padre me confieso que el otro día me acosté con una jovencita de 15 años. - Bueno hijo, tampoco es para tanto. Ya lo dicen las Escrituras: 'Hay que enseñar al que no sabe'. - Sí padre, pero después encontré una señora de 65, que estaba de muy buen ver, y no me negué a su proposición. - Jesucristo dijo: 'Dad de comer al hambriento'. - Ya padre, pero lo más grave es que ayer vi a un moro agachado, con el culito todo redondito, y no me pude reprimir. - ¡Vaya hijo! Eso ya es más complicado... ¿Pero sabes qué te digo? ¡Al que no crea en Dios que le den por culo!!! -------------------------------------------------------------------------------- Están un niño y una niña jugando. El niño le pregunta a la niña: - ¿Sabes cómo se hacen los niños? - No, no lo sé... - Pues mira, el papá pone la semillita en la vagina de la mamá... - ¿Y luego? - Luego la empuja con la polla. -------------------------------------------------------------------------------- En un casting para un programa de televisión se pide a los participantes que den el nombre, los apellidos y una característica que les haga especiales. Llega el primero: 'Pepe Romerales. 100 m lisos en 10 segundos'. El siguiente: 'Manuel Vargas. Bailarín profesional'. En eso llega otro y dice: 'José Unamuno. Una polla de 28 cm .' La que estaba apuntando le mira con los ojos desorbitados y le pregunta: - ¿¿¿Una qué??? - Unamuno, joder, ¡Como el escritor!!! Imágenes de un cementerio chileno (by Carlos): Fotos curiosas (By Evas): Buen fin de semana a todos! :-)
** crossposting desde el blog de Lluís Franco en geeks.ms **
|
-
:-) Esta mañana estaba dándole una ojeada al problema anunciado por Luciano Bello sobre la vulnerabilidad descubierta en OpenSSL, que ha generado claves débiles entre el mes de Septiembre de 2006 y el 31 de Marzo de este año. Cuando de repente me he encontrado con estas tiras del genial Dilbert al respecto de la generación de aleatorios: Y No, no voy a hablar sobre el tema, que ya se ha hablado demasiado... Simplemente hacer la reflexión sobre que pasaría si el fallo de seguridad se hubiese producido en el sistema operativo más maléfico del mundo, creado por Spectra la compañía que pretende dominar el mundo a golpe de Software. Más info en: http://metasploit.com/users/hdm/tools/debian-openssl/ Saludos,
** crossposting desde el blog de Lluís Franco en geeks.ms **
|
-
Impersonation => RunAs En los dos primeros artículos pudimos ver los objetos del API de Windows que íbamos a usar para poder acceder a la caché de los archivos temporales de Internet, y como persistir estos datos en formato XML. Hoy vamos a usar Impersonation para ejecutar nuestra aplicación con otras credenciales de usuario, de este modo nos conectaremos a una ubicación remota a la cual sólo tienen permisos para conectarse los administradores. Supongamos que tenemos una ubicación similar a esta: \\RootDFS\Logs\IECacheQuery\ y que sólo los usuarios del grupo administradores pueden acceder a leer y modificar su contenido. Si nuestra aplicación se ejecuta con permisos del usuario o servicio local, no podremos guardar los archivos de registro. Sin embargo, en un momento dado nuestra aplicación puede elevar su nivel de privilegios usando las credenciales del administrador, ejecutar las tareas requeridas, y posteriormente volver a su nivel predeterminado para continuar con su ejecución como si tal cosa. A este proceso se le llama impersonar o suplantar, aunque personalmente no me gusta demasiado la traducción por las posibles connotaciones negativas que conlleva. Y básicamente se trata de que durante un lapso de tiempo nuestra aplicación va a estar ejecutándose con un usuario distinto al usuario que ha iniciado la aplicación. Artistas invitados: LogonUser y DuplicateToken La primera de ellas verifica si las credenciales suministradas son correctas, validándolas contra un dominio (que puede ser la estación local) y devuelve un token, mientras que la segunda crea una copia de un token existente. Hay que mencionar un detalle importante y es que el token devuelto por DuplicateToken es lo que se llama un 'impersonation token', que no es válido para la función CreateProcessAsUser ya que ésta necesita un token primario. Si deseamos usar ésta función debemos usar como alternativa la función DuplicateTokenEx. [DllImport("advapi32.dll", SetLastError = true)]public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
Más información aquí:
LogonUser Function:
DuplicateToken Function: http://msdn.microsoft.com/en-us/library/aa446616(VS.85).aspx
Bien, ahora que ya nos conocemos todos vamos a ver un fragmento del código de la aplicación, que usa estas funciones para impersonar un usuario y devolver un objeto de tipo WindowsImpersonationContext. Posteriormente podremos usar el método 'Undo' de este objeto para deshacer el contexto de suplantación y volver al contexto anterior, de forma que la aplicación seguirá ejecutándose con el usuario que la ha lanzado.
public WindowsImpersonationContext ImpersonateUser( string sUsername, string sDomain, string sPassword) { IntPtr pExistingTokenHandle = new IntPtr(0); IntPtr pDuplicateTokenHandle = new IntPtr(0); pExistingTokenHandle = IntPtr.Zero; pDuplicateTokenHandle = IntPtr.Zero; if (sDomain == "") sDomain = System.Environment.MachineName; try { string sResult = null; const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_INTERACTIVE = 2; bool bImpersonated = LogonUser(sUsername, sDomain, sPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref pExistingTokenHandle); if (false == bImpersonated) { int nErrorCode = Marshal.GetLastWin32Error(); sResult = "LogonUser() failed with error code: " + nErrorCode + "\r\n"; throw new Exception(sResult); } sResult += "Before impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n"; bool bRetVal = DuplicateToken(pExistingTokenHandle, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref pDuplicateTokenHandle); if (false == bRetVal) { int nErrorCode = Marshal.GetLastWin32Error(); CloseHandle(pExistingTokenHandle); sResult += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n"; throw new Exception(sResult); return null; } else { WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle); WindowsImpersonationContext impersonatedUser = newId.Impersonate(); sResult += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n"; this.ImpersonationContext = impersonatedUser; return impersonatedUser; } } catch (Exception ex) { throw ex; } finally { if (pExistingTokenHandle != IntPtr.Zero) CloseHandle(pExistingTokenHandle); if (pDuplicateTokenHandle != IntPtr.Zero) CloseHandle(pDuplicateTokenHandle); } }
Vaya, esto es Impersionante...
Cabe destacar que en la llamada a LogonUser, se pasa un puntero al token de usuario 'pExistingTokenHandle' que posteriormente se usa en la llamada a DuplicateToken. Ésta devuelve el duplicado mediante 'pDuplicateTokenHandle', que posteriormente es usado para crear una nueva identidad y llamar a su método Impersonate, que es realmente la que se encarga de realizar la suplantación con el nuevo token.
(*) El siguiente código no funcionará en plataformas Windows 98 o Windows Me, ya que no poseen la posibilidad de trabajar con tokens de usuarios.
Una vez realizado el trabajo que requería de la suplantación de identidad, para volver al contexto predeterminado basta con invocar al método Undo del objeto WindowsImpersonationContext del siguiente modo:
Security.SecurityContext sec = new Security.SecurityContext(); if (Properties.Settings.Default.SaveResultsUsingImpersonation) { sec.ImpersonateUser( Properties.Settings.Default.ImpersonateUser, Properties.Settings.Default.ImpersonateDomain, Properties.Settings.Default.ImpersonatePwd); appEventLog.WriteEntry( string.Format("Begin Impersonation, Current user as {0}", WindowsIdentity.GetCurrent().Name)); } // //Realizar acciones que requieren suplantación.. // if (Properties.Settings.Default.SaveResultsUsingImpersonation) { sec.ImpersonationContext.Undo(); appEventLog.WriteEntry( string.Format("End Impersonation, Current user as {0}", WindowsIdentity.GetCurrent().Name)); }
Conclusión
Espero que este post haya clarificado cómo realizar este proceso. En el siguiente veremos cómo encapsular toda la aplicación para ejecutarse en forma de servicio. Ah! y antes que me lo diga alguno, ya se que no sería el mejor ejemplo para mostrar un servicio de Windows (porque corriendo con permisos de LOCALSYSTEM no se tiene permiso a la caché del usuario), pero tenía un post al respecto pendiente desde hace tiempo. Así que lo juntaremos todo en un mix o refrito o como queráis llamarlo, para que sirva de ejemplo.
También al final de la serie, postearé un recopilatorio de los pasos que hemos seguido y el código completo del ejemplo, por si alguien está tan loco como para querer probarlo :-P
Ahora, a impersonar todos. Pero si lo hacéis, por favor hacerlo bien... no de este modo:
Saludos desde Andorra
** crossposting desde el blog de Lluís Franco en geeks.ms **
|
-
:-D Y es que esta mañana me he dado cuenta de que en una aplicación ASP.NET que estoy desarrollando me ha aparecido uno de esos feos mensajes de error de Javascript. Para sorpresa mía me informaba de que se ha producido un error en la línea 64.591.517 Vamos a ver: Si llevo un par de meses y medio con la aplicación........ me salen...... a ver......... sobre un millón de líneas al día. No está nada mal, no señor! Si ya sabía yo que era un mondtuo picando código, jejeje... Saludos desde Andorra, bajo el agua.
** crossposting desde el blog de Lluís Franco en geeks.ms **
|
-
:-/ Hola de nuevo, Siento haber tardado más de lo previsto en publicar este post, pero hemos tenido un fallecimiento en la familia este fin de semana y todavía andamos un poco descolocados. De todos modos, siguiendo con el hilo del primer post de esta serie, vamos a ver como leer los valores de los archivos temporales de Internet (imágenes que son almacenadas en caché, cookies, sitios visitados, etc.). Para acceder al contenido de esta caché vamos a usar un par de llamadas al API de windows y vamos a guardar el contenido de estas consultas en un medio persistente para su posterior análisis. El objetivo de todo esto: Nuestro objetivo va a consistir en recoger información de la caché cada X tiempo (supongamos cada hora), y mediante serialización persistirla en ficheros XML en una ubicación remota. Además, para complicarlo un poquito más vamos a hacer que esta ubicación no sea accesible desde el usuario actual, de modo que debamos impersonar otro inicio de sesión que si tenga permisos sobre esta ubicación. Todo esto, lo encapsularemos como un servicio de Windows y crearemos un proyecto de instalación que podremos distribuir en los clientes que deseamos inspeccionar. Una vez terminado este ejercicio habremos mostrado varias cosas como: - Acceder a la caché de Internet Explorer
- Serializar objetos a ficheros XML
- Impersonar la ejecución de nuestra aplicación con otro usuario
- Crear un servicio de Windows que ejecute un proceso cada X tiempo
De todos modos me gustaría dejar clara una cosa, para que nadie me malinterprete: ** El objetivo de todo esto no es ni mucho menos crear una herramienta para espiar a nadie, si no que lo he considerado más bien como un projecto que reune varias cosillas interesantes a explicar, un modo de reunirlas bajo un mismo ejercicio ** Preparando el terreno: El primer paso va a ser declarar una clase que almacene la información de las entradas devueltas por las funciones FindFirstUrlCacheEntry | |
|