취약점 정보
2022년 MS Patch Tuesday에서 RPC관련 CVSS 9.8 스코어를 가진 CVE-2022-26809 취약점이 공개 되었습니다.
이 취약점은 별도의 인증이나 사용자의 상호작용이 필요 없는 wormable 취약점입니다.
이로 인해 노출되어 있는 1,329,075개의 windows 서버 및 desktop 대상으로 악의적인 행위에 이용될 수 있습니다.
취약점 발생 함수 진입 방법
취약점 패치 이전 버전의 바이너리와 취약점 패치 이후 버전의 바이너리를 대상으로 디핑 수행 결과, 하기의 script를 사용해 취약점이 발생한 OSF_SCALL::ProcessReceivedPDU 함수에 도달할 수 있었습니다.
바이너리 디핑 및 취약점 발생 함수 접근
바이너리 디핑 도구인 Diaphora(https://github.com/joxeankoret/diaphora)를 사용해 취약점 패치 이전 버전의 바이너리 rpcrt4.dll(10.0.19041.1466)과 취약점 패치 이후 버전의 바이너리 rpcrt4.dll(10.0.19041.1645)을 대상으로 바이너리 디핑을 수행했습니다.
OSF_SCALL::ProcessReceivedPDU 함수 내에 패치 된 부분을 대조해 보았을 때, integer overflow 검사함수가 추가되었음을 확인할 수 있었습니다.
rpcrt4!OSF_SCALL::ProcessReceivedPDU (10.0.19041.1466) / rpcrt4!OSF_SCALL::ProcessReceivedPDU(10.0.19041.1645)
취약점이 발생한 위치에 접근하기 위해, [MS-RPCE] 문서를 참조하였고, python3 impacket 라이브러리를 이용하여 script를 작성했습니다.
하기의 script를 이용해 OSF_SCALL::ProcessReceivedPDU 함수에 접근 할 수 있었습니다.
from impacket.dcerpc.v5 import transport
from impacket.structure import Structure
from impacket.uuid import *
rpctransport = transport.DCERPCTransportFactory(r"ncacn_ip_tcp:192.168.137.128")
rpctransport.set_dport(135)
rpctransport.setRemoteHost('192.168.137.128')
dce = rpctransport.DCERPC_class(rpctransport)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(uuidtup_to_bin(('e1af8308-5d1f-11c9-91a4-08002b14a0fa','3.0')))
dce.call(0, b"")
Python
복사
상세분석
OSF_SCALL::ProcessReceivedPDU 함수 내 break point를 설정한 뒤, stack trace를 출력 시켰을 때, 하기와 같은 내용이 출력 됩니다.
OSF_SCALL::ProcessReceivedPDU 함수 내에서 취약점을 trigger하기 위해서 *(this+145)변수의 값이 초기화되어 있어야 합니다.
__int64 __fastcall OSF_SCALL::ProcessReceivedPDU(OSF_SCALL *this, struct rpcconn_common *a2, int a3, int a4)
{
...
...
...
if ( !*((_DWORD *)this + 145) || *((_DWORD *)this + 115) )
{
...
...
...
}
else
{
...
...
...
if ( !(unsigned int)QUEUE::PutOnQueue((OSF_SCALL *)((char *)this + 600), (char *)a2 + 24, Size) )
{
*((_DWORD *)this + 147) += v43;
if ( (v57 & 2) != 0 )
{
v15 = (*((_BYTE *)this + 736) & 4) == 0;
*((_DWORD *)this + 135) = 3;
if ( !v15 )
{
_InterlockedAnd((volatile signed __int32 *)(*((_QWORD *)this + 38) + 428i64), 0xFFFFFFFD);
*((_DWORD *)this + 184) &= ~4u;
}
}
...
...
}
...
...
}
C++
복사
OSF_SCALL::BeginRpcCall 함수에서 OSF_SCONNECTION::LookupBinding 함수를 호출하여 binding 된 v8 변수를 가져옵니다.
(v8 + 0x38) & 2 변수의 값이 초기화되어 있을 때, *(this + 145)변수의 값을 초기화합니다.
binding된 객체는 RpcServerRegisterIf2 함수에서 할당되고, RPC_INTERFACE::RPC_INTERFACE 함수에서 처음 initialize 됩니다.
RPC_INTERFACE *__fastcall RPC_INTERFACE::RPC_INTERFACE(
RPC_INTERFACE *this,
struct _RPC_SERVER_INTERFACE *a2,
struct RPC_SERVER *a3,
int a4,
unsigned int a5,
unsigned int a6,
int (__stdcall *a7)(void *, void *),
void *a8,
int *a9,
struct RPCP_INTERFACE_GROUP *a10)
{
int updated; // eax
bool v15; // zf
*((_DWORD *)this + 2) = 0;
RtlInitializeCriticalSectionAndSpinCount((PRTL_CRITICAL_SECTION)((char *)this + 16), 0);
*((_DWORD *)this + 0xE) = 0;
*((_DWORD *)this + 50) = 0;
*((_QWORD *)this + 27) = (char *)this + 0xE8;
*((_QWORD *)this + 0x1C) = 4i64;
*(_OWORD *)((char *)this + 0xE8) = 0i64;
*(_OWORD *)((char *)this + 0xF8) = 0i64;
*((_DWORD *)this + 0x53) = 0;
*((_DWORD *)this + 84) = 0;
*((_DWORD *)this + 85) = 0;
*((_DWORD *)this + 86) = 0;
*((_DWORD *)this + 87) = 0;
*((_DWORD *)this + 88) = 0;
*((_DWORD *)this + 89) = 0;
*((_DWORD *)this + 90) = 0;
*((_DWORD *)this + 91) = 0;
RtlInitializeSRWLock((char *)this + 480);
*((_QWORD *)this + 47) = (char *)this + 368;
*((_QWORD *)this + 46) = (char *)this + 368;
*((_DWORD *)this + 96) = 0;
*((_DWORD *)this + 122) = 0;
*((_QWORD *)this + 62) = (char *)this + 512;
*((_QWORD *)this + 63) = 4i64;
*((_OWORD *)this + 32) = 0i64;
*((_OWORD *)this + 33) = 0i64;
*((_DWORD *)this + 136) = 0;
*((_QWORD *)this + 69) = 0i64;
*(_QWORD *)this = a3;
*((_QWORD *)this + 0x1A) = 0i64;
*((_QWORD *)this + 0x17) = 0i64;
*((_QWORD *)this + 0x46) = 0i64;
*((_QWORD *)this + 0x47) = 0i64;
updated = RPC_INTERFACE::UpdateRpcInterfaceInformation(this, a2, a4, a5, a6, a7, a8, a10);
v15 = a2->Length == 0x60;
*a9 = updated;
if ( v15 && (a2->Flags & 1) != 0 )
*((_DWORD *)this + 14) |= 2u;
return this;
}
C++
복사
a2->Flags & 1 변수의 값이 초기화 되어있을 때, (_DWORD *)this + 14)(==(v8 + 0x38))변수의 값을 초기화합니다.
따라서 RPC를 등록하는 함수 RpcServerRegisterIf 함수의 첫 번째 인자로부터 +0x38의 상대 주소의 위치한 변수가 초기화 되어 있을 때 취약점을 trigger할 수 있을 것으로 의심됩니다.
TODO
Antonio Cocomazzi (https://twitter.com/splinter_code)가 custom RPC 서버를 이용해 취약점이 발생한 함수에 접근한 사실을 확인할 수 있었습니다.
custom RPC 서버를 이용해 취약점이 발생한 basic block에 접근할 수 있는 condition 을 구성 해 취약점을 trigger 할 수 있을 것으로 생각됩니다.
결론
분석을 수행 한 몇몇 연구원들은 default 상태의 RPC 서버를 통해 취약점이 발생한 basic block 에 접근하기 어렵다는 의견입니다.