美文网首页
分析Windows启动过程(二)

分析Windows启动过程(二)

作者: xtex | 来源:发表于2020-02-23 12:09 被阅读0次

    分析Windows启动过程(一)
    没看过上一篇别过来.
    接着上一篇,
    确定了引导用的(活动分区)分区之后到文件偏移0x34的位置
    此时bp为DPT记录的偏移
    代码如下:

    00000034  885600            mov [bp+0x0],dl
    00000037  55                push bp
    00000038  C6461105          mov byte [bp+0x11],0x5
    0000003C  C6461000          mov byte [bp+0x10],0x0
    

    由于上面没有初始化dl或dx有关的代码, 笔者认为这是置bp处的一个字节为0(也就是设置当前分区在内存中的记录为非活动分区)
    然后push bp将bp压入栈, 接着把内存中下一个分区记录的开始磁头设置为0x05, 把分区类型设置为0(非活动分区)
    然后看代码:

    00000040  B441              mov ah,0x41
    00000042  BBAA55            mov bx,0x55aa
    00000045  CD13              int 0x13
    

    这里是一个int 0x13的中断调用, 根据int-13上的信息, 我们可以找到这个页面:Int 13/AH=41h/BX=55AAh
    这个页面的要求和上面的代码一模一样, AH为0x41, BX为0x55AA, 看看他是干嘛的吧.
    它的名称是IBM / MS INT 13扩展-安装检查
    用于检测一个设备是否安装了这个扩展(可是我还是不明白指定设备用的dl在哪指定)
    然后两行代码:

    00000047  5D                pop bp
    00000048  720F              jc 0x59
    

    这里从堆栈恢复bp(见00000037), 根据上面那个int 0x13操作的描述, 如果不支持扩展则设置CF, 所以如果不支持扩展则跳到0x59
    如果支持扩展, 则会经过下面这段代码:

    0000004A  81FB55AA          cmp bx,0xaa55
    0000004E  7509              jnz 0x59
    00000050  F7C10100          test cx,0x1
    00000054  7403              jz 0x59
    00000056  FE4610            inc byte [bp+0x10]
    

    根据描述, 如果支持扩展并且已安装, 则bx被置为0xaa55, 所以这里前两句判断是否已安装(提示: JNZ == JNE), 没有安装也跳转到0x59
    然后比较cx和0x1, 根据说明, cx保存了支持的扩展API, 0x1的二进制是00000001, 这两行里, 如果cx == 0x1则跳转0x59, 所以仅cx != 0b00000001时才会继续, 那么00000001对应什么呢?对应着removable drive controller functions supported, 也就是说, 如果只支持这种功能, 那也没用
    如果有用(支持并安装了扩展API)则执行inc byte [bp+0x10], 这句代码把[bp+0x10]作为一个字节 +1, 也就是让引导分区的分区起始相对扇区+0xff
    接着代码:

    00000059  6660              pushad
    0000005B  807E1000          cmp byte [bp+0x10],0x0
    0000005F  7426              jz 0x87
    00000061  666800000000      push dword 0x0
    00000067  66FF7608          push dword [bp+0x8]
    0000006B  680000            push word 0x0
    0000006E  68007C            push word 0x7c00
    00000071  680100            push word 0x1
    00000074  681000            push word 0x10
    00000077  B442              mov ah,0x42
    00000079  8A5600            mov dl,[bp+0x0]
    0000007C  8BF4              mov si,sp
    0000007E  CD13              int 0x13
    00000080  9F                lahf
    00000081  83C410            add sp,byte +0x10
    00000084  9E                sahf
    00000085  EB14              jmp short 0x9b
    00000087  B80102            mov ax,0x201
    0000008A  BB007C            mov bx,0x7c00
    0000008D  8A5600            mov dl,[bp+0x0]
    00000090  8A7601            mov dh,[bp+0x1]
    00000093  8A4E02            mov cl,[bp+0x2]
    00000096  8A6E03            mov ch,[bp+0x3]
    00000099  CD13              int 0x13
    0000009B  6661              popad
    

    可以将此段代码看作一个过程, pushad保存当前环境, 最后popad恢复环境, 然后看中间的代码.

    0000005B  807E1000          cmp byte [bp+0x10],0x0
    0000005F  7426              jz 0x87
    

    这两段代码判断是否支持扩展API, 不支持则跳转到0x87
    如果不跳转, 则执行这段代码:

    00000061  666800000000      push dword 0x0
    00000067  66FF7608          push dword [bp+0x8]
    0000006B  680000            push word 0x0
    0000006E  68007C            push word 0x7c00
    00000071  680100            push word 0x1
    00000074  681000            push word 0x10
    00000077  B442              mov ah,0x42
    00000079  8A5600            mov dl,[bp+0x0]
    0000007C  8BF4              mov si,sp
    0000007E  CD13              int 0x13
    00000080  9F                lahf
    00000081  83C410            add sp,byte +0x10
    00000084  9E                sahf
    00000085  EB14              jmp short 0x9b
    

    这段代码将0x00推入栈, 将引导分区的结束柱面推入栈, 推入0x00, 0x7c00, 0x1, 0x10, 然后调用了一个int 0x13的中断, 这个中断的信息在Int 13/AH=42h这里, 是"扩展读取", DL指向读取的设备, si指向一个设备地址包, 这里si指向sp, 也就是栈顶, 所以上面的push实际上是在构造一个disk address packet
    从后面看起, 最后面push了0x10, 而且是个word, 很好地处理了一个字节的保留字节
    倒数第二个push是0x1, 根据描述02h WORD number of blocks to transfer (max 007Fh for Phoenix EDD), 0x1是指传输块数, 然后是两个word, 这两个word并成一个dword指向缓冲区, 而缓冲区地址就是0x7c00, 现在明白了为什么一开始要复制引导扇区到0x600了吧! 代码顶上两个dword组成一个qword, 根据定义08h QWORD starting absolute block number,
    这个qword指向起始绝对块号, 而它的值是分区的分区起始相对扇区号, 也就是说, 读取分区的第一个扇区到0x7c00!!!

    00000080  9F                lahf
    00000081  83C410            add sp,byte +0x10
    00000084  9E                sahf
    

    这三行代码我就不知道是干嘛的了, 知道的说一下, 我觉得像是给PF标志位置位.
    最后是jmp short 0x9b也就是跳转到这个过程的末尾


    回到上面, 如果不支持扩展API, 那么会怎么样呢?
    如果不支持扩展API则会执行:

    00000087  B80102            mov ax,0x201
    0000008A  BB007C            mov bx,0x7c00
    0000008D  8A5600            mov dl,[bp+0x0]
    00000090  8A7601            mov dh,[bp+0x1]
    00000093  8A4E02            mov cl,[bp+0x2]
    00000096  8A6E03            mov ch,[bp+0x3]
    00000099  CD13              int 0x13
    

    看到int第一时间就应该查文档, 然而笔者看到那句mov ax, 0x201头瞬间炸了, 这什么操作?
    想了想, 这应该等于mov ah, 0x2mov al, 0x01, 看来微软为了节约空间真是不惜所措啊
    于是找到了Int 13/AH=02h这个页面
    这个调用用于读取扇区到内存
    AL是要读取的扇区数, 1个
    CH起始柱面号, CL起始扇区号, DH磁头号, DL设备号
    Ok, 所以这些数据都是从分区记录里读取的, 而BX指向数据缓冲区, 0x7c00

    接着看后面的代码:

    0000009D  731C              jnc 0xbb
    

    如果没出错, 那么跳转到0xbb, 因为如果int 0x13出错, CF标志位会被置位
    先看错误处理程序:

    0000009F  FE4E11            dec byte [bp+0x11]
    000000A2  750C              jnz 0xb0
    000000A4  807E0080          cmp byte [bp+0x0],0x80
    000000A8  0F848A00          jz near 0x136
    000000AC  B280              mov dl,0x80
    000000AE  EB84              jmp short 0x34
    000000B0  55                push bp
    000000B1  32E4              xor ah,ah
    000000B3  8A5600            mov dl,[bp+0x0]
    000000B6  CD13              int 0x13
    000000B8  5D                pop bp
    000000B9  EB9E              jmp short 0x59
    

    dec byte [bp+0x11]将分区起始相对扇区号 -1
    jnz 0xb0如果分区起始相对扇区号 == 0则跳转到0xb0
    如果不跳转则执行:

    000000A4  807E0080          cmp byte [bp+0x0],0x80
    000000A8  0F848A00          jz near 0x136
    000000AC  B280              mov dl,0x80
    000000AE  EB84              jmp short 0x34
    

    检查设备编号是否0x80, 是则直接跳转到0x136
    然后将dl设置为0x80, 之后jmp回到之前刚刚找到活动分区的地方, 我认为启动过程中, 第一次都会先执行到这里, 然后下一次才有可能成功
    那如果跳到0x136呢?

    00000136  A0B607            mov al,[0x7b6]
    00000139  EB03              jmp short 0x13e
    

    打印0x7b6的文字(但我没看出有什么文字在那里)
    如果如果分区起始相对扇区号 == 0, 则执行:

    000000B0  55                push bp
    000000B1  32E4              xor ah,ah
    000000B3  8A5600            mov dl,[bp+0x0]
    000000B6  CD13              int 0x13
    000000B8  5D                pop bp
    000000B9  EB9E              jmp short 0x59;
    

    push bppop bp用于保护bp的值
    xor ah,ah同等于mov ah, 0, 将ah设置为0
    将dl设置为设备编号, 然后int 0x13
    对于ah=0的int 0x13调用我已经很熟悉了, 就是重置设备dl
    最后一句jmp short 0x59就重新回去执行上面那个尝试读取过程了
    意思就是重置一次设备并重试读取
    那么如果读取成功了呢?

    插一句话: 看到Notepad++的滚动条愈发向下, 笔者更有信心了

    000000BB  813EFE7D55AA      cmp word [0x7dfe],0xaa55
    000000C1  756E              jnz 0x131
    000000C3  FF7600            push word [bp+0x0]
    000000C6  E88D00            call 0x156
    000000C9  7517              jnz 0xe2
    

    这段代码检查上面读入的引导扇区是否以0xaa55结尾, 也就是是否有效扇区
    不是则跳转到0x131
    0x131会干嘛呢?

    00000131  A0B707            mov al,[0x7b7]
    00000134  EB08              jmp short 0x13e
    

    输出0x7b7的文字, 但我一脸懵逼, 那里根本没有文字, 然后调用0x156, 后面讲

    000000CB  FA                cli
    000000CC  B0D1              mov al,0xd1
    000000CE  E664              out 0x64,al
    000000D0  E88300            call 0x156
    000000D3  B0DF              mov al,0xdf
    000000D5  E660              out 0x60,al
    000000D7  E87C00            call 0x156
    000000DA  B0FF              mov al,0xff
    000000DC  E664              out 0x64,al
    000000DE  E87500            call 0x156
    000000E1  FB                sti
    

    在执行这段代码时, 首尾clisti说明中途需要关闭中断, 否则会有问题
    我一开始以为是进入保护模式, 发现原来不是, 只是打开A20地址线, 但是我见过的书都没有这么长的代码, 到Wiki搜索之后才发现是A20地址线, Wiki上说, 有些BIOS默认打开A20, 但Windows并没有测试A20是否已打开, 直接尝试打开
    Wiki给出这样的代码用于打开A20:

    enable_A20:
            cli
            call    a20wait
            mov     al,0xAD
            out     0x64,al
            call    a20wait
            mov     al,0xD0
            out     0x64,al
            call    a20wait2
            in      al,0x60
            push    eax
            call    a20wait
            mov     al,0xD1
            out     0x64,al
            call    a20wait
            pop     eax
            or      al,2
            out     0x60,al
            call    a20wait
            mov     al,0xAE
            out     0x64,al
            call    a20wait
            sti
            ret
    a20wait:
            in      al,0x64
            test    al,2
            jnz     a20wait
            ret
    a20wait2:
            in      al,0x64
            test    al,1
            jz      a20wait2
            ret
    

    哎这其实很像Windows的代码啊!
    Windows的代码有很多call 0x156啊?
    看看这里的代码:

    00000156  2BC9              sub cx,cx
    00000158  E464              in al,0x64
    0000015A  EB00              jmp short 0x15c
    0000015C  2402              and al,0x2
    0000015E  E0F8              loopne 0x158
    00000160  2402              and al,0x2
    00000162  C3                ret
    

    sub cx, cx将cx减去cx??可能是置0吧
    in al, 0x64和Wiki一样, 后面的就不一样了, 可是...jmp short 0x15c有什么用?我也不知道, and al,0x2是位与, 然后loopne 0x158, 只要cx != 0就跳转
    好乱啊, 总之这段代码打开A20就行了, 好乱!
    对了, 后面最后那个and是前面用的, 在000000C9有用, 000000C9根据判断会可选的跳过打开A20, 所以应该是判断是否BIOS自动打开吧.

    随后我二脸懵逼:

    000000E2  B800BB            mov ax,0xbb00
    000000E5  CD1A              int 0x1a
    

    http://www.ctyme.com/intr/int-1a.htm没有任何记载, 哎!

    000000E7  6623C0            and eax,eax
    000000EA  753B              jnz 0x127
    000000EC  6681FB54435041    cmp ebx,0x41504354
    000000F3  7532              jnz 0x127
    000000F5  81F90201          cmp cx,0x102
    000000F9  722C              jc 0x127
    

    也就是eax != 0则跳到0x127, ebx == 0x41504354 则跳0x127, 最后一个跳转我百思不得其解, 因为我不知道cmp设置CF的条件

    000000FB  666807BB0000      push dword 0xbb07
    00000101  666800020000      push dword 0x200
    00000107  666808000000      push dword 0x8
    0000010D  6653              push ebx
    0000010F  6653              push ebx
    00000111  6655              push ebp
    00000113  666800000000      push dword 0x0
    00000119  6668007C0000      push dword 0x7c00
    0000011F  6661              popad
    

    上面的一堆push模拟了一个pushad操作, 然后popad, 其中, ebx和ebp没有更改, esp变成ebx
    pushad的顺序:(E)AX, (E)CX, (E)DX, (E)BX, (E)SP, (E)BP, (E)SI, (E)DI
    所以相当于下面代码:

    mov eax, 0xbb07
    mov ecx, 0x200
    mov edx, 0x8
    mov esp, ebx
    mov esi, 0x0
    mov edi, 0x7c00
    

    然后还是设置:

    00000121  680000            push word 0x0
    00000124  07                pop es
    

    设置es为0x0
    然后:

    00000125  CD1A              int 0x1a
    

    上面设置了eax为0xbb07, 隐含mov ax, 0x07
    mov ax, 0x07等于mov al, 0x07
    真不明白, TIME - SET ALARM (AT,XT286,PS)是Int 1A/AH=06h, 可是从未调用过
    这个int 0x1a定义在http://www.ctyme.com/intr/rb-2283.htm
    取消时间报警?

    00000127  5A                pop dx
    00000128  32F6              xor dh,dh
    0000012A  EA007C0000        jmp 0x0:0x7c00
    0000012F  CD18              int 0x18
    

    pop dx又是什么操作, 然后dh置0, 所以只取dl?
    好吧是取引导设备号, 在上面push的

    000000C3  FF7600            push word [bp+0x0]
    

    一句jmp执行加载的引导扇区

    0000012A  EA007C0000        jmp 0x0:0x7c00
    

    顺便补充下上一篇的问题, int 0x18是中断启动, 一旦int 0x18, 不会继续引导!
    一般设备int 0x18会显示"No bootable device"等


    总结

    Windows的硬盘引导扇区其实是一个Loader, 寻找活动分区, 加载该分区第一个扇区并执行, 所以真正的代码应该在引导分区的第一个扇区, 好吧我承认这次找错了

    可是!你都看完两章了, 还不赞助一下, 感觉被你白嫖了...

    爱发电赞助通道(推荐)

    相关文章

      网友评论

          本文标题:分析Windows启动过程(二)

          本文链接:https://www.haomeiwen.com/subject/pktrqhtx.html