Acceder a la caché de Internet Explorer (IV)

windowsservice

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.

CreateWindowsService

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á 😛

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:

AddInstallerClass

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…”

InstallerComponents

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.

CreateSetupProject

Agregamos la salida del proyecto anterior al nuevo proyecto de instalación:

AddProjectOutput

AddProjectOutputDialog

Ahora mostraremos la ventana de “custom actions” para agregar como acción al instalador la salida de nuestro proyecto de servicio de Windows.

AddCustomAction

AddCustomActionDialog

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.

Setup

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.

AdminServices

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,

Por fin es viernes (30/05/2008)

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 puta 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  puta 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):

Imagen1

Imagen2

Imagen3

Imagen4


Fotos curiosas (By Evas):

Imagen1

Imagen2

Imagen3

Imagen4

Imagen5

Imagen6

Imagen7

Imagen8

Imagen9

Imagen11

Imagen12

Imagen13

Imagen14

Imagen15

Imagen17

Imagen18

Imagen19

Imagen20

Imagen23

Buen fin de semana a todos!
:-)

Debian y sus juguetes, Dilbert y los aleatorios

:-)
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:

debian1

debian2

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,

Acceder a la caché de Internet Explorer (III)

RunAs

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: \RootDFSLogsIECacheQuery 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 + "rn";
            throw new Exception(sResult);
        }
 
        sResult += "Before impersonation: " + WindowsIdentity.GetCurrent().Name + "rn";
        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 + "rn";
            throw new Exception(sResult);
            return null;
        }
        else
        {
            WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
            WindowsImpersonationContext impersonatedUser = newId.Impersonate();
            sResult += "After impersonation: " + 
                WindowsIdentity.GetCurrent().Name + "rn";
            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 😛

Ahora, a impersonar todos. Pero si lo hacéis, por favor hacerlo bien… no de este modo:

blas

Saludos desde Andorra

Creo que cobro poco para lo que trabajo…

😀
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

err_javascript

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.

Acceder a la caché de Internet Explorer (II)

ie

:-/
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 y FindNextUrlCacheEntry.

[Serializable]
public class IECacheEntry
{
    public string url;
    public string size;
    public string lastAccessTime;
    public string lastModifiedTime;
    public string expireTime;
    public string filename;
 
    public IECacheEntry()
    {
    }
 
    public IECacheEntry(
        string _url,
        string _size,
        string _lastAccessTime,
        string _lastModifiedTime,
        string _expireTime, 
        string _filename)
    {
        url = _url;
        size = _size;
        lastAccessTime = _lastAccessTime;
        lastModifiedTime = _lastModifiedTime;
        expireTime = _expireTime;
        filename = _filename;
    }
}

Esta clase la vamos a usar conjuntamente con esta clase, que identifica la consulta y recoge los datos que se guardarán cada vez que se ejecute la consulta a la caché. Esta clase también la decoraremos con el atributo serializable para posteriormante poder persistirla a un fichero XML:

[Serializable]
public class IECacheQuery
{
    public IECacheQuery() { }
 
    public string hostname = string.Empty;
    public string username = string.Empty;
    public DateTime date = DateTime.Now;
    public List<IECacheEntry> results;
}

Empezando a ensamblar las piezas:

EL corazón de este ejercicio consiste en esta pequeña función “getResults”, que a partir de una cadena que contiene el patrón de búsqueda devuelve todas las instancias de la caché que coinciden con ésta. Este patrón de búsqueda puede contener el valor “cookies”, “visited”, una URL en concreto o bien NULL (que devuelve todos los valores de la caché). A continuación estos resultados se almacenan en una lista genérica que se devuelve como valor de retorno de la función.

Nota 1: Este código se ha realizado con compatibilidad con estaciones Windows 2000 que admiten versiones del Framework hasta la 2.0, de modo lamentablemente que no se han podido utilizar las bondades del Framework 3.5 (en particular he echado mucho de menos a LINQ to objects).

Nota 2 : En el código pueden observarse llamadas a funciones que no han sido descritas en el artículo (como el método ToStringFromFileTime en el código siguiente). Sin embargo estas funciones si pueden encontrarse en el código completo del ejemplo que se podrá descargar al final de la serie.

private List<IECacheEntry> getResults(string patternText)
{
    List<IECacheEntry> listresults = new List<IECacheEntry>();
    List<IECacheAPI.INTERNET_CACHE_ENTRY_INFO> results =
        IECacheAPI.FindUrlCacheEntries(patternText);
 
    foreach (IECacheAPI.INTERNET_CACHE_ENTRY_INFO result in results)
    {
        IECacheEntry entry = new IECacheEntry();
 
        listresults.Add(
            new IECacheEntry
                (
                    result.lpszSourceUrlName,
                    ((((Int64)result.dwSizeHigh) << 32) + result.dwSizeLow).ToString(),
                    Win32API.ToStringFromFileTime(result.LastAccessTime),
                    Win32API.ToStringFromFileTime(result.LastModifiedTime),
                    Win32API.ToStringFromFileTime(result.ExpireTime),
                    result.lpszLocalFileName
                )
            );
    }
    return listresults;
}

Bien, de momento ya tenemos la función encargada de devolver los resultados de la caché, ahora ¿que hacemos con ellos?


Persistir objetos, que facil es con .NET!

Serializar objetos a XML es muy pero que muy sencillo. De hecho en nuestro caso ni siquiera vamos a aplicar un formateador SOAP ni binario, y tampoco vamos a aplicar atributos para serializar miembros de clases como nodos o atributos. En este caso vamos a usar la serialización del siguiente modo:

public sealed class Manager
    {
        public static void Save(IECacheQuery query, string pfilename)
        {
            try
            {
                XmlSerializer xmlDoc = new XmlSerializer(typeof(IECacheQuery));
                StreamWriter sw = new StreamWriter(pfilename);
                xmlDoc.Serialize(sw, query);
                sw.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }

A esta función se le pasan dos argumentos, el objeto que deseamos persistir i el nombre del archivo XML en el que se quiere persistir. En ella se crea un objeto XmlSerializer preparado para el tipo de objeto IECacheQuery, a continuación se crea un Stream al fichero XML para serializar el objeto. A continuación se cierra el Stream y se acabó lo que se daba.

Qué? Facil verdad? Esta es una de las maravillas que me cautivaron cuando empecé con .NET (y es que con anterioridad había que hacer maravillas para poder realizar este tipo de cosas).

xml


Próximos pasos:

De momento ya tenemos la función que devuelve resultados de la caché y tenemos un modo de persistir un objeto en un fichero XML. A continuación, después de la publicidad (o más bien en el próximo post), mostraremos como Impersonar nuestra aplicación para que se ejecute como otro usuario, a fin de tener acceso a una supuesta ubicación de red en la que gusrdar los ficheros de resultados.

Hasta el próximo post.

Acceder a la caché de Internet Explorer (I)

Internet2

En algunos casos puede ser interesante acceder a la caché de navegación de Internet Explorer. Por ejemplo, para poder guardar un registro de los sitios que visitan los usuarios de nuestra organización sin tener que montar un ISA server o similar.

Para ello, y como todavía no he encontrado nada implementado en el propio Framework, vamos a hacer uso de algunos elementos del API de Windows. Ese gran amigo que todavía nos sigue sacando las castañas del fuego algunas veces… 😀


El punto de partida:

Cuando accedemos a la carpeta de archivos temporales de Internet, podemos eliminar el contenido o cambiar la ubicación de la carpeta que utilizamos para almacenar este información:

iecache_config

Incluso si pulsamos la opción de ver archivos, el explorador nos muestra una vista similar a esto:

iecache_files

Sin embargo, esta vista se genera automáticamente a partir del contenido de un fichero llamado Index.dat, que se encuentra en la carpeta “Archivos temporales de InternetContent.IEX”, siendo X la versión de IE del sistema. De modo que, para poder acceder a la información contenida en este fichero no nos queda más remedio que utilizar algunas funciones del API de Windows.


Presentando a los protagonistas:

Como os decía anteriormente, a falta de poder usar código manejado vamos a utilizar los ladrillos del propio S.O.

Esta estructura representa un elemento dentro de la caché de Internet.

[StructLayout(LayoutKind.Sequential)]
public struct INTERNET_CACHE_ENTRY_INFO
{
    public UInt32 dwStructSize;
    public string lpszSourceUrlName;
    public string lpszLocalFileName;
    public UInt32 CacheEntryType;
    public UInt32 dwUseCount;
    public UInt32 dwHitRate;
    public UInt32 dwSizeLow;
    public UInt32 dwSizeHigh;
    public Win32API.FILETIME LastModifiedTime;
    public Win32API.FILETIME ExpireTime;
    public Win32API.FILETIME LastAccessTime;
    public Win32API.FILETIME LastSyncTime;
    public IntPtr lpHeaderInfo;
    public UInt32 dwHeaderInfoSize;
    public string lpszFileExtension;
    public UInt32 dwExemptDelta;
}; 
 
Más información acerca de esta estructura en:
 
INTERNET_CACHE_ENTRY_INFO Structure
 
También nos basaremos en estas dos funciones, encargadas de devolver un puntero a un elemento del tipo INTERNET_CACHE_ENTRY_INFO dentro de la caché. La primera de ellas devuelve el primer valor encontrado en la caché, a partir de un puntero a una cadena que representa el patrón a buscar. Mientras que la segunda recibe el manejador devuelto por la primera llamada a FindFirstUrlCacheEntry.
 
[DllImport("wininet.dll", SetLastError=true)]
private static extern IntPtr FindFirstUrlCacheEntry(
  string lpszUrlSearchPattern, IntPtr lpFirstCacheEntryInfo, 
  out UInt32 lpdwFirstCacheEntryInfoBufferSize );
 
[DllImport("wininet.dll", SetLastError=true)]
private static extern long FindNextUrlCacheEntry(
  IntPtr hEnumHandle, IntPtr lpNextCacheEntryInfo, 
  out UInt32 lpdwNextCacheEntryInfoBufferSize );

Podéis encontrar más información acerca de ellas en:

FindNextUrlCacheEntry Function
http://msdn.microsoft.com/en-us/library/aa384049(VS.85).aspx

FindFirstUrlCacheEntry Function
http://msdn.microsoft.com/en-us/library/aa384026(VS.85).aspx


Y ahora que?

Pues ahora nada, que me he quedado sin tiempo… hasta aquí este primer post. Mañana continuaremos con la construcción de un pequeño proyecto de ejemplo que utiliza estas funciones para acceder a la caché de Internet y muestre el contenido en pantalla. Y si da tiempo, sería interesante crear una aplicación o servicio que vaya monitorizando y guardando este contenido en algún medio persistente (tal vez XML o una base de datos).

Hasta mañana!