首页
社区
课程
招聘
[原创]利用调试寄存器提升PCI扩展ROM代码的兼容性
发表于: 2013-11-21 17:05 11211

[原创]利用调试寄存器提升PCI扩展ROM代码的兼容性

2013-11-21 17:05
11211
Author:Sandman
Date:2013.11.20

前几天一个做硬件的朋友向我求助,他开发的一款磁盘数据加密卡在测试的过程中出现了兼容性问题。通过调试发现问题是由软件的问题造成的。通过一下午的调试,最终使用CPU提供的调试机制完美的解决了兼容性问题。希望本文可以对相关开发人员有所帮助。

PS:虽然本文标题含有“PCI、ROM”相关字样,但是本文不涉及复杂的数字电路理论,解决问题的过程全部是由软件代码完成的,请读者不必恐慌...

出现兼容性问题硬件的故障现象:部分机型插卡开机以后,扩展卡初始化界面没有出现,直接进入OS。但是在OS中可以看到安装的新硬件。

出现故障的机型:
1.以联想天开S4250为代表的联想OEM品牌机
2.HP DC5700等机型
3.部分H61、Q75主板的兼容机

出现兼容性问题硬件的基本原理:
问题硬件基于PCI扩展ROM机制,利用在开机引导过程中PCI扩展ROM中的代码会被BIOS执行的特性,挂钩INT19h拦截OS引导,并在INT19h的中断钩子中挂钩INT13h,INT13h钩子做OS读盘时的数据解密工作。PC的引导过程可以概括为如下6点:
1.开机,BIOS代码被加载至F000h:0000h处执行;BIOS POST自检
2.初始化显示设备
3.初始化外围硬件设备(PCI扩展ROM会在此时执行)
4.内存测试、设备测试等过程
5.调用INT19h加载OS
6.INT19h调用INT13h读取引导磁盘的0磁道0柱面1扇区(MBR)到0000h:7C00h处,并jmp far 0000h:7C00h跳转至MBR所在内存,最终MBR完成指定OS的加载。

在PCI规范中提供了一种机制,使PCI设备可以带一个扩展ROM。通过执行ROM中存放的代码来完成与设备有关的初始化的操作,同时也有可能辅助系统的引导功能,例如挂接中断等操作。问题硬件就利用了这个特性挂钩INT19H,抢在OS加载前获取执行权限。很多还原卡也是利用了这个特性抢在操作系统之前执行。
但是,执行扩展ROM的任务是由BIOS来完成的,不同的主板厂商使用的BIOS不同,极少数的主板甚至不会去执行扩展ROM中的代码。因此就要考虑一个问题:我们的扩展ROM代码会不会被BIOS执行?如果答案是否定的话,解决这个问题的办法只有一个:联系主板厂商升级BIOS。

如果ROM代码被部分执行了,只是在某个阶段出现了问题,这样就要通过调试的方式来解决。大厂商可以使用类似Arium ECM-XDP3这样的硬件调试器来进行实时调试。但是由于本人没有硬件调试器,所以只能使用黑箱排查的方式一步步寻找问题所在。

本文的调试过程基于WCH CH360S扩展ROM调试板,如图所示。

其中J1是外部输入引脚引出,可以通过读写I/O寄存器读出其状态。
根据CH360S的DataSheet,J1引脚为通用输入引脚,内置上拉电阻,默认为高电平,若将J1短接,则GPI1引脚接地,输入为低电平。可以利用这个特性在扩展ROM加载时作判断,如果被短接则不执行后面的代码。这样做的目的是为了方便调试,防止由于程序的错误导致无法进入系统。

在ROM的初始化代码段中,我加入了向屏幕打印输出INT19h中断地址的代码,同时HOOK INT19h中断,在HOOK以后再次打印INT19h中断地址的代码查看是否挂钩成功。HOOK处理例程中设置一个死循环,造成一个黑屏死机的假象,证明我们的INT19h被BIOS调用了。详细代码如下。
BITS 16

CONST_VID EQU 1A00H
CONST_DID EQU 8088H

ROM_START:

ROM_HEADER:
  DB 55H        ;; 00H: SIGNATURE 1
  DB 0AAH        ;; 01H: SIGNATURE 2
  DB (ROM_END-ROM_START) / 512  ;; 02H: IMAGE_SIZE
  JMP ROM_ENTRY      ;; 03H: JUMP INSTRUCTION
  times 12H DB 00H      ;; 06H: PADDING
  DW (PCI_DATA_STRUC-ROM_START)  ;; 18H: PCI_DATA_STRUC POINTER
  DW 0000H
  
PCI_DATA_STRUC:
  DB 'PCIR'        ;; 00H: device flag
  DW CONST_VID        ;; 04H: vendor ID
  DW CONST_DID        ;; 06H: device ID
  DW 0000H        ;; 08H: VPD pointer
  DW 0018H        ;; 0AH: PCI_DATA_STRUC's length
  DB 00H          ;; 0CH: PCI_DATA_STRUC's version
  DB 00H,80H,01H        ;; 0DH: device type.Class Code
  DW (ROM_END-ROM_START) / 512    ;; 10H: Image length
  DW 0000H        ;; 12H: the version of the code data
  DB 00H          ;; 14H: the code type of ROM
  DB 80H          ;; 15H: the flag used to make sure if or not the last ROM image
  DW 0000H        ;; 16H: Reserved

Int19h_Hook_Hanlder:
    jmp $      ;卡在这里,证明INT19H确实被执行了
    int 89h
    iret

Install_Int19h_Hook:
    push ds
    xor ax,ax
    mov ds,ax
    mov eax,[ds:(19h*4)]
    mov [ds:(89h)*4],eax
    mov word [ds:(19h*4)],Int19h_Hook_Hanlder
    mov word [ds:(19h*4)+2],cs
    pop ds
    ret

ROM_ENTRY:
    push ax      ;AX=由BIOS传来的总线号/设备号/功能号
    pusha
    mov bx,ax
    mov ax,0b109h
    mov di,PC_BASE_ADDR  ;读取扩展ROM卡的IO地址
    int 1ah
    and cx,0FFF0h
    mov dx,cx
    mov dl,CH367_GPIR
    in al,dx      ;读取J1引脚的状态,如果被短接则退出
    and al,02h
    jz Exit_Init_Rom
  
    call PrintInt19h    ;打印初始的INT19H地址
    call Install_Int19h_Hook  ;安装INT19h钩子
    call PrintInt19h    ;打印修改后的INT19H地址
    
    

Exit_Init_Rom:
    popa
    pop ax    
    retf
%include "CH360DEF.ASM"
%include "PCIE_CFG.ASM"
%include "ShowIVT.asm"

times 32*1024-($-$$) db 0
ROM_END:

以上代码使用NASM编译,编译后写入调试卡,插卡开机后的结果如下图所示:

可以看到,原始INT19h的地址为F000:E6F2 我们的INT19h处理例程为:CC80:0034
但是,在显示延时结束后,并没有达到造成黑屏死机的效果,而是直接进入了OS引导过程。难道这些主板不是用INT19h进行启动的(UEFI主板我开启了CSM支持)?为了证实这个问题,我编写了一个简单的驱动程序,映射物理地址0~0x400(IVT所在的物理地址内存区域),将BIOS的IVT打印输出。代码如下:
PULONG MapPhysicalMemory(ULONG StartAddress,ULONG RegionSize)
{
  PHYSICAL_ADDRESS PhyAddr;

  PhyAddr.HighPart = 0;
  PhyAddr.LowPart = StartAddress;

  return MmMapIoSpace(PhyAddr,RegionSize,FALSE);
}

VOID UnMapPhysicalMemory(PVOID StartAddress,ULONG RegionSize)
{
  MmUnmapIoSpace((ULONG)StartAddress,RegionSize);
}

VOID PrintInterruptVectorTable(PULONG  IVTBuffer)
{
  ULONG    i;
  USHORT    Segment;
  USHORT    Offset;

  for(i=0;i<0xFF;i++)
  {
    Segment = (USHORT)((IVTBuffer[i] & 0xFFFF0000) >> 16);
    Offset = (USHORT)(IVTBuffer[i] & 0xFFFF);
    DbgPrint("INT 0x%.2X:%.4X:%.4X\n",i,Segment,Offset);
  }
}

VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj)
{  

}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
{
  PULONG    IVTBuffer;

  IVTBuffer = MapPhysicalMemory(0,0x100000);
  if(IVTBuffer)
  {
    DbgPrint("Mapped IVT:%.p\n",IVTBuffer);
    PrintInterruptVectorTable(IVTBuffer);
    UnMapPhysicalMemory(IVTBuffer,0x100000);
  }

  pDriverObj->DriverUnload = DriverUnload;

  return STATUS_SUCCESS;
}

下图为输出结果

结果非常出人意料,竟然INT19h的处理函地址又变回了F000:E6F2!!难怪后续的初始化不成功,原来是BIOS在某个时刻又重载了INT19h的处理函数地址。
如果INT19h会被重载,那我们应该如何在OS加载以前获取控制权限?
我把还把思路放回到中断上。继续在ROM代码的执行过程中尝试挂钩INT 13h,INT 15h,INT18h等开机必执行的中断,结果发现这些关键的中断处理例程都会在操作系统启动前的某一个时刻被一起重载,难道BIOS会重载整个IVT?
为了验证这个猜想,我想到了漏洞挖掘中FUZZ的思路:写一段代码,在ROM执行的过程中,从0地址开始,按照16字节的长度,将IVT中的项目填0,然后进入到Windows中查看那些地址被重载了,挑出从那些没有被重载的中断,查阅相关手册,看那些可以利用。代码如下:
xor ax,ax
    mov es,ax
    mov di,ax
    mov cx,8
    cld
    rep stosw

经过一次的填充发现,INT 0 INT1 INT2 INT3中断不会被重载。我立刻把目光转向INT 1中断。INT 1是x86构架CPU用于处理调试异常的中断,用于响应单步异常和硬件断点。

这是我有了一个想法:设置硬件断点到0x7C00,装入我们自己的INT 1处理例程,在硬件断点被触发时装入INT13h钩子后删除硬件断点,返回到MBR继续执行,让MBR加载操作系统,此时硬件的中断钩子也随之就生效了。同时保持原有的INT19h钩子不变,用于兼容老系统。这样既达到了在操作系统加载前获取执行权限,又可以最小限度的修改产品的代码。
实现代码如下:
Install_HW_BreakPoint:
    pushf
    push ds
    push eax
    xor eax,eax
    mov ds,ax
    mov word [ds:(1h*4)],Int1h_BreakPoint_Handler    ;安装INT 1h钩子
    mov word [ds:(1h*4)+2],cs
    mov ax,7C00h
    mov dr0,eax          ;设置DR0=0x7C00
    mov eax,dr7
    bts eax,1
    mov dr7,eax          ;启用DR0断点;TYPE:EXCUTE LEN=1 BYTE
    pop eax
    pop ds
    popf
    ret
    
Clear_HW_BreakPoint:
    pushf
    push eax
    mov eax,dr7
    btr eax,1            ;取消DR0断点
    mov dr7,eax
    xor eax,eax
    mov dr0,eax          ;DR0清零
    pop eax
    popf
    ret

Int1h_BreakPoint_Handler:
    pusha
    call Setup_Self
    popa
    iret

Setup_Self:
    call Clear_HW_BreakPoint        ;取消断点
    ;这里可以安装INT13h钩子或者做一些其它的事情
    ret

Install_Int19h_Hook:
    push ds
    xor ax,ax
    mov ds,ax
    mov eax,[ds:(19h*4)]
    mov [ds:(89h)*4],eax
    mov word [ds:(19h*4)],Int19h_Hook_Hanlder
    mov word [ds:(19h*4)+2],cs
    pop ds
    ret
    
Restore_Int19h_Hook:
    push ds
    xor ax,ax
    mov eax,[ds:(89h)*4]
    mov [ds:(19h)*4],eax
    pop ds
    ret
    

Int19h_Hook_Hanlder:
    pusha
    
    call Setup_Self
    pushf
    cli
    call Restore_Int19h_Hook
    popf

    popa
    
    int 89h
    iret

ROM_Entry:  
    pushf
    cli
    call Install_Int19h_Hook        ;安装INT 19h钩子
    call Install_HW_BreakPoint        ;设置硬件断点
    popf

将修改后的代码写入到板卡中,开机测试,产品的一切功能全部正常。同时利用本方法,可以一次性完美解决上面提到的那些硬件的兼容性问题。下图为加密卡成功挂钩INT13h的截图

[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (6)
雪    币: 84
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
给力。。。。
2013-11-21 19:25
0
雪    币: 360
活跃值: (122)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
3
赶脚搞硬件的都是高富帅+牛人
2013-11-21 19:46
0
雪    币: 608
活跃值: (643)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
屌丝路过
2013-11-21 19:55
0
雪    币: 360
活跃值: (122)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
5
高富屌丝你好。。。。
2013-12-2 19:41
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
都玩硬件了
2013-12-2 20:20
0
雪    币: 5266
活跃值: (2306)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
大佬,您好,非常喜欢您的精彩分享,你代码中有3个引用文件,能不能分享一下啊。正好手头也有个PCI板卡,也想试试。
 "CH360DEF.ASM"、 "PCIE_CFG.ASM"、 "ShowIVT.asm"。
多谢!
2021-4-1 15:04
0
游客
登录 | 注册 方可回帖
返回
//