X86-asm code to make a usb disk non-bootable

927 views Asked by At

we have developed a USB composite device (CDC and MSD classes) on a custom MCU-based hardware. The disk is a RAM disk (24 kb of space) and at power up, it is formatted (FAT 12) by the MCU vendor firmware. Unfortunately, the device is seen as a bootable disk and a typical BIOS tries to boot it. However, we know that connecting various USB disks to different PCs yields different results, depending both on the disk FAT type and on the BIOS behavior (obviously each BIOS has USB boot prior than HDD). Our goal is to have an unbootable USB disk (FAT12 formatted).

We have tried many solutions but none worked. In our opinion it seems BIOSes do more than simply checking the presence of a valid boot sector, copy the first sector to address 0x7C00 and jump at that address, giving control to the x86 ASM boot code. In fact, we have formatted a commercial USB stick with Linux with the command:

mkdosfs -F 12 /dev/sda1

This USB stick has now a FAT12 Volume Boot Record and we have not found to date a BIOS that gets stuck while trying to boot from it.

Therefore, we copied its boot sector to our device and we expected it to work as the commercial USB stick. Nope: the BIOS hangs while trying to boot the disk. Here follows the disassembly of the boot sector generated by the above Linux command.

;Jump instructions
0x00007c00 eb 3c                            jmp    0x00007c3e
0x00007c02 90                               nop

;VBR segment
0x00007c03 6d                               
0x00007c04 6b 66 73 2e                      
0x00007c08 66 61                              

..............

;Boot code
0x00007c3e 0e                               push   %cs
0x00007c3f 1f                               pop    %ds
0x00007c40 be 5b 7c                         mov    $0x7c5b,%si
0x00007c43 ac                               lods   %ds:(%si),%al
0x00007c44 22 c0                            and    %al,%al
0x00007c46 74 0b                            je     0x00007c53
0x00007c48 56                               push   %si
0x00007c49 b4 0e                            mov    $0xe,%ah
0x00007c4b bb 07 00                         mov    $0x7,%bx
0x00007c4e cd 10                            int    $0x10
0x00007c50 5e                               pop    %si
0x00007c51 eb f0                            jmp    0x00007c43
0x00007c53 32 e4                            xor    %ah,%ah
0x00007c55 cd 16                            int    $0x16
0x00007c57 cd 19                            int    $0x19
0x00007c59 eb fe                            jmp    0x00007c59

;String to be displayed followed by zeros

0x00007c5b 54                               
0x00007c5c 68 69 73                         

..........

;Boot signature
0x00007dfe 55                               
0x00007dff aa                               

Apparently, the above code prints the string "This is not a bootable disk. Please insert a bootable floppy and press any key to try again" and then calls INT16H and INT19H.

Why does the BIOS from the PC I am writing from, for example, prints: "Attempting boot from USB disk" and then jumps to my HDD while the same assembly code written in our device does not? That is, it writes "Please insert..."? There exists a code that can substitute this and never try to boot my custom USB disk?

In addition, does the BIOS do some special communications with a USB device before reading the first sector reading, for example, something from the device descriptor in order to understand if it can be used as a boot disk?

Thank you all in advance.

2

There are 2 answers

1
Ross Ridge On BEST ANSWER

Ultimately there may not anything to prevent your custom USB device from used as a boot device on any PC it might be plugged into. However there are couple of things you can do that should cover most cases, the first is not to have the 0xAA55 signature at the end of the boot sector and the second is to invoke INT 0x18 the boot sector to ask the BIOS to try the next boot device.

How BIOSes boot USB devices

The basic problem is here is that there's no standard for BIOS booting of USB mass storage devices. How exactly a BIOS might go about determining if a USB device is bootable varies between BIOS implementations. There are however two basic steps all implementations will follow.

The first is to determine whether to emulate the USB device as a "floppy", "hard drive", "cdrom", "zip" or other kind of drive. For your device it should come down to choosing between floppy or hard drive emulation. Various criteria are possible for making the this choice, like the size of the drive, whether its reports itself as being removable and the command set used. In particular there's an upper limit to the size of a drive floppy emulation can support, about 530MB I believe, and this size is often used for determining the device type. Some BIOSes can also be configured to force a certain device type in their settings.

The next step is to determine whether the device is bootable. This means at the very least reading the first sector of the drive, since if it can't be read it can't be booted. Then the BIOS may try to determine if the boot sector is valid. If it's emulating a hard drive it should always check for the presence of the 0xAA55 signature, but if it's emulating a floppy it may or may not perform this check. If it's emulating a hard drive it might also check for a valid MBR-style partition, and if's emulating a floppy it might check for a valid FAT BIOS Parameter Block (BPB).

So not having the 0xAA55 magic number at the end of the boot sector would prevent the USB device from being used a boot device in at least some cases. However it's most likely that the BIOS will choose to use floppy emulation for your device, and many BIOSes won't do any validation of the boot sector beyond verifying it's readable. However, if you can have your USB device pretend it's much larger than it actually is, beyond what floppy emulation supports, then it should make it far more likely for hard drive emulation to be used.

Invoking INT 0x18

However all is not lost even if the the BIOS does load and execute the boot sector of your device. You can invoke INT 0x18, which originally was used to start cassette BASIC on an original IBM PC, but was redefined by the BIOS Boot Specification to be the recovery vector for failed boot attempts. On a modern PC, including any that supports booting USB devices, invoking this will tell the BIOS to try the next boot device. (INT 0x18 is actually called after a boot failure in standard MBR implementations back since the MBR was invented, so it's not actually as unusual as it might sound.)

Here's the source code for a boot sector that invokes INT 0x18 I used for testing on various PCs I had available:

    .code16

    .text

    jmp start
    nop
    .org    0x03    # insure a two byte long JMP insn was used

    .org    0x3e    # skip over FAT BIOS Parameter Block
start:
    xor %ax, %ax
    mov %ax, %ss
    mov $0x7c00, %sp

    call    print_message

    .ascii  "\r\nOOPS... The BIOS tried to boot the custom device.\r\n"
    .byte   0

    xor %ax, %ax
    int $0x16       # read from keyboard

    # Try using the BIOS Boot Specification's recovery vector so
    # that BIOS tries another boot device. On an a real 5150 IBM PC
    # this starts cassette BASIC.  On a PC that doesn't support the
    # BBS then it may print an error message, do nothing or crash.

    int $0x18

    call    print_message

    # INT 0x18 didn't work, didn't start BASIC and didn't crash.
    # Print an error message to user and invoke the INT 0x19
    # reboot vector.

    .ascii  "\r\nRemove the custom device and press any key to reboot"
    .ascii  " the computer.\r\n"
    .byte   0

    xor %ax, %ax
    int $0x16       # read from keyboard

    int $0x19       # reboot

infinite_loop:
    jmp infinite_loop

print_message:
    pop %si
loop_message:
    lods    %cs:(%si), %al
    test    %al,%al
    jz  end_message
    mov $0xe, %ah
    mov $0x7, %bx
    int $0x10       # print a character
    jmp loop_message
end_message:
    jmp *%si

    .org    0x1FF
    .byte   00      # note no 55 aa signature

You should be able to modify this code for use with your device. Make sure to copy the FAT12 BPB (the bytes between offsets 0x003 and 0x03e) from your device's BPB. You'll probably want to remove the first invocation of INT 0x16 which waits for a key press before invoking INT 0x18. To assemble and link the code you can use the following commands:

as -o myfat12.o myfat12.s
ld --oformat=binary -o myfat12.bin -Ttext=0x7c00 myfat12.o

To copy the BPB from your device into the boot sector created with the above commands you can use something like:

dd conv=notrunc if=/dev/sda of=myfat12.bin bs=1 skip=3 seek=3 count=59

In my testing it the code above worked fairly well, but in one case the BIOS just tried the same boot device over and over again. In an another case the PC (a 15 year-old Pentium 4) crashed when trying to boot the code off a USB flash drive, but not off of an actual USB floppy disk. The crash was probably related the unusual 24k FAT12 BPB I was using to try to replicate your device as closely as possible.

The second "Remove the custom device..." message was never printed in my testing. Either the boot block was never executed in the first place or invoking 0x18 did something and never returned. You might want to move those instructions to the first message in case INT 0x18 doesn't do anything helpful.

0
Timothy Baldwin On

Try calling INT 18H, this is specified by the BIOS Boot Specification (Compaq, Phoenix, Intel 1996) to try the next boot device.

Alternatively it may need to load the bootsector of the next disk by itself using INT 13H.