1
votes

Important Note: Code in this question can render media unbootable!!

So I was trying for my stack to never overflow, but here I am with a question.

I tried to do a far call to the interrupt vector table address for INT 0x13 (in my case 0000f000 at 0x4C) after I pushed flags, from a bootloader. Int 0x13 (that writes to disk 200h starting with 0x0 address) didn't work. This makes no sense as wiki says interrupts are interchangeable in real mode with far calls preceded by a flags push: https://en.wikipedia.org/wiki/INT_(x86_instruction) . So it's crucial for this to work. Just in case, I tried the call with 4 variations of the address (F0 in every byte), to no avail.

[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

I used this to write IVT to sector 4 of the same drive this boatloader boots from:

[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

For Russian speakers I duplicated this question in Russian: БИОС прерывание INT, подмененное на CALL, не срабатывает

As it states in the beginning, please use media without an operating system on it, as this code overwrites the bootloader and a sector behind it - which would make the media unbootable. It would also erase the partition information for any partitions on it. Use empty or unused media.

2
Comments are not for extended discussion; this conversation has been moved to chat.Bhargav Rao♦
Corrected. Thank you. And no, just wanted to see the first 512 bytes of it.user145453
Not related to your issue (i don't believe). You can just delete the mov dl, 0x80 and the value passed in DL by the BIOS will be used. The BIOS passes the boot drive number in DL for you already. This is preferred because this would work if booting off a hard drive other than 0x80 (or even removable media that might be 0x00 or 0x01 etc).Hard coding 0x80 just means this will only ever work from the first hard drive.Michael Petch
To see if your code is actually dumping anything to disk, try overwriting the target sector with random data, or all-ones. So after booting this boot sector, you can see if the data on whatever storage medium has changed or not. IIRC, you were using seg:0000 for your far call, which seemed unlikely, so sanity checking your IVT dump is important. Also, do the other IVT entries look plausible? Post some of them.Peter Cordes

2 Answers

4
votes

Your FAR CALL to 0xF000:0x0000 doesn't work because it isn't the address for Int 0x13 in your interrupt table. Although 0xF000 looks like a reasonable segment in the BIOS area, the chance that Int 0x13's vector starts at offset 0x0000 isn't very high.

I spent considerable time on this question, but some persistence paid off. I found a specific Intel BIOS that exhibits this kind of odd behavior. If the physical address to write starts at 0x00000 the write returns success but the data is incorrect. On my system it seems to write part of BIOS memory rather than the IVT. BIOS bugs of this nature do occur, but fringe cases to find them are hard to find unless you are deliberately debugging the BIOS. It likely went unnoticed because writing the memory from 0x0000:0x0000 with Int 0x13 isn't something you see everyday. My BIOS information:

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

Alternatively, you can use a different mechanism other than writing to disk. You could get the IVT entries by displaying them to the screen, outputting them on a serial port etc. You could write code to access the hard disk controller directly rather than using Int 0x13 to do it for you.

I didn't attempt to update the BIOS, although BIOS revision history doesn't say anything about this bug/feature.


To find workarounds I even went out of my way to turn off the A20 line so that I could try other segment:offset addresses that map to the same physical address 0x00000. 0xFF00:0x1000 = 0x10000, 0xFFFF:0x0010 = 0x10000, and 0x0000:0x0000 = 0x00000. When A20 is off physical address 0x10000 equals 0x00000. If I start reading from the second entry in the IVT at 0x0000:0x0004 there is absolutely no issue.

It very much smells like a bug. I couldn't find any work around except to copy the IVT table to another location in memory (I copied it right after the bootloader) and then write it to disk. I took your original code and enhanced it:

  • Copy the 1024 byte IVT to 0x0000:0x7e00.
  • Keep interrupts enabled with STI (although you can probably still use CLI).
  • Use the boot drive number passed by BIOS in register DL. This allows us to write(or read) the disk that was booted on without having to hard code a value.
  • Retry the disk operation 3 times before failing with an error message.
  • Inform user when the disk operation says it is successful.
  • Prompt user to hit any key before continuing the boot cycle.
  • Since I was using USB and Hard Drive emulation I had to add a partition table with a bootable entry. The partition is self referential and makes the entire disk look like a partition with the MBR as the first sector. This can be removed if you are booting from real hard drives, but keeping it shouldn't hurt.
  • Commented the code

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

It can be built into a bootloader/MBR with:

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

On my real system with this bootloader, rather than getting memory I didn't request this code gave me a proper dump of the real mode IVT. The hex dump seems reasonable as most entries have 0xF000 as the segment and a non zero offset:

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
0
votes

The following unusual behavior of BIOS has been uncovered that explains why the far call didn't work.

When you execute the code in question with just INT 0x13, intending to write your interrupt vector table from es:bx of 0x0:0x0 (replace mov bx, 0x7c00 with mov bx, 0x0) to a sector on your drive, you may not get an exact RAM memory imprint. In my case, I was getting zeros, I was getting ASCII, then not legit addresses. Even when I tried far calling to the seemingly correct looking address I saw at some point, it didn't execute the interrupt code, which illustrates a fake IVT I was getting.

Reading and displaying raw bits to the screen buffer, not using interrupts does seem to display the real IVT, certainly the data in complete discordance with the output of interrupt 13h. I believe the negative ramifications of this are obvious.

I quickly jotted tonight the awesombler code to print the first 20 entries of the interrupt vector table to screen without using interrupts whatsoever:

[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

Now you can find your exact interrupt routines' addresses and dissasemble them for educational purposes. If you get weird readings it may point to presence of a BIOS rootkit, among other things. You may even find meaningful offsets/segments that read "F0FA" as a phonetic transcript of Vova, which is Vladimir Putin's first name.

A convenient reminder on how to read ivt addresses: say, you get 0x12345678 as the IVT entry for an interrupt. Your segment and offset will be 0x7856:0x3412.

And using the address acquired via this method for the far call after pushf will exhibit the expected behavior and invoke the interrupt code. I just tested it, and it works.