Microsoft가 2023년 4월 11일에 발표한 보안 업데이트는 90개 이상의 개별 취약점을 해결했습니다. 특히 주목할 만한 것은 QueueJumper라는 이름의 CVE-2023-21554 , Microsoft Message Queueing(MSMQ) 서비스에 영향을 미치는 원격 코드 실행 취약점입니다. MSMQ는 애플리케이션에서 로컬 및 원격으로 모두 연결할 수 있는 메시지 큐를 통해 메시지를 교환할 수 있도록 하는 선택적 Windows 구성 요소입니다. 이 분석은 Valentina Palmiotti, Fabius Watson, Aaron Portnoy가 Randori 및 X-Force Adversary Services 팀과 협력하여 수행했습니다.
초기의 관심을 끌었던 CVE-2023-21554 특성은 다음과 같습니다.
Think 뉴스레터를 통해 AI, 자동화, 데이터 등 가장 중요하고 흥미로운 업계 동향에 대한 최신 소식을 받아보세요. IBM 개인정보 보호정책을 참조하세요.
MSMQ의 존재를 감지하는 방법을 결정하기 위해 실험실 환경에서 구성 요소로 테스트 시스템을 구성했습니다. 포트 1801에서 MSMQ 엔드포인트에 연결할 때 서버가 데이터로 응답하지 않았습니다. 서버가 합리적인 형식의 데이터를 수신하지 않으면 응답하지 않는다고 가정할 수 있습니다. 엔드포인트와 대화를 수행하는 방법을 이해하려면 기본 코드를 추가로 검사해야 했습니다.
해당 권고사항에서는 어떤 바이너리가 패치되었는지 즉시 알 수 없습니다. 변경된 코드를 식별하는 첫 번째 단계는 업데이트 파일을 다운로드하는 것이었습니다. Microsoft 업데이트 카탈로그에서 KB5025224를 검색하면 됩니다.
KB5025224에 대한 Microsoft Update Catalog 검색 결과
구체적으로 누적 업데이트 파일
windows10.0-kb5025224-x64_3522b1b562ed44bc949541dd1c7d08e1e967d925.msu
Windows 11 x86 기반 시스템용 파일을 확보했습니다.
7-Zip 추출기를 사용하여 MSU 파일과 그 안에 포함된 CAB 파일의 내용을 압축 해제했습니다. 압축 해제된 결과 폴더의 모든 파일에서 “msmq” 문자열을 검색한 결과, 다음 항목을 포함하고 있는 Container Index 파일
Windows11.0-KB5025239-x64/express.psf.cix.xml을
확인할 수 있었습니다.
<File id="21296" name="amd64_microsoft-windows-msmq-queuemanager-core_31bf3856ad364e35_10.0.22621.1555_none_5f975f8fdb349517\f\mqqm.dll" length="34811" time="133245266510000000" attr="128">
이 항목은
MQQM.dll1.00
패치된 MQQM.dll과 패치되지 않은 MQQM.dll 간의 BinDiff 결과
Hex-Rays Decompiler를 사용하여 이러한 함수를 분석한 결과, Microsoft 보안 연구 센터(MSRC) 취약점 식별자를 참조하는 것으로 보이는 자세한 함수 이름이 포함된 패치 버전의 심볼이 발견되었습니다.
MQQM.DLL 내의 기능 플래그 기호
자세히 살펴보면 MSRC 사례 번호가 포함된 기호는 취약점 수정을 위한 기능 플래그에 해당합니다. 기능 플래그는 다양한 기능과 실험을 토글하는 Windows의 구성 요소입니다. 여기서 발견된 ‘기능’은 실제로는 취약점에 대한 패치입니다. 기능이 활성화되면 패치된 안전한 코드 경로가 실행됩니다. 그렇지 않으면 패치되지 않은 원래 코드 경로가 실행됩니다.
취약점 수정을 위한 기능 플래그를 보여주는 디컴파일된 코드 스니펫
사용자 및 커널 구성 요소 모두에서 CVE-2023-21554를 구성하는 각 취약점에 대한 기능 플래그 검사가 있었습니다. 보안 업데이트 패키지에 포함된 나머지 바이너리를 분석한 결과, 2023년 4월 패치 화요일에 수정된 대부분의 취약점 또는 모든 취약점에 대해 기능 플래그가 도입된 것으로 나타났습니다.
이러한 플래그를 교차 참조함으로써 패치된 코드 위치를 신속하게 특정할 수 있었습니다. 특히 MSRC76146_MSMQ_OOBRWFixes 플래그와 관련된
이 기능 플래그에 대한 확인은 함수 내에서 8번 이루어집니다. 이러한 검사 중 하나가 아래에 나와 있습니다.
취약점 수정을 보여주는 디컴파일된 코드 스니펫
위의 디컴파일된 코드 스니펫에서 패치된 코드 경로는
GetNextSectionPtrSafe 함수의 디컴파일 스니펫
이 기능 플래그 및 기타 항목의 모든 발생을 검토한 결과, CVE-2023-21554가 다양한 MSMQ 메시지 섹션 유형의 크기를 불충분하게 확인하는 몇 가지 버그로 구성되어 있는 것으로 확인되었습니다. 이러한 취약점으로 인해 MSMQ 메시지 끝에 대한 포인터가 원래 할당된 버퍼의 경계 외부로 임의의 32비트 오프셋만큼 증가할 수 있습니다.
패킷 처리 이후,
메시지 종료를 위한 경계 밖의 메모리 접근 검사
취약점의 익스플로잇 가능성을 평가하는 과정에서 MSMQ 서비스에 대응하는 커널 드라이버 에서 추가적인 커널 취약점이 식별되었습니다. 해당 취약점들은 다시 한 번 기능 플래그 심볼을 통해 식별되었으며, 에서 관찰된 바와 같이 MSRC 사례 76146도 참조하고 있습니다.
커널 드라이버 mqac.sys의 취약점 수정을 위한 기능 플래그를 보여주는 디컴파일된 코드 스니펫
위에 제시된 디컴파일된 코드 스니펫에서 볼 수 있듯이, 기능 플래그가 활성화된 경우 패킷 버퍼에서 해당 헤더를 가져오기 위해 “안전" 함수
프로토콜 파싱과 관련된 대부분의 코드는mqsvc.exe 라이브러리에 위치합니다. 에서 참조된 코드에 도달하려면 클라이언트로부터 유효한 MSMQ 요청이 전송되어야 합니다. MSMQ 메시지 규격과 프로토콜은 방대하고 복잡합니다. 초기에는 MSMQ 서버를 스캔하는 공개 코드가 GitHub gist에 게시되어 있었으며, 이는 패킷 처리 과정을 보다 심층적으로 분석하는 데 기초 자료로 활용되었습니다. 또한 Microsoft 문서는 MSMQ 메시지 형식을 이해하는 데 유용했습니다.
아래 디버깅 세션에서 볼 수 있듯이 DLL 내의 코드는 WSARecv 함수를 호출하여 포트 1801을 통해 데이터를 수신합니다.
0:020> bp ws2_32!wsarecv ; g
중단점 0 히트
WS2_32!WSARecv:
00007ffd`651115c0 48895c2408 mov qword ptr [rsp+8],rbx ss:000000aa`f07fef30=0000000000000006
0:019> k
# Child-SP RetAddr Call Site
00 000000aa`f07fef28 00007ffd`57ba9cb0 WS2_32!WSARecv
01 000000aa`f07fef30 00007ffd`57b9683d
MQQM!NoReceivePartialBuffer+0x38
02 000000aa`f07fefb0 00007ffd`57b2078e MQQM!CWinsockConnection::ReceivePartialBuffer+0x7d
03 000000aa`f07ff010 00007ffd`57b25b28
MQQM!CSockTransport::BeginReceive+0xe6
04 000000aa`f07ff060 00007ffd`57b2d40b
MQQM!CSockTransport::NewSession+0x194
05 000000aa`f07ff3f0 00007ffd`57b2d367 MQQM!CSessionMgr::AcceptSockSession+0x6f
06 000000aa`f07ff430 00007ffd`645e26bd MQQM!AcceptIPThread+0x6a7
07 000000aa`f07ff7d0 00007ffd`6650a9f8
KERNEL32!BaseThreadInitThunk+0x1d
08 000000aa`f07ff800 00000000`00000000 ntdll!RtlUserThreadStart+0x28
0:019> r @$t0=@rdx
0:019> pt
WS2_32!WSARecv+0x19f:
00007ffd`6511175f c3 ret
0:019> dc poi(@$t0+8) LC
000001b5`880ff490 62f06110 524f494c 00000070 5a5a5a5a .a.bLIORp…ZZZZ
000001b5`880ff4a0 0073006e 00610074 0063006e 00200065 n.s.t.a.n.c.e. .
000001b5`880ff4b0 00440049 00000000 00000000 00000000 I.D………….
0:019> ba r1 poi(@$t0+8) ; g
중단점 1 히트
MQQM!CBaseHeader::SectionIsValid+0x154:
00007ffd`57b53450 7462 je
MQQM!CBaseHeader::SectionIsValid+0x1b8 (00007ffd`57b534b4) [br=1]
함수는 새로운 메시지의 시작 부분을 파싱하는 역할을 하며, 메시지는 다음과 같은 형식의 BaseHeader 구조로 시작합니다.
BaseHeader 객체 구조
서버가 응답을 전송하기 전에 수신된 메시지 필드는
MQQM.dll 내 SectionIsValid 디스어셈블리 코드
초기 유효성 검증 이후, 프로토콜 파싱의 대부분은
EodHeader 처리 과정의 취약점을 보여주는 디컴파일 결과
위 디컴파일 결과에서 패치된 경로는
EodHeader 섹션의 총 크기를 계산하는 디스어셈블리
위 디스어셈블리에서는
잘못된 형식의 MSMQ 메시지 끝에 쓸 때 액세스 위반을 표시하는 WinDbg 세션
취약점에서 얻은 경계 밖 쓰기의 강도를 평가하기 위해, 우리는 먼저 기본 메모리의 할당과 관리를 조사했습니다.
MSMQ에서 할당된 버퍼로 복사됩니다. 이 함수는 커널 드라이버를 다음 경로를 통해 호출합니다.NtDeviceIoControl.
패킷 버퍼 할당 함수의 디컴파일
커널 구성 요소는 디스크 파일의 매핑된 뷰에서 분할된 버퍼를 반환하며, 해당 파일은
패킷 버퍼 메모리가 파일 매핑임을 보여주는 MQSVC 프로세스의 ProcessHacker 메모리 레이아웃
패킷 버퍼 주소는 무작위 패킷 핸들과 함께 IOCTL을 통한 할당 요청 후 사용자 공간으로 반환됩니다. 드라이버는 사용자 공간의 요청을 처리할 때 핸들을 사용하여 패킷 버퍼 주소를 계산합니다.
취약한 버퍼의 할당 메모리 영역은 프로세스 힙이 아닌 매핑된 파일 영역에 존재합니다. 또한 해당 메모리 영역에는 객체가 저장되어 있지 않습니다. 파일 매핑에는 MSMQ 메시지의 내용과 함께 저장된 메타데이터만 포함되어 있습니다.
또한 메모리 레이아웃에 대한 사전 지식이 없으면 객체 포인터를 덮어쓰기 위해 프로세스 힙에 매핑되는 파일의 상대적 위치를 알 수 없습니다. 따라서 원격 코드 실행을 실현하기 위해서는 메모리를 예측 가능한 위치로 형성하는 메서드가 필요합니다.
대상 시스템에 정상적인 MSMQ 메시지를 전송하는 실험을 진행한 결과, 다수의 메시지를 반복적으로 대량 전송함으로써
MQ 파일 매핑 인접 영역에 새로운 힙이 할당된 모습을 보여주는 MQSVC 프로세스 메모리 레이아웃
그러나 이를 위해서는 공격자가 대상에 있는 하나 이상의 대기열에 메시지를 보낼 수 있는 액세스 권한이 있어야 합니다. 그렇지 않으면 메시지가 삭제되고 덮어쓰기되므로 패킷 메모리를 지속적으로 차지하지 않습니다.
수행된 연구 실험에서 권한이 없는 사용자는 기본적으로 누구에게나 원격으로 메시지를 보낼 수 있는 대기열을 만들 수 있다는 것이 발견되었습니다. 대기열은 시스템에서 무기한 지속됩니다. 대부분의 최신 MSMQ 용도는 선택적 구성 요소 및 어댑터로 사용되고 있지만 Oracle, Veritas 및 Xerox와 같은 공급업체는 MSMQ를 핵심 종속성으로 사용하는 엔터프라이즈 소프트웨어를 제공합니다. 따라서 공격 시나리오는 비현실적이지 않습니다.
경계 외 메모리 접근 취약점은 공격에 활용 가능한 기본 동작을 제공하며, 이를 통해 악의적인 클라이언트가 패킷에 할당된 메모리 경계를 넘어OnDiskExtensionHeader
오프셋에 값을 쓸 수 있는 취약점을 평가할 때는 다음과 같은 몇 가지 속성을
고려해야 합니다.
QueueJumper 취약점의 경우 공격자가 제어하는 데이터를 사용하여 계산되기 때문에 OOB 쓰기에 할당된 버퍼의 오프셋을 제어할 수 있습니다. 그러나 OOB 쓰기의 내용은 제한적으로 제어할 수 있습니다.
여기서Address 헤더가 기록될 패킷 메시지의 끝을 가리키는 손상된 포인터를 의미합니다. 발생하는 일련의 동작은 다음과 같습니다.
0x000000000000000C
이 기록됩니다.Address0x00000000Address+0x2
에 기록됩니다.0x0000000000000000Address+0x12
에 기록됩니다.0x0000000000000000
이 Address+0x1A
에 기록됩니다.0x00000094
이 Address+0xE
에 기록됩니다.0x0000Address+0x220x0000
이 Address+0x62
에 기록됩니다.memcpy(Address+0xA6, Source, Source->AddressLength+0x08)
위에서memcpySource 구조체는 특정 유형(예: NetBIOS)의 단일 전송 주소를 정의합니다. 정의는 다음과 같습니다.
typedef struct _TA_ADDRESS {
USHORT AddressLength;;
USHORT AddressType; Info;
UCHAR Address[1];;
} TA_ADDRESS, *PTA_ADDRESS;
지정된 AddressType의 주소에 있는 바이트 수를 지정합니다.
전송 주소의 유형을 지정합니다.
전송 주소가 포함된 가변 크기 배열을 지정합니다.
아래 디버깅 세션에서TA_ADDRESS
rax=0000014e1d110180 rbx=0000014e1d110180 rcx=0000014e1d110184
rdx=0000014e1ce86b10 rsi=0000014e1d110001 rdi=0000000545d7fa50
rip=00007ff843208074 rsp=0000000545d7f940 rbp=0000000545d7f980
r8=000000000000000c r9=0000000000000002 r10=0000000000000000
r11=0000000545d7f938 r12=0000000000000002 r13=0000000000001000
r14=0000014e1ce86b10 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
MQQM!CQmPacket::CQmPacket+0x89c:
00007ff8`43208074 e8a5780a00 call MQQM!memcpy (00007ff8`432af91e)
0:017> dc rcx
0000014e`1d110184 00010004 00000000 8620a8c0 00000000 ………. …..
위에서TA_ADDRESS, 유형1을 가집니다. 각 바이트를 IPv4 옥텟으로 변환하면 주소가 되며, 이는 클라이언트의 IPv4 소스 주소입니다.
이 시나리오를 사용하면 소스 주소를 통해 공격자가 제어하는 데이터를 메모리의 패킷 할당의 제어된 오프셋에 쓸 수 있습니다. 매우 제한적이고 비실용적일 수 있지만 이론적으로는 패킷 오프셋을 줄이면서 다양한 소스 주소에서 여러 요청을 수행하여 메모리에 기록되는 값을 정확하게 제어하는 데 사용할 수 있습니다.
블라인드 원격 코드 실행을 달성하는 데 발생하는 많은 제한 사항으로 인해 정보 유출과 같은 대체 공격 시나리오가 조사되었습니다.
고려된 한 가지 공격은 특정 메시지 섹션에 대한 포인터가 범위를 벗어난 메모리를 가리키도록 손상시킨 다음 나중에 메시지를 검색하여 인접한 메모리를 읽는 것이었습니다.
그러나 각 섹션 구문 분석의 시작 부분에서는 현재 섹션의 위치를 확인하므로 구문 분석 로직을 속여 범위를 벗어난 데이터 섹션을 구문 분석하도록 할 수는 없습니다.
또 다른 가능한 공격 시나리오는 다른 메시지의 OnDiskExtensionHeader를 덮어써 메시지 수신자를 속여 수신된 메시지의 출처를 찾는 것입니다. 마찬가지로 공격자가 액세스할 수 없는 대기열용 메시지의 내용을 덮어쓸 수 있습니다. 메시지의 특정 내용을 덮어쓰려면 공격자가 메시지 크기와 같은 메시지에 대한 정보를 가지고 있어야 합니다.
다음과 같은 형식으로 범위를 벗어난 데이터 액세스에 취약한 Soapheader라는 이름의 선택적 섹션이 있습니다.
Soapheader 패킷 형식
버퍼가 해제될 때 MSMQ 패킷 데이터를 저장하는 데 사용되는 메모리가 제대로 지워지지 않는 현상이 관찰되었습니다. 결과적으로 이전 MSMQ 패킷보다 작은 크기의 MSMQ 패킷을 전송하면 메모리 레이아웃에 현재 패킷의 데이터와 이전에 처리된 MSMQ 패킷의 데이터가 포함된 메모리 레이아웃이 생성됩니다.
제어되지 않는 추가 쓰기가 인접 메모리를 손상시키는 쓰기 콘텐츠에 대한 제한된 제어와 메시지 레이아웃에 대한 제한된 지식은 악용에 큰 장애물이었습니다.
분석 시점에는 MSMQ 메시지 패킷의 초기 처리 과정에서 발생하는 사용자 공간 취약점에 대해서만 익스플로잇 가능성을 평가했습니다. 커널 변종 취약점을 추가로 분석하면 새로운 익스플로잇 경로가 드러날 수 있습니다. 예를 들어, 손상된 헤더를 포함시킴으로써 파일의 커널 메모리 매핑 영역에서 쓰기 원시 동작을 획득할 가능성이 있습니다. 일부 경우에는 사용자 공간 처리 단계의 검사가 커널에서 특정 후속 처리 작업을 수행하지 못하도록 막습니다.
또한, 커널 변형의 경우 공격자가 메시지 내용을 직접 제어할 수 없으며, 사용자 공간과 동일한 파일 매핑을 사용하며, 커널 풀(힙)에서 동작하지 않습니다. KASLR과 같은 완화 조치도 원격 악용에 있어 극복하기 어려운 장애물입니다.
패치된 코드 구현 방식으로 인해 원격 클라이언트는 시스템에 업데이트가 적용되었는지 안전하게 확인할 수 있습니다. 취약점을 트리거하되 실제로 경계 외 접근을 발생시키지 않는 MSMQ 메시지를 전송함으로써 서비스 프로세스를 크래시시키지 않고 이를 확인할 수 있습니다. 메시지의 UserHeader에 HTTP 플래그가 설정되어 있으면 SRMPEnvelopeHeader가 존재해야 합니다. 패치에서는 필드를 2배 한 값에 대해 정수 오버플로 검사를 수행합니다. 해당 길이 값을 조작하면 곱셈 결과가 실제 데이터 크기와 동일한 작은 값으로 오버플로되도록 설정할 수 있습니다.
SRMPEnvelopeHeader DataLength에 대한 정수 오버플로 검사를 보여주는 디컴파일된 코드 스니펫
대상에 패치가 적용되면 취약성으로 인해 예외가 발생하고 서버가 메시지 처리를 중단하므로 클라이언트로 응답이 다시 전송되지 않습니다. 패치가 적용되지 않은 경우 서버는 정상적으로 메시지를 처리하고 응답을 보냅니다.
취약한 버퍼가 할당된 메모리 영역의 위치 특성상, 대상 시스템의 임의 큐로 메시지를 전송할 수 있는 공격자에게는 원격 코드 실행이 보다 현실적인 공격 시나리오로 보입니다. 이 시나리오에서는 힙 그루밍을 통해 인접한 힙 영역이 할당되도록 유도할 수 있습니다. 쓰기 내용에 대한 제어가 극히 제한적이기 때문에 원격 익스플로잇에는 상당한 제약이 따릅니다. ASLR과 같은 완화 기법이 적용되어 있으므로, 추가적으로 정보 유출 취약점도 확보해야 합니다. 공격 원시 동작을 통해 획득한 상수(0xC, 0x0
궁극적으로 우리는 QueueJumper를 원격으로 악용하는 것이 불가능하지는 않지만 앞서 언급한 요구 사항으로 인해 이를 달성하기 어렵다는 결론을 내렸습니다. 이 블로그 게시물은 매우 복잡한 메시지 사양, 서비스 및 프로토콜의 표면적인 부분만 다루고 있습니다. 사용자 공간과 커널 구성 요소 작업 모두에 대한 추가 연구가 필요합니다.