miércoles, 27 de enero de 2010

Análisis de un caso ¿real?, #3

Andaba reorganizando el disco duro de mi portátil, que como a base de discos he aprendido, el tamaño que ocupa el material que pretendo almacenar siempre es superior a la partición destinada a tal efecto. En esas me hallaba cuando me encontré un directorio olvidado conteniendo volcados de memoria; cansado como estaba de andar buceando entre papers, manuales, herramientas y demás ficheros imprescindibles el resto era fácilmente previsible.

Después de un par de minutos activando neuronas hace tiempo dadas por muertas conseguí recordar el skydrive de hogfly. A modo de resumen, un proyecto encaminado a potenciar el análisis offline de la memoria de sistemas Windows infectados por diferentes tipos de malware. Previo upload a un sistema virtual Ubuntu Linux 9.10 y accediendo a un subdirectorio al azar, es decir, ordenando por nombre y abriendo el último de ellos, me encontré con el siguiente contenido:

# ls -l

total 89612
-rw-r--r-- 1 root root 55 2009-03-26 03:05 about.txt
-rw-r--r-- 1 root root 41943040 2009-03-26 03:05 exemplar18.tar.gz.001
-rw-r--r-- 1 root root 41943040 2009-03-26 03:05 exemplar18.tar.gz.002
-rw-r--r-- 1 root root 7589925 2009-03-26 03:05 exemplar18.tar.gz.003
-rw-r--r-- 1 root root 415 2009-03-26 03:13 hashes.txt
-rw-r--r-- 1 root root 164952 2009-03-26 03:05 virustotal_wmpupdate.pdf

Como soy de natural obsesivo/impulsivo ya no podía pensar en otra cosa que no fuera analizar el volcado, así que lo primero reensamblar el tarball y extraer su contenido:
# cat exemplar18.tar.gz.00* | tar xvz

exemplar18.vmem

Y ahora, teniendo en cuenta que el contenido del fichero hashes.txt es como sigue:
# cat hashes.txt

MD5 (about.txt) = 931e688d09ac7455e7991f6764988a58
MD5 (exemplar18.tar.gz.001) = 9094563564f8832234954719a66eb10b
MD5 (exemplar18.tar.gz.002) = 4388549992a4540c553a5fc2fc0fa093
MD5 (exemplar18.tar.gz.003) = 0f9fdd290ea4e9aaf03d85dd5677f0f6
MD5 (exemplar18.vmem) = 040f75c4eddea4504b93682821d747a2
MD5 (hashes.txt) = d41d8cd98f00b204e9800998ecf8427e
MD5 (virustotal_wmpupdate.pdf) = c955c2336fd73333bc4771786662e66d

me aprovecharé de la magia del comando sed para formatearlo adecuadamente, canalizarlo hacia md5sum y obtener el resultado de la comprobación de los ficheros en una sola línea de comandos:
# sed -n 's/.* (\(.[^)]*\)) = \(.*\)/\2  \1/p' hashes.txt | md5sum -c

about.txt: La suma coincide
exemplar18.tar.gz.001: La suma coincide
exemplar18.tar.gz.002: La suma coincide
exemplar18.tar.gz.003: La suma coincide
exemplar18.vmem: La suma coincide
hashes.txt: La suma no coincide
virustotal_wmpupdate.pdf: La suma coincide
md5sum: ADVERTENCIA: 1 de 7 sumas de comprobacion NO coinciden

Parece que el fichero de hashes no contiene una suma válida para sí mismo, pero no es que sea muy relevante, sobre todo teniendo en cuenta que esto no es una investigación oficial sino solo un pasatiempo, raro, pero un pasatiempo. Ahora que ya tengo el volcado lo analizaré utilizando el framework Volatility, en concreto la última versión estable descargada mediante subversion. El comando necesario para ello:
# svn checkout http://volatility.googlecode.com/svn/trunk/ volatility-read-only

... y al volver la vista atras

Resulta curiosa la relevancia que tiene actualmente el proceso de adquisición y análisis de la memoria de los sistemas implicados en una investigación forense o de respuesta ante incidentes. Sobre todo si tenemos en cuenta que hasta hace aproximadamente 5 años lo más que ofrecía era un listado de cadenas sin un contexto definido; vamos, casi como buscar una aguja en un pajar.

Sin embargo todo cambió con el desafío publicado en la edición del 2005 del DFRWS, para el que se propuso el análisis de un volcado de la memoria de un sistema Windows 2000 comprometido. Allí dos soluciones, con sus respectivas herramientas, marcaron el comienzo de una nueva era.

La primera de la mano de Chris Betz y su memparser, cuyo código fuente está disponible para descarga desde sourceforge y que puede compilarse facilmente, por ejemplo, con la versión Express Edition del IDE MS Visual C++ 2008.

La segunda, cortesía de George M. Garner Jr. y Robert-Jan Mora, kntlist, cuyo desarrollo sigue todavía activo siendo gratuita aunque sólo disponible para personal acreditado en el sector. Ya decía yo que me sonaba el primero de los nombres, y es que resulta que también es el autor de las FAU.

Desde entonces han surgido diferentes alternativas, algunas open source como volatility, otras de código fuente cerrado pero gratuitas como Memoryze o WMFT, y otras comerciales y de código fuente cerrado como HBGary Responder Pro. Y sin olvidar tampoco las encargadas del proceso de volcado como son mdd de Mantech (que creo que ha pasado a estar discontinuada), windd de Mathieu Suiche, fastdump de HBGary (con versión community y versión pro), etc. Vamos, que por falta de opciones no será. La diferencia básica: el número de sistemas operativos y arquitecturas soportadas.

Y todo empezó por el principio

Decidida, descargada y preparada la herramienta, copiado el volcado en el directorio adecuado, dispuesta la mesa y autoconvencido el ejecutor (usease yo mismo) seguiré con el "sudoku" :-) El primer paso lógico será comprobar que volatility es capaz de entender las estructuras en memoria para la máquina virtual de la que proviene el fichero:
# python volatility ident -f exemplar18.vmem

Image Name: exemplar18.vmem
Image Type: Service Pack 2
VM Type: pae
DTB: 0x7d0000
Datetime: Wed Jan 07 20:57:25 2009

La fecha se muestra en el formato del sistema local desde el que se realiza el análisis, o sea mi Linux, aunque no creo que resulte muy complicado obtener la fecha en formato UTC.

Se me ocurre ahora obtener la lista de procesos en ejecución cuando se capturó el snashot. Una curiosidad que he aprendido hace poco es la particularidad en cuanto al funcionamiento interno de los diferentes comandos con objetivos similares. Me explico; para la lista de procesos, por ejemplo, dispongo de tres opciones, cuatro en realidad si cuento con el plugin pstree de Scudette.

De forma similar a lo que hace modules para los drivers, connections para las conexiones TCP/IP y sockets para los sockets abiertos (creo sobra recordar que utilizando diferentes estructuras de datos), pslist obtiene el listado recorriendo la doble lista enlazada ActiveProcessLinks, mantenida por el kernel en memoria para las estructuras EPROCESS. Sin embargo, este método aunque rápido y efectivo, es vulnerable a la técnica DKOM.

También tenemos modscan/modscan2 para los drivers, connscan/connscan2 para las conexiones TCP/IP y sockscan/sockscan2 para los sockets. La diferencia entre las dos versiones de los comandos radica en el número de comprobaciones que se ejecutan antes de considerar el objeto hallado como válido, por lo que la versión scan2 es mucho más rápida. Y la diferencia con sus respectivas contrapartidas es la estructura en la que se fundamenta la búsqueda, POOL_HEADER en este caso. Creo recordar que el "truco" venía explicado en el documento "Pool Allocations as an Information Source in Windows Memory Forensics".

Por último quedan psscan/psscan2 para los procesos y thrdscan/thrdscan2 para los threads que los primeros contienen, donde la versión simple utiliza la estructura DISPATCHER_HEADER (aquí la teoría) y, como en los anteriores, la versión "avanzada" se basa en la estructura POOL_HEADER siendo algo más rápida. Como automuestra un benchmark, aunque no es que sea el más fiable de la historia:
# time python volatility psscan -f exemplar18.vmem

...
real 2m16.191s
user 2m13.112s
sys 0m1.580s
# time python volatility psscan2 -f exemplar18.vmem
...
real 0m4.680s
user 0m0.948s
sys 0m1.008s

En resumen, que existen dos técnicas básicas para el análisis de la memoria y una que combina las otras dos:
  • List walking:
    Encontramos la cabeza y recorremos la lista enlazada mantenida por el kernel para los diferentes objetos.
  • Scanning:
    Conocidas una serie de firmas para los objetos buscados realizamos un análisis completo del espacio de direcciones en crudo (sin confiar en la API de Windows.
  • Cross-view:
    Básicamente, comparamos los resultados obtenidos mediante los dos métodos anteriores para encontrar las discrepancias.

Si es que de vez en cuando no hay nada mejor que pararse a pensar en voz alta para que todo empiece a encajar; máxime con material tan valioso desperdigado por la red como la presentación de Andreas Schuster sobre Volatility.

Ahora es cuando la cosa se pone interesante: el listado de procesos mediante cualquiera de los métodos anteriores no muestra ningún indicio sobre una posible infección. Probaré al menos a obtener una representación gráfica:
# python volatility psscan2 -d -f exemplar18.vmem | dot -Tpng -o procesos.png

Creo que la idea original la implementó Andreas Schuster en su herramienta ptfinder (modificada para soportar nuevos sistemas operativos por Csaba Barta), y desde luego no se puede pedir nada más visual:


Como bien dijo alguien, cuyo nombre no recuerdo, "la ausencia de pruebas ya es en sí misma una prueba".

Y seguimos para bingo

Como la lista de procesos no es que haya sido de mucha ayuda probaré con las conexiones TCP/IP establecidas desde/hacia el sistema objetivo y además compararé la salida de dos de los comandos disponibles:

# python volatility connections -f exemplar18.vmem

Local Address Remote Address Pid
192.168.30.128:1052 94.247.2.107:80 4
# python volatility connscan2 -f exemplar18.vmem
Local Address Remote Address Pid
------------------------- ------------------------- ------
192.168.30.128:1057 94.247.2.107:80 888
192.168.30.128:1052 94.247.2.107:80 4
192.168.30.128:1058 67.210.14.81:80 888
192.168.30.128:1059 192.168.30.254:80 1980

La diferencia salta a la vista y esto explica la tozudez de los desarrolladores por incluir varias versiones para comandos con objetivos similares. Conociendo el formato de la salida obtenida mediante pslist seguiré con las "Linux power tools", awk en esta ocasión, para obtener los nombres de los procesos cuyos PIDs aparecen en el último listado de conexiones:
# python volatility pslist -f exemplar18.vmem 

| awk '$2 ~ /^(4|888|1980)$/ {print $2 "\t" $1}'
4 System
888 svchost.exe
1980 spoolsv.exe

Sólo por curiosidad, y para conocer la ubicación aproximada de las IPs anteriores, probaré la herramienta GeoEdge liberada recientemente por NII Consulting. Se trata de un script en python, aunque también está disponible un ejecutable para Windows, que consulta las bases de datos de Maxmind y geoiptool para obtener la información:
# python geoedge.py 94.247.2.107

*************************************
*Geoedge v0.1 *
*Coded by Laramies *
*cmartorella@edge-security.com *
*************************************
 
Searching in Maxmind....
 
Information for 94.247.2.107 by Maxmind
===========================================
 
IP/Host: 94.247.2.107
Country: NL,Netherlands
City: ,
Coordinates: 52.5000,5.7500
Provider: ,
 
 
Searching in Geoiptool....
 
Information by Geoiptool
============================
 
Country: Latvia ,LV (LVA)
City: ,
Coordinates: 57,25
# python geoedge.py 67.210.14.81
*************************************
*Geoedge v0.1 *
*Coded by Laramies *
*cmartorella@edge-security.com *
*************************************
 
Searching in Maxmind....
 
Information for 67.210.14.81 by Maxmind
===========================================
 
IP/Host: 67.210.14.81
Country: US,United States
City: New York,Albany
Coordinates: 42.6849,-73.8318
Provider: Internet Path,Internet Path
 
 
Searching in Geoiptool....
 
Information by Geoiptool
============================
 
Country: United States ,US (USA)
City: New York,Albany
Coordinates: 42.6849,-73.8318

Independientemente de los datos de localización de las direcciones IP, al menos ya tengo por donde seguir tirando del hilo...

Análisis de svchost

Voy a empezar por el proceso con PID 888 viendo los ficheros que tenía abiertos:
# python volatility files -p 888 -f exemplar18.vmem

Pid: 888
File \WINDOWS\system32
File \WINDOWS\WinSxS\...
File \net\NtControlPipe2
File \Endpoint
File \Endpoint
File \Winsock2\CatalogChangeListener-378-0
File \Endpoint
File \Endpoint
File \Endpoint
File \Documents and Settings\NetworkService\...\index.dat
File \Win32Pipes.00000378.00000001
File \epmapper
File \epmapper
File \WINDOWS\WinSxS\...
File \Win32Pipes.00000378.00000001
File \Documents and Settings\NetworkService\...\index.dat
File \Documents and Settings\NetworkService\...\index.dat
File \WINDOWS\WinSxS\...
File \WINDOWS\WinSxS\...

He tenido que recortar la salida sustituyendo las cadenas demasiado largas, pero aún así no es difícil advertir que no he obtenido nada destacable; eso si exceptuamos que acabo de descubrir que Windows XP almacena 2 perfiles "superhidden" para las cuentas NetworkService y LocalService, las cuales son utilizadas como contexto para aumentar la seguridad en la ejecución de determinados servicios. Nunca te acostarás sin saber una cosa más.

Revisaré la lista de librerías cargadas por el proceso (el resultado completo aquí):
# python volatility dlllist -p 888 -f exemplar18.vmem

svchost.exe pid: 888
Command line : C:\WINDOWS\system32\svchost -k rpcss
Service Pack 2
 
Base Size Path
0x1000000 0x6000 C:\WINDOWS\system32\svchost.exe
0x7c900000 0xb0000 C:\WINDOWS\system32\ntdll.dll
...

Como me estaba temiendo el listado anterior no muestra, al menos a priori, ningún fichero sospechoso. Pero se me está ocurriendo algo, y está relacionado con el árbol VAD, el paper de Brendan Dolan-Gavitt, "The VAD tree: A process-eye view of physical memory", y las herramientas que desarrolló en su momento, las VadTools, y que ahora también están integradas en Volatility.

De forma muy resumida: el árbol VAD es una estructura de datos utilizada por el gestor de memoria virtual integrado en el kernel de Windows para registrar regiones reservadas por un proceso mediante una llamada a VirtualAlloc. Hasta que las direcciones almacenadas en el árbol VAD no son directamente referenciadas por el proceso que realizó la reserva no se crearán las entradas adecuadas en la page table y la page directory. Si cualquiera de las regiones de memoria registradas en el árbol VAD se ha utilizado para almacenar un fichero accedido por un proceso (una DLL por ejemplo) se podrá extraer su nombre y ubicación original.

Mediante el comando vadinfo obtendré bloques de texto con información para cada uno de los nodos del árbol VAD de todos los procesos en ejecución, a no ser que especifique un PID concreto. Cuando un nodo del árbol VAD contenga una referencia a un objeto de tipo fichero la información mostrada será:
VAD node @8170a1f8 Start 76fc0000 End 76fc5fff Tag Vad

Flags: ImageMap
Commit Charge: 2 Protection: 7
ControlArea @815dc9f8 Segment e1044f60
Dereference list: Flink 00000000, Blink 00000000
NumberOfSectionReferences: 0 NumberOfPfnReferences: 5
NumberOfMappedViews: 3 NumberOfSubsections: 5
FlushInProgressCount: 0 NumberOfUserReferences: 3
Flags: Accessed, HadUserReference, Image, File
FileObject @81406028 (01806028), Name: \WINDOWS\system32\rasadhlp.dll
WaitingForDeletion Event: 00000000
ModifiedWriteCount: 0 NumberOfSystemCacheViews: 0
First prototype PTE: e1044fa0 Last contiguous PTE: fffffffc
Flags2: Inherit
File offset: 00000000

Sabiendo esto, el siguiente comando me mostrará el listado de los ficheros mapeados en alguna de las regiones que componen el árbol VAD para el proceso (la salida completa aquí):
# python volatility vadinfo -p 888 -f exemplar18.vmem | grep Name

FileObject @816532a8 (01a532a8), Name: \WINDOWS\Registration\R000000000007.clb
...

Compararé las discrepancias entre ambos resultados. Como las salidas a comparar son algo extensas mejor me curro un script de bash que ejecute el proceso por mí:
# ./crossview.sh

Usage: ./crossview.sh PID DMPFILE
# ./crossview.sh 888 exemplar18.vmem
\WINDOWS\Registration\R000000000007.clb
\WINDOWS\system32\ctype.nls
\WINDOWS\system32\gaopdxtmsnsftaavppfgmkbshkvxtlvnrjypjq.dll
\WINDOWS\system32\locale.nls
\WINDOWS\system32\sortkey.nls
\WINDOWS\system32\sorttbls.nls
\WINDOWS\system32\unicode.nls

Ahora sí que acabo de encontrar algo. La librería gaopdxtmsnsftaavppfgmkbshkvxtlvnrjypjq.dll no aparece en la salida del comando dlllist pero sin embargo sí aparece mapeada en la memoria del proceso. Puedo comprobar este punto de forma definitiva utilizando el plugin ldr_modules desarrollado por Michael Hale Ligh. Éste plugin compara el contenido de las tres listas doblemente enlazadas para las librerías cargadas, y que se localizan en el PEB del proceso en cuestión, con las librerías que aparecen mapeadas en las regiones de memoria contempladas por el árbol VAD del proceso, mostrando las discrepencias en el caso de haberlas:
# python volatility ldr_modules -p 888 -f exemplar18.vmem

Pid Name PEB nLoad nMem nInit nMapped
378 svchost.exe 0x7ffd8000 49 49 48 50

Parece que, efectivamente, existen diferencias las cuales puedo confirmar utilizando el flag verbose para el comando (he recortado la salida para mostrar únicamente los datos que me interesan, el resultado completo aquí):
# python volatility ldr_modules -p 888 -v -f exemplar18.vmem

Pid Name PEB nLoad nMem nInit nMapped
378 svchost.exe 0x7ffd8000 49 49 48 50
 
InLoadOrderModuleList
No. Map? Offset Base Size Path
...
InMemoryOrderModuleList
No. Map? Offset Base Size Path
...
InInitializationOrderModuleList
No. Map? Offset Base Size Path
...
Mapped Files
No. Load? Mem? Init? 0xBase Name
...
[ 15] [ ] [ ] [ ] 0x10000000 \...\gaopdxtmsnsftaavppfgmkbshkvxtlvnrjypjq.dll

Si repaso la información del nodo del árbol VAD en el que aparece mapeada esta librería (de nuevo utilizando la magia de la shell y los comandos de linux) puedo extraer la dirección inferior y superior para la región de memoria que la alberga:
# python volatility vadinfo -p 888 -f exemplar18.vmem 

| sed -e '/./{H;$!d;}' -e 'x;/gao[a-z]*.dll/!d;'
 
VAD node @815e3368 Start 10000000 End 10006fff Tag Vad
Flags: ImageMap
Commit Charge: 7 Protection: 7
ControlArea @8162e220 Segment e11a6a40
Dereference list: Flink 00000000, Blink 00000000
NumberOfSectionReferences: 0 NumberOfPfnReferences: 4
NumberOfMappedViews: 1 NumberOfSubsections: 5
FlushInProgressCount: 0 NumberOfUserReferences: 1
Flags: HadUserReference, Image, File
FileObject @815e33b8 (019e33b8), Name: \...\gaopdxtmsnsftaavppfgmkbshkvxtlvnrjypjq.dll
WaitingForDeletion Event: 00000000
ModifiedWriteCount: 0 NumberOfSystemCacheViews: 0
First prototype PTE: e11a6a80 Last contiguous PTE: fffffffc
Flags2: Inherit
File offset: 00000000

Si ahora vuelco todos los nodos del árbol VAD con las diferentes regiones mantenidas en memoria para el proceso y busco la que contiene el mapeo anterior podré saber algo más del "fichero":
# python volatility vaddump -p 888 -d dirofdump -f exemplar18.vmem

************************************************************************
Pid: 888
# mv dirofdump/*.10000000-10006fff.dmp dirofdump/gaopdx_.dll
# file dirofdump/gaopdx_.dll
dirofdump/gaopdx_.dll: PE32 executable for MS Windows (DLL) (GUI) Intel 80386 32-bit

Veré ahora lo que virustotal tiene que decir al respecto. Si subo el fichero anterior y repaso el análisis obtenido parece que la librería en cuestión muy beneficiosa no es que parezca, máxime teniendo en cuenta que seguramente no se corresponda al 100% con el contenido de la original.

Análisis de spoolsv

Le toca al proceso con PID 1980 que también aparece en el listado de conexiones TCP/IP. Básicamente repetiré los pasos ejecutados para el análisis del proceso svchost, así que lo primero el listado de ficheros abiertos:
# python volatility files -p 1980 -f exemplar18.vmem

Pid: 1980
File \WINDOWS\system32
File \WINDOWS\WinSxS\...
File \net\NtControlPipe10
File \spoolss
File \spoolss
File \WINDOWS\WinSxS\...
File \Documents and Settings\LocalService\...\index.dat
File \Documents and Settings\LocalService\Cookies\index.dat
File \Documents and Settings\LocalService\...\index.dat
File \WINDOWS\WinSxS\...
File \WINDOWS\WinSxS\...
File \ROUTER
File \ROUTER

Como antes, nada extraño. Veamos la lista obtenida mediante dlllist (resultado completo aquí):
# python volatility dlllist -p 1980 -f exemplar18.vmem

spoolsv.exe pid: 1980
Command line : C:\WINDOWS\system32\spoolsv.exe
Service Pack 2
 
Base Size Path
0x1000000 0x10000 C:\WINDOWS\system32\spoolsv.exe
0x7c900000 0xb0000 C:\WINDOWS\system32\ntdll.dll
...

Y ahora la lista de ficheros mapeados en los nodos del árbol VAD para el proceso (resultado completo aquí):
# python volatility vadinfo -p 1980 -f exemplar18.vmem | grep Name

FileObject @815fae00 (019fae00), Name: \WINDOWS\system32\sortkey.nls
...

Si comparo la salida de ambos comandos para observar las discrepancias obtengo:
# ./crossview.sh 1980 exemplar18.vmem

\DOCUME~1\foo\LOCALS~1\Temp\tmp5A.tmp
\DOCUME~1\foo\LOCALS~1\Temp\tmp5B.tmp
\WINDOWS\Registration\R000000000007.clb
\WINDOWS\system32\ctype.nls
\WINDOWS\system32\locale.nls
\WINDOWS\system32\sortkey.nls
\WINDOWS\system32\sorttbls.nls
\WINDOWS\system32\unicode.nls

El fichero tempo-447187.tmp no aparece en la lista anterior, dado que el script únicamente muestra las discrepancias y éste aparece en ambos listados; pero como lo veo bastante sospechoso lo incluiré también en los análisis posteriores. Ahora volcaré los nodos del árbol VAD para el proceso y analizaré los ficheros sospechosos:
# python volatility vaddump -p 1980 -d dirofdump -f exemplar18.vmem

************************************************************************
Pid: 1980

Primero el fichero tempo-447187.tmp y su análisis en virustotal:
# python volatility vadinfo -p 1980 -f exemplar18.vmem 

| sed -e '/./{H;$!d;}' -e 'x;/tempo-447187.tmp/!d;'
 
VAD node @81730120 Start 002a0000 End 002affff Tag Vad
Flags: ImageMap
Commit Charge: 14 Protection: 7
ControlArea @81479488 Segment e173d510
Dereference list: Flink 00000000, Blink 00000000
NumberOfSectionReferences: 0 NumberOfPfnReferences: 15
NumberOfMappedViews: 1 NumberOfSubsections: 6
FlushInProgressCount: 0 NumberOfUserReferences: 1
Flags: HadUserReference, Image, File
FileObject @812cea90 (016cea90), Name: \WINDOWS\Temp\tempo-447187.tmp
WaitingForDeletion Event: 00000000
ModifiedWriteCount: 0 NumberOfSystemCacheViews: 0
First prototype PTE: e173d550 Last contiguous PTE: fffffffc
Flags2: Inherit
File offset: 00000000
 
# mv dirofdump/*.002a0000-002affff.dmp dirofdump/tempo-447187.tmp
# file dirofdump/tempo-447187.tmp
dirofdump/tempo-447187.tmp: PE32 executable for MS Windows (DLL) (GUI) Intel 80386 32-bit

Ahora el fichero tmp5A.tmp y su análisis en virustotal:
# python volatility vadinfo -p 1980 -f exemplar18.vmem 

| sed -e '/./{H;$!d;}' -e 'x;/tmp5A.tmp/!d;'
 
VAD node @81637f18 Start 10000000 End 1000ffff Tag Vad
Flags: ImageMap
Commit Charge: 25 Protection: 7
ControlArea @81693bb0 Segment e1968d50
Dereference list: Flink 00000000, Blink 00000000
NumberOfSectionReferences: 0 NumberOfPfnReferences: 8
NumberOfMappedViews: 1 NumberOfSubsections: 6
FlushInProgressCount: 0 NumberOfUserReferences: 1
Flags: HadUserReference, Image, FloppyMedia, File
FileObject @813be898 (017be898), Name: \DOCUME~1\foo\LOCALS~1\Temp\tmp5A.tmp
WaitingForDeletion Event: 00000000
ModifiedWriteCount: 0 NumberOfSystemCacheViews: 0
First prototype PTE: e1968d90 Last contiguous PTE: fffffffc
Flags2: Inherit
File offset: 00000000
 
# mv dirofdump/*.10000000-1000ffff.dmp dirofdump/tmp5A.tmp
# file dirofdump/tmp5A.tmp
dirofdump/tmp5A.tmp: PE32 executable for MS Windows (DLL) (GUI) Intel 80386 32-bit

Por último el fichero tmp5B.tmp y su análisis en virustotal:
# python volatility vadinfo -p 1980 -f exemplar18.vmem 

| sed -e '/./{H;$!d;}' -e 'x;/tmp5B.tmp/!d;'
 
VAD node @81637380 Start 77c10000 End 77c67fff Tag Vad
Flags: ImageMap
Commit Charge: 94 Protection: 7
ControlArea @81485328 Segment e14743c0
Dereference list: Flink 00000000, Blink 00000000
NumberOfSectionReferences: 0 NumberOfPfnReferences: 83
NumberOfMappedViews: 1 NumberOfSubsections: 5
FlushInProgressCount: 0 NumberOfUserReferences: 1
Flags: HadUserReference, Image, FloppyMedia, File
FileObject @815b8b68 (019b8b68), Name: \DOCUME~1\foo\LOCALS~1\Temp\tmp5B.tmp
WaitingForDeletion Event: 00000000
ModifiedWriteCount: 0 NumberOfSystemCacheViews: 0
First prototype PTE: e1474400 Last contiguous PTE: fffffffc
Flags2: Inherit
File offset: 00000000
 
# mv dirofdump/*.77c10000-77c67fff.dmp dirofdump/tmp5B.tmp
# file dirofdump/tmp5B.tmp
dirofdump/tmp5B.tmp: PE32 executable for MS Windows (DLL) (GUI) Intel 80386 32-bit

Utilizaré el plugin ldr_modules para comprobar si se han ocultado ficheros (la salida completa aquí):
# python volatility ldr_modules -p 1980 -v -f exemplar18.vmem

Pid Name PEB nLoad nMem nInit nMapped
7bc spoolsv.exe 0x7ffde000 64 64 63 64
 
InLoadOrderModuleList
No. Map? Offset Base Size Path
...
InMemoryOrderModuleList
No. Map? Offset Base Size Path
...
InInitializationOrderModuleList
No. Map? Offset Base Size Path
...
Mapped Files
No. Load? Mem? Init? 0xBase Name
...
[ 1] [x] [x] [x] 0x2a0000 \WINDOWS\Temp\tempo-447187.tmp
...
[ 18] [ ] [ ] [ ] 0x10000000 \DOCUME~1\foo\LOCALS~1\Temp\tmp5A.tmp
...
[ 47] [ ] [ ] [ ] 0x77c10000 \DOCUME~1\foo\LOCALS~1\Temp\tmp5B.tmp
...

Tal cual imaginaba tempo-447187.tmp es directamente visible pero no pasaría lo mismo con tmp5A.tmp y tmp5B.tmp; sus respectivas entradas han sido deliberamente eliminadas de las listas enlazadas mantenidas por el kernel.

Buscando modulos del kernel

Teniendo en cuenta que la mayoría de los ficheros encontrados hasta el momento estaban "escondidos" no me extrañaría que existiera algún módulo ejecutándose en ring0 (aka driver) y encargado del proceso de ocultación; siguiendo con mi sentido arácnido creo que imagino parte del nombre:
# python volatility modscan2 -f exemplar18.vmem | grep gao

\systemroot\system32\drivers\gaopdxserv.sys 0x00f836a000 0x015000 gaopdxserv.sys

además parece que también se ha ocultado a sí mismo porque el comando modules no consigue encontrarlo; no obstante, y dado que conozco el offset en que se encuentra, lo volcaré desde la memoria:
# python volatility moddump -o 0x00f836a000 -f exemplar18.vmem

Dumping (unknown module name) @f836a000
Memory Not Accessible: Virtual Address: 0xf8375000 File Offset: 0xb000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf8376000 File Offset: 0xc000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf8377000 File Offset: 0xd000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf8378000 File Offset: 0xe000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf8379000 File Offset: 0xf000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf837a000 File Offset: 0x10000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf837b000 File Offset: 0x11000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf837c000 File Offset: 0x12000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf837d000 File Offset: 0x13000 Size: 0x1000
Memory Not Accessible: Virtual Address: 0xf837e000 File Offset: 0x14000 Size: 0x1000
# mv driver.f836a000.sys gaopdxserv.sys
# file gaopdxserv.sys
gaopdxserv.sys: PE32 executable for MS Windows (DLL) (native) Intel 80386 32-bit

Y ahora el análisis en virustotal. Voy a ver si consigo algo más de contexto para la infección y para ello voy a exprimir los ficheros del registro que puedan encontrarse en la memoria del sistema. Las herramientas necesarias y el proceso de instalación lo encontré muy detallado en este mensaje de un blog así que obviaré repetirlo. Lástima no haber podido hacer funcionar el módulo Inline::Python bajo Windows.

Primero obtendré un listado con el offset para las estructuras CMHIVE localizadas en memoria y que representan las diferentes ramas del registro de Windows:
# python volatility hivescan -f exemplar18.vmem

Offset (hex)
34786144 0x212cb60
35029896 0x2168388
36798472 0x2318008
52190048 0x31c5b60
61227776 0x3a64300
62263304 0x3b61008
62692192 0x3bc9b60
78032904 0x4a6b008
117499936 0x700e820
117721952 0x7044b60
118016032 0x708c820
181174280 0xacc8008
182220832 0xadc7820

Ahora, y utilizando uno de los offset dentro del fichero de memoria para cualquiera de las estructuras CMHIVE localizadas, utilizare el comando hivelist para concretar las direcciones en memoria virtual y los nombres para las diferentes ramas:
# python volatility hivelist -o 34786144 -f exemplar18.vmem

Address Name
0xe179e008 [no name]
0xe1a58b60 \Documents and Settings\foo\NTUSER.DAT
0xe1548008 [no name]
0xe1535820 \Documents and Settings\LocalService\NTUSER.DAT
0xe1095820 [no name]
0xe107e820 \Documents and Settings\NetworkService\NTUSER.DAT
0xe13a3008 \WINDOWS\system32\config\software
0xe1397300 \WINDOWS\system32\config\default
0xe13a0b60 \WINDOWS\system32\config\SECURITY
0xe1362b60 \WINDOWS\system32\config\SAM
0xe11c2008 [no name]
0xe1018388 \WINDOWS\system32\config\system
0xe1008b60 [no name]

Una vez conocidos los datos anteriores llega el momento de utilizar la potencia de regripper, desarrollada por Harlan Carvey (más información aquí y aquí) combinada con el plugin VolRip de Brendan Dolan-Gavitt. De momento obtendré la lista de servicios recortando la salida para mostrar solo lo más relevante (el resultado completo aquí):
# perl rip.pl -r exemplar18.vmem@0xe1018388 -p services

Launching services v.20080507
ControlSet001\Services
Lists services/drivers in Services key by LastWrite times
 
Thu Jan 8 01:54:09 2009Z
Name = gaopdxserv.sys
Display =
ImagePath = \...\gaopdxqfpbafqrnrvkjotkowksievpuyyidagn.sys
Type = Kernel driver
Start = System Start
Group = file system
 
...

Es interesante mencionar que en el listado obtenido mediante regripper y el plugin services.pl los servicios aparecen ordenados por la fecha de última modificación para las diferentes claves del registro, apareciendo primero las más recientes, y que además las fechas están en formato UTC.

Volcaré ahora el contenido completo del registro para las ramas system y software utilizando el comando hivedump, que forma parte del plugin VolReg, y buscaré referencias al driver sospechoso:
# python volatility hivedump -i 0xe1018388 -f exemplar18.vmem

Dumping \WINDOWS\system32\config\system => e1018388.csv
# python volatility hivedump -i 0xe13a3008 -f exemplar18.vmem
Dumping \WINDOWS\system32\config\software => e13a3008.csv
# grep gao *.csv
e1018388.csv:1231379649,Thu Jan 8 02:54:09 2009,ControlSet001\Services\gaopdxserv.sys
e1018388.csv:1231379649,Thu Jan 8 02:54:09 2009,ControlSet001\Services\gaopdxserv.sys\modules
e1018388.csv:1231379649,Thu Jan 8 02:54:09 2009,ControlSet001\Services\gaopdxserv.sys\Enum
e13a3008.csv:1231379649,Thu Jan 8 02:54:09 2009,gaopdx
e13a3008.csv:1231379649,Thu Jan 8 02:54:09 2009,gaopdx\disallowed

Los ficheros resultantes, en formato csv, toman como nombre el offset en memoria de la rama del registro volcada y cada una de las líneas que los componen constan de 3 campos:
  • timestamp: valor de tiempo en formato unixepoch
  • time: la fecha y hora en un formato más legible para nosotros
  • path: clave del registro modificada en la fecha y hora indicadas

Obtendré el contenido de cualquiera de las claves anteriores utilizando el comando printkey, también contenido en el plugin VolReg. Muy revelador me ha resultado este mensaje sobre los servicios en Windows. Como la salida es bastante difícil de formatear mejor incluyo solo los comandos empleados (el resultado completo aquí):
# python volatility printkey -o 0xe1018388

"ControlSet001\Services\gaopdxserv.sys\modules" -f exemplar18.vmem > printkey.out
# python volatility printkey -o 0xe1018388
"ControlSet001\Services\gaopdxserv.sys\Enum" -f exemplar18.vmem >> printkey.out
# python volatility printkey -o 0xe13a3008
"gaopdx\disallowed" -f exemplar18.vmem >> printkey.out

Por todos los datos anteriores el malware en cuestión, según la nomenclatura empleada por Symantec, sería el W32.Tidserv.G. Podría, una vez conocido, buscar más huellas de su paso, pero además de que sería mucho más simple me conformo con lo conseguido hasta el momento :-)

Hacia detrás y hacia delante

Llevaba tiempo queriendo aprender algo de python y éste pasatiempo al final ha sido el detonante. El análisis de los ficheros csv obtenidos mediante el uso del comando hivedump está algo descontextualizado ya que los registros no se ordenan por fecha, lo cual creo que resultaría muy útil. Además en ocasiones resulta interesante poder analizar los cambios sufridos unos minutos antes o unos minutos después de un determinado instante, y ese es el objetivo del script intimerange o, como me gusta pensar en él, mi "HolaMundo" en python:
# python intimerange.py -h

Usage: intimerange.py [options] timestamp1 [timestamp2]
 
Script to get the registry keys modification date which falls within the
indicated time interval.
 
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-f INFILE, --file=INFILE
file with the output of hivedump volatility plugin
-o OUTFILE, --output=OUTFILE
write the program execution result to this file

El script necesita para funcionar un fichero csv obtenido como resultado de la ejecución del comando hivedump y al menos un valor de tiempo (timestamp); si sólo se indica uno se utilizará la fecha y hora actuales como final del intervalo, y en el caso de indicar dos, el más antiguo constituirá el inicio y el más moderno el final del intervalo de búsqueda. Pero creo que lo más fácil será verlo en acción con un ejemplo:
# python intimerange.py -f e1018388.csv -o temp.csv \
"Thu Jan 8 02:49:09 2009" "Thu Jan 8 02:54:09 2009"

El comando anterior realiza una búsqueda en el fichero e1018388.csv incluyendo en el resultado, temp.csv, las claves del registro cuya fecha de modificación esté comprendida entre las 02:49 y las 02:54, hora local, del 8 de Enero de 2009, ordenando el resultado de más antiguo a más moderno.

Epílogo

En primer lugar quisiera agradecer la ayuda prestada por mi compañero y amigo Hilario. Sin sus comentarios y correcciones los comandos y scripts incluidos y desarrollados expresamente para esta historia no tendrían, ni de lejos, un aspecto tan "decente".

En segundo lugar indicar que, aunque parezca que todo lo narrado se ha obtenido como un sucesión lógica de causas y efectos, la realidad no es tan simple. Me he dejado los ojos a base de pruebas y leído "cienes" de documentos para llegar a las conclusiones indicadas, y aún así seguro que he cometido algún error.

Por último, si he conseguido entreteneros me alegro; pero si además he conseguido transmitiros algo de lo que yo mismo he aprendido todas las horas empleadas las doy por aprovechadas.

En breve más, y mejor, espero.

7 comentarios:

Unknown dijo...

Muy bueno, pero muy largo. :-)

En serio muy bueno!

--
http://hmontoliu.blogspot.com

conexioninversa dijo...

Genial!! como siempre.

tuxoe dijo...

Que tal , llevo algo de tiempo leyendo tus historias y me parecen muy interesantes espero que sigas con el blog, en nuestro trabajo al retroalimentacion de conocimientos es muy importante.

neofito dijo...

@hilario:
Ya, al final ha quedado demasiado largo :-(

@pedro:
Gracias!!

@tuxoe:
Creo que queda blog para rato :-)

Novlucker dijo...

Muy didáctico, al igual que las dos partes anteriores :)

Y me dices que te sobreestimo, con que cara!?

Saludos

sch3m4 dijo...

muy bueno neo, bien explicado y desarrollado. Sigue así ;)

neofito dijo...

@Novlucker
Efectivamente, lo sigo pensando.

@sch3ma
Gracias, un placer tenerte por aqui.

Saludos