ActivitiesCache.db-wal: por qué importa el archivo WAL
Cómo el modo WAL de SQLite esconde la actividad más reciente de Windows Timeline en ActivitiesCache.db-wal, cómo funcionan los checkpoints y cómo analizarlo.
Resumen. ActivitiesCache.db funciona en modo WAL de SQLite: los cambios confirmados van a ActivitiesCache.db-wal y solo llegan al archivo principal en un checkpoint (por defecto, cuando el WAL alcanza 1.000 páginas). En un sistema encendido o usado recientemente, la actividad más reciente suele estar solo en el WAL. Adquiera ambos archivos en el mismo instante, no abra nunca los originales con un cliente SQLite normal y use un parser que reproduzca los frames confirmados e indique qué filas proceden del WAL.
Si solo recuerda una cosa de esta serie, que sea esta: un Windows Timeline sin su WAL es un Timeline que se detiene antes de tiempo. Y «antes de tiempo» suele significar «justo antes del incidente», porque el incidente es reciente.
Cómo funciona el write-ahead logging
El registro write-ahead de SQLite invierte el diario clásico. En lugar de apartar las páginas originales y escribir los cambios en la base, SQLite deja la base intacta y añade las páginas modificadas a un archivo -wal independiente. Una transacción queda confirmada cuando su registro de commit se añade al WAL (sqlite.org/wal.html).
Los lectores buscan cada página primero en el WAL y, si no está, en el archivo de base. Devolver las páginas a la base se llama checkpoint. Por defecto, SQLite lanza uno automáticamente cuando un commit hace que el WAL supere las 1.000 páginas (sqlite.org). kacos2000 hizo la misma observación para esta base y anotó un tamaño de página de 4.096 bytes en los archivos que examinó (kacos2000).
Qué significa esto para Timeline:
| Situación | Dónde están las filas más recientes |
|---|---|
| Usuario activo, WAL por debajo del umbral de checkpoint | Solo en el WAL |
| Checkpoint recién ejecutado | En la base; el WAL puede reiniciarse y reutilizarse |
| Última conexión cerrada limpiamente | Volcadas; el WAL «normalmente» se borra (sqlite.org) |
| Caída, corte de corriente, desconexión | El WAL puede quedar en disco con transacciones confirmadas no volcadas (sqlite.org) |
Una fila de Timeline no se inserta una sola vez. Una sesión de foco (tipo 6) se actualiza mientras continúa: su EndTime y su activeDurationSeconds crecen. Esas actualizaciones también llegan primero al WAL. Así que el WAL afecta no solo a qué filas existen, sino a lo que dicen.
Dentro del archivo WAL
El formato está documentado en la especificación del formato de archivo de SQLite:
| Estructura | Tamaño | Campos relevantes |
|---|---|---|
| Cabecera WAL | 32 bytes | Magic, tamaño de página, número de secuencia de checkpoint, salt-1, salt-2, checksum |
| Cabecera de frame | 24 bytes | Número de página, tamaño de la base tras el commit (distinto de cero solo en frames de commit), salts, checksum acumulado |
| Cuerpo del frame | una página | La nueva versión de esa página de la base |
Un frame solo es válido si sus salts coinciden con la cabecera y su checksum acumulado es correcto. Un frame con el campo «tamaño de la base» distinto de cero es un frame de commit: cierra una transacción. Los frames posteriores al último frame de commit pertenecen a una transacción que nunca se confirmó y deben ignorarse (sqlite.org).
Tras un checkpoint, cuando el siguiente escritor reinicia el WAL, salt-1 se incrementa y salt-2 se genera al azar, lo que invalida los frames antiguos. La especificación añade que el archivo puede truncarse en ese reinicio, pero no es obligatorio (sqlite.org). Ahí está la oportunidad forense: frames de generaciones anteriores pueden seguir ahí, detrás de los actuales.
Por qué un cliente SQLite normal es la herramienta equivocada
Abrir ActivitiesCache.db con una biblioteca SQLite estándar funciona, e incluso lee el WAL por usted si está junto a la base. El problema es lo que ocurre después. Según la documentación de SQLite, la última conexión que se cierra hace un último checkpoint y luego borra el WAL y su archivo de memoria compartida (sqlite.org/wal.html).
Dicho de otro modo, el simple hecho de mirar puede reescribir la base y destruir el WAL, incluidos los frames obsoletos que contenían datos anteriores. Reglas prácticas:
- Calcule el hash de los originales y trabaje sobre copias.
- Si tiene que usar un cliente SQLite, abra una copia, o use los modos de solo lectura / immutable sobre una copia desechable.
- Prefiera un parser que lea los bytes por sí mismo y nunca escriba.
Cómo trata el WAL el Windows Timeline Parser
El Windows Timeline Parser incluye su propio lector SQLite de solo lectura, escrito en Rust y compilado a WebAssembly. No incorpora ningún motor SQLite ni tiene ninguna ruta de código que escriba. Con el WAL:
- Valida el magic de la cabecera WAL, el tamaño de página y el checksum de la cabecera.
- Recorre los frames mientras los salts coinciden y el checksum acumulado se mantiene.
- Superpone sobre la base la última versión confirmada de cada página; los frames de una transacción no confirmada se cuentan y se ignoran.
- Analiza el resultado, después analiza la base sin el WAL y compara fila a fila.
- Marca cada fila como Solo en el WAL (nueva desde el último checkpoint) o Modificada en el WAL (la base tiene una versión anterior).
También se protege de un error clásico: un -wal de otra base de datos. Nada en el formato de archivo vincula un WAL con su base, así que un lector ingenuo aplicaría en silencio un WAL con el mismo tamaño de página. La herramienta comprueba que la base reproducida siga siendo un Timeline plausible y, si no lo es, ignora el WAL con una advertencia.
Estas marcas convierten «el WAL importa» en triaje práctico. Filtre por Solo en el WAL y estará viendo la actividad posterior al último checkpoint, que en un equipo intervenido en caliente suele corresponder a las últimas horas antes de la adquisición.
Lo que el WAL puede contener y nadie le muestra
Tres tipos de datos pueden sobrevivir en el WAL o a su alrededor:
| Datos | Dónde | ¿Los analiza hoy esta herramienta? |
|---|---|---|
| Filas confirmadas no volcadas | Frames actuales del WAL | Sí |
| Transacción no confirmada | Frames tras el último commit | Se cuentan y se ignoran (a propósito) |
| Versiones anteriores de páginas | Frames de una generación de salt anterior | No (se informa, no se recuperan) |
| Filas eliminadas | Freeblocks y páginas de la freelist de la base | No |
La investigación sobre recuperación en SQLite, como el trabajo bring2lite presentado en DFRWS, muestra que es posible recuperar registros eliminados de estas estructuras (Meng y Baier, 2019). La herramienta de carving del portapapeles de kacos2000 afirma recuperar entradas eliminadas tanto de la base como del WAL (kacos2000/WindowsTimeline). Si su caso depende de datos de Timeline eliminados, trabaje sobre una copia con una herramienta de carving y documente el método por separado de la cronología de registros vivos.
Lista de comprobación
-
ActivitiesCache.dbyActivitiesCache.db-waladquiridos en el mismo instante (guía de adquisición). - Hashes de los originales calculados; análisis solo sobre copias.
- El parser indica cuántos frames se confirmaron, cuántos se ignoraron y por qué se detuvo.
- El informe distingue las filas que están solo en el WAL de las ya volcadas.
- Frames obsoletos del WAL y páginas libres preservados para carving si la eliminación forma parte del alcance.
FAQ
¿Qué es ActivitiesCache.db-wal?
Es el registro write-ahead de SQLite de la base de datos de Windows Timeline. Los cambios confirmados se añaden primero ahí y solo se copian a ActivitiesCache.db en un checkpoint, por lo que el WAL suele contener la actividad más reciente.
¿Es seguro abrir ActivitiesCache.db en DB Browser for SQLite?
Solo sobre una copia. Una conexión SQLite normal lee el WAL que está junto a la base y puede volcarlo en el archivo principal y borrarlo al cerrarse la última conexión, lo que altera los archivos.
¿Puede el WAL contener actividad eliminada?
Puede. Los frames antiguos de generaciones anteriores del WAL quedan invalidados por un cambio de salt, pero no necesariamente sobrescritos, así que pueden contener versiones anteriores de las páginas. Recuperarlos requiere herramientas de carving; la mayoría de los parsers los ignoran.