BIOS 中断替换为远程呼叫不工作

BIOS interrupt replaced with far-call NOT working

重要提示:这个问题中的代码可以渲染媒体 unbootable!!

所以我试图让我的堆栈永远不会溢出,但我有一个问题。

在我从引导加载程序推送标志后,我尝试对 INT 0x13 的中断向量 table 地址进行远程调用(在我的例子中是 0000f000,位于 0x4C)。 Int 0x13(从 0x0 地址开始写入磁盘 200h)不起作用。这是没有意义的,因为 wiki 说中断在实模式下是可以互换的,远程调用前面有一个标志 push: https://en.wikipedia.org/wiki/INT_(x86_instruction) 。因此,它的工作至关重要。为了以防万一,我尝试了地址的 4 种变体(每个字节中的 F0)的调用,但无济于事。

[bits 16]
[org 0x7c00]
xor ax, ax
cli
mov es, ax
mov al, 0x01
mov bx, 0x7c00
mov cx, 0x0004
mov dl, 0x80
xor dh, dh
mov ah, 0x03
pushf
call 0xf000:0x0000
;int 0x13
times 510 - ($ - $$) db 0
dw 0xaa55

我用它来将 IVT 写入此 boatloader 从中引导的同一驱动器的扇区 4:

[bits 16]
[org 0x7c00]
xor ax, ax
cli
mov es, ax
mov al, 0x01
mov bx, 0x0
mov cx, 0x0004
mov dl, 0x80
xor dh, dh
mov ah, 0x03
int 0x13
times 510 - ($ - $$) db 0
dw 0xaa55

对于说俄语的人,我用俄语重复了这个问题:БИОС прерывание INT, подмененное на CALL, не срабатывает

如开头所述,请使用没有操作系统的媒体,因为此代码会覆盖引导加载程序及其后面的扇区 - 这会使媒体取消 table。它还会擦除其上任何分区的分区信息。使用空的或未使用的媒体。

已发现 BIOS 的以下异常行为,这解释了远调用不起作用的原因。

当您仅使用 INT 0x13 执行相关代码时,打算从 0x0:0x0 的 es:bx 写入中断向量 table(将 mov bx、0x7c00 替换为 mov bx, 0x0) 到驱动器上的一个扇区,您可能无法获得准确的 RAM 内存印记。在我的例子中,我得到的是零,我得到的是 ASCII,然后不是合法地址。即使当我尝试远距离调用我在某个时候看到的看似正确的地址时,它也没有执行中断代码,这说明我得到了一个假的 IVT。

读取原始位并将其显示到屏幕缓冲区,不使用中断似乎确实显示了真正的 IVT,当然数据与中断 13h 的输出完全不一致。我相信这带来的负面影响是显而易见的。

我今晚很快记下了在不使用任何中断的情况下将中断向量 table 的前 20 个条目打印到屏幕的很棒的代码:

[bits 16]
[org 0x7C00]

xor ax, ax
cli
mov ax, 0xb800
mov es, ax
mov di, 0
mov ax, 0
mov ds, ax
mov si, 0x0     ;starting RAM address, 0 for IVT
mov cx, 0
mov dl, 20      ;how many interrupt vectors to print (<25 for vga)
moreints:
mov bh, 4       ;counter for bytes of each interrupt vector entry to print
morebytes:      
mov ah, [ds:si]
mov bl, 8       ;counter for bits of each byte to print
morebits:
shl ah, 1
mov al, 48
jnc zero
mov al, 49
zero:
mov [es:di], al
inc di
inc di
dec bl
jnz morebits
mov al, 32
mov [es:di], al
inc di
inc di
inc si
dec bh
jnz morebytes
add cx, 80*2
mov di, cx
dec dl
jnz moreints
times 510 - ($ - $$) db 0
dw 0xaa55

现在您可以找到确切的中断例程的地址并出于教育目的分解它们。如果您得到奇怪的读数,则可能表明存在 BIOS rootkit 等。您甚至可能会发现有意义的 offsets/segments 读作 "F0FA" 作为 Vova 的音标,这是 Vladimir Putin 的名字。

一个关于如何读取 ivt 地址的方便提示:比如说,你得到 0x12345678 作为中断的 IVT 入口。您的分段和偏移量将为 0x7856:0x3412.

并且将通过此方法获取的地址用于 pushf 之后的远调用将表现出预期的行为并调用中断代码。我刚刚测试了它,它可以工作。

您对 0xF000:0x0000 的 FAR CALL 不起作用,因为它不是中断 table 中 Int 0x13 的地址。尽管 0xF000 看起来像是 BIOS 区域中的合理段,但 Int 0x13 的向量从偏移量 0x0000 开始的可能性不是很高。

我在这个问题上花了很多时间,但一些坚持得到了回报。我发现一个特定的 Intel BIOS 表现出这种奇怪的行为。如果要写入的物理地址从 0x00000 开始,写入 returns 成功但数据不正确。在我的系统上,它似乎写入了 BIOS 内存的一部分而不是 IVT。这种性质的 BIOS 错误确实会发生,但很难找到它们的边缘情况,除非您有意调试 BIOS。它可能没有引起注意,因为用 Int 0x13 从 0x0000:0x0000 写入内存并不是你每天都能看到的。我的 BIOS 信息:

BIOS Manufacturer: Intel
Version: CR94510J.86a.0045.2007.0418.1532
Date:April 18th, 2007

或者,您可以使用不同于写入磁盘的其他机制。您可以通过将它们显示在屏幕上、将它们输出到串行端口等来获取 IVT 条目。您可以将代码直接写入 access the hard disk controller 而不是使用 Int 0x13 为您完成。

我没有尝试更新 BIOS,尽管 BIOS revision history 对此没有任何说明 bug/feature。


为了找到解决方法,我什至特意关闭了 A20 line,这样我就可以尝试映射到同一物理地址 0x00000 的其他 segment:offset 地址。 0xFF00:0x1000 = 0x10000,0xFFFF:0x0010 = 0x10000,0x0000:0x0000 = 0x00000。当 A20 关闭时,物理地址 0x10000 等于 0x00000。如果我从 0x0000:0x0004 的 IVT 中的第二个条目开始读取,那绝对没有问题。

它闻起来很像虫子。除了将 IVT table 复制到内存中的另一个位置(我在引导加载程序之后复制它)然后将其写入磁盘之外,我找不到任何解决方法。我采用了您的原始代码并对其进行了增强:

  • 将 1024 字节的 IVT 复制到 0x0000:0x7e00。
  • 使用 STI 保持中断启用(尽管您可能仍然可以使用 CLI)。
  • 使用BIOS在寄存器DL中传递的引导驱动器号。这允许我们写入(或读取)启动的磁盘而无需硬编码值。
  • 在失败并显示错误消息之前重试磁盘操作 3 次。
  • 磁盘操作成功时通知用户。
  • 提示用户在继续启动循环之前按任意键。
  • 因为我使用的是 USB 和硬盘驱动器仿真,所以我不得不添加一个带有 bootable 条目的分区 table。该分区是自引用的,使整个磁盘看起来像一个以 MBR 作为第一个扇区的分区。如果您从真正的硬盘驱动器启动,可以删除它,但保留它应该不会受到伤害。
  • 注释代码

boot.asm:

DISK_RETRIES EQU 3              ; Retry disk operation 3 times on error

bits 16
org 0x7c00

start:
    xor ax, ax
    mov es, ax                  ; Set ES=0
    mov ds, ax                  ; Set DS=0
    mov ss, ax
    mov sp, 0x7c00              ; Place our stack below the bootloader @ 0x0000:0x7c00
    sti                         ; Enable external interrupts

    ; Appears to be a bug with Int 0x13 on some BIOSes where an attempt
    ; to write to disk from the physical address 0x00000 fails. Copy the
    ; 1024 byte IVT from 0x0000:0x0000 to 0x0000:0x7e00 just after bootloader

    mov di, 0x7e00              ; Destination ES:DI = 0x0000:0x7e00
    mov si, 0x0000              ; Source      DS;SI = 0x0000:0x0000
    mov cx, 1024/ 2             ; IVT is 512 16-bit words
    rep movsw                   ; Copy all 512 words (1024 bytes)

write_sector:
    mov bp, DISK_RETRIES        ; Set disk retry count

    ; Write CHS=(0,0,4)
    mov es, ax
    mov bx, 0x7e00              ; ES:BX=0x0000:0x7e00 where copy of the IVT resides
    mov cx, 0x0004              ; ch=cylinder=0, cl=sector=4
    xor dh, dh                  ; dh=heads=0
                                ; Use DL passed by the BIOS
.retry:
    mov ax, 0x0302              ; Call function 0x03 of int 13h (write sectors)
                                ;     AL = 2 = Sectors to write
    int 0x13                    ; Write 512 bytes from memory @ 0x0000:0x7e00
                                ;     to CHS=(0,0,4) LBA=3 on the boot drive
    jc .disk_error              ; If CF set then disk error

.success:
    mov si, successMsg          ; Display messageabout success
    call print_string
    mov si, anyKeyMsg           ; Press any key
    call print_string

    xor ax,ax                   ; Wait for keystroke
    int 0x16
    xor ax,ax                   ; Tell BIOS to continue and disk non-bootable
    int 0x18                    ; Our bootloader has exited back to BIOS at this point

.disk_error:
    xor ah, ah                  ; Int13h/AH=0 is drive reset
    int 0x13
    dec bp                      ; Decrease retry count
    jge .retry                  ; If retry count not exceeded then try again

error_end:
    ; Unrecoverable error; print drive error; enter infinite loop
    mov si, diskErrorMsg        ; Display disk error message
    call print_string

end_loop:
    hlt
    jmp end_loop                ; Infinite loop to prevent processor from
                                ;    getting past this point.

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

diskErrorMsg: db "Unrecoverable disk error!", 0x0d, 0x0a, 0
successMsg:   db "Successfully written data to disk!", 0x0d, 0x0a, 0
anyKeyMsg:   db "Press any key to continue...", 0x0d, 0x0a, 0

times 446-($-$$) db 0   ; Pad remainder of boot sector up to first partition entry
part1_entry:
db 0x80                 ; Bootable partiion
db 0x00, 0x01, 0x00     ; CHS of first absolute sector (MBR) of hard drive
                        ;     Head=0, Sector=1, Cylinder=0
db 0x0c                 ; Partition type (has to be non-zero)
                        ;     0x0c = Win 95 FAT32 (LBA)
db 0x00, 0x01, 0x00     ; CHS of last absolute sector (MBR) of hard drive
                        ;     Head=0, Sector=1, Cylinder=0
                        ;     We are effectively saying Size of partition is 1 sector
dd 0x0                  ; LBA of first absolute sector (0=MBR)
dd 0x1                  ; Number of sectors in partition. We set it to 1 but if you
                        ;     wish you could set it to the number of sectors on the disk

times 510-($-$$) db 0   ; Pad remainder of boot sector up to boot signature. This zeroes
                        ;     partition entries 2,3,4 effectively making them inactive

dw 0xaa55

它可以内置到 bootloader/MBR 中:

nasm -f bin boot.asm -o boot.bin 

在我的带有这个引导加载程序的真实系统上,我没有请求这段代码来获取内存,而是给了我一个真实模式 IVT 的正确转储。十六进制转储似乎是合理的,因为大多数条目都有 0xF000 作为段和非零偏移量:

0000000 ff53 f000 ff53 f000 e2c3 f000 27b8 f000
0000010 ff53 f000 ff54 f000 27b8 f000 27b9 f000
0000020 fea5 f000 e987 f000 27b9 f000 27b9 f000
0000030 27b9 f000 27ac f000 ef57 f000 27b9 f000
0000040 0014 c000 f84d f000 f841 f000 4902 f000 <--- offset 0x4c is Int 0x13 0xf000:0x4902
0000050 e739 f000 ec6a f000 e82e f000 efd2 f000
0000060 53b5 f000 e6f2 f000 fe6e f000 ff53 f000
0000070 ff53 f000 f0a4 f000 efc7 f000 7011 c000
0000080 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000090 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000a0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000b0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000c0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000d0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000e0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000f0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000100 ec59 f000 00b0 0040 f065 f000 6c11 c000
0000110 27b9 f000 27b9 f000 00c0 0040 27b9 f000
0000120 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000130 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000140 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000150 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000160 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000170 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000180 0000 0000 0000 0000 0000 0000 0000 0000
0000190 0000 0000 0000 0000 0000 0000 27b9 f000
00001a0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00001b0 27b9 f000 0014 c000 27b9 f000 27b9 f000
00001c0 660b f000 27a3 f000 27b9 f000 c394 f000
00001d0 2c77 f000 2794 f000 3229 f000 3229 f000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
... The remainder up to 0x400 was all zero