Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

윈도우 커널 익스플로잇

240 views

Published on

윈도우 커널 익스플로잇에 대해서 알아보자.

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

윈도우 커널 익스플로잇

  1. 1. Windows-Kernel-Exploit 1. 개요 1.1. 왜 터널인가. 난이도가 높다. DEP, ASLR, UAC, Heap checks, Protected Mode, Sandboxes 등을 유저 모드에 도입하여 유저 모드와 난이도가 비슷하 거나 오히려 더 쉬울 수도 있다. 유저 모드 보안과 병행한 많은 OS 기능이 커널로 이동하여 새로운 사용자 - 커널 인터페이스가 도입된 후 커널의 공격 영역이 크게 증가했다. 1.2. 무엇이 다른 가 잘못하면 블루스크린이나 시스템 재부팅으로 이어질 수 있다. 사용 권한에 대해 걱정할 필요가 없다. 더 많은 지식이 필요하다. 프로세스 경계가 없으므로 더 많은 것을 해볼 수 있다. 1.3. 필요 배경지식 Kernel APIs Memory Layout Interrupts, IRQLs, DPCs, IRPs Synchronization: Events, Spinlocks, Mutexes, Timers, Semaphores, Resources Paging mechanism Intel System Architecture Device Driver structure, MJ functions, IOCTLs 1.4. 취약점 연구 먼저 하이 레벨 접근법부터 시작하자 복잡한 로직을 찾는다. 서로 다른 팀의 협력이 필요한 인터페이스가 취약하다(예를 들어 사용자와 커널 간의 상호 작용이 필요한 부분) Remote 취약점보다 권한 상승이 더 쉽다. 여러 약한 공격이 하나의 강력한 공격으로 구성될 수 있다. 2. 본론 2.1. 취약점 발견을 위한 세 가지 접근법
  2. 2. 1. 하이 레벨 접근법 전체 시스템의 동작 과정을 이해하고 취약한 부분을 찾는다. 2. 로우 레벨 접근법 복잡해보이는 함수를 찾고 분해하며 버그를 찾는다. 3. 퍼징 접근법(블랙 박스 / 무차별 대입) 가능한 모든 유형의 값을 삽입하여 충돌을 유발시키고 충돌 정보를 살펴본다. 2.1.1. 하이 레벨 접근법 사용자 가이드, 레퍼런스 문서, 버그 리포트 등 분석 대상에 대해서 알 수 있는 정보들을 숙지한다. “이 기능을 개발하려면 어 떻게 해야할 까”를 고민하며 발생 가능한 보안 위협에 대한 항목을 설정하고 해당 코드를 검색한다. 2.1.2. 로우 레벨 접근법 코드에서 논리적으로, 기술적으로 복잡한 기능이나 연산을 분류한다. 분류한 기능의 코드 분석 혹은 역공학을 통해 버그를 찾 는다. 버그가 발견되면 공격 가능한 방법이 있는 지 파악한다. 2.1.3. 퍼징 접근법(블랙박스/무차별 대입) 모든 코드 내 존재하는 입력 필드를 분류한다. 분류된 입력 필드에서 CRC, Length와 같은 계산된 데이터를 포함하는 구조를 찾는다. 입력 테스트를 진행한다. 잘못 다뤘을 가능성이 있는 입력 사항을 생각 입력을 생성하는 스크립트 작성 퍼징 스크립트 수행 2.2. 버그 2.2.1. Common bugs Buffer overflows (stack and pool) NULL dereference Faulty input validation 2.2.2. Less common bugs Use-after-free Direct calling to User code Logical bugs 2.2.3 Exploit 개발 Exploit 개발을 위해서는 다음에 대한 지식이 필요하다 Constant memory addresses Memory layouts Heaps, Pools, Stacks APIs, Objects CPU
  3. 3. Assembly 커널 모드에서는 프로세스 영역이 없으므로 모든 곳을 할 수 있다. 2.3. Example 2.3.1 User-after-free 이미 해제된 오브젝트를 엑티브 오브젝트 링크드 리스트에 유지하는 버그. 오브젝트들이 접근하기 전, 해제된 버퍼에 임의의 데이터를 삽입. 1. 드라이버 버그를 이용하여 CPU 기아현상을(기아현상 : 시스템 부하가 많아서 준비 큐에 낮은 등급 프로세스가 무한정 대기 하는 현상) 발생시켜 다른 오브젝트가 버퍼를 가로채지 못하게 한다. 2. 적절한 크기의 버퍼를 할당하고 데이터를 복사하는 두 번째 드라이브에서 DPC(Deffrred Procedure Call) 코드를 사용한다. 3. 해제된 오브젝트를 사용하는 티켓 드라이버의 코드를 활성화하여 쉘 코드가 실행되도록 한다. 2.3.2 DRM 커널 Exploit은 아니지만 커널과 유저 간 인터페이스 사용의 좋은 예가 된다. - 유저와 커널 사이의 안전하지 못한 인터페이스(서로 다른 팀의 협력이 필요한 인터페이스가 취약하다) 2.3.2.1. 영화 복사 방지 DRM 예시 지정된 컴퓨터 외 다른 컴퓨터에서는 재생되지 않게 하는 DRM 시스템을 예로 알아보자. 조건 모든 영화는 암호화되어 있다. 복호화 코드는 영화 내부에 내장되어 있다. 하드웨어를 기반으로 컴퓨터 별 라이센스가 부여된다. 하드웨어에 접근하기 위해서는 커널 코드가 필요하므로 영화 내 복호화 코드가 드라이버를 호출한 후 사용자 코드를 다시 호출하게 된다. Hook DeviceloControl 1. try … catch 문에서 드라이버 호출 대신 유저 모드 코드를 호출하게 한다. 2. BIOS를 읽거나 하드웨어에 접근하는 경우 예외를 발생시키는 데, 특정 파일에서 내용을 읽어들이는 것으로 예외 처리를 한다. 이를 이용하여 모든 컴퓨터에서 동일한 하드웨어를 인식하도록 속여 동일한 라이센스를 다른 컴퓨 터에서 이용할 수 있게 한다. 2.4. Tip 임의의 데이터 삽입 : 덮어쓰기 좋은 위치 고정 주소가 있는 일반 장소(OS 빌드 별) 콜백 함수 포인터 데이터 세그먼트 변수 GDT / LDT 표 (http://j00ru.vexillium.org/?p=290) Dispatch Table (새로운 OS에서는 차단됨) 우리는 BIOS / 하드웨어 대신 파일에서 데이터를 읽어오는 것으로 예외를 처리 하므로 동일한 하드웨어로 인식 하도록 속여 동일한 라이센스를 사용할 수 있다. 비 고정 주소 추출 Windows Handle Table은 유저 주소 공간에 매핑되어 함수 포인터가 있는 객체에 대한 커널 포인터를 포 함한다. (http://www.mista.nu/research/mandt- win32k-paper.pdf)
  4. 4. 1. Kernel window 생성 (win32k가 생성하고 등록한 window 클래스로 커널 안에 window 프로시저가 Menus 및 Tooltips과 같이 있음) 2. Handle Table에서 Kernel window 객체에 대한 포인터를 가져온다. 3. WndProc 포인터를 덮어쓴다. 4. WndProc 포인터를 트리거하는 메시지를 window에 보낸다. 유저 공간으로 전달되는 다른 커널 포인터. 일부 win32k.sys 시스템 호출은 VOID 또는 USHORT로 정의되고 반환 값에서 전체 또는 일부의 커 널 포인트를 노출한다. (http://j00ru.vexillium.org/?p=762) 블루스크린은 끝이 아니다. Exception 이후에 실행되는 많은 코드가 있으며 그 중에는 덮어쓸 수 있는 콜백 함수를 호출하는 경우가 많다. 특 히 ACCESS_VIOLATIONS의 경우 page-fault handler로 먼저 이동하므로 많은 공격 옵션이 존재한다. KeBugCheck 내부에도 덮어 쓸 수 있는 콜백이 존재한다. 컨텍스트를 수정하고 정상 실행를 하는 것은 까다로운 일이지만, 할 수 있습니다. WOW64 processes 64비트 시스템에서 32비트 프로세스를 실행할 때, NtQuerySystemInformation을 호출하려고 하면 리턴된 모든 포인터가 32비트로 잘린다. 내장된 call gate를 사용하여 일시적으로 64비트로 전환하여 NtQuerySystemInformation을 호출 한 다음 32 비트 코드로 돌아와 극복 할 수 있습니다. (자세한 내용은 http://vxheavens.com/lib/vrg02.html 참조) 64 비트 TEB는 gs : 0에 매핑되어 있기 때문에 64 비트를 모두 switching하지 않고 직접 액세스 할 수 있다. 2.5. 커널에서 사용자까지 많은 경우 커널 Exploit은 유저 모드 페이로드 설치 나 유저모드 코드의 실행이 필요하다. 하지만 시스템 권한으로 실행되는 커널에서 유저 모드 코드를 실행한다는 것은 매우 어려운 일이다. 이제 몇 가지 기법과 각 기법의 장단점을 살펴보자. Changing the process token 방법 : 이미 제어하고 있는 프로세스 토큰을 시스템 토큰으로 변경한다. (예를 들어 Exploit을 시작한 프로세스) 장점 : 구현 방법이 쉽고 안정적이다. 단점 : 유저 모드 코드로 모든 까다로운 작업을 수행해야하므로(예를 들어 시스템 프로세스에 코드 삽입) 사용자 API를 후킹하는 백신 프로그램 혹은 보안 프로그램에 취약하다. User-mode APCs 방법 : 시스템 프로세스의 스레드를 대상으로 유저 모드 APC를 준비시킨다. 장점 : 원하는 위치로 바로 이동이 가능하며, 시스템의 모든 프로세스에 삽입이 가능하다. 단점 : Alertable 상태에 있는 스레드만 대상으로 지정할 수 있으며 이를 찾을 수 있는 일반적인 방법은 없다. 스레 드를 Alertable 상태로 강제하는 방법이 있지만, 대기 상태가 중단되고 대기 함수가 중간에 리턴되어 시스템이 불 안정해지거나 충돌을 일으킬 수 있다. 관련 구조에 대한 문서화가 아직 이루어지지 않았으며 OS 버전 마다 차이 가 있다. 그러므로 잘 알고 있는 스레드를 댓ㅇ으로 하지 않는 한, 스레드가 대기 상태(예를 들어 LoaderLock)로 들어갈 때 스레드가 일부 Lock을 holding하고 있으면 이 방법을 사용 시 교착 상태가 발생할 수 있다. Thread Hijacking 방법 : 시스템 프로세스에서 기존 스레드의 컨텍스트를 변경하여 삽입한 코드를 실행한다. 장점 : 원하는 위치로 바로 이동이 가능하며, 시스템의 모든 프로세스에 삽입이 가능하다. 단점 : 컨텍스트 복원이 매우 어려울 수 있다. 임의의 스레드를 Hijacking하는 것은 교착 상태, 불안정성 또는 충 돌을 일으킬 수 있다. Creating a new thread 방법 : 시스템 프로세스에 새로운 유저 모드 스레드를 생성한다. 장점 : 거의 완벽한 해결책으로 어떤 위험이나 컨텍스트 문제 없이 원하는 위치로 정확하게 이동할 수 있으며, 시 스템의 모든 프로세스에 삽입이 가능하다. 단점 : 구현하기가 매우 어렵다. 새 스레드가 작동하려면 CSRSS에 등록해야 한다. API와 구조가 복잡하고 문서 화되어 있지 않으며 Windows 업데이트로 끊임없이 변경되고 있다.
  5. 5. API hooking 방법 : 호출될 것임을 알고 있는 유저 모드 API 또는 시스템 프로세스 내에서 호출되도록 할 수 있는 유저 모드 API hooking. 장점 : 시스템 프로세스에 직접 삽입 가능하며 매우 안정적이다. 단점 : 적절한 API를 찾아내는 것이 어렵다. 이 방법은 일반적이지 않고 대상 API를 자주 호출하는 시스템 프로세 스에서만 적합하다. 간단한 커널 Exploit 예제 - AFD.SYS AfdGetRemoteAddress를 살펴보자. 어떤 문제가 존재하는 가. // Attacker controls OutputBuffer and OutputBufferLength void IOCTL_handler(...) { [...] try { ProbeForWrite (OutputBuffer, OutputBufferLength, sizeof (UCHAR)); RtlCopyMemory( OutputBuffer, (PUCHAR)context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset, endpoint->Common.VcConnecting.RemoteSocketAddressLength ); } except( AFD_EXCEPTION_FILTER(&status) ) { } [...] } 힌트 : 실제 포인터와 무관하게 길이가 0인 경우에 대해 ProbeForWrite에서 예외(throw) 처리를 하지 않는다. 우리는 커널 주소를 포함한 원하는 주소에 우리가 원하는 값을 쓸 수 있지만 실제로 제어할 수는 없다. 작성된 데이터는 다음 과 같다. - 02 00 XX XX YY YY YY, 여기서 XXXX는 포트, YYYYYYYY는 IP이다. 함수가 동작하려면 활성 TCP 연결이 있어야한다. 무엇을 해야 하는가? 데이터를 완전히 제어 할 필요가 없는 것은 아닐까. 우리는 127.0.0.1, 즉 7F 00 00 01에 연결할 수 있다. Port 445(01 BD)는 Windows 컴퓨터에서 항상 열려 있다. 01 BD 7F 00 00 01? 인텔은 Little Endian이므로 0x00007FBD가 된다. 이제 덮어 쓸 포인터가 필요하다. 이것은 XP에서만 작동하는 오래된 버그이 므로 Dispatch Table을 사용할 수 있다. Exploit 1. 0x7fb0에 페이지를 할당하고 쉘코드를 복사한다. 2. HookAddress = ZwQueryIntervalProfile에 대한 Dispatch Table entry 3. 127.0.0.1:445에 연결(connect()). 4. DeviceIoControl(HANDLE)sock, 0x1203F, NULL, 0, (PVOID)(HookAddress - 3), 0, &Result, NULL) 5. ZwQueryIntervalProfile() Background Window Class를 등록할 때, OS에게 윈도우 객체와 함께 Extra Bytes를 저장하도록 요청할 수 있다. Extra Bytes는 커널 의 WND Struct에 추가된다. ​ |WND Struct|Extra Bytes| 일부 window types(Menus, Tooltips)는 커널에서만 접근할 수 있는 private data가 있다. ​ |WND Struct|PrivateData|Extra Bytes| Extra bytes의 데이터를 변경하기 위해서 value와 Extra Bytes index와 함께 SetWindowLongPtr 함수를 호출한다. 함수는 제공된 index가 private data 또는 user extra bytes에 속하는 지 확인한다. 만약 private bytes에 속한다면 함수
  6. 6. 가 실패하므로, private 커널 데이터를 변경할 수 없다. ​​ |WND Struct|(X)PrivateData|Extra Bytes| SetWindowLongPtr 함수는 전체 allocated bytes size(WND struct + private)가 있는 window types Table을 사용하여 private data 내에 속하는 지 확인한다. “Window type”은 하드 코딩된 값 목록(해당 클래스가 아닌)에서 window type 의 실제 식별자인 FNID를 나타낸다. 확인을 위한 pseudo code는 다음과 같다. if (index < (int)(window_class_alloc_sizes[fnid]-sizeof(WND)))) FAIL; Window Type(FNID) allocated bytes Menu 0xa4 (WND size) + 4 (private bytes) == 0xa8 Tooltip 0xa4 (WND size) +4 (private bytes) == 0xa8 The bug.part1 문서화되지 않은 unexported 함수인 RegisterClassExWOWW에 internal window type과 extra bytes에 대해 음수를 사용하면 테이블을 임의의 값으로 덮어쓰는 것이 가능하다. 이 버그는 extra bytes 값을 검증하지 않아서 발생한다. ​Window Type(FNID) allocated bytes Menu 0xa4 (WND size) + (-0xa8) (private bytes) == -4 Tooltip 0xa4 (WND size) +4 (private bytes) == 0xa8 할당된 바이트 수를 음수로 덮어쓴 테이블을 사용하면 검증 로직 우회가 가능하다. (index < (int)(window_class_alloc_sizes[fnid]-sizeof(WND)))) == always FALSE 우리는 이를 이용하여 인덱스를 0으로 설정하고 SetWindowLongPtr함수를 호출하여 window private kernel data를 수정할 수 있다. |WND Struct|(V)Private Data|Extra Bytes| The bug.part2 이제 private kernel data에서 덮어썼을 때 유용하게 사용할 수 있는 window type을 찾아야 한다. Menu window type은 구조체에 대한 포인터를 저장하고 window가 제거된 동안 해당 구조체의 포인터가 NULL로 설정되어 시스템의 32/64 비트 값을 NULL로 지정할 수 있습니다. (Bingo!) Menu window private 구조는 윈도우즈 버전에 따라 변경되므로 우리는 Exploit을 두번 수행한다. 포인터가 non-NULL인 배열의 포인터를 가진 구조체에 처음 덮어 쓰므로 NULL이 있는 offset을 찾을 수 있다. 두 번째로 NULL에 삽입할 주소를 가진 포인터와 첫 번째 단계에서 발견 된 offset을 뺀다. 이제 쉘 코드를 페이지 0에 할당하고 함수 포인터를 덮어 쓴 후 호출한다. Flow 1. Find the address of RegisterClassExWOWW using diStorm 2. RegisterClassExWOWW() passing the FNID for a Menu and a WNDCLASSEX structure with a negative number for the extra bytes 3. CreateWindow() 4. SetWindowLongPtr() with a non-NULL array 5. DestroyWindow() 6. Find offset 7. Repeat steps 3-5, this time passing the actual address to overwrite minus the offset 8. Get the overwritten pointer to be called 2.6. Windows8 특징
  7. 7. Null-dereference is blocked – first 64k can’t be allocated New integrity checks to the kernel pool memory allocator (see http://blogs.msdn.com/b/b8/archive/2011/09/15/protecting-you-from-malware.aspx) Improved Linked-Lists security to protect against corrupted/dangling list pointers (see http://www.alex- ionescu.com/?p=69) 본문에서 사용된 Kernel Exploit POC Code : ExploitDB

×