Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2023-28252 — CVE-2023-28252에 대한 기술 분석 및 개념 증명 익스플로잇, Nokoyawa 랜섬웨어 공격에 사용된 Windows Common Log File System (CLFS) 드라이버 권한 상승 취약점입니다. | Kitploit
도구/GitHubGitHub/fortra/cve-2023-28252
Privilege EscalationMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringDebuggersBinary Exploitation
GitHubfortra/cve-2023-28252

CVE-2023-28252

CVE-2023-28252에 대한 기술 분석 및 개념 증명 익스플로잇, Nokoyawa 랜섬웨어 공격에 사용된 Windows Common Log File System (CLFS) 드라이버 권한 상승 취약점입니다.

저장소 보기
1844423년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

2022년 2월부터 Trend Micro의 연구에 따르면 Windows 0-day 취약점을 사용하는 것으로 보이는 새로운 랜섬웨어가 보고되었습니다.
이 랜섬웨어에 대한 자세한 정보는 이 링크에서 확인할 수 있습니다.
Kaspersky의 분석에 따르면 Nokoyawa 랜섬웨어 그룹은 2022년 6월부터 CLFS(Common Log File System) 드라이버를 대상으로 하는 유사하지만 뚜렷한 특징을 가진 다른 익스플로잇을 사용했으며, 모두 단일 익스플로잇 개발자와 연결되어 있습니다.
2023년 4월 Microsoft가 패치를 출시했을 때 CVE-2023-28252가 할당되었습니다.
이전에 2022년에는 동일한 구성 요소의 유사한 버그가 저희에 의해 연구되었으며, 이 블로그 게시물에 문서화되었습니다.

CLFS(Common Log File System) 파일 형식:

분석을 수행하려면 취약한 CLFS(Common Log File System) 드라이버인 CLFS.sys가 처리하는 .blf 파일 형식을 알아야 하며, 이 드라이버는 system32 내의 드라이버 폴더에 있습니다.

이 파일 형식에 대한 자세한 정보는 아래 링크에서 확인할 수 있습니다:

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

https://github.com/ionescu007/clfs-docs/blob/main/README.md

https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe

취약점:

이 분석은 Windows 11 21H2, clfs.sys 버전 10.0.22000.1574를 대상으로 하지만 Windows 10 21H2, Windows 10 22H2, Windows 11 22H2 및 Windows Server 2022에서도 작동합니다.

이전 Windows 버전에서는 일부 값을 조정해야 하며, 그렇지 않으면 BSOD가 발생합니다.

2023년 4월 Microsoft 패치 화요일.

컴퓨터 스크린샷 Description 자동 생성됨 (보통 신뢰도)드라이버 버전을 확인하는 방법은 위와 같습니다.

취약점이 공개되었을 때인 2023년 4월, 저는 Esteban Kazimirow와 함께 CLFS.sys 드라이버 리버싱을 시작했습니다. 하지만 이 경우 패치만 분석하는 것은 버그가 어디에 있는지, 어떻게 트리거하는지 추론하기 매우 어려웠습니다. 익스플로잇이 매우 복잡하기 때문입니다.

이후, 악성코드 샘플을 통해 HexRays가 디컴파일한 코드의 일부와 익스플로잇이 어디를 대상으로 해야 하는지에 대한 정보를 제공하는 블로그 게시물이 나왔습니다.

제공된 정보가 완전하지는 않았지만, 이 도움 없이는 PoC를 구축하고 나중에 작동하는 익스플로잇을 만드는 것은 불가능했을 것입니다.

이해를 돕기 위해 먼저 PoC를 구축하는 방법을 설명한 다음 취약점 분석을 수행하겠습니다.

이 블로그 게시물은 두 개의 섹션으로 구성되어 있습니다:

PoC 구축:

1- 익스플로잇에 필요한 커널 주소 얻기

2- .blf 파일을 생성할 경로 준비:

3- CreateLogFile() 함수를 사용하여 "트리거 blf" 파일 생성

4- "트리거 blf" 파일 조작

5- 트리거 blf의 BASE BLOCK 커널 주소 얻기

6- 트리거 blf의 핸들로 AddLogContainer 호출

7- 스프레이 blf 파일 준비

8- 스프레이를 수행할 메모리 준비

9- 버그 트리거

디버깅:

1- 메모리 스프레이 확인

2- 트리거 blf의 RecordOffset[12] 살펴보기

3- 스프레이 blf 파일의 iFlushBlock 값 확인

4- BLOCK 0 CONTROL 대신 BLOCK 1 SHADOW에서 읽는 이유?

5- 블프 스프레이 파일에서 체크섬이 0인 이유?

6- 익스플로잇 종료.

7- 실제 패치

PoC 구축:

1- 익스플로잇에 필요한 커널 주소 얻기

InitEnvironment라는 함수를 만들어 필요한 일부 커널 주소를 얻겠습니다.

내 프로세스의 EPROCESS 주소를 가져와 g_EProcessAddress 변수에 저장하고, SYSTEM 프로세스의 EPROCESS 주소를 가져와 system_EPROCESS에 저장합니다. 그런 다음 내 프로세스의 메인 스레드의 EHTREAD 주소를 가져와 g_EThreadAddress에 저장하고, 마지막으로 PREVIOUS MODE의 주소를 가져오지만 이 PoC 버전에서는 사용되지 않습니다.

컴퓨터 코드 스크린샷 Description 자동 생성됨 (낮은 신뢰도)

이 방법은 잘 알려져 있으며, GetObjectKernelAddress 함수는 첫 번째 인수 SystemExtendedHandleInformation으로 NtQuerySystemInformation을 두 번 호출합니다. 첫 번째 호출은 잘못된 크기로 전달되어 오류를 반환하지만, 두 번째 호출에 사용되는 올바른 크기도 반환하며 모든 핸들에 대한 정보를 얻습니다. 그런 다음 각 핸들의 정보를 루프로 검토하여 올바른 handleinfo의 Object 필드에서 커널에서 찾고자 하는 주소를 얻습니다.

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림 Description 자동 생성됨

또한 CLFS.sys에서 내보낸 다음 함수들의 커널 주소가 필요합니다:

• ClfsEarlierLsn

• ClfsMgmtDeregisterManagedClient

그리고 NTOSKRNL.exe에서 내보낸 함수들:

• RtlClearBit/PoFxProcessorNotification

• SeSetAccessStateGenericMapping

이러한 주소를 얻기 위해 두 모듈의 커널 베이스를 얻는 데 사용되는 것과 유사한 방법을 사용합니다. NtQuerySystemInformation을 두 번 호출하지만, 이번에는 첫 번째 인수가 SYSTEM_INFORMATION_CLASS가 됩니다(PoC에서는 이 목적으로 FindKernelModulesBase 함수를 사용합니다).

텍스트, 글꼴, 스크린샷, 줄이 포함된 그림 Description 자동 생성됨그런 다음 CLFS.sys와 NTOSKRNL.exe를 LoadLibrary를 호출하여 사용자 모드에서 일반 모듈로 로드하고, GetProcAddress로 사용자 모드 주소를 얻은 다음 각각에서 imagebase를 빼서 함수의 오프셋을 얻습니다. 마지막으로 각 오프셋을 해당 커널 베이스에 더하여 필요한 모든 함수의 커널 주소를 얻습니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림 Description 자동 생성됨

2- .blf 파일을 생성할 경로 준비:

createInitialTriggerBlfFile이라는 함수를 만들어 .blf 파일을 생성하고 씁니다.

CreateLogFile에 인수로 사용되는 경로는 일반 경로와 다릅니다. 예를 들어 C:\Users\Public 폴더에 있는 1280.blf 파일을 열려면 LOG:C:\Users\Public\1280 경로를 설정해야 합니다. 이는 stored_name_CreateLog 변수에 저장됩니다.

**wsprintfW()**를 사용하여 이를 수행합니다. stored_env는 환경 변수에서 이전에 얻은 경로 C:\Users\Public을 저장합니다. 이 문자열 앞에 LOG: 문자열을 추가하고 끝에 임의의 이름을 추가합니다(.blf 확장자 없이).

컴퓨터 스크린샷 Description 자동 생성됨 (보통 신뢰도)

이것이 제가 "트리거 blf"라고 부를 초기 파일의 경로입니다. 물론, 앞에 **LOG:**가 없고 BLF 확장자가 있는 동일한 파일의 일반 경로도 저장해야 합니다. 예: C:\Users\Public\1280.blf. 이 경로는 **CreateFile(), WriteFile()**을 사용하여 다른 파일과 마찬가지로 열고 수정하기 위한 것이며 stored_name_fopen 변수에 저장됩니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림 Description 자동 생성됨

물론 두 경로 모두 동일한 파일에 해당하며, 필요에 따라 하나를 사용해야 합니다.

3- CreateLogFile() 함수를 사용하여 "트리거 blf" 파일 생성

CreateLogFile 함수는 **CreateFile()**과 매우 유사한 기능을 수행합니다(새 파일을 만들거나 기존 파일을 열고 핸들을 얻음). 일부 인수도 유사하지만 **CreateLogFile()**은 blf 파일에서만 작동합니다.

또한 기존 파일을 열 때 형식이 올바른지 확인하며, 각 블록에 체크섬이 있고 올바르지 않으면 오류를 반환합니다.

2가지 종류의 BLF 파일을 만들겠습니다:

  1. 트리거 blf

  2. 스프레이 blf

둘 다 blf 파일이지만 다른 방식으로 수정됩니다.

컴퓨터 코드 클로즈업 Description 자동 생성됨 (낮은 신뢰도)이런 식으로 PoC는 먼저 CreateLogFile을 사용하여 "트리거 blf" 파일을 만듭니다. 경로는 예: 이전에 설정한 LOG:C:\Users\Public\1280이며 stored_name_CreateLog 변수에 저장되었습니다.

다섯 번째 인수 fCreateDisposition은 ***CreateFileA()***와 마찬가지로 다음 값을 가질 수 있습니다:

텍스트, 글꼴, 줄, 영수증이 포함된 그림 Description 자동 생성됨

이 경우 OPEN_ALWAYS 인수를 사용합니다. 따라서 파일이 없으면 생성되고 있으면 열립니다. 파일이 아직 없으므로 임의의 이름으로 생성됩니다.

logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);

**CreateLogFile()**은 6개의 블록과 해당 체크섬을 포함한 "트리거 blf" 파일을 생성하고, logFile 변수에 저장될 핸들을 반환합니다.

텍스트, 스크린샷, 글꼴, 숫자가 포함된 그림 Description 자동 생성됨

각 블록은 왼쪽 열에 표시된 오프셋부터 헤더를 가지며, 그 크기는 0x70바이트입니다.

예를 들어 CONTROL BLOCK의 헤더는 오프셋 0x0에서 0x70까지입니다.

컴퓨터 스크린샷 Description 자동 생성됨 (낮은 신뢰도)

모든 블록의 모든 헤더는 _CLFS_LOG_BLOCK_HEADER라는 동일한 구조를 가집니다.

다음은 헤더 구조입니다:

컴퓨터 스크린샷 Description 자동 생성됨 (보통 신뢰도)

헤더의 오프셋 0xC에서 체크섬을 찾을 수 있습니다. 따라서 CONTROL BLOCK이 오프셋 0부터 시작하므로 체크섬은 파일의 오프셋 0xC에 있으며, 각 블록은 해당 블록 시작부터 오프셋 0xC에 체크섬이 있습니다.

컴퓨터 스크린샷 Description 자동 생성됨 (낮은 신뢰도)

4- "트리거 blf" 파일 조작:

트리거 blf 파일을 수정하려면 CreateFileA 또는 fopen으로 일반 파일처럼 열고, 각각 WriteFile 또는 fwrite로 수정해야 합니다. 이 작업은 PoC의 fun_prepare 함수 시작 부분에서 수행합니다.

일반 경로는 stored_name_fopen 변수에 저장되어 있으므로 이를 사용하여 wfopen_s(유니코드 문자열을 지원하는 fopen의 변형)로 파일을 엽니다.

파일은 fun_prepare에서 호출되는 craftTriggerBlfFile 함수에서 수정됩니다.

텍스트, 줄, 글꼴, 스크린샷이 포함된 그림 Description 자동 생성됨

그런 다음 fseek를 호출하여 변경할 오프셋을 가리키고 fwrite로 파일을 수정합니다.

컴퓨터 스크린샷 Description 자동 생성됨 (보통 신뢰도)"트리거 blf" 파일에 적용할 변경 사항은 다음과 같습니다:

이러한 변경을 수행한 후 FixCRCFile을 호출하여 새 체크섬을 계산하고 처음 4개 블록의 체크섬을 수정합니다. 다음 두 블록에는 변경 사항이 없으므로 체크섬을 다시 계산할 필요가 없습니다.

텍스트, 글꼴, 스크린샷, 숫자가 포함된 그림 Description 자동 생성됨

5- 트리거 blf의 BASE BLOCK 커널 주소 얻기:

CLFS.sys 드라이버는 파일의 6개 블록을 읽고, 그 내용을 저장하기 위해 커널 풀에 할당합니다.

텍스트, 스크린샷, 글꼴, 숫자가 포함된 그림 Description 자동 생성됨

이전 CVE-2022-37969 블로그 게시물에서 리버싱을 통해 크기 0x90의 매우 중요한 구조를 발견했으며, 일부 필드를 찾아 pool_0x90이라고 명명했습니다. 훨씬 더 많은 리버싱 후에 이제 실제 이름이 m_rgBlocks임을 알게 되었습니다. 컨트롤러가 파일에서 각 블록의 내용을 복사하기 위해 메모리를 할당함에 따라 각 블록의 크기, 시작 오프셋 및 저장된 커널 주소를 저장합니다.

텍스트, 스크린샷, 글꼴이 포함된 그림 Description 자동 생성됨

블록 번호별로 각 블록에 해당하는 6개의 CLFS_METADATA_BLOCK이 있습니다.

각 CLFS_METADATA_BLOCK 구조는 0x18바이트 길이입니다. (0x18*6=0x90)

텍스트, 글꼴, 줄, 숫자가 포함된 그림 Description 자동 생성됨오프셋 0에는 유니온이 있지만, 적어도 이 익스플로잇에서는 pbImage 필드만 사용됩니다. 따라서 단순화하면 다음과 같습니다:

해당 구조의 할당은 새 파일을 생성하거나 기존 파일을 여는 경우에 따라 CLFS.sys 드라이버의 두 위치에서 수행될 수 있습니다. 새 파일이 생성되는 경우 드라이버는 CClfsBaseFilePersisted::CreateImage+28A에서 0x90바이트를 할당하고, 기존 파일의 경우 CClfsBaseFilePersisted::ReadImage+6E에서 할당합니다.

그런 다음 트리거 blf 파일의 블록 2에 해당하는 시작 주소를 얻습니다. 이를 BASE BLOCK이라고 하며 오프셋 0x800에서 시작하고 길이는 0x7a00입니다.

텍스트, 스크린샷, 글꼴, 숫자가 포함된 그림 Description 자동 생성됨

fun_prepare 함수 내에서 아래 코드 조각을 사용하여 이 주소를 커널에서 찾습니다.

컴퓨터 코드 스크린샷 Description 자동 생성됨 (낮은 신뢰도)

먼저 getBigPoolInfo 함수는 "Clfs" 태그와 크기 0x7a00을 가진 풀의 모든 할당을 찾아 배열에 저장합니다.

그런 다음 CreateLogFile을 OPEN_EXISTING 인수로 사용하여 이전에 수정한 트리거 blf 파일을 다시 엽니다. 이렇게 하면 기존 파일이 열리고, 그 BASE BLOCK의 할당이 수행됩니다.

getBigPoolInfo가 다시 호출되면 크기 0x7a00인 새로운 "Clfs" 풀이 하나 생기며, NtQuerySystemInformation을 두 번 호출하여 해당 주소를 검색합니다.

트리거 blf 파일의 BASE BLOCK 주소는 CLFS_kernelAddrArray 변수에 저장됩니다.

수정된 트리거 blf 파일의 체크섬이 올바르지 않으면 CreateLogFile() 함수가 실패합니다.

6- 트리거 blf의 핸들로 AddLogContainer 호출:

fun_prepare 함수의 마지막 부분에서는 트리거 blf 파일의 핸들을 사용하여 AddLogContainer api를 호출합니다.

컴퓨터 코드 클로즈업 Description 자동 생성됨 (낮은 신뢰도)

7- 스프레이 blf 파일 준비:

PoC의 마지막 함수인 to_trigger에서는 두 번째 유형의 blf 파일을 생성합니다.

이를 스프레이 blf라고 합니다.

이 유형의 파일은 메모리 공간을 채우는(스프레이) 데 사용되며, 동일한 파일 10개가 필요하지만 처음에는 하나만 생성됩니다. 텍스트, 글꼴, 줄, 스크린샷이 포함된 그림 Description 자동 생성됨

이 파일들의 임의 이름을 저장할 세 개의 배열을 생성합니다:

stored_log_arrays: CreateLogFile과 함께 사용될 .blf 파일의 10개의 새 임의 이름을 저장합니다.

stored_container_arrays: 10개의 새 컨테이너 파일을 생성하기 위한 임의 이름을 저장합니다.

stored_fopen_arrays: 첫 번째 배열(stored_log_arrays 변수)의 로그 파일 이름을 일반 경로("LOG:" 문자열 없음)와 .blf 확장자로 저장합니다.

컴퓨터 코드 스크린샷 Description 자동 생성됨 (보통 신뢰도)

각 반복에서 CopyFileW를 사용하여 blf 파일이 복사되고 배열에 저장된 이름이 할당됩니다.

fun_trigger 함수는 craftSprayBlfFile을 호출하여 각 파일을 수정하고 FixCRCFile이 CRC를 수정합니다.

컴퓨터 프로그램 스크린샷 Description 자동 생성됨 (보통 신뢰도)

요약하면, 다음과 같은 수정 사항을 적용한 10개의 유사한 파일(스프레이 blf)을 임의 이름으로 생성했습니다:

컴퓨터 스크린샷 Description 자동 생성됨 (보통 신뢰도)

마지막 변경은 전체 블록 0(CONTROL BLOCK)을 블록 1(CONTROL BLOCK SHADOW)로 복사하는 것입니다.

낮은 신뢰도로 자동 생성된 컴퓨터 화면 캡처 Description

이러한 변경 사항과 트리거 blf 파일에 적용된 변경 사항의 효과는 디버깅 장에서 나중에 설명됩니다.

이러한 변경 사항 중 일부는 취약점을 발생시키는 반면, 다른 변경 사항은 드라이버 검사를 우회하는 데만 필요합니다.

이 시점에서 파일이 이미 생성 및 수정되어 스프레이를 수행할 준비가 되었습니다. 그런 다음 CreateLogFile로 열면 우리가 원하는 메모리 영역에 위치하게 됩니다. 이는 나중에 보여드리겠습니다.

8- 스프레이를 수행할 메모리 준비

to_trigger 함수에서 12개 요소로 구성된 배열을 만듭니다. 여기에는 트리거 blf 파일의 BASE BLOCK 주소 + 0x30이 포함됩니다.

그런 다음 fun_pipeSpray 함수에서 파이프 스프레이로 메모리를 채웁니다. 내부에는 CreatePipe를 호출하고 첫 번째 인수로 전달된 수의 파이프를 생성하는 루프가 있으며, 두 번째 인수는 생성된 모든 파이프의 핸들을 저장할 배열입니다.

루프 내에서 CreatePipe를 호출하여 읽기-쓰기 파이프를 생성합니다.

이런 식으로 먼저 0x5000개의 파이프를 생성한 다음 다시 호출하여 다른 0x4000개의 파이프를 생성합니다.

그런 다음 WriteFile을 사용하여 처음 5000개의 파이프에 트리거 blf 파일의 BASE BLOCK + 0x30의 주소가 포함된 방금 만든 배열을 씁니다.

컴퓨터 코드 스크린샷 Description 자동 생성됨 (낮은 신뢰도)

이제 메모리에 컴팩트 블록이 생성되었습니다. 0x2000에서 0x2667까지의 파이프 0x667개를 해제합니다. 메모리에서 파이프가 생성된 순서와 동일하지 않기 때문에 이 메모리 블록에 빈 공간이 생깁니다.

텍스트, 스크린샷, 글꼴, 소프트웨어가 포함된 그림 Description 자동 생성됨파이프 할당의 사용자 크기는 0x90바이트이므로 해제되면

파이프로 가득 찬 메모리 사이에 크기 0x90의 메모리 공간이 해제됩니다.
그런 다음 루프를 돌며 10개의 스프레이 blf 파일에 대해 CreateLogFile을 호출합니다.
기존 파일을 열기 위해 CreateLogFile이 호출되면 각 스프레이 blf 파일에 대해 m_rgBlocks의 0x90바이트 할당이 수행됩니다. 따라서 이러한 할당은 동일한 크기이므로 파이프 해제 시 남겨진 공백을 차지합니다.

컴퓨터 코드 스크린샷 Description 자동 생성됨 (보통 신뢰도)

그런 다음 마지막 0x4000개의 파이프에 트리거 blf의 BASE BLOCK +0x30 주소가 있는 배열을 쓰는 과정을 반복합니다.

9- 버그 트리거

이러한 모든 조작은 제어된 메모리 공간을 만듭니다. 디버깅할 때 어떻게 되는지 보여드리겠습니다. 기본 아이디어는 각 스프레이 blf 파일의 m_rgBlocks가 해제된 0x90바이트 공백을 차지한다는 것입니다.

그런 다음 마지막 부분에서 while( 1 ) 루프 내에서 스프레이 blf 파일에 대한 AddLogContainer 호출을 사용하여 버그가 트리거됩니다.텍스트, 글꼴, 줄, 숫자가 포함된 그림자동 생성됨

이 while 내에서 버그가 트리거될 때:

컴퓨터 프로그램의 스크린샷, 신뢰도 낮은 자동 생성 설명

이 while은 NtFsControlFile 함수를 사용하여 파이프의 속성을 읽으면서 System 토큰을 찾을 때 종료됩니다.

컴퓨터 코드의 스크린샷, 신뢰도 낮은 자동 생성 설명

그런 다음 CreateLogFile을 사용하여 우리 프로세스의 토큰을 방금 찾은 System 토큰으로 다시 덮어쓰고, 이렇게 하여 권한 상승을 달성합니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림, 자동 생성 설명

그런 다음 일부 값을 복원하고, 파이프와 blf 파일의 핸들을 닫은 후, System으로 Notepad를 실행하여 제대로 상승되었는지 확인합니다.

컴퓨터의 스크린샷, 중간 신뢰도의 자동 생성 설명

PUBLIC 폴더에 생성된 blf 파일을 참고하세요. 다시 시도하려면 먼저 생성된 파일을 삭제해야 합니다. 일부 파일은 잠겨 삭제할 수 없지만 PoC는 여전히 작동합니다.

컴퓨터의 스크린샷, 중간 신뢰도의 자동 생성 설명

디버깅:

1- 메모리 스프레이 확인

악용을 수행하기 위해 trigger blf와 spray blf 파일에 대한 변경 효과를 시작하기 전에, 파이프 스프레이와 고정된 수의 파이프 해제 후에 spray blf 파일의 m_rgBlocks가 메모리 분포에서 발생하는 구멍에 위치하는지 확인해야 합니다.

이 절차가 종료되면, m_rgBlocks의 0x90 바이트 아래에 파이프가 위치해야 하며, 따라서 m_rgBlocks가 사용될 때 OUT OF BOUNDS가 발생하여 아래에 있는 해당 파이프에서 읽게 됩니다.

PoC에는 중단점을 설정하기에 이상적인 지점이 있습니다:

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

이 시점에서 spray blf 파일 열기가 완료되었으며 AddLogContainer 함수는 아직 호출되지 않았습니다.

사용자 모드에서 디버깅하려면 x64dbg를 사용하고, 커널 모드에서는 Windbg 플러그인과 함께 IDA를 사용합니다.

신뢰도 낮은 컴퓨터 스크린샷, 자동 생성 설명

이 시점에서 메모리는 이미 준비되어 있어야 하며, 분포를 확인할 수 있습니다.

중단점을 설정할 흥미로운 지점을 찾기 위해 IDA를 일시 중지하겠습니다.

AddLogContainer에서 호출되는 CClfsBaseFilePersisted::AddContainer에 중단점을 설정하겠습니다. 시작 부분에서 RCX 레지스터가 CClfsBaseFilePersisted 구조체를 가리키며, 오프셋 0x30에 m_rgBlocks에 대한 포인터가 있습니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

중단점에 도달하면 호출 스택에서 AddLogContainer가 내 PoC에서 호출되고 있는지 확인합니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

RCX 레지스터는 다음을 가리킵니다:

신뢰도 낮은 컴퓨터 스크린샷, 자동 생성 설명 신뢰도 낮은 컴퓨터 스크린샷, 자동 생성 설명

첫 번째 필드는 vtable에 대한 포인터(CLFS! CClfsBaseFilePersisted::'vftable')이고, 오프셋 0x30에는 m_rgBlocks에 대한 포인터가 있습니다.

중간 신뢰도의 컴퓨터 코드 스크린샷, 자동 생성 설명

블록 0, 1, 4 및 5는 아직 pbImage를 저장하지 않았지만, 블록 2(BASE BLOCK)와 블록 3(SHADOW BLOCK)은 저장했습니다.

m_rgBlocks 테이블의 각 블록에는 cbOffset(파일에서 블록이 시작되는 오프셋), cbImage(블록 크기), eBlockType(블록 유형)이 있습니다.

스프레이가 올바르다면, m_rgBlocks 아래에 파이프가 있어야 하며, 그 안에는 trigger blf의 BASE BLOCK + 0x30에 대한 포인터가 있어야 합니다.

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

windbg의 "!pool" 명령은 메모리 분포를 표시합니다:

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

각 m_rgBlocks는 "Clfs" 태그를 가지며 크기는 0xa0입니다(사용자 크기 0x90 + 헤더 0x10). 그 아래에는 "NpFr" 태그를 가진 파이프가 있으며, 동일한 0x90 사용자 크기 + 0x10 헤더를 가집니다.

분포가 정확한 과학은 아니기 때문에 일부 "Clfs"는 연속적으로 배치되어 바람직하지 않지만, 현재 작업 중인 것은 파이프 뒤에 올바르게 배치되었습니다.

2-trigger blf의 RecordOffset[12] 확인

영향을 미치는 첫 번째 변경 사항 중 하나는 trigger blf 파일의 오프셋 0x858에서 값 0x369가 저장되는 것입니다.

신뢰도 낮은 표지 자동 생성 설명

BASE BLOCK은 파일의 오프셋 0x800에서 시작합니다.

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명

_CLFS_LOG_BLOCK_HEADER 내부, 오프셋 0x800+0x58(BASE BLOCK 헤더 시작부터 0x58)에 위치합니다.

텍스트, 스크린샷, 글꼴, 디스플레이가 포함된 그림, 자동 생성 설명

오프셋 0x28에서 RecordOffsets(DWORD) 배열이 시작됩니다.

0x30 바이트 앞으로 이동하면 오프셋 0x58(시작부터 0x828+0x30=0x858)에 RecordOffsets의 필드 12가 있습니다.

텍스트, 글꼴, 스크린샷, 그래픽이 포함된 그림, 자동 생성 설명

아래 이미지와 같이 PoC를 실행하여 CreateLogFile을 호출합니다:

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명 신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

컴퓨터 스크린샷, 자동 생성

CreateLogFile에 진입하기 전에, 값 0x369가 아직 사용되지 않은 위치에 중단점을 설정하겠습니다.

CreateLogFile이 기존 파일을 여는 경우, m_rgBlocks 구조체는 여기서 할당됩니다:

CClfsBaseFilePersisted::ReadImage+6E

따라서 IDA에서 바로 여기에 중단점을 설정하겠습니다:

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

중단점이 트리거되면:

텍스트, 스크린샷, 글꼴, 숫자가 포함된 그림, 자동 생성 설명

m_rgBlocks에는 아직 초기화되지 않았기 때문에 쓰레기 값이 있지만, 블록 2의 pbImage가 할당되자마자 주소가 시작부터 오프셋 0x30에 저장됩니다. 각 CLFS_METADATA_BLOCK 내부의 첫 번째 필드는 pbImage이기 때문입니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명

이제 쓰기 하드웨어 중단점을 설정합니다: ba w1 ffffd003'7f5bea30

0으로 초기화된 후, pbImage를 저장할 때 중단됩니다.

분석 결과, 이것은 block0에 해당한다고 나오지만, 이후에 r14*8 상수(0x30)를 고려하지 않아 실제로는 block 2의 pbImage를 쓰고 있는 것입니다.

텍스트, 스크린샷, 글꼴이 포함된 그림, 자동 생성 설명

CClfsBaseFilePersisted::ReadMetadataBlock은 인자로 전달된 크기를 사용하여 블록 중 하나를 할당하는 데 사용됩니다.

텍스트, 글꼴, 스크린샷, 줄이 포함된 그림, 자동 생성 설명

이제 베이스 블록의 0x58에 읽기/쓰기 중단점을 설정하여 값 0x369가 언제 사용되는지 확인합니다.

ba r1 FFFF978A'16ECF000+0x58

중단점이 적중되면, **RecordOffset[12]**에 있는 값 0x369를 읽고, r14의 이상한 포인터에 더한 후 RAX+r14의 내용을 증가시킵니다.

코드의 몇 줄 위에서 ESI는 값 0x13을 가지며, 0x18을 곱합니다(이것은 m_rgBlocks의 각 블록 크기입니다).

WINDBG>? 0x18*0x13

계산 결과: 456 = 00000000'000001c8

r8= 0x1c8(0x90보다 큼)의 값을 m_rgBlocks의 시작 주소에 더하면, OUT OF BOUNDS를 읽게 됩니다.

컴퓨터 스크린샷, 자동 생성

m_rgBlocks 아래에는 BASE BLOCK + 0x30에 대한 포인터가 있는 파이프가 있으며, 파이프 내부에 전략적으로 배치된 이 포인터를 읽습니다.

신뢰도 낮은 컴퓨터 프로그램 스크린샷, 자동 생성 설명 코드의 현재 위치는 메인 모듈의 while(1) 문에서 호출되었습니다.

spray blf 파일 내부에서 오프셋 0x48a(iFlushBlock)에 값 0x13을 전략적으로 배치했습니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림, 자동 생성 설명

3-spray blf 파일의 iFlushBlock 값 확인

spray blf 파일의 오프셋 0x8a에는 BLOCK 0의 iFlushBlock이 위치하며, 그 값은 4입니다. 반면 오프셋 0x48a는 BLOCK 1의 iFlushBlock에 속하며, 그 값은 0x13입니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

이제 BLOCK 0의 iFlushBlock = 4 대신 BLOCK 1에서 iFlushBlock = 0x13을 읽는 이유를 알아내야 합니다.

4-BLOCK 0 CONTROL 대신 BLOCK 1 SHADOW에서 읽는 이유

0x13이 어디서 왔는지 확인하기 위해 되돌아보면, 호출 스택에서 WriteMetadataBlock이 CClfsBaseFilePersisted::ExtendMetadataBlock+416에서 호출되었으며, 두 번째 iFlushBlock 인수는 EDX=0x13이고, 이는 r9w에서 왔습니다.

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명

신뢰도 낮은 컴퓨터 프로그램 스크린샷, 자동 생성 설명 몇 줄 전에 CClfsBaseFile::GetControlRecord가 호출되어 BLOCK 0의 주소를 검색했습니다. 아마 문제가 여기에 있을 수 있으므로 재부팅하고 해당 위치에 중단점을 설정하겠습니다.

GetControlRecord는 CClfsBaseFile::AcquireMetadataBlock을 호출하며, 이 함수는 m_rgBlocks 테이블을 block 0의 주소로 채워야 합니다. 이 함수를 지나가면 block 1의 주소를 가져오므로, 문제는 CClfsBaseFile::AcquireMetadataBlock 내부에서 발생합니다.

검색된 주소에 0x8A를 더하면 BLOCK 1에 속하는 0x13 값이 있음을 확인할 수 있습니다.

중간 신뢰도의 컴퓨터 코드 스크린샷, 자동 생성 설명

재부팅하고 그곳에 중단점을 설정하겠습니다:

신뢰도 낮은 컴퓨터 프로그램 스크린샷, 자동 생성 설명

CClfsBaseFile::GetControlRecord+27 call CClfsBaseFile::AcquireMetadataBlock

컴퓨터 스크린샷, 자동 생성

AcquireMetadataBlock에 전달된 두 번째 인수는 0이며, 이는 block 0에 해당합니다. 파일에서 복사하여 m_rgBlocks에 주소를 저장합니다. 중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명.

_CLFS_METADATA_BLOCK_TYPE 블록 유형 열거형에는 제가 사용한 이름과 다른 이름이 있지만, 동일한 6개의 블록입니다.

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명

블록 유형이 최대 m_cBlocks=6보다 작은지 확인한 후, 동일한 블록을 두 번 읽지 않도록 reference 값을 저장합니다.

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

ReadMetadataBlock이 호출되며, block 0 대신 block 1을 읽는 문제는 이 함수 내부에 있을 것입니다.

텍스트, 글꼴, 숫자, 줄이 포함된 그림, 자동 생성 설명

모든 것이 정상이면, cbImage를 크기로 할당하고, m_rgBlocks의 block 0->pbImage 필드에 주소를 저장합니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림, 자동 생성 설명

!pool 명령은 할당된 태그와 크기를 표시합니다.

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명 따라서 m_rgBlocks에 저장된 block 0의 pbImage 주소를 이미 가지고 있습니다. 이제 block 0의 바이트 대신 block 1의 바이트를 거기에 복사하는 이유를 확인해야 합니다.

CClfsContainer::ReadSector 호출에 도달하며, pbImage를 포함하는 변수에 대한 포인터가 전달되어 바이트를 씁니다.

ReadSector를 넘어갈 때 pbimage 내용의 변경 사항을 확인하세요.

pbImage에 0x8a를 더하면 올바른 값인 4를 찾을 수 있고, 0x13이 아니므로 문제는 나중에 발생해야 합니다.

ClfsDecodeBlock을 호출한 후 오류 0x0C01A000A를 반환합니다.

CClfsBaseFilePersisted::ReadMetadataBlock+153이 ClfsDecodeBlock을 호출합니다.

이 오류 후, 유형에 1을 더하고 CClfsBaseFilePersisted::ReadMetadataBlock을 다시 호출하지만, 유형 1로 block 1을 읽습니다.

컴퓨터 스크린샷, 자동 생성

CClfsBaseFilePersisted::ReadMetadataBlock에서 block 1에 대해 m_rgBlocks에 새로운 pbImage를 할당하고 저장합니다.

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

Blocks 0과 1은 서로 다른 주소를 가지며, 이제 block 1의 주소에 0x8a를 더하면 값이 0x13입니다.

아마도 block 0이 오류를 반환했기 때문에 block 1을 사용하여 GetControlRecord에 Control Block으로 반환합니다.

앞서 보여준 바와 같이, 4 대신 0x13 값을 사용하면 m_rgBlocks의 범위를 벗어나 제가 제어하는 파이프 스프레이 값을 읽습니다.

텍스트, 스크린샷, 글꼴이 포함된 그림, 자동 생성 설명 그런 다음 block 0에서 pbImage를 해제하고 block 1의 포인터를 block 0에 복사합니다.

컴퓨터 스크린샷, 자동 생성

ClfsDecodeBlock 내부에서 오류 0x0C01A000A를 발생시키는 값을 찾아야 할 필요가 있습니다.

ClfsDecodeBlock 내부에서 첫 번째 블록의 체크섬이 0이며, 이것이 오류 0xC01A000A입니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

5-blf spray 파일에서 체크섬이 0인 이유

AddLogContainer를 호출하기 전에 16진수 편집기로 spray blf 파일을 열면 체크섬이 0으로 변경되었습니다.

컴퓨터 스크린샷, 자동 생성

CreateLogFile로 열렸을 때 이전에 변경되었어야 합니다.

텍스트, 스크린샷, 디스플레이, 글꼴이 포함된 그림, 자동 생성 설명

어떤 이유로 spray blf 파일은 CreateLogFile을 종료한 후 block 0의 체크섬이 0이 되고 유효한 핸들을 반환합니다. 이것이 왜 발생하는지 살펴보겠습니다.

CreateLogFile에서 일부 spray blf 파일을 열기 전에 중단합니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

CreateLogFile을 호출하기 전에 spray files는 block 0에 올바른 체크섬을 가지고 있지만, 함수가 완료된 후 체크섬 값이 0으로 변경됩니다.

신뢰도 낮은 컴퓨터 코드 스크린샷, 자동 생성 설명

따라서 CClfsBaseFile::GetControlRecord에 중단점을 설정하여 내부를 살펴보겠습니다.

텍스트, 스크린샷, 글꼴, 줄이 포함된 그림, 자동 생성 설명 CClfsContainer::ReadSector를 통과한 후 체크섬은 0이 아닙니다.

CRC32를 계산하기 전에 메모리에서 체크섬 필드를 0으로 설정하여 CRC를 계산하며, 결과는 올바릅니다.

텍스트, 글꼴, 스크린샷이 포함된 그림, 자동 생성 설명

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림, 자동 생성 설명

그런 다음 eExtendState =2 값을 확인하고 WriteMetadataBlock으로 이동합니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

여기서 체크섬은 여전히 메모리에서 0이며, 이 값이 파일에 기록되는 시점을 확인하면 됩니다.

텍스트, 글꼴, 줄, 스크린샷이 포함된 그림, 자동 생성 설명

blf spray 파일에 조작된 일부 값을 확인하여 CClfsBaseFilePersisted::ExtendMetadataBlock에 도달합니다.

중간 신뢰도의 컴퓨터 프로그램 스크린샷, 자동 생성 설명

아직 읽지 않은 블록을 읽는 루프 후에도 block 0은 checksum = 0으로 유지됩니다.

신뢰도 낮은 검은색 텍스트가 있는 흰색 직사각형, 자동 생성 설명

WriteMetadataBlock에 도착합니다.

block 0을 1로 바꾸기 전에 실행 중이므로 blf spray 파일의 iFlushBlock 값은 여전히 올바른 값인 4입니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

이제 block 4로 작업 중이며, 파일에 block 4를 쓸 것입니다. 여기서는 아직 문제가 아닙니다.

그런 다음 CClfsBaseFilePersisted::FlushControlRecord에 도달합니다.

중간 신뢰도의 컴퓨터 프로그램 스크린샷, 자동 생성 설명

내부에서 WriteMetadataBlock에 도달하지만, 인수 0을 사용하여 block 0을 파일에 씁니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

그런 다음 ClfsEncodeBlock이 오류 0xC01A000A를 반환하지만, 바로 아래 CClfsContainer::WriteSector에서 잘못된 block 0으로 파일을 쓸 것입니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

변수 var_54는 0xC01A000A 오류 값을 저장하며, 함수를 종료하기 전에 확인됩니다.

중간 신뢰도의 컴퓨터 스크린샷, 자동 생성 설명

그러나 CClfsContainer::WriteSector를 호출한 후 오류를 반환하지 않으므로 var_54의 내용이 0으로 덮어씌워집니다.

따라서 함수는 오류 없이 0을 반환하고 계속 작동하여 CreateLogFile이 오류 값 대신 핸들을 반환합니다.

컴퓨터 스크린샷, 자동 생성

6-악용 종료

iFlushBlock의 값 0x13은 범위를 벗어나게 하여 파이프에 있는 trigger blf의 Base Block +30을 가리키는 포인터를 읽게 합니다.

컴퓨터 스크린샷, 자동 생성그런 다음 해당 포인터에 0x28을 더합니다(트리거 blf의 기본 블록 시작부터 0x58) 여기에는 값 0x369가 있습니다.

중간 신뢰도로 자동 생성된 컴퓨터 프로그램 스크린샷

INC 명령어는 값 0x14를 1씩 증가시키고 4번 반복하므로 0x14는 0x18이 됩니다.

WINDBG>db r14+369

ffffcb82'091e7397 14 00 00 00

그 후 CreateLogFile이 호출되고 0x1858 값을 읽습니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

낮은 신뢰도로 자동 생성된 카드 근접 촬영GetSymbol은 이전에 트리거 blf에서 생성된 가짜 블록이 오프셋 0x1858이 가리키는 위치에 올바른 값을 가지고 있는지 확인합니다.

자동 생성된 텍스트, 글꼴, 선, 스크린샷이 포함된 그림

포인터가 여러 번 증가하지 않았다면 원래 값 0x1458을 가지고 올바른 블록을 가리킬 것입니다.

GetSymbol을 종료한 후 여기서 그 가짜 블록을 사용합니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

그런 다음 가짜 블록의 오프셋 0x18 값을 읽습니다. 여기에 0x05000000을 배치했으며, 그곳에 있는 내용으로 점프합니다.

WINDBG>dps 0x5000000

00000000'05000000 00000000'05001000

0x05000000의 내용을 읽고 0x05001000이며 거기에 ClfsEarlierLsn이 있습니다.

낮은 신뢰도로 자동 생성된 컴퓨터 프로그램 스크린샷

이 함수는 RDX에 값 0xFFFFFFFF를 반환하는 데 사용되지만, 처음에는 이 값이 사용되지 않습니다.

두 번째 호출은 여기서 발생하며, 0x501000 +8에 있던 PoFxProcessorNotification을 호출합니다.

자동 생성된 텍스트, 글꼴, 스크린샷, 선이 포함된 그림 자동 생성된 텍스트, 글꼴, 스크린샷, 선이 포함된 그림

WINDBG>dps 00000000**'05001000**

00000000'05001000 fffff805'7ab13220 CLFS! ClfsEarlierLsn

00000000'05001008 fffff805'769dc3b0 nt! PoFxProcessorNotification

낮은 신뢰도로 자동 생성된 컴퓨터 화면 스크린샷

이 함수에서 RCX = 0x05000000이며, 0x40 바이트 뒤의 값이 0이 아니어야 하는지 확인합니다.

WINDBG>dps rcx+40

00000000'05000040 00000000'05000000

점프할 주소는 0x68 뒤입니다.

WINDBG>dps rcx+68

00000000'05000068 fffff805'7ab2bfb0 CLFS! ClfsMgmtDeregisterManagedClient

그리고 인수는 0x48 바이트 뒤에 있습니다.

WINDBG>dps rcx+48

00000000'05000048 00000000'05000400

ClfsMgmtDeregisterManagedClient는 인수를 제어할 수 있고 제가 제어하는 함수로 점프하는 두 개의 점프가 있기 때문에 편리한 함수입니다.

자동 생성된 컴퓨터 스크린샷

첫 번째 호출은 다시 ClfsEarlierLsn이며, RDX=0xFFFFFFFF를 반환했습니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

RDX=0xFFFFFFFF의 내용에서 쓸 소스를 가져옵니다.

WINDBG>dps rdx

00000000'ffffffff ffff8005'3a4ee000

주소 0xFFFFFFFF에는 system_EPROCESS & 0xfffffffffffff000을 저장했습니다.

자동 생성된 텍스트, 글꼴, 선, 숫자가 포함된 그림

대상은 0x5000400 +0x48에 위치한 포인터입니다.

*(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18;

자동 생성된 컴퓨터 스크린샷

"A"로 채워진 버퍼를 가리키는 커널의 PipeAttribute 포인터는 SYSTEM EPROCESS 포인터의 상위 부분으로 덮어쓰여집니다.

이 포인터는 이전에 _NtFsControlFile을 "A"로 가득 찬 버퍼로 호출했을 때 생성되었습니다.

중간 신뢰도로 자동 생성된 컴퓨터 프로그램 스크린샷

해당 속성의 내용은 NtFsControlFile을 사용하여 읽을 수 있습니다.

중간 신뢰도로 자동 생성된 컴퓨터 화면 스크린샷

이제 파이프 속성은 더 이상 "A"가 있는 버퍼를 가리키지 않고 system_EPROCESS & 0xffffffffffffff000을 가리킵니다.

낮은 신뢰도로 자동 생성된 컴퓨터 프로그램 스크린샷

이 코드는 시스템 토큰을 검색할 때까지 반복됩니다.

중간 신뢰도로 자동 생성된 컴퓨터 코드 스크린샷

자동 생성된 컴퓨터 스크린샷

Windows 11에서 시스템 토큰은 방금 읽은 EPROCESS 구조의 오프셋 0x4b8에 있습니다.

자동 생성된 컴퓨터 스크린샷

CreateLogFile을 호출하여 해당 시스템 토큰을 내 프로세스에 쓰기만 하면 됩니다.

자동 생성된 텍스트, 글꼴, 선, 스크린샷이 포함된 그림

이 작업을 수행하려면 시스템 토큰을 읽는 데 사용된 단계를 반복하면 됩니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

이중 호출에서는 먼저 ClfsEarlierLsn을 호출하여 RDX에 0xFFFFFFFF를 반환한 다음 nt_SeSetAccessStateGenericMapping을 호출합니다.

자동 생성된 텍스트, 스크린샷, 글꼴, 선이 포함된 그림

RDX가 가리키는 값이 System Token인지 확인합니다.

자동 생성된 텍스트, 스크린샷, 글꼴이 포함된 그림

내 프로세스의 토큰은 다음과 같습니다:

거기에 쓰려고 합니다.

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'f601c06c

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'ef841919

이제 내 프로세스는 System입니다. 메모장을 실행하여 확인할 수 있습니다.

자동 생성된 텍스트, 스크린샷, 글꼴, 선이 포함된 그림

자동 생성된 컴퓨터 스크린샷

자동 생성된 컴퓨터 스크린샷

7-실제 패치

BINDIFF는 변경된 많은 함수를 보여줍니다.

자동 생성된 컴퓨터 스크린샷

취약한 함수는 여기에 있습니다:

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

자동 생성된 컴퓨터 스크린샷

첫 번째는 패치된 버전이고, 두 번째는 취약한 버전입니다.

패치는 CflsEncodeBlock의 반환 값( 0xC01A000A )을 테스트하여 변수 var_54에 저장하고, 음수이므로 이를 확인하고 WriteSector를 방지합니다.

패치는 파일을 쓰지 않을 뿐만 아니라 함수가 올바르게 0xc01a000a를 반환하므로 CreateLogFile이 핸들을 반환하지 않아 익스플로잇이 계속될 수 없습니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷

자동 생성된 컴퓨터 스크린샷

ClfsDecodeBlock이 음수가 아닌 경우에만 WriteSector로 이동하지만, 음수 값 0xC01A000A를 반환하는 것은 그대로 둡니다.

중간 신뢰도로 자동 생성된 컴퓨터 스크린샷이것이 실제로 방금 첨부한 PoC를 사용한 익스플로잇을 방지하는 실제 패치입니다.

이 시점에서 버그가 어떻게 익스플로잇되었는지 설명했습니다. 이는 SYSTEM 토큰을 읽고 이를 우리 프로세스에 써서 로컬 권한 상승을 달성할 수 있는 함수를 제어하게 됩니다. 작동하는 PoC는 Fortra의 GitHub에서 찾을 수 있습니다.

도움이 되길 바랍니다. 문의 사항이 있으면 연락주세요:

[email protected]
@ricnar456

[email protected]
@solidclt

도구 다운로드