안랩 ASEC은 랜섬웨어를 포함하여 국내 악성코드 유포에 널리 사용되는 IE 취약점 CVE-2018-8174에 대한 분석을 진행하였다. 해당 취약점은 메그니베르 랜섬웨어 유포에도 사용되고 있으며, 보안패치 적용을 통해 피해를 예방하는 작업이 필요하다.


AhnLab_분석팀_CVE-2018-8174_분석보고서.pdf



MS 보안 업데이트 페이지 (CVE-2018-8174)

https://portal.msrc.microsoft.com/ko-kr/security-guidance/advisory/CVE-2018-8174




01. 요약

1) CVE-2018-8174 개요

CVE-2018-8174 취약점은 VBScript엔진의 Use After Free 발생으로 인한 객체 재사용으로 발생한다.

원격 실행이 가능한 취약점이며, 영향받는 버전은 Internet explorer8, Internet explorer9, Internet explorer10, Internet explorer11(1803 이하 버젼), Windows 10(1803 이하), Windows 7, Windows 8, Windows Server 이다.


02. 사전지식

1) VBScript 엔진이 스크립트를 실행하는 방법

) 정의

VBScript 엔진에서 스크립트는 엔진이 해석 가능한 Precompiled Code(이하 P-Code) 로 변환되어 실행된다. 변환된 P-Code 0x00 ~ 0x6F112개의 값을 가지며, VBScript 엔진의 내부 함수인 RunNoEH 에서 해석 후 P-Code에 적합한 VBScript 함수를 호출한다. RunNoEH 함수는 [그림 1] 와 같다.


[그림 1] RunNoEH 함수 정의

 

) 실행방법

P-CodeRunNoEH 함수에서 실행이 되며 [그림 1]의 첫번째 파라미터인 CScriptRuntime 클래스 내부에 위치한다. CScriptRuntime 클래스는 [그림 2]와 같으며 0xC0에 위치한 멤버변수 Compiled Script를 통해 P-Code의 위치를 알 수 있다. [그림 2]0xB4에 위치한 멤버변수 Position Counter는 다음 실행할 P-Code 명령어를 가리키고 있다. 이는 EIP 레지스터와 유사한 역할을 수행한다.


[그림 2] CScriptRuntime 클래스

 

VBScript엔진에서 P-Code[그림 3]처럼 Call-Return 방식으로 실행된다. Call-Return 방식이란 Global CodeP-Code를 실행하면서 내부적으로 Call을 호출하는 P-Code를 만났을 때 다음 실행할 P-Code를 저장하고 해당 함수의 P-Code 명령어를 실행하는 방식이다


[그림 3] P-Code 실행 흐름

 

) 도구 소개

Kaspersky 사에서 P-Code를 추출하는 스크립트를 github에 공개했다(참고문헌[1]). 이를 이용하면 위에서 설명한 CScriptRuntime 클래스의 Compiled Script 를 추출하여 분석에 이용할 수 있다.

 


03. 상세 분석

1) CVE-2018-8174

) 취약점 발생 원리


CVE-2018-8174 취약점은 VBScript엔진의 Use After Free 발생으로 인한 객체 재사용 문제를 명명한 것이다.

아래 [그림 4], [그림 5] CVE-2018-8174 취약점에서 Use After Free 발생원리를 정리한 그림이다. 그림의 스크립트는 CVE-2018-1874 PoC(Proof of Concept) 스크립트 이다. 그림에 정의된 숫자 순서대로 진행되며 [그림 4], [그림 5]는 순서대로 이어진다.

 


[그림 4] Use After Free 원리-1

 

[그림 4]의 스크립트 내용 중 1번의 Dim은 변수 선언이며, 전역변수 o를 선언하게 된다. 2번의 Redim의 의미는 배열 선언이고 arr로 명명된 2개의 배열이 선언된다. 3번의 과정을 통해 cal1 클래스객체를 선언된 첫번째 배열 arr(0)에 할당 한다. 이 과정으로 첫번째 배열 arr(0)cal1 클래스객체의 주소가 저장되고 클래스 객체에 대한 참조 횟수(레퍼런스 카운트)1이 된다.

 

[그림 5] Use After Free 원리-2

 

[그림 4] 과정 이후, [그림 5] 4번의 메모리 해제 과정을 진행한다. Erase 문에 의해서 선언된 배열 arr 삭제를 시도 한다. arr배열의 첫 번째 요소인 arr(0)을 삭제할 때 해당 배열에 저장된 cla1 클래스의 Class_Terminate함수가 콜백함수로 호출되어 5번의 과정을 진행한다. 5번의 과정은 1번의 과정에서 선언된 전역변수 oarr(0)에 저장된 cla1 클래스객체 주소 값을 저장한다. 자신을 참조하는 변수가 늘었으므로 cla1 클래스의 참조 횟수는 1 증가한다. 6번 에서 arr(0)에 저장된 클래스객체 주소를 다른 값으로 바꾸게 되고 이로 인해 cla1 클래스의 참조 횟수는 1 감소된다. 이후 7번에서 콜백함수는 종료 되며, 참조 횟수의 증가, 감소가 일정하여 클래스객체 주소에 대한 free() 함수를 호출한다. 마지막 8번을 통해 전역변수 o의 값을 출력하면 이미 해제된 메모리 주소를 가리키고 있다.

 


[그림 6] VBScriptClass::Release 함수

 

매모리 해제는 VBScript.dll 에 정의된 [그림 6] 의 함수를 통해 진행된다. 코드에서 알 수 있듯 InterlockedDecrement 호출 후 참조 횟수가 0이면 VBScriptClass::TerminateClass 함수가 호출되며, 이 후 해제 대상인 클래스(v1 변수)의 참조 횟수가 1이면 free()함수가 호출된다. Class_Terminate 콜백 함수 호출 시 콜백 함수 내에서 전역변수 o에 해제하기 전 클래스 객체의 주소값을 저장하고, 참조 횟수에 대한 균형을 맞춰줌으로써 free () 함수를 호출한다. 따라서 실제 메모리는 해제되므로 해당 주소값을 Dangling pointer로 사용할 수 있다.

 

) Use After Free

앞서 설명한 Use After Free 의 발생원리를 아래 [그림 7]의 코드(참고문헌 [7])를 이용하여 상세히 설명한다.

 

[그림 7] Use After Free 스크립트

 

[그림 7] 1번 부분에서 Redim 은 내부적으로 [그림 8]과 같은 함수들을 호출한다.


[그림 8] Redim 내부 호출

 

SafeArrayAllocData 함수까지 호출이 끝나면 tagSAFEARRAY 구조체가 생성된다. tagSAFEARRAY 구조체는 [그림 9]과 같은 구조를 가진다(참고문헌 [2]).

 

[그림 9] tagSAFEARRAY 구조체

 

[그림 10] 할당된 메모리 값

 


 

[그림 11] Variant 구조체

 

[그림 7] 1번 부분의 redim 으로 할당되어 초기화 된 메모리 영역이 생성되며, Set 명령어를 통해 값이 할당된다. [그림 10]는 이 과정을 통해 할당된 값 이다. 이때 저장되는 값의 형태는 Variant 구조체이다(참고문헌 [4]). Variant 구조체는 많은 유형의 데이터를 전달하는 구조체로, 첫번째 멤버인 VT(Variant Type)(참고문헌 [5])에 따라 유효한 데이터 유형이 결정된다. 현재 할당된 Variant 구조체의 VT값이 0x09(VT_DISPATCH) 임을 확인할 수 있다[그림 11].

 

[그림 12] VBScriptClass 객체

 

Variant구조체의 VTVT_DISPATCH은 객체 포인터가 저장되며 0x021C6AB8 이 저장되어 있음을 확인할 수 있다. 이 주소에 저장된 값은 VBScriptClass로 할당된 cla1의 객체 정보가 저장된 공간이다. Set을 통해 cla1에 대한 참조값이 증가하여 참조 횟수가 2가 되었음을 확인할 수 있다.

 

[그림 13] VbsErase 내부 호출

 

[그림 7] 1번 부분의 Erase 는 내부적으로 [그림 13] 과 같은 함수들을 호출한다. oleaut32!ReleaseResources 함수는

FreedObjectArray 배열을 순회하며 저장된 값을 초기화하는 기능을 수행하고 oleaut32!VariantClear 함수는 Variant구조체를 초기화 하는 함수이다. VariantClear 함수는 Variant구조체의 VT값에 따라 Clear하는 루틴이 달라진다. Variant구조체의 VT값이 DISPATCH이므로 VBScriptClass::TerminateClass가 호출된다. [그림 7] Erase 함수가 호출되는 시점은 할당 후 즉시 해제하였으므로 참조 횟수는 아래 [그림 13]처럼 1이 되고 이후에 TerminateClass가 호출되고 초기화 함수를 거치면서 [그림 7]번의 2번 콜백 함수부분으로 넘어간다.


[그림 14] Erase 함수 호출 후 참조 횟수

 

초기화를 거친 cla1배열의 참조 횟수는 4 이며, [그림 7] 2번 부분에서 Set부분은 cla1 객체 주소 정보를 가지고 있는 FreedObjectArray 배열을 UafArrayA에 할당하여 참조 횟수를 1 증가시킨다. UafArrayA에 저장된 주소값이 Dangling pointer로 사용된다[그림 15].

 

[그림 15] Dangling pointer

 

[그림 16] FreedObjectArray(1) 후 메모리 변화

 

이후 Redim으로 선언된 FreedObjectArray(1) 배열에 새로운 값을 할당하여 vbscript내부적으로 AssginVar가 호출된다. 이 때 1Variant구조체의 VT값 중 0x02(VT_I2)에 해당하기 때문에 기존 0x09(VT_Dispatch) 값은 0x02(VT_I2)로 위 [그림 16]처럼 할당된다. FreedObjectArray(1) 배열에 객체 참조로 인해 cla1의 참조 횟수는 1 감소한다. 콜백 함수에서 시작값인 4와 동일하게 맞춰진 상태에서 콜백 함수 호출이 종료되어 참조 횟수는 0이 되어 메모리는 해제된다.

결과적으로 클래스 구조체의 참조 횟수에 대한 검증이 제대로 이루어 지지 않아 Use After Free가 발생하였다. 여기서 발생된 Dangling pointerUafArrayA 배열에 저장되고 [그림 7] 3번과정을 통해 ReuseClass 로 재할당되어 이후 TypeConfusion 취약점에 사용된다.

 

) TypeConfusion

TypeConfusion이란 선언된 Type에 대해 혼동을 주는 것을 뜻한다. 아래 [그림 17] 에서 처럼 할당된 Type A 형태의 데이터가 있다고 가정하면 구성된 쉘 코드를 통해 할당된 데이터의 영역에 덮어쓰게 되면 덮어써진 쉘 코드에 의해 데이터의 타입이 변형된다. 변형된 Type A의 영역을 읽게 되면 할당한 Type A가 아닌 변형된 Type B로 인식된다. 이 방식이 TypeConfusion의 원리이다.

 

[그림 17] TypeConfusion 원리

 

CVE-2018-8174 취약점에서 Use After Free2번 진행되며 2개의 Dangling Pointer2ReuseClass 로 할당 한다. Use After Free의 부분에서는 방법과 원리에 차이가 없어 1개의 설명만 했지만, TypeConfusion 부분에서는 2개가 각각 다르게 사용된다. 우선 첫번째 방식을 설명하고 이어 차이점을 뒷 부분에 기술한다.

아래 [그림 18]CVE-2018-8174 코드의 TypeConfusion 부분이다. [그림 7] 3 과정인 Use After Free 로 구해진 Dangling pointer로 할당된 ReuseClass의 주소 resueObjectA_arrTypeConfusion을 위한 메모리를 구성하는 코드이다. resueObjectA_arr 클래스의 SetProp 함수가 호출되면 ReuseClass에 선언된 디폴트 프로퍼티를 수행한다. 디폴트 프로퍼티함수 내에 구성된 Q 0x0C, 0x20값으로 Variant 구조체의 VT값이 0x200C가 되며 변수형은 VT_ARRAY, T_VARIANT 이다. UafArrayA0을 대입하면 VBScript Terminate Class를 호출하게 되고 참조 횟수가 0이 되면서 ReuseClassfree 된다. 이후 objectImitatingArray FakeReuseClass 객체를 할당하는데 이때 ReuseClass 객체 주소와 같은 주소를 할당한다.

 

[그림 18] TypeConfusion 스크립트-1

 

할당된 FakeReuseClass 객체는 아래 [그림 19]와 같다. 아래 [그림 19]의 윗 부분은 ReuseClass 객체를 할당한 공간이며 아래 부분은 FakeReuseClass 객체를 할당한 공간이다. 흰색 네모박스 부분이 함수명, 변수명을 나타내며 흰색 네모박스의 앞의 공간이 할당된 데이터이다. Function0x4C값으로 시작하며, dim으로 선언된 변수는 0x00으로 시작한다.

[그림 19]ReuseClass 객체를 할당한 공간에 FakeReuseClass 객체를 할당하는데 첫번째 함수명이 p에서 10개의 문자열이 추가되어 16byte가 늘어나고 SPP 함수가 이후에 할당된다.

 

[그림 19] TypeConfusion 전 후 메모리 값

 

FakeReuseClass 객체로 할당한 objectImitatingArray mem 에 미리 구성된 FakeArrayString 로 덮어쓴다. FakeArrayStringString으로 구성된 값(0x08,BSTR)으로 비정상적인 크기의 tagSAFEARRAY 를 만들기 위해 구성된 값이다. 쓰는 값인 FakeArrayString BSTR memVT값이 0x08로 바뀐다. 이 중 0xFFFF, 0x7FFF 값이 있는데, 이 값은 TypeConfusion을 할 때 SafeArray 배열의 크기를 0x7FFFFFFF크기로 만들기 위해 구성된 값이다. 또한 0x00010x0880, 0x0001값이 있는데 이 값은 tagSAFEARRAY 구조체의 첫번째, 두번째, 세번째 인자값으로 설정되는데 각각 배열의 차원정보, 배열 피쳐정보, 배열 요소의 크기값이 된다. 이렇게 구성된 값을 objectImitatingArraymem 변수에 덮어쓴다. 이렇게 덮어쓰면 아래 [그림 20]처럼 변경된 tagSAFEARRAY 구조를 볼 수 있다. 1차원 배열의 0x880( FADF_VARIANT | FADF_HAVEVARTYPE )속성과 배열 요소의 크기가 10x7FFFFFFF 개의 배열을 갖게 된다. 디폴트 프로퍼티 함수가 끝나고 SetProp로 돌아가서 정의된 mem 을 구성된 Q 값으로 ReuseClassmem 위치에 덮어쓰게 되어 타입이 0x08(BSTR)에서 0x200C(ARRAY, VARIANT)로 바뀌게 된다. 이 후 reuseObjectA_arrmem을 호출하게 되면 0x7FFFFFFF 크기의 배열을 사용할 수 있게 된다. 비 정상적인 크기의 이 배열은 스크립트가 로드된 iexplore.exe의 힙 영역으로 같은 크기만큼의 메모리를 접근할 수 있다.

 

[그림 20] TypeConfusionreuseObjectA_arr 메모리

 

resueObjectB_intresueObjectA_arr 와 같은 방식으로 TypeConfusion이 된다. 위 설명과 차이점은 디폴트 프로퍼티함수 내에 구성된 P 값과 미리 구성된 FakeArrayString 값이다. 디폴트 프로퍼티함수 내에 구성된 P 값의 VT값이 BSTR(0x08) 에서 LONG(0x03) 으로 바뀌며 비어있는 16byte로 채워진다. 비어있는 공간의 reuseObjectB_int mem을 전역변수 주소를 some_memory 에 넣는다. 비어있는 값은 스크립트가 로드된 iexplore.exe의 힙 영역으로 이후 메모리의 특정 공간을 저장할 때 사용된다.

 


[그림 21] TypeConfusion 스크립트-2

 

[그림 22] TypeConfusionreuseObjectB_int 메모리


 

 

) LeakVBAddr

LeakVBAddr 부분에서는 앞선 TypeConfusion으로 만들어진 배열(resueObjectA_arr) 과 힙 주소(resueObjectB_int)를 이용하여 VBScript.dllbase addresss를 획득한다.

 

[그림 23] LeakVBAddr 스크립트

 

[그림 24] LeakVBAddr 과정의 메모리 변화

 

비어있는 함수 EmptySub emptySub_addr_placeholder 변수에 할당함으로써 vbscript.dll 내부 주소를 포함한 객체가 할당된다. 이때 빈 함수를 변수에 할당하는 것은 문법상 오류가 나지만, On Error Resume Next를 통해서 이후 루틴이 수행가능하다. 이후 null 을 할당하여 VT값이 Func(0x4C) 에서 NULL(0x01) 로 바뀜을 확인할 수 있다. EmptySub를 할당 후 Null을 할당한 상태에서 VTNull(VT=0x01)에서 LONG(0x03)으로 수정하여 메모리 주소를 변수로 출력하여 메모리 주소 누출 시킨다. TypeConfusion 함수를 통해 얻어낸 0x7FFFFFFF 크기의 배열(reuseObjectA_arr.mem) heap Address (some_memory) 에 접근하여 타입변경을 자유롭게 수행할 수 있다.

 

) Execute ShellCode

 

[그림 25] 코드 실행 스크립트

 

쉘 코드의 실행은 0x4D값을 할당하면서 실행된다. 0x4D값을 할당하면 AssignVar가 호출되며 Variant구조체의 VT값을 확인하여 값에 맞는 분기를 한다. VT값이 0x4D일때는 VAR::Clear가 호출되고 구성된 메모리로 인해 NtContinue VirtualProtect로 쉘 코드까지 실행 된다.

 

[그림 26] 코드 실행 과정

 

[그림 27]VirtualProtect로 쉘 코드에 실행 권한을 주기 위해 메모리를 구성하는 스크립트이다. VirtualProtect는 대상주소, 크기, 보호옵션으로 호출된다. 실행 권한으로 64(0x40)가 사용되며 이는 PAGE_EXECUTE_READWRITE 권한이다.

 

[그림 27] VirtualProtect 콜 파라메터 메모리 구성 스크립트

 

[그림28 ]NtContinue를 호출할 때 사용되는 CONTEXT 구조체를 구성하는 스크립트이다. 레지스트리 위치에는 패딩값으로 채워지며, EIP, ESP 값의 위치에 각각 VirtualProtect 의 주소값과 쉘 코드의 EP주소값을 써서 메모리를 구성한다.

 

[그림 28] CONTEXT 구조체 구성 스크립트

 

위에서 설명한 것 처럼 VT0x4D값을 할당하면 AssignVar Var::Clear로 호출되는데, Var::Clear에서 아래 [그림 29]의 분기문을 수행하게 된다. [그림 29]loc_6E50089C에서 첫 번째 줄은 Variant 구조체의 데이터에 해당하는 위치([esi+8])로 위 [그림 28] StructForNtContinue 로 구성한 메모리를 가리킨다. 이후 StructForNtContinue 로 구성한 메모리의 주소를 push 하고 call하여 NtContinue 를 실행하게 한다.

 

[그림 29] 구성된 메모리 실행

 

NtContinue가 호출될 때 위에서 구성된 CONTEXT구조체 주소와 함께 호출되어 VirtualProtect함수를 호출하게 된다.

구성된 CONTEXT구조체로 인해 VirtualProtect 에서 쉘 코드 주소를 받아 실행권한을 주고 return 하여 쉘 코드로 분기하여 쉘 코드가 실행된다.

 

[그림 30] 쉘 코드 실행

 

[그림 31] 메모리 실행권한 변화

 

) 취약점 패치

앞서 설명한 CVE-2018-8174 Use After Free 취약점은 oleaut32.dll 모듈 아래와 같이 패치 되었다(참고문헌 [6]). [그림 32]oleaut32.dll 패치 전 후의 VariantClear함수이다. 그림의 왼쪽 부분이 패치 전 이며 오른쪽 부분이 패치 후 이다. 할당된 Variant 구조체를 해제할 때 호출되는 함수는 [그림 13]과 같다. vbscript.dll VbsErase가 호출되고 내부 호출에 의해 oleaut32.dll 모듈의 VariantClear함수가 호출된다. 호출된 VariantClear함수에서 vbscript.dll 모듈의 TerminateClass로 해제되는데, [그림 32]의 오른쪽 부분처럼 해제할 때 해당 함수에서 Variant Type 값을 0x00(VT_EMPTY)로 채워 넣고 해제 하는 형태로 패치 되었다. 패치 전후의 메모리 값을 보면 VT값이 0x09(VT_DISPATCH)에서 0x00(VT_EMPTY)로 바뀌는 것을 볼 수 있다. 이렇게 패치되어 Use After Free 로 얻어진 Dangling pointer 를 사용면 UafArrayA 배열에 객체 주소를 복사하는 과정에서 오류가 발생하고 객체 재사용이 불가능 하다.

 

[그림 32] CVE-2018-8174 취약점 패치 전 후 VariantClear

 

 

04. 참고문헌

[1] https://github.com/KasperskyLab/VBscriptInternals , Github, KasperskyLab

[2] https://msdn.microsoft.com/ko-kr/windows/hardware/ms221482(v=vs.71) , MSDN, SAFEARRAY structure

[3] https://msdn.microsoft.com/en-us/library/cc237824.aspx , MSDN, ADVFEATUREFLAGS Advanced Feature Flags

[4] https://msdn.microsoft.com/en-us/library/ms931135.aspx , MSDN, VARIANT structure

[5] https://msdn.microsoft.com/en-us/library/cc237865.aspx , MSDN, VARIANT Type Constants

[6] https://portal.msrc.microsoft.com/ko-kr/security-guidance/advisory/CVE-2018-8174 , Microsoft , 보안 업데이트 가이드

[7] https://github.com/piotrflorczyk/cve-2018-8174_analysis/blob/master/analysis.vbs , Github, piotrflorczyk



Posted by 분석팀