SMBGhost(CVE-2020-0796) 분석(2)

[0x00] Overview

해당 챕터에서는 본격적인 분석을 시작합니다. 매우 길고 어지러울 수 있습니다.

[0x01] Analysis(BugCheck)

먼저 windbg로 커널 디버깅 연결을 준비합니다. 이전 챕터에서 Page Fault Exception을 발생시키는 PoC 코드를 활용할 것입니다. 해당 PoC 코드를 실행하면 아래와 같은 출력을 디버거에서 확인할 수 있습니다.

*** Fatal System Error: 0x00000050
                       (0xFFFF93901618A05F,0x0000000000000000,0xFFFFF8027DA35EE7,0x0000000000000002)

Break instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

For analysis of this file, run !analyze -v
nt!DbgBreakPointWithStatus:
fffff802`7d5c4580 cc              int     3

예외가 발생하면 오류에 대한 내용을 꼭 읽어봐야 합니다. !analyze -v 명령을 이용하여 상세 에러 메시지를 확인합니다.

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffff93901618a05f, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff8027da35ee7, If non-zero, the instruction address which referenced the bad memory
	address.
Arg4: 0000000000000002, (reserved)

Debugging Details:
------------------
…

READ_ADDRESS:  ffff93901618a05f Nonpaged pool
FAULTING_IP: 
nt!RtlDecompressBufferLZNT1+57
fffff802`7da35ee7 0fb71e          movzx   ebx,word ptr [rsi]

…

ANALYSIS_SESSION_HOST:  SHH0YA
ANALYSIS_SESSION_TIME:  03-23-2020 21:24:07.0961
ANALYSIS_VERSION: 10.0.18362.1 amd64fre

TRAP_FRAME:  fffff00184c12b90 -- (.trap 0xfffff00184c12b90)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff00184c12d58 rbx=0000000000000000 rcx=fffff00184c12d50
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8027da35ee7 rsp=fffff00184c12d20 rbp=fffff00184c12d70
 r8=0000000000000000  r9=0000000000000241 r10=fffff8027da35e90
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
nt!RtlDecompressBufferLZNT1+0x57:
fffff802`7da35ee7 0fb71e          movzx   ebx,word ptr [rsi] ds:00000000`00000000=????
Resetting default scope

LAST_CONTROL_TRANSFER:  from fffff8027d6a6492 to fffff8027d5c4580

STACK_TEXT:  
fffff001`84c12148 fffff802`7d6a6492 : ffff9390`1618a05f 00000000`00000003 fffff001`84c122b0 fffff802`7d524f20 : nt!DbgBreakPointWithStatus
fffff001`84c12150 fffff802`7d6a5b82 : fffff802`00000003 fffff001`84c122b0 fffff802`7d5d0ce0 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12
fffff001`84c121b0 fffff802`7d5bc917 : fffff802`7d8611b8 fffff802`7d6cffe5 ffff9390`1618a05f ffff9390`1618a05f : nt!KeBugCheck2+0x952
fffff001`84c128b0 fffff802`7d600b0a : 00000000`00000050 ffff9390`1618a05f 00000000`00000000 fffff001`84c12b90 : nt!KeBugCheckEx+0x107
fffff001`84c128f0 fffff802`7d4c91df : fffff802`7d863880 00000000`00000000 00000000`00000000 ffff9390`1618a05f : nt!MiSystemFault+0x18fafa
fffff001`84c129f0 fffff802`7d5ca69a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0000003c : nt!MmAccessFault+0x34f
fffff001`84c12b90 fffff802`7da35ee7 : 00000000`00000000 00000000`00001100 ffff938f`170fd8c0 00000000`00001000 : nt!KiPageFault+0x35a
fffff001`84c12d20 fffff802`7d467666 : ffff9390`1936804f ffff938f`170fd8c0 ffff9390`1936804f fffff802`7d4675da : nt!RtlDecompressBufferLZNT1+0x57
fffff001`84c12db0 fffff802`836ae0bd : 00000000`00000002 00000000`00000241 00000000`ffffffff fffff802`00000000 : nt!RtlDecompressBufferEx2+0x66
fffff001`84c12e00 fffff802`837e7f41 : ffff938f`00010020 ffff938f`19369150 00000000`00000001 00000000`ffffffff : srvnet!SmbCompressionDecompress+0xdd
fffff001`84c12e70 fffff802`837e699e : 00000000`00000000 ffff938f`125468e0 00000000`00000002 ffffffff`ffffffff : srv2!Srv2DecompressData+0xe1
fffff001`84c12ed0 fffff802`83829a7f : ffff938f`125468f0 ffff938f`17087001 00000000`00000000 fffff802`7d532e00 : srv2!Srv2DecompressMessageAsync+0x1e
fffff001`84c12f00 fffff802`7d5c004e : fffff001`84c10050 fffff001`839e2901 ffffffff`ee1e5d00 fffff001`84c12fd1 : srv2!RfspThreadPoolNodeWorkerProcessWorkItems+0x13f
fffff001`84c12f80 fffff802`7d5c000c : fffff001`84c12fd1 ffff938f`17087040 fffff001`84c13000 fffff802`7d4c045e : nt!KxSwitchKernelStackCallout+0x2e
fffff001`839e28f0 fffff802`7d4c045e : fffff001`84c12fd1 fffff001`84c13000 00000000`00000140 ffff938f`1252e9c0 : nt!KiSwitchKernelStackContinue
fffff001`839e2910 fffff802`7d4c025c : fffff802`83829940 ffff938f`15588e50 00000000`00000002 00000000`00000000 : nt!KiExpandKernelStackAndCalloutOnStackSegment+0x18e
fffff001`839e29b0 fffff802`7d4c00d3 : 00000000`00000080 00000000`00000088 ffff938f`17087040 fffff001`839e2b00 : nt!KiExpandKernelStackAndCalloutSwitchStack+0xdc
fffff001`839e2a20 fffff802`7d4c008d : fffff802`83829940 ffff938f`15588e50 ffff938f`15588e50 00000000`00000088 : nt!KeExpandKernelStackAndCalloutInternal+0x33
fffff001`839e2a90 fffff802`838297d7 : ffff938f`00000000 00000000`00000000 ffff8389`2b3029a0 00000000`00000000 : nt!KeExpandKernelStackAndCalloutEx+0x1d
fffff001`839e2ad0 fffff802`7db104a7 : ffff938f`125d3000 ffff938f`17087040 fffff802`7a774180 00000000`00000000 : srv2!RfspThreadPoolNodeWorkerRun+0x117
fffff001`839e2b30 fffff802`7d530925 : ffff938f`17087040 fffff802`7db10470 ffff8389`2b3029a0 0000246f`b19bbdff : nt!IopThreadStart+0x37
fffff001`839e2b90 fffff802`7d5c3d5a : fffff802`7a774180 ffff938f`17087040 fffff802`7d5308d0 00000000`00000246 : nt!PspSystemThreadStartup+0x55
fffff001`839e2be0 00000000`00000000 : fffff001`839e3000 fffff001`839dc000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x2a

FOLLOWUP_IP: 
srvnet!SmbCompressionDecompress+dd
fffff802`836ae0bd 8bd8            mov     ebx,eax

FAULT_INSTR_CODE:  c085d88b
SYMBOL_STACK_INDEX:  9
SYMBOL_NAME:  srvnet!SmbCompressionDecompress+dd
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: srvnet
IMAGE_NAME:  srvnet.sys

---------

최대한 간추려 보았으나 매우 소중한 정보들입니다. 여기서 중요한 부분은 STACK_TEXT 필드입니다. nt!KiPageFault 가 발생하기 전 콜 스택을 확인하면 nt!RtlDecompressBufferLZNT1+0x57 으로 확인됩니다. 해당 함수로부터 살펴보면, nt!RtlDecompressBufferEx2+0x66, srvnet!SmbCompressionDecompress0xdd, srv2!Srv2DecompressData+0xe1 으로 거슬러 올라옵니다.

소개에서 언급했던 함수입니다. 최종적으로 PageFault가 발생한 함수를 srvnet!SmbCompressionDecompress+0xdd 로 분석하고 있습니다. srv2!SrvDecompressData 함수 내에서 문제의 SmbCompressionDecompress 함수를 호출하며, 해당 함수는 IMPORT 함수로 srvnet.sys에 존재합니다.

3: kd> lmvm srvnet
Browse full module list
start             end                 module name
fffff802`83690000 fffff802`836e2000   srvnet     (pdb symbols)          C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\sym\srvnet.pdb\D72719A4AB2C7D0D9ADBB5166CFD74EF1\srvnet.pdb
    Loaded symbol image file: srvnet.sys
    Image path: \SystemRoot\System32\DRIVERS\srvnet.sys
    Image name: srvnet.sys
    Browse all global symbols  functions  data
    Image was built with /Brepro flag.
    Timestamp:        5F4A3694 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         0004FD20
    ImageSize:        00052000
    Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4
    Information from resource tables:

이번엔 .trap 명령을 이용하여 크래시 발생 직전의 컨텍스트로 전환합니다.

3: kd> .trap 0xfffff00184c12b90
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff00184c12d58 rbx=0000000000000000 rcx=fffff00184c12d50
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8027da35ee7 rsp=fffff00184c12d20 rbp=fffff00184c12d70
 r8=0000000000000000  r9=0000000000000241 r10=fffff8027da35e90
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
nt!RtlDecompressBufferLZNT1+0x57:
fffff802`7da35ee7 0fb71e          movzx   ebx,word ptr [rsi] ds:00000000`00000000=????

rsi 레지스터가 0인데 역참조를 하므로 예외가 발생한 것입니다. 콜 스택 확인해보면 RtlDecompressBufferLZNT1 함수는 guard_dispatch_icall 을 통해 호출된 것을 알 수 있습니다.(Control Flow Guard 참조)

이것 외에 특이한 점은 없습니다. 먼저 콜 스택에 존재하는 함수들을 알아보겠습니다.

[-] nt!RtlDecompressBufferLZNT1

[-] nt!RtlDecompressBufferEx2

[-] srvnet!SmbCompressionDecompress

[-] srv2!Srv2DecompressData

[0x02] Analysis(Details)

실제로 어떻게 동작하는지 확인하기 위해 동적분석을 진행하겠습니다. srv2!Srv2DecompressData 함수에 브레이크 포인트를 설치합니다. 우리는 잘못된 오프셋과 길이로 인해 발생하는 Overflow라고 확인하였습니다. 그렇다면 이 값이 srv2!Srv2DecompressData 에서 참조될 것이라 예상할 수 있습니다.

[-] Srv2DecompressData

먼저 어셈블리 코드를 살펴보겠습니다.

srv2!Srv2DecompressData:
fffff800`46387e60 488bc4          mov     rax,rsp
fffff800`46387e63 48895810        mov     qword ptr [rax+10h],rbx
fffff800`46387e67 48896818        mov     qword ptr [rax+18h],rbp
fffff800`46387e6b 48897020        mov     qword ptr [rax+20h],rsi
fffff800`46387e6f 57              push    rdi
fffff800`46387e70 4156            push    r14
fffff800`46387e72 4157            push    r15
fffff800`46387e74 4883ec40        sub     rsp,40h
fffff800`46387e78 83600800        and     dword ptr [rax+8],0
fffff800`46387e7c 488bf9          mov     rdi,rcx
fffff800`46387e7f 488b81f0000000  mov     rax,qword ptr [rcx+0F0h]
fffff800`46387e86 83782410        cmp     dword ptr [rax+24h],10h
fffff800`46387e8a 0f8204010000    jb      srv2!Srv2DecompressData+0x134 (fffff800`46387f94)
fffff800`46387e90 488b4018        mov     rax,qword ptr [rax+18h]
fffff800`46387e94 0f1000          movups  xmm0,xmmword ptr [rax] ds:002b:ffffe50f`41720050=ffffffffffff00010000023e424d53fc
fffff800`46387e97 488b4150        mov     rax,qword ptr [rcx+50h]
fffff800`46387e9b 0f11442430      movups  xmmword ptr [rsp+30h],xmm0
fffff800`46387ea0 488b88f0010000  mov     rcx,qword ptr [rax+1F0h]
fffff800`46387ea7 660f73d808      psrldq  xmm0,8
fffff800`46387eac 8ba98c000000    mov     ebp,dword ptr [rcx+8Ch]
fffff800`46387eb2 66480f7ec1      movq    rcx,xmm0
fffff800`46387eb7 0fb7c1          movzx   eax,cx
fffff800`46387eba 3be8            cmp     ebp,eax
fffff800`46387ebc 740a            je      srv2!Srv2DecompressData+0x68 (fffff800`46387ec8)
fffff800`46387ebe b8bb0000c0      mov     eax,0C00000BBh
fffff800`46387ec3 e9d1000000      jmp     srv2!Srv2DecompressData+0x139 (fffff800`46387f99)
fffff800`46387ec8 488b442430      mov     rax,qword ptr [rsp+30h]
fffff800`46387ecd 33d2            xor     edx,edx
fffff800`46387ecf 48c1e820        shr     rax,20h
fffff800`46387ed3 48c1e920        shr     rcx,20h
fffff800`46387ed7 03c8            add     ecx,eax
fffff800`46387ed9 4c8b15489a0200  mov     r10,qword ptr [srv2!_imp_SrvNetAllocateBuffer (fffff800`463b1928)]
fffff800`46387ee0 e8fbe2f6ff      call    srvnet!SrvNetAllocateBuffer (fffff800`462f61e0)

해당 함수에 브레이크 포인트를 설치하였고 PoC 코드를 실행하면 해당 함수에서 멈추는 것을 확인할 수 있습니다. 트레이싱을 하며 아래의 this 주석 위치까지 실행합니다.

srv2!Srv2DecompressData:
fffff800`46387e60 488bc4          mov     rax,rsp
fffff800`46387e63 48895810        mov     qword ptr [rax+10h],rbx
fffff800`46387e67 48896818        mov     qword ptr [rax+18h],rbp
fffff800`46387e6b 48897020        mov     qword ptr [rax+20h],rsi
fffff800`46387e6f 57              push    rdi
fffff800`46387e70 4156            push    r14
fffff800`46387e72 4157            push    r15
fffff800`46387e74 4883ec40        sub     rsp,40h
fffff800`46387e78 83600800        and     dword ptr [rax+8],0
fffff800`46387e7c 488bf9          mov     rdi,rcx
fffff800`46387e7f 488b81f0000000  mov     rax,qword ptr [rcx+0F0h]
fffff800`46387e86 83782410        cmp     dword ptr [rax+24h],10h
fffff800`46387e8a 0f8204010000    jb      srv2!Srv2DecompressData+0x134 (fffff800`46387f94)
fffff800`46387e90 488b4018        mov     rax,qword ptr [rax+18h] <== this

현재 명령에서 마지막 명령인 mov rax, [rax+18h] 를 실행하면 rax는 Compression Transform Header 의 값입니다. 아래 그림은 PoC 코드를 통해 블루 스크린이 발생했을 때의 패킷에서 SMB2 Compression Transform Header의 캡쳐입니다.

그리고 명령을 실행하고 rax 레지스터를 확인하면 해당 내용이 헤더와 일치하는 것을 확인할 수 있습니다.

1: kd> db @rax
ffffe50f`41720050  fc 53 4d 42 3e 02 00 00-01 00 ff ff ff ff ff ff  .SMB>...........
ffffe50f`41720060  3d 32 fe 53 4d 42 40 00-01 00 00 00 00 00 01 00  =2.SMB@.........
ffffe50f`41720070  00 01 00 00 00 00 00 00-00 00 02 00 00 00 00 00  ................
ffffe50f`41720080  00 00 00 00 00 00 00 00-00 00 01 00 00 00 00 80  ................
ffffe50f`41720090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffe50f`417200a0  00 00 19 00 00 02 00 00-00 00 00 00 00 00 58 00  ..............X.
ffffe50f`417200b0  e6 01 00 00 00 00 00 00-00 00 4e 54 4c 4d 53 53  ..........NTLMSS
ffffe50f`417200c0  50 00 03 00 00 00 18 00-18 00 7c 00 00 00 42 01  P.........|...B.

ProtocolId 필드부터 Compressed SMB3 Data 필드까지 동일한 것을 확인할 수 있습니다. 다음 명령에서는 xmm 레지스터를 이용하여 Offset 필드까지 저장합니다.

이러한 내용을 근거로하여 주석을 달면 아래와 같습니다.

마지막 주석에서부터 확인하면 movzx eax, cxCompressionAlgorithm(0x0001)eax 레지스터에 저장하고, ebp 레지스터와 비교합니다. 이 때 레지스터 값을 확인하면 ebp = 1 임을 알 수 있습니다. 즉 압축 알고리즘이 지원되는지에 대한 검증 로직으로 확인할 수 있습니다.

의사코드를 보기 좋게 만들기 전에, SMB2_COMPRESSION_TRANSFORM_HEADER 를 포함한 몇 가지 구조체를 만들어봤습니다.

아래는 의사코드입니다.

이전보다 훨씬 보기 편해졌습니다. 단지 몇 가지 로직을 확인했을 뿐인데 코드다워졌습니다.

여기서 재밌는 코드를 확인할 수 있습니다. SrvNetAllocateBuffer 는 함수 이름대로 버퍼를 할당하는 함수로 보입니다. 첫 번째 파라미터를 OriginalCompressedSegmentSize(OriginalSize)+ Offset/Length(Offset_Length)로 전달합니다. 해당 함수는 첫 번째 파라미터를 unsigned int로 받지만, 명령에서는 -1로 처리되어 패킷 기준으로 0x23e + (-1) 으로, 전달할 때 0x23d 가 전달됩니다. 이로 인해 커널에서 의도한 값보다 작은 값을 가지게 됩니다.

OriginalCompressedSegmentSize(OriginalSize)는 이름 그대로 압축된 세그먼트의 원래의 크기(=압축되지 않은 데이터의 크기)를 의미합니다. Offset/Length 필드의 경우, 두 가지 경우에 따라 Offset으로 계산되거나 Length로 계산됩니다.

위의 설명에 따르면, 만약 플래그 필드가 0x0(SMB2_COMPRESSION_FLAG_NONE)으로 설정되면 Offset/Length 필드는 Length로 해석됩니다. 그렇지 않은 경우에는 해당 필드는 오프셋으로 해석되며, 헤더의 끝에서부터 압축 된 데이터 세그먼트의 시작까지의 오프셋 입니다.

위의 의사코드를 보면 확인할 수 있듯이 길이에 대한 검증이 존재하지 않습니다. 좀 더 정리한 의사코드와 어셈블리 코드입니다. 다시 한번 의사코드와 주석을 정리합니다.

[-] SmbCompressionDecompress

다음은 srvnet!SmbCompressionDecompress 함수입니다.

nt!RtlGetCOmpressionWorkSpaceSize 함수부터 살펴보겠습니다.

NT_RTL_COMPRESS_API NTSTATUS RtlGetCompressionWorkSpaceSize(
 USHORT CompressionFormatAndEngine,
 PULONG CompressBufferWorkSpaceSize,
 PULONG CompressFragmentWorkSpaceSize
);

CompressionFormatAndEngine 의 경우, CompressionAlgorithm 값으로 넘겨준 CompType_LZNT1의 값을 기준으로 결정됩니다. 우리가 전달한 LZNT1 값은 아래 비트 마스크에 의해 2로 설정됩니다.

다음은 CompressBufferWorkSpaceSize 입니다. 버퍼를 압축하는데 필요한 크기를 바이트 단위로 받는 버퍼에 대한 포인터입니다. 이 값은 RtlCompressBuffer의 WorkSpace 버퍼의 정확한 크기를 결정하는데 사용한다고 되어 있습니다.

마지막으로 CompressFragmentWorkSpaceSize 입니다. 압축 버퍼를 조각 단위(Fragments)로 해제하는데 필요한 크기를 바이트 단위로 받는 버퍼에 대한 포인터 입니다. RtlDecompressFragment 의 WorkSpace 버퍼의 정확한 크기를 결정하는데 사용되지만 현재는 해당 함수가 존재하지 않는다고 합니다.

위의 의사코드를 다시 보면 if문은 or 조건으로 RtlGetCompressionWorkSpaceSize 의 반환 값이 STATUS_SUCCESS 가 아니거나, ExAllocatePoolWithTag 함수로 인해 버퍼가 할당되는 경우로 되어있습니다.

if ( RtlGetCompressionWorkSpaceSize(v13, &NumberOfBytes, &v18) < 0
    || (v6 = ExAllocatePoolWithTag(0x200, NumberOfBytes, '%2SL')) != 0i64 )
  {
    v14 = a6;
    v17 = invalid_packetlength_param;
    v15 = a5;
    v10 = RtlDecompressBufferEx2(
            v13,
            invalid_compressedData_param,
            a5,
            invalid_smb2header_param,
            v17,
            0x1000,
            a6,
            v6,
            v18);
    if ( (v10 & 0x80000000) == 0 )
      *v14 = v15;
    if ( v6 )
      ExFreePoolWithTag(v6, '%2SL');
  }

디버깅을 해보면 아래와 같이 RtlGetCompressionWorkSpaceSize 함수는 STATUS_SUCCESS 를 반환하기 때문에 조건에 부합하지 않습니다.

1: kd> p
srvnet!SmbCompressionDecompress+0x6d:
fffff804`45c1e04d e84e9524fb   call  nt!RtlGetCompressionWorkSpaceSize (fffff804`40e675a0)
1: kd> p
srvnet!SmbCompressionDecompress+0x72:
fffff804`45c1e052 85c0      test  eax,eax
1: kd> r @rax
rax=0000000000000000

ExAllocatePoolWithTag의 경우 정상적으로 버퍼를 할당하므로 조건에 부합하므로 RtlDecompressBufferEx2 함수를 호출할 수 있습니다.

1: kd> p
srvnet!SmbCompressionDecompress+0x8c:
fffff804`45c1e06c e89fbf54fb      call    nt!ExAllocatePoolWithTag (fffff804`4116a010)
1: kd> p
srvnet!SmbCompressionDecompress+0x91:
fffff804`45c1e071 488bf8          mov     rdi,rax
1: kd> r @rax
rax=ffff9f021a9ce000

[-] RtlDecompressBufferEx2

매우 간략하게 이루어져있습니다. RtlDecompressBufferProcs 배열에 저장되어 있는 함수를 호출합니다. 첫 번째 파라미터는 배열의 인덱스 값으로 확인되며 2보다 작거나 4보다 큰 경우 정상적으로 호출되지 않습니다.

RtlDecompressBufferProcs는 아래와 같이 총 3개의 함수로 이루어져 있습니다. 예상과 같이 해당 부분에서 크래시를 일으키는 RtlDecompressBufferLZNT1 함수를 호출합니다.

디버깅을 하면 예상한대로 아래와 같이 RtlDecompressBufferLZNT1 함수를 호출합니다.

3: kd> p
nt!RtlDecompressBufferEx2+0x61:
fffff801`19467661 e89adb1500      call    nt!guard_dispatch_icall (fffff801`195c5200)
3: kd> u @rax
nt!RtlDecompressBufferLZNT1:
fffff801`19a35e90 48895c2418      mov     qword ptr [rsp+18h],rbx
fffff801`19a35e95 48894c2408      mov     qword ptr [rsp+8],rcx
fffff801`19a35e9a 55              push    rbp
fffff801`19a35e9b 56              push    rsi
fffff801`19a35e9c 57              push    rdi
fffff801`19a35e9d 4154            push    r12
fffff801`19a35e9f 4155            push    r13
fffff801`19a35ea1 4156            push    r14

[-] RtlDecompressBufferLZNT1

아래 그림을 확인하면 PageFault Point 주석을 작성했습니다. pSmbHeader는 아래와 같은 연산을 거쳐 해당 함수에 전달됬습니다.

pSmbHeader = &Smb2CompressionTransformHeader + Smb2CompressionTransformHeader.Offset(0xffffffff) + 0x10

어셈블리 코드와 매칭하여 확인하면 잘못된 오프셋과 주소 값의 계산으로 인해 잘못된 메모리 영역임을 알 수 있습니다.

노란 박스의 코드에서 확인해보면 아래와 같이 잘못된 메모리 영역임을 확인할 수 있습니다.

nt!RtlDecompressBufferLZNT1+0x57:
fffff801`19a35ee7 0fb71e          movzx   ebx,word ptr [rsi]
3: kd> dp @rsi
ffffa507`d525c05f  ????????`???????? ????????`????????
ffffa507`d525c06f  ????????`???????? ????????`????????
ffffa507`d525c07f  ????????`???????? ????????`????????
ffffa507`d525c08f  ????????`???????? ????????`????????
ffffa507`d525c09f  ????????`???????? ????????`????????
ffffa507`d525c0af  ????????`???????? ????????`????????
ffffa507`d525c0bf  ????????`???????? ????????`????????
ffffa507`d525c0cf  ????????`???????? ????????`????????
3: kd> p
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x00000050
                       (0xFFFFA507D525C05F,0x0000000000000000,0xFFFFF80119A35EE7,0x0000000000000002)


A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

[0x03] Conclusion

최대한 많은 분석 내용을 담으려고 노력했습니다. 다음 챕터에서는 PoC 코드를 작성하여 실제 악용될 수 있는 사례를 소개하겠습니다.