시스템 통제권 장악하는 제로데이 취약점 상세 분석
2019년 9월, 구글의 위협 분석 그룹(Threat Analysis Group)의 연구자가 마이크로소프트 인터넷 익스플로러에서 발견된 제로데이 취약점을 발견해 알린 바 있다. 이 취약점은 메모리를 변형시켜 원격 코드 실행이 가능하게 한다. 즉, 공격자가 이를 익스플로잇하면 사용자와 동일한 권한을 갖게 되므로 사실상 시스템에 통제권을 갖게 되는 것이다. 사태의 심각성을 인지한 마이크로소프트는 동일 취약점에 대해 9월 23일, 10월 3일, 10월 8일 등 3차례에 걸쳐 보안 패치를 배포했다. 이 취약점이 바로 CVE-2019-1367이다. 이 글에서는 CVE-2019-1367 취약점의 발생 원인과 공격 과정, 패치, 탐지 절차를 설명한다.
1. 개요
CVE-2019-1367은 인터넷 익스플로러의 Jscript.dll에서 소트(sort) 함수의 콜백(callback) 내에서 아규먼츠(Arguments) 객체를 처리하는 과정 중 가비지 콜렉션(Garbage Collection) 대상에 포함되지 않아 프리(Free)된 메모리 공간을 참조하게 되는 UAF(Use-After-Free) 취약점이다. 앞서 언급했듯이 이 취약점은 2019년 10월 8일 3번째 배포한 보안 패치로 제거되었다.
이 취약점에 영향 받는 Internet Explorer 버전은 다음과 같다.
● Windows 7 SP1 - Internet Explorer 11
● Windows 8.1 - Internet Explorer 11
● Windows 10 1607, 1703, 1709, 1803, 1903 – Internet Explorer 11
● Windows Server 2008 SP2 – Internet Explorer 9
● Windows Server 2008 R2 - Internet Explorer 11
● Windows Server 2012 - Internet Explorer 10, 11
● Windows Server 2012 R2 - Internet Explorer 11
● Windows Server 2016 - Internet Explorer 11
● Windows Server 2019 - Internet Explorer 11
2. 취약점 발생 코드 분석
CVE-2019-1367은 소트(sort) 함수의 콜백(callback) 내에서 아규먼츠(Arguments) 객체가 가비지 콜렉션(Garbage Collection)의 대상에 포함되지 않아 프리(Free)된 메모리를 참조하는 단글링 포인터(Dangling Pointer)를 가지는 UAF(Use-After-Free) 취약점이다.
[그림 1]은 취약점을 발생시키는 코드이고, [그림 2]는 취약점 발생 과정을 도식화한 그림이다.
소트(sort) 함수의 콜백 함수로 C함수가 실행된다. 이때, 변수 t의 배열에 아규먼츠 객체로 채워준다. 삽입된 아규먼츠 객체에 정규표현식(RegExp) 객체를 가리키는 주소 배열인 p의 데이터를 삽입해 아큐먼츠 객체가 정규표현식(RegExp) 객체를 참조하게 한다. 변수 p의 배열을 널(null)로 채워지면 RegExp 객체 베리언트(Variant)의 메모리가 참조되지 않아 콜렉트가비지(CollectGarbage)를 통해 메모리가 프리(Free)된다. 가비지 콜렉션(Garbage Collection)이 발생 후에도 아큐먼츠(arguments) 객체가 여전히 RegExp 객체를 참조하고 있기 때문에 UAF(Use-After-Free) 취약점이 발생한다.
배열의 인덱스(index)에 조작된 베리언트(Variant) 데이터로 설정했을 때, 프리(Free)된 메모리에 베리언트(Variant) 데이터를 덮어씌우고 타입 컨퓨전(Type Confusion)이 발생한다.
[그림 4]는 [그림 3]의 취약점 코드에서 3번까지 진행을 통해 아규먼츠(Arguments) 객체가 RegExp 객체의 베리언트(Variant) 주소를 참조하게 되는 경우를 도식화한 그림이다. 변수 p의 배열에 RegExp 객체를 참조하는 베리언트(Variant) 주소를 삽입하게 되고 해당 주소를 변수 t에 삽입한다. [그림 4]는 변수 t에 값이 삽입되기 전과 삽입 후를 비교한 표이다. 삽입 후에 변수 t의 아규먼츠(arguments) 객체를 확인하게 되면 RegExp 객체의 베리언트(Variant) 주소를 참조하는 것을 확인할 수 있다.
취약점 공격 코드에서 4번을 통해 변수 p의 배열의 값들을 널(null)로 채워줘서 RegExp 객체의 베리언트(Variant) 주소를 참조하지 않게 되고 가비지 콜렉션(Garbage Collection)의 대상이 된다. 이후, 콜렉트가비지(CollectGarbage)를 통해 변수 p의 배열이 참조하고 있던 RegExp 객체의 베리언트(Variant) 메모리가 프리(Free) 과정을 수행하게 된다. 가비지 콜렉션(Garbage Collection)이 수행됐지만 아규먼츠(Arguments) 객체는 계속 프리(Free)된 메모리를 참조하고 있기 때문에 UAF(Use-After-Free) 취약점이 발생한다.
UAF 취약점 발생 이후, 배열의 인덱스(index) 부분에 조작된 베리언트(Variant) 데이터를 삽입 시 새로운 메모리 공간의 할당이 발생한다. 이때, 아규먼츠(Arguments) 객체가 참조하고 있는 메모리가 새로 할당되게 되고 jscript!NameList::FCreateVval에서 memcpy를 통해 해당 메모리에 인덱스(index)로 사용된 베리언트(Variant) 데이터가 삽입된다. 삽입된 데이터를 통해 기존의 0x00000000으로 존재하던 데이터를 변조해 메모리에 존재하는 RegExp 객체의 베리언트 타입(Variant Type)이 넘버 타입(Number Type)으로 변경되며 타입 컨퓨전(Type Confusion)이 발생한다.
3. 취약점 공격 코드 분석
3.1 공격 개요
CVE-2019-1367 취약점을 사용한 실제 POC 코드를 통해 분석을 진행한다. 해당 POC 코드는 다음과 같은 과정을 통해 임의의 코드를 실행한다.
● 소트(Sort) 함수의 콜백(callback)을 통해 취약점 공격
● 페이크(Fake) RegExp 객체를 생성
● 메모리 릭(Memory Leak)을 통한 주소 값 로드
● RegExp 객체의 test 메소드를 통한 Shellcode 호출
UAF 및 타입 컨퓨전(Type Confusion)을 통해 타입(Type)이 조작 가능한 RegExp 객체의 베리언트(Variant) 주소를 획득할 수 있다. 해당 값을 통해 조작 가능한 페이크(Fake) RegExp 객체를 생성하고 메모리 릭(Memory Leak)을 통한 주소 값을 획득한다. 이후, RegExp 객체의 테스트(test) 메소드를 통해 메모리 쓰기(Memory Write)를 수행하고 구성한 페이로드(Payload)를 호출해 임의의 명령을 수행한다.
3.2 취약점 공격 – Fake RegExp 객체 생성
[그림 11]은 취약점 공격을 통해 타입(Type)이 조작 가능한 RegExp 객체를 통해 페이크(Fake) RegExp 객체를 생성하는 코드다. 타입 컨퓨전(Type Confusion)을 통해 RegExp 객체의 베리언트 타입(Variant Type)을 스트링(String)으로 변경하고 RegExp 객체의 데이터를 복사 및 조작해 변수 L에 삽입한다.
[그림 13]은 조작된 페이크(Fake) RegExp 객체의 주소를 획득하는 코드다. 생성한 RegExp 객체의 소스(source) 부분에 페이크(Fake) RegExp 객체의 주소를 삽입하고 타입 컨퓨전(Type Confusion)을 통해 페이크(Fake) RegExp 객체의 주소를 변수 O에 삽입한다. 이후, 타입 컨퓨전(Type Confusion)을 통해 페이크(Fake) RegExp의 객체 주소의 베리언트 타입(Variant Type)을 오브젝트(Object)로 변경해 조작된 페이크(Fake) RegExp 객체를 사용한다.
3.3 취약점 공격 – Memory Leak을 통한 주소 값 로드
메모리 읽기/쓰기(Memory read & write)를 위해 [그림 14]와 같은 코드를 실행한다.
컴파일 된 패턴을 통해 RegExp 객체의 테스트(test) 메소드를 호출 시 인자 값으로 쓰기(write) 되는 주소 값, 메모리 값을 데이터로 사용한다. 메모리 읽기(Memory read)를 수행 시 페이크(Fake) RegExp 객체의 소스(source) 부분에 스트링 타입(string type)으로 데이터를 삽입한다. 삽입된 데이터에서 길이(length) 부분을 통해 원하는 메모리의 값을 읽기(read) 합니다.
jscript!RegExpExec::FExecAux에서 컴파일 된 패턴에 따라 코드 루틴이 실행되면서 인자 값에 존재하는 데이터를 통해 메모리 쓰기(memory write)를 수행한다.
[그림 16]은 Jscript.dll의 베이스 어드레스(Base Address)를 획득하기 위한 코드다. 페이크(Fake) RegExp 객체에서 Vtable의 주소를 가져와 0xFFFF0000로 AND 연산을 통해 Jscript.dll의 베이스 어드레스(Base Address)를 획득한다.
Jscript.dll 내부에 존재하는 Kenrel32.dll 함수의 주소를 찾아 kernel32.dll의 베이스 어드레스(Base Address)의 주소를 찾는 코드다. Jscript.dll 내부에 존재하는 KERNEL32.dll 문자열이 존재하는 오프셋(offset)을 찾고 해당 오프셋(offset)을 통해 kernel32.dll 함수의 주소를 찾는다. 함수 주소로부터 0x10000를 감소시키며 베이스 어드레스(Base Address)를 찾는다.
[그림 20]는 페이로드(payload) 구성을 위한 pop ebx, ret 가젯을 구하고 GetModuleHandleA, GetProcAddress 함수 주소를 구하는 코드다. kernel32.dll의 주소로부터 필요한 가젯 및 함수의 주소를 구해 이후 페이로드(payload) 구성에 사용한다.
3.4 취약점 공격 – RegExp 객체의 test 메소드를 통한 Shellcode 호출
[그림 21]은 페이로드(payload)를 구성하고 최종적으로 구성한 페이로드(payload)를 실행하는 코드다. 해당 코드를 통해 페이크(Fake) RegExp 객체에 쉘코드(Shellcode)를 삽입하고 페이크(Fake) RegExp 객체의 테스트(test) 메소드를 통해 메모리 주소의 데이터를 조작해 페이로드(payload) 구성 및 ebp 레지스터값을 조작하고 페이로드(Payload)를 실행한다.
해당 코드는 페이크(Fake) RegExp 객체에 삽입한 쉘코드(Shellcode)에 함수 주소 및 인자값을 세팅하기 위한 코드다. 쉘코드(Shellcode) 호출을 통해 해당 부분이 실행된다.
메모리를 조작해 jscript!RegExpExec::Exec에서 ret 명령을 통해 구성한 페이로드가 실행된다. 수집한 가젯 및 함수 주소를 메모리에 페이로드를 구성한다.
jscript!RegExpExec::Exec 함수 내부에서 msvcrt!_setjmp3를 통해 기존의 ebp 레지스터의 값을 메모리에 저장한다. 이후 저장된 ebp 레지스터 값을 조작하고 msvcrt!__longjmp를 통해 해당 값을 ebp 레지스터로 변경한다.
jscript!RegExpExec::Exec 함수 내부에서 msvcrt!_setjmp3를 호출해 ebp 레지스터의 값을 메모리에 삽입하는 것을 확인할 수 있다.
jscript!RegExpExec::FExecAux의 내부에서 기존의 ebp 레지스터 값을 저장한 메모리의 값으로 페이로드를 구성한 메모리의 주소로 변조한다.
이후 msvcrt!__longjmp를 통해 메모리에 삽입된 값으로 ebp 레지스터를 복구한다. 이때, 정상적인 ebp 주소 값이 아닌 변조된 ebp 레지스터의 값으로 복구된다.
최종적으로 변경된 ebp 값을 참조해 구성된 페이로드를 실행한다.
4. 취약점 공격 탐지
안랩 제품군에서는 CVE-2019-1367 취약점을 다음과 같이 탐지하고 있다.
● 행위 진단: Malware/MDP.Exploit.M2718
● 파일 진단: Exploit/JS.CVE-2019-1367.S1073
5. 취약점 조치 방법
CVE-2019-1367 취약점에 대한 패치는 2019년 10월 8일 마이크로소프트로부터 제공되었으며, 해당 패치를 통해 조치가 가능하다.
마이크로소프트 패치를 통해 조치가 불가능할 경우 다음과 같은 방법을 통해 조치가 가능하다.
■ JScript.dll에 대한 액세스 제한
32비트 시스템의 경우 관리 명령 프롬프트에서 다음 명령을 입력한다.
takeown /f %windir%\system32\jscript.dll
cacls %windir%\system32\jscript.dll /E /P everyone:N
64비트 시스템의 경우 관리 명령 프롬프트에서 다음 명령을 입력한다.
takeown /f %windir%\syswow64\jscript.dll
cacls %windir%\syswow64\jscript.dll /E /P everyone:N
takeown /f %windir%\system32\jscript.dll
cacls %windir%\system32\jscript.dll /E /P everyone:N
■ 해결 방법의 영향
Internet Explorer 9/10/11 버전에서 사용하는 Jscript9.dll은 이 취약성의 영향을 받지 않는다. 이 취약성은 jscript를 스크립팅 엔진으로 활용하는 특정 웹 사이트에만 영향을 미친다.
■ 해결 방법을 실행 취소하는 방법
32비트 시스템의 경우 관리 명령 프롬프트에서 다음 명령을 입력한다.
cacls %windir%\system32\jscript.dll /E /R everyone
64비트 시스템의 경우 관리 명령 프롬프트에서 다음 명령을 입력한다.
cacls %windir%\system32\jscript.dll /E /R everyone
cacls %windir%\syswow64\jscript.dll /E /R everyone
6. 참고
1. CVE-2019-1367 취약점 안내
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1367
2. IE 취약점(CVE-2019-1367)에 대한 V3 행위 탐지(Fileless 형태)
- AhnLabTI개발팀 이세빈 연구원