[ZERO DAY & APT] APT 공격의 최대 위협 '제로데이 공격 해부'
ZERO Day & APT CVE-2014-1761
RTF 파일 처리 관련 원격코드 실행 취약점 CVE-2014-1761
지난 3월 마이크로소프트사에서 개발한 마이크로소프트 워드(Microsoft Word 2010)에서 제로데이(Zero Day) 취약점이 발견되었다. 이 취약점은 RTF 파일을 처리하는 과정에서 발생하는 원격코드 실행 취약점(CVE-2014-1761)이다. 마이크로소프트사는 4월 정기 업데이트를 통해 이 취약점에 대한 ‘긴급’ 보안 업데이트를 제공했다. 그러나 이 취약점은 여전히 위협적이며, 공격자들에 의해 APT 공격에 악용되고 있다. 이 글에서는CVE-2014-1761을 통해 취약점이 발생하는 원리와 공격자의 의도에 따라 어떻게 악성 행위가 발생하는지에 대해 자세히 알아본다. 또한 안랩의 트러스와처가 제로데이 공격을 어떻게 효과적으로 대처할 수 있는지에 대해서도 살펴보자.
개요
지난 3월 마이크로소프트 워드에서 제로데이 취약점이 공개되었다. 마이크로소프트 워드에서 메모리 변형(Memory corruption) 취약점이 존재한 것. 이 취약점은 정교하게 조작된 RTF(Rich Text Format) 문서를 파싱하는 과정에서 힙(Heap)에 저장된 구조체를 부적절하게 사용할 때 발생한다.
CVE Reference
CVE-2014-1761
Ref1. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-1761
취약점 발생 모듈
• Name: WWLIB.DLL in Microsoft Office 2010
• Version: 14.0.7113.5001.
• Description: MS Word에서 RTF 문서를 파싱하는 모듈
취약점이 발생할 수 있는 프로그램
• Microsoft Word 2003
• Microsoft Word 2007
• Microsoft Word 2010
• Microsoft Word 2013
• Microsoft Word 2013 RT
• Microsoft Word Viewer
• Microsoft Office Web Apps 2010
• Microsoft Office Web Apps Server 2013
• Microsoft Office Compatibility Pack
• Microsoft Office for Mac 2011
• Microsoft Microsoft SharePoint Server Word Automation Services 2010
취약점이 발생하는 파라미터
listoverridecountN , lfolevel
취약점 발생 원인
이 취약점은 “ListoverridecountN”이 16(0x10)이상이고, “lfolevel”의 개수가 N보다 크면 힙 버퍼 오버플로우(heap buffer overflow)가 발생한다. 이때 “ListoverridecountN”의 N은 반드시 0, 1 또는 9 값이 저장되어야 한다. “ListoverridecountN” 의 자세한 설명은 마이크로소프트에서 작성한 Ref2(http://www.microsoft.com/en-us/download/details.aspx?id=10725)에 기술되어 있다.
[그림 1] RTF Control word 인 Listoverridecount 대한 마이크로소프트사의 설명
예를 들어 “ListoverridecountN”의 N값이 25일 경우 25*8byte 만큼만 힙(Heap)에 쓸 수 있는 공간이 생성되지만, “lfolevel”의 개수가 25를 넘으면 다른 모듈에서 사용하는 구조체의 데이터에 값을 덮어쓰게 되어 힙 오버플로우가 발생한다.
익스플로잇(Exploit) 동작 원리 상세 분석
지금부터 아래와 같은 샘플을 통해 악성 RTF 파일이 이용하는 취약점의 동작원리에 대해 상세 분석을 진행하겠다.
• 분석대상 샘플 파일의 MD5: a2fe8f03adae711e1d3352ed97f616c7
분석 환경
• OS: Windows 7
• MSCOMCTL.OCX (version 6.01.9834)
악성 샘플이 최초로 이동하는 주소와 공격에 이용되는 ROP 체인(Chain)의 주소는 MSCOMCTL.OCX Version 6.01.9834에 맞추어져 있기 때문에 Windows XP MSCOMCTL.OCX 에서는 악성 쉘코드(Shellcode)가 동작하지 않는다.
[그림 2] MSCOMCTL.OCX Version 6.01.9834 파일이 가상메모리에 로드 된 상태
• Application: MS Word 2010
• wwlib.dll (version 14.0.7113.5001)
이 취약점은 MS Word 2010 version 14.0.7113.5001 이하 하위 버전에서도 RTF 파싱 하는 모듈에서 “lfolevel”를 카운트 하는 인덱스의 최고 값을 검증하지 않기 때문에 힙 오버플로우가 발생하는 것을 확인할 수 있다.
[그림 3] wwlib.dll ver..14.0.7113.5001 모듈이 가상메모리 공간에 로드 된 상태
취약점 상세 분석
“listoverridecount” 16(0x10) 이상이고, “lfolevel”의 개수가 16보다 크면 취약점이 발생하는 조건에 충족한다.
[그림 4] RTF 파일 내부의 취약점을 발생시키는 위치
지금까지는 샘플 파일 자체에 대한 정적 분석 내용을 소개했다. 이제부터 디어셈블리 과정을 통해 동적 분석 과정과 결과를 살펴보자.
• 취약점이 발생하는 모듈인 RTF 메인 파서
• WWLIB.DLL Base Address: 0x64E60000
• RTF 메인 파서 시작 주소: BA(0x64E60000) + RVA(0x63BAFF) = VA(0x6549BAFF)
[그림 5] RTF 메인 파서
아래 [그림 6]과 같이 0x654918A2 주소에서 [EBP-0x2E04] 값 0x19(25)를 스택에 입력하고, 0x654918B4 에서 힙(Heap) 공간에 0x19*8 크기의 데이터를 쓰는 메인 파서 주소(0x6549BAFF)로 이동한다.
[그림 6] listoverridecount N 값을 입력 받음
[그림 7]과 같이 EAX에 힙 버퍼의 인덱스를 로드하고, 0x0F와 N(0x19)를 And 연산하여 취약점이 발생하는 16(0x10) 보다 큰 값인 N(0x19) 입력되었다면 0x654A31DF 주소로 이동한다.
만약, N 값이 16(0x10) 이하이면 취약점이 발생하지 않는다.
[그림 7] listoverridecount N 값이 16이상 확인
악성 행위가 발생하는 시점인 아래 [그림 8]은 힙 오버플로우 취약점을 발생시키는 단서가 되는 중요한 코드이기 때문에 CPU 레지스터(Register) 값을 보면서 단계별로 기술하겠다.
[그림 8] lfolevel 개수만큼 카운트를 증가시킴
[그림 9]의 0x654A31F9 에서는 [EAX+80FC] 값을 EAX에 저장한다. EAX 값은 파싱 된 RTF 정보의 구조체 포인터를 저장하는 위치이고, 0x80FC 는 오프셋(offset)을 의미한다.
[그림 9] RTF 정보의 구조체 포인터를 저장
[그림 10]과 같이 INC EDI 를 증가시키며 루프 돌면서 구조체 포인터 주소에 N*8byte 크기만큼 데이터를 힙에 기록한다. 이 부분에서 취약점이 발생하는 이유는 “listoverridecountN” 값이 0x19 로 입력되었기 때문에 0x19 만큼의 공간만 할당을 해두었는데 EDI의 개수가 0x19 보다 크기 때문에 할당된 영역을 벋어난 다른 모듈의 구조체를 덮어쓰게 되기 때문이다. 인덱스(Index) 값으로 사용되는 EDI의 개수는 “lfolevel”의 개수와 같다.
[그림 10] lfolevel에 있는 값을 힙 공간에 기록
[그림 11]에서 첫 번째 “lfolevel” 값이 null이기 때문에 [그림 10] 덤프(Dump) 영역에 아무런 데이터가 입력되지 않는다. 27번째 “lfolevel” 값부터는 힙 공간에 8byte 크기의 데이터를 입력한다. 이 부분에서 취약점이 발생하는데 “listoverridecountN”의 값이 25(0x19) 값만 할당된다. 그런데 27번째 “lfolevel” 데이터를 쓰는 공간에는 다른 API가 사용하는 구조체 포인터가 저장되어 있는 공간을 덮어쓰게 된다.
[그림 11] RTF파일 내부에 취약점을 발생시키는 부분
[그림 12]와 같이 EDI 인덱스 값이 29번째(0x1D) 값이 되어 다른 API에서 정상적으로 사용하는 구조체 부분을 덮어쓰게 된다.
[그림 12] lfolevel 의 0x1D 번째 값을 입력하는 부분
[그림 13]은 정상 적인 구조체의 데이터가 존재하는 모습이다. [그림 12]의 0x654A3207 주소에서 함수가 실행되고 나면 [그림 14]와 같이 악성 행위를 하기 위해 조작된 데이터가 구조체 공간을 덮어 쓰게 된다.
[그림 13] 힙 오버플로우 전 정상 구조체가 입력된 모습
[그림 14] 힙 오버플로우 발생 후 정상 구조체를 의도한 값으로 덮어쓴 후의 모습
0x02E617E0 주소에 있는 0x034F0150 주소로 이동하면 [그림 15]와 같이 정교하게 조작된 공격자가 의도한 데이터가 입력되어 있는 것을 볼 수 있다. 아래와 같은 데이터가 입력되고 나면 0x034F0154 주소에 0xE8485A27이 입력 되고, 이를 리틀 엔디언(Little-Endian)으로 변경하면 이 된다.0x275A48E8이 된다.
[그림 15] lfolevel의 변수를 Heap Overflow한 공간에 저장한 모습
0x64E8C19C 주소에서 [ECX+4]의 값은 이 되고 악성 쉘코드의 시작 주소인 0x275A48E8 주소로 EIP가 이동한다. 0x275A48E8이 되고 악성 쉘코드의 시작 주소인 0x275A48E8 주소로 EIP가 이동한다.
[그림 16] MACOMCTL.OCX 내부 주소인 0x275A48E8로 이동하는 CALL
0x275A48E8 주소는 MSCOMCTL.OCX 모듈의 내부 주소이다. 공격자가 의도한 대로 쉘코드가 실행되려면 MSCOMCTL.OCX 모듈의 0x275A48E8 주소에는 [그림 17]과 같은 OPCode가 존재해야만 악성 쉘코드가 성공적으로 실행된다.
본 문서에서 사용하는 MSCOMCTL.OCX 모듈의 버전은 Windows 7에서 사용하는 버전 6.01.9834 이기 때문에 0x275A48E8 위치에서는 [그림 17]과 같이 OPCode가 존재하여 악성 쉘코드가 성공적으로 실행된다. 하지만 Windows XP에서는 wwlib.dll 모듈 내부의 취약점은 발생은 성공하지만, MSCOMCTL.OCX 에서 공격자가 의도한 대로 OPCode가 실행되지 않기 때문에 악성 행위는 발생하지 않는다.
[그림 17] MACOMCTL.OCX 내부 주소인 0x275A48E8 에 존재하는 OPCode
아래 [표 1]은 취약점이 발생하여 쉘코드를 성공적으로 실행할 수 있도록 정교하게 만들어진 29번째 “lfolevel” 데이터 보여주고 있다. 아래 조작된 데이터가 어떻게 [그림 15]와 같이 0x02E617E0 공간에 공격자가 의도한 주소인 0x275A48E8과 데이터를 입력하였는지 알아보자.
[표 1] MSCOMCTL 모듈 내부의 0x275A48E8 로 EIP를 이동시키기 위해 정교하게 조작된 데이터
아래 [그림 18]의 0x7B7B 를 입력하기 위해서는 “levelstartat31611” 값을 이용한다. 파라미터 31611을 HEX 값으로 변환하면 0x7B7B가 된다.
[그림 18] levelstartat31611 값이 0x7B7B로 저장
[그림 19]의 0xE8을 입력하기 위해서는 “levelnfcn232” 값을 이용한다. 파라미터 232를 HEX 값으로 변환하면 0xE8이 된다.
[그림 19] levelnfcn232 값이 0xE8로 저장
[그림 20]에서 0x48을 입력하기 위해서는 “levelnfcn1”, “levelnorestart1” 값을 이용한다. 48을 bit로 표현하면 0100 1000으로 표현되고, “levelnfcn1”의 값은 0x40(0100 0000) 값으로 쓰이고, “levelnorestart1” 값은 0x8(0000 1000)값으로 사용된다.
[그림 20] “levelnfcn1”, “levelnorestart1” 이 0x40으로 저장
[그림 21]에서 0x5A 0x27 0x89 0x64 0x58 0x27 0xEF 0xB8 0x58을 입력하기 위해서는 “levelnumbers” 의 컨트롤 워드 5A 를 이용한다. 컨트롤 워드인 0x5A가 입력되면 프로그램은 [그림 22]에 있는 0x27 0x89 0x64 0x58 0x27 0xEF 0xB8 0x58 데이터를 읽어 들여 힙 공간에 기록한다.
[그림 21] levelnumbers의 값으로 0x27 0x89 0x64 0x58 0x27 0xEF 0xB8 0x58로 저장
[그림 22] RTF 파일의 levelnumbers의 데이터
[그림 23]의 0x27을 입력하기 위해서는 “levelfollow39“ 값을 이용한다. 파라미터 39를 HEX 값으로 변환하면 0x27이 된다.
[그림 23] levelfollow39가 0x27로 저장
[그림 24]의 0x5959를 입력하기 위해서는 “levelspace22873“ 값을 이용한다. 파라미터 22873를 HEX 값으로 변환하면 0x5959 가 된다.
[그림 24] levelspace22873이 0x5959로 저장
[그림 25]의 0x5A5A를 입력하기 위해서는 “levelindent23130“ 값을 이용한다. 파라미터 23130를 HEX 값으로 변환하면 0x5A5A가 된다.
[그림 25] levelindent23130이 0x5A5A로 저장
쉘코드(ShellCode) 동작 분석
취약점 공격이 성공적으로 완성되고 나면 쉘코드 내용은 샘플마다 바뀔 수 있기 때문에 쉘코드 동작에 대한 상세한 분석은 지양하고 공격자가 의도한 주소인 0x275A48E8로 이동하여 악성행위를 하는 과정을 간략하게 살펴 보겠다.
MACOMCTL 모듈 내부에서 ROP 체인을 이용하여 쉘코드가 동작할 공간을 생성한다.
[그림 26] ROP 체인 값이 Stack에 저장됨
쉘코드가 동작할 공간을 만들기 위해서 Kernel32.dll.VirtualAlloc API를 이용하여 0x40000000 공간부터 0x100000공간 만큼의 Heap 공간을 실행 가능한 공간으로 생성한다.
[그림 27] ROP를 이용하여 쉘코드가 실행할 공간 생성
ROP 체인(Chain)은 쉘코드가 실행할 수 있는 공간을 만들고, 0x40000000 주소 영역에 쉘코드를 기록한다. 쉘코드 기록이 끝나면 EIP를 0x4000040 주소로 이동하여 본격적인 악성행위를 시작한다.
[그림 28]의 쉘코드는 전형적인 쉘코드 시작 단계로 kernel32.dll 주소를 가져오는 기능을 한다.
[그림 28] 쉘코드 시작 부분
“C:\Windows\SoftwareDistribution\ReportingEvents.log” 파일을 읽어 온다. 최신 업데이트된 날짜가 특정 날짜 이후의 값을 갖고 있으면 악성 행위를 중단하고 프로세스를 종료한다.
[그림 29] 쉘코드가 CreateFileA 를 이용하여 ReportingEvents.log 정보 확인
[그림 30] ReportingEvents.log 파일 내용
ReportingEvents.log 파일을 읽어 최신 보안 패치 로그가 2014년 4월 8일 이후 값이 존재하면 악성 행위를 중단하고 프로세스를 종료한다.
[그림 31] 2014년 4월 8일 이후 업데이트 확인
쉘코드 내부에서 API를 호출(Call)할 때마다 안티 디버깅 기능을 사용하여 행위분석 자동화 툴을 우회하는 로직을 실행한다.
[그림 32] 쉘코드 내부의 안티 후킹 코드
PEB 구조체의 BOOLEAN BeingDebugged 멤버의 값을 확인하여 디버깅을 당하고 있는지의 여부를 확인하여 디버깅하고 있다고 판단되면 프로세스를 종료한다.
[그림 33] PEB 구조체의 BOOLEAN BeingDebugged 멤버의 값
디버깅 상태로 실행되면 pushFD가 제대로 실행이 되지 않아 프로그램이 정상 동작하지 않는다. 이것은 쉘코드에서 주로 쓰는 안티 디버깅 기법이다. 이외에도 쉘코드 내부에 다른 여러 가지 안티 디버깅 기능들이 존재하지만 자세한 설명은 생략한다.
[그림 34] 안티 디버깅 코드
상기 안티 디버깅 기능들을 우회하면 쉘코드 디코딩 과정을 거쳐 최종적으로 템프(temp) 디렉터리에 svchost.exe(md5: cf89602d6cd2327bb7fa2627e5d46052) 파일을 드롭(Drop)한 후 실행하고, 정상 문서를 오픈 하여 사용자를 속이는 로직을 실행한 후에 오피스 버전의 레지스트리 값을 삭제하고 프로세스를 종료한다.
[그림 35]CreateFileA API를 이용하여 svehost.exe 파일을 생성
[그림 36] Office에서 사용하는 레지스트리 삭제
제로데이 취약점과 안랩 트러스와처
지금까지 제로데이 취약점(CVE-2014-1761)이 발생하는 원리와 공격자의 의도에 따라 어떻게 악성 행위가 발생하는지에 대해 자세히 알아보았다. 앞서 언급했듯이 이 취약점이 발견된 것은 지난 3월이며, 4월 MS 정기 보안업데이트에 패치가 발표되었다. 해당 애플리케이션 사용자는 한 달이라는 기간 제로데이 공격의 위협에 무방비 상태로 노출된 것이다. 이렇듯 제로데이 취약점이 발견되면 보안 패치가 발표되기 전까지 위험에 그대로 노출될 수 밖에 없는 것일까.
제로데이 공격에 대응하기 위해서는 취약점이 발생하여 쉘코드를 실행시키기 전에 이를 탐지함으로써 악의적인 행위가 발생하지 못 하도록 해야 한다. 그러나 일반적인 행위기반 탐지 기술로는 이러한 공격을 탐지하는데 한계가 있다. 앞서 살펴본 바와 같이 최근의 고도화된 악성코드는 타깃 시스템을 살핀 후 공격이 성공하기 어려운 조건이 확인되면 어떠한 행위도 하지 않기 때문이다. APT와 같은 고도화된 공격에 대응하기 위해서는 행위기반 탐지 기술 뿐만 아니라 최신 공격 동향에 최적화된 탐지 기술이 추가로 필요하다.
안랩의 APT 사전 대응 솔루션인 ‘안랩 트러스와처(영문명 AhnLab MDS)는 행위기반 탐지 기술뿐만 아니라 최근 증가하고 있는 문서 파일을 이용한 비실행형(non-PE) 파일 탐지에 최적화된 동적 콘텐츠 분석(DICA: Dynamic Intelligent Content Analysis) 기술이 적용되어 있다. 트러스와처의 DICA 기술은 알려지지 않은 취약점이 익스플로잇되어 악의적인 행위를 수행하기 위해 ROP 체인의 쉘코드가 동작하도록 준비하는 시점([그림 26])에 이를 탐지하고 무력화시키는 'ROP 탐지(ROP Detector)' 기능을 제공한다. 또한 악성 행위가 시작되는 시점, 즉 EIP가 쉘코드에 위치한 지점으로 이동하는 순간([그림 28])에도 탐지가 가능해 악성 행위의 발현 여부와 상관없이 공격 시도를 억제할 수 있다. 이 밖에도 조직 네트워크 영역에서 실시간으로 탐지된 신종 악성코드를 삭제하는 기능은 물론, USB 또는 암호화 트래픽을 통해서 유입된 악성코드의 실행을 차단하고 분석하는 ‘실행 보류(execution holding)’ 기능을 제공해 잠재적인 위협까지 사전 대응이 가능하다.
한편 안랩은 트러스와처에 취약점 발현 단계에서 이를 탐지하여 차단할 수 있는 기술을 연내 적용할 예정이다. 이를 통해 제로데이 등 알려지지 않은 공격을 완벽하게 차단할 수 있을 뿐만 아니라 공격에 대한 상세한 분석 정보를 제공해 더욱 강력한 APT 대응이 가능할 것으로 기대되고 있다. @
- AhnLab네트워크 개발실 임차성 선임연구원