Boot Loader: boot0 | |
; | |
; A small boot sector program written in x86 assembly whose only | |
; responsibility is to locate the active partition, load the | |
; partition booter into memory, and jump to the booter's entry point. | |
; It leaves the boot drive in DL and a pointer to the partition entry in SI. | |
; | |
; This boot loader must be placed in the Master Boot Record. | |
; | |
; In order to coexist with a fdisk partition table (64 bytes), and | |
; leave room for a two byte signature (0xAA55) in the end, boot0 is | |
; restricted to 446 bytes (512 - 64 - 2). If boot0 did not have to | |
; live in the MBR, then we would have 510 bytes to work with. | |
; | |
; boot0 is always loaded by the BIOS or another booter to 0:7C00h. | |
; | |
; This code is written for the NASM assembler. | |
; nasm boot0.s -o boot0 | |
; | |
; This version of boot0 implements hybrid GUID/MBR partition scheme support | |
; | |
; Written by Tamás Kosárszky on 2008-03-10 and JrCs on 2013-05-08. | |
; | |
; Turbo added EFI System Partition boot support | |
; | |
; Added KillerJK's switchPass2 modifications | |
; | |
; JrCs added FAT32/exFAT System Partition boot support on GPT pure partition scheme | |
; | |
; | |
; boot0af and boot0ss share the same code except. | |
; The ACTIVEFIRST macro is used to select the right code | |
; boot0af - define ACTIVEFIRST | |
; boot0ss - do not define ACTIVEFIRST | |
; | |
; | |
; Set to 1 to enable obscure debug messages. | |
; | |
DEBUG EQU 0 | |
; | |
; Set to 1 to enable verbose mode | |
; | |
VERBOSE EQU 0 | |
; | |
; Various constants. | |
; | |
kBoot0Segment EQU 0x0000 | |
kBoot0Stack EQU 0xFFF0 ; boot0 stack pointer | |
kBoot0LoadAddr EQU 0x7C00 ; boot0 load address | |
kBoot0RelocAddr EQU 0xE000 ; boot0 relocated address | |
kMBRBuffer EQU 0x1000 ; MBR buffer address | |
kLBA1Buffer EQU 0x1200 ; LBA1 - GPT Partition Table Header buffer address | |
kGPTABuffer EQU 0x1400 ; GUID Partition Entry Array buffer address | |
kPartTableOffset EQU 0x1be | |
kMBRPartTable EQU kMBRBuffer + kPartTableOffset | |
kSectorBytes EQU 512 ; sector size in bytes | |
kBootSignature EQU 0xAA55 ; boot sector signature | |
kHFSPSignature EQU 'H+' ; HFS+ volume signature | |
kHFSPCaseSignature EQU 'HX' ; HFS+ volume case-sensitive signature | |
kEXFATSignature EQU 'EX' ; exFAT volume signature | |
kFAT32BootCodeOffset EQU 0x5a ; offset of boot code in FAT32 boot sector | |
kBoot1FAT32Magic EQU 'BO' ; Magic string to detect our boot1f32 code | |
kGPTSignatureLow EQU 'EFI ' ; GUID Partition Table Header Signature | |
kGPTSignatureHigh EQU 'PART' | |
kGUIDLastDwordOffs EQU 12 ; last 4 byte offset of a GUID | |
kPartCount EQU 4 ; number of paritions per table | |
kPartTypeEXFAT EQU 0x07 ; exFAT Filesystem type | |
kPartTypeFAT32 EQU 0x0c ; FAT32 Filesystem type | |
kPartTypeHFS EQU 0xaf ; HFS+ Filesystem type | |
kPartTypePMBR EQU 0xee ; On all GUID Partition Table disks a Protective MBR (PMBR) | |
; in LBA 0 (that is, the first block) precedes the | |
; GUID Partition Table Header to maintain compatibility | |
; with existing tools that do not understand GPT partition structures. | |
; The Protective MBR has the same format as a legacy MBR | |
; and contains one partition entry with an OSType set to 0xEE | |
; reserving the entire space used on the disk by the GPT partitions, | |
; including all headers. | |
kPartActive EQU 0x80 ; active flag enabled | |
kPartInactive EQU 0x00 ; active flag disabled | |
kAppleGUID EQU 0xACEC4365 ; last 4 bytes of Apple type GUIDs. | |
kEFISystemGUID EQU 0x3BC93EC9 ; last 4 bytes of EFI System Partition Type GUID: | |
; C12A7328-F81F-11D2-BA4B-00A0C93EC93B | |
kBasicDataGUID EQU 0xC79926B7 ; last 4 bytes of Basic Data System Partition Type GUID: | |
; EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | |
%ifdef FLOPPY | |
kDriveNumber EQU 0x00 | |
%else | |
kDriveNumber EQU 0x80 | |
%endif | |
; | |
; Format of fdisk partition entry. | |
; | |
; The symbol 'part_size' is automatically defined as an `EQU' | |
; giving the size of the structure. | |
; | |
struc part | |
.bootid resb 1 ; bootable or not | |
.head resb 1 ; starting head, sector, cylinder | |
.sect resb 1 ; | |
.cyl resb 1 ; | |
.type resb 1 ; partition type | |
.endhead resb 1 ; ending head, sector, cylinder | |
.endsect resb 1 ; | |
.endcyl resb 1 ; | |
.lba resd 1 ; starting lba | |
.sectors resd 1 ; size in sectors | |
endstruc | |
; | |
; Format of GPT Partition Table Header | |
; | |
struc gpth | |
.Signature resb 8 | |
.Revision resb 4 | |
.HeaderSize resb 4 | |
.HeaderCRC32 resb 4 | |
.Reserved resb 4 | |
.MyLBA resb 8 | |
.AlternateLBA resb 8 | |
.FirstUsableLBA resb 8 | |
.LastUsableLBA resb 8 | |
.DiskGUID resb 16 | |
.PartitionEntryLBA resb 8 | |
.NumberOfPartitionEntries resb 4 | |
.SizeOfPartitionEntry resb 4 | |
.PartitionEntryArrayCRC32 resb 4 | |
endstruc | |
; | |
; Format of GUID Partition Entry Array | |
; | |
struc gpta | |
.PartitionTypeGUID resb 16 | |
.UniquePartitionGUID resb 16 | |
.StartingLBA resb 8 | |
.EndingLBA resb 8 | |
.Attributes resb 8 | |
.PartitionName resb 72 | |
endstruc | |
; | |
; Macros. | |
; | |
%macro DebugCharMacro 1 | |
mov al, %1 | |
call print_char | |
%endmacro | |
%macro LogString 1 | |
mov di, %1 | |
call log_string | |
%endmacro | |
%if DEBUG | |
%define DebugChar(x) DebugCharMacro x | |
%else | |
%define DebugChar(x) | |
%endif | |
;-------------------------------------------------------------------------- | |
; Start of text segment. | |
SEGMENT .text | |
ORG kBoot0RelocAddr | |
;-------------------------------------------------------------------------- | |
; Boot code is loaded at 0:7C00h. | |
; | |
start: | |
; | |
; Set up the stack to grow down from kBoot0Segment:kBoot0Stack. | |
; Interrupts should be off while the stack is being manipulated. | |
; | |
cli ; interrupts off | |
xor ax, ax ; zero ax | |
mov ss, ax ; ss <- 0="" span="">-> | |
mov sp, kBoot0Stack ; sp <- of="" span="" stack="" top="">-> | |
sti ; reenable interrupts | |
mov es, ax ; es <- 0="" span="">-> | |
mov ds, ax ; ds <- 0="" span="">-> | |
; | |
; Relocate boot0 code. | |
; | |
mov si, kBoot0LoadAddr ; si <- source="" span="">-> | |
mov di, kBoot0RelocAddr ; di <- destination="" span="">-> | |
cld ; auto-increment SI and/or DI registers | |
mov cx, kSectorBytes/2 ; copy 256 words | |
repnz movsw ; repeat string move (word) operation | |
; | |
; Code relocated, jump to start_reloc in relocated location. | |
; | |
jmp kBoot0Segment:start_reloc | |
;-------------------------------------------------------------------------- | |
; Start execution from the relocated location. | |
; | |
start_reloc: | |
DebugChar('>') | |
%if DEBUG | |
mov al, dl | |
call print_hex | |
%endif | |
; | |
; Since this code may not always reside in the MBR, always start by | |
; loading the MBR to kMBRBuffer and LBA1 to kGPTBuffer. | |
; | |
xor eax, eax | |
mov [my_lba], eax ; store LBA sector 0 for read_lba function | |
mov al, 2 ; load two sectors: MBR and LBA1 | |
mov bx, kMBRBuffer ; MBR load address | |
call load | |
jc error ; MBR load error | |
; | |
; Look for the booter partition in the MBR partition table, | |
; which is at offset kMBRPartTable. | |
; | |
mov si, kMBRPartTable ; pointer to partition table | |
call find_boot ; will not return on success | |
error: | |
LogString(boot_error_str) | |
hang: | |
hlt | |
jmp hang | |
;-------------------------------------------------------------------------- | |
; Find the active (boot) partition and load the booter from the partition. | |
; | |
; Arguments: | |
; DL = drive number (0x80 + unit number) | |
; SI = pointer to fdisk partition table. | |
; | |
; Clobber list: | |
; EAX, BX, EBP | |
; | |
find_boot: | |
; | |
; Check for boot block signature 0xAA55 following the 4 partition | |
; entries. | |
; | |
cmp WORD [si + part_size * kPartCount], kBootSignature | |
jne .exit ; boot signature not found. | |
xor bx, bx ; BL will be set to 1 later in case of | |
; Protective MBR has been found | |
inc bh ; BH = 1. Giving a chance for a second pass | |
; to boot an inactive but boot1h aware HFS+ partition | |
; by scanning the MBR partition entries again. | |
.start_scan: | |
mov cx, kPartCount ; number of partition entries per table | |
.loop: | |
; | |
; First scan through the partition table looking for the active | |
; partition. | |
; | |
%if DEBUG | |
mov al, [si + part.type] ; print partition type | |
call print_hex | |
%endif | |
mov eax, [si + part.lba] ; save starting LBA of current | |
mov [my_lba], eax ; MBR partition entry for read_lba function | |
cmp BYTE [si + part.type], 0 ; unused partition? | |
je .continue ; skip to next entry | |
cmp BYTE [si + part.type], kPartTypePMBR ; check for Protective MBR | |
jne .testPass | |
mov BYTE [si + part.bootid], kPartInactive ; found Protective MBR | |
; clear active flag to make sure this protective | |
; partition won't be used as a bootable partition. | |
mov bl, 1 ; Assume we can deal with GPT but try to scan | |
; later if not found any other bootable partitions. | |
.testPass: | |
cmp bh, 1 | |
jne .Pass2 | |
.Pass1: | |
%ifdef ACTIVEFIRST | |
jmp SHORT .tryToBootIfActive | |
%else | |
jmp SHORT .tryToBootSupportedFS | |
%endif | |
.Pass2: | |
%ifdef ACTIVEFIRST | |
jmp SHORT .tryToBootSupportedFS | |
%endif | |
.tryToBootIfActive: | |
; We're going to try to boot a partition if it is active | |
cmp BYTE [si + part.bootid], kPartActive | |
jne .continue | |
xor dh, dh ; Argument for loadBootSector to skip file system signature check. | |
jmp SHORT .tryToBoot | |
.tryToBootSupportedFS: | |
; We're going to try to boot a partition with a supported filesystem | |
; equipped with boot1x in its boot record regardless if it's active or not. | |
mov dh, 1 ; Argument for loadBootSector to check file system signature. | |
cmp BYTE [si + part.type], kPartTypeHFS | |
je .tryToBoot | |
cmp BYTE [si + part.type], kPartTypeFAT32 | |
je .tryToBoot | |
cmp BYTE [si + part.type], kPartTypeEXFAT | |
jne .continue | |
.tryToBoot: | |
; | |
; Found boot partition, read boot sector to memory. | |
; | |
call loadBootSector | |
jne .continue | |
jmp SHORT initBootLoader | |
.continue: | |
add si, BYTE part_size ; advance SI to next partition entry | |
loop .loop ; loop through all partition entries | |
; | |
; Scanned all partitions but not found any with active flag enabled | |
; Anyway if we found a protective MBR before we still have a chance | |
; for a possible GPT Header at LBA 1 | |
; | |
dec bl | |
jnz .switchPass2 ; didn't find Protective MBR before | |
call checkGPT | |
.switchPass2: | |
; | |
; Switching to Pass 2 | |
; try to find a boot1h aware HFS+ MBR partition | |
; | |
dec bh | |
mov si, kMBRPartTable ; set SI to first entry of MBR Partition table | |
jz .start_scan ; scan again | |
.exit: | |
ret ; Giving up. | |
; | |
; Jump to partition booter. The drive number is already in register DL. | |
; SI is pointing to the modified partition entry. | |
; | |
initBootLoader: | |
DebugChar('J') | |
%if VERBOSE | |
LogString(done_str) | |
%endif | |
jmp kBoot0LoadAddr | |
; | |
; Found Protective MBR Partition Type: 0xEE | |
; Check for 'EFI PART' string at the beginning | |
; of LBA1 for possible GPT Table Header | |
; | |
checkGPT: | |
push bx | |
mov di, kLBA1Buffer ; address of GUID Partition Table Header | |
cmp DWORD [di], kGPTSignatureLow ; looking for 'EFI ' | |
jne .exit ; not found. Giving up. | |
cmp DWORD [di + 4], kGPTSignatureHigh ; looking for 'PART' | |
jne .exit ; not found. Giving up indeed. | |
mov si, di | |
; | |
; Loading GUID Partition Table Array | |
; | |
mov eax, [si + gpth.PartitionEntryLBA] ; starting LBA of GPT Array | |
mov [my_lba], eax ; save starting LBA for read_lba function | |
mov cx, [si + gpth.NumberOfPartitionEntries] ; number of GUID Partition Array entries | |
mov bx, [si + gpth.SizeOfPartitionEntry] ; size of GUID Partition Array entry | |
push bx ; push size of GUID Partition entry | |
; | |
; Calculating number of sectors we need to read for loading a GPT Array | |
; | |
; push dx ; preserve DX (DL = BIOS drive unit number) | |
; mov ax, cx ; AX * BX = number of entries * size of one entry | |
; mul bx ; AX = total byte size of GPT Array | |
; pop dx ; restore DX | |
; shr ax, 9 ; convert to sectors | |
; | |
; ... or: | |
; Current GPT Arrays uses 128 partition entries each 128 bytes long | |
; 128 entries * 128 bytes long GPT Array entries / 512 bytes per sector = 32 sectors | |
; | |
mov al, 32 ; maximum sector size of GPT Array (hardcoded method) | |
mov bx, kGPTABuffer | |
push bx ; push address of GPT Array | |
call load ; read GPT Array | |
pop si ; SI = address of GPT Array | |
pop bx ; BX = size of GUID Partition Array entry | |
jc error | |
; | |
; Walk through GUID Partition Table Array | |
; and load boot record from first supported partition. | |
; | |
; If it has boot signature (0xAA55) then jump to it | |
; otherwise skip to next partition. | |
; | |
%if VERBOSE | |
LogString(gpt_str) | |
%endif | |
.gpt_loop: | |
mov eax, [si + gpta.PartitionTypeGUID + kGUIDLastDwordOffs] | |
cmp eax, kAppleGUID ; check current GUID Partition for Apple's GUID type | |
je .gpt_ok | |
; | |
; Turbo - also try EFI System Partition | |
; | |
cmp eax, kEFISystemGUID ; check current GUID Partition for EFI System Partition GUID type | |
je .gpt_ok | |
; | |
; JrCs - also try FAT2 System Partition | |
; | |
cmp eax, kBasicDataGUID ; check current GUID Partition for Basic Data Partition GUID type | |
jne .gpt_continue | |
.gpt_ok: | |
; | |
; Found a possible good partition try to boot it | |
; | |
mov eax, [si + gpta.StartingLBA] ; load boot sector from StartingLBA | |
mov [my_lba], eax | |
mov dh, 1 ; Argument for loadBootSector to check file system signature. | |
call loadBootSector | |
jne .gpt_continue ; no boot loader signature | |
mov si, kMBRPartTable ; fake the current GUID Partition | |
mov [si + part.lba], eax ; as MBR style partition for boot1h | |
mov BYTE [si + part.type], kPartTypeHFS ; with HFS+ filesystem type (0xAF) | |
jmp SHORT initBootLoader | |
.gpt_continue: | |
add si, bx ; advance SI to next partition entry | |
loop .gpt_loop ; loop through all partition entries | |
.exit: | |
pop bx | |
ret ; no more GUID partitions. Giving up. | |
;-------------------------------------------------------------------------- | |
; loadBootSector - Load boot sector | |
; | |
; Arguments: | |
; DL = drive number (0x80 + unit number) | |
; DH = 0 skip file system signature checking | |
; 1 enable file system signature checking | |
; [my_lba] = starting LBA. | |
; | |
; Returns: | |
; ZF = 0 if boot sector hasn't kBootSignature | |
; 1 if boot sector has kBootSignature | |
; | |
loadBootSector: | |
pusha | |
mov al, 3 | |
mov bx, kBoot0LoadAddr | |
call load | |
jc error | |
or dh, dh | |
jz .checkBootSignature | |
.checkHFSSignature: | |
%if VERBOSE | |
LogString(test_str) | |
%endif | |
; | |
; Looking for HFSPlus ('H+') or HFSPlus case-sensitive ('HX') signature. | |
; | |
mov ax, [kBoot0LoadAddr + 2 * kSectorBytes] | |
cmp ax, kHFSPSignature ; 'H+' | |
je .checkBootSignature | |
cmp ax, kHFSPCaseSignature ; 'HX' | |
je .checkBootSignature | |
; | |
; Looking for exFAT signature | |
; | |
mov ax, [kBoot0LoadAddr + 3] | |
cmp ax, kEXFATSignature ; 'EX' | |
je .checkBootSignature | |
; | |
; Looking for boot1f32 magic string. | |
; | |
mov ax, [kBoot0LoadAddr + kFAT32BootCodeOffset] | |
cmp ax, kBoot1FAT32Magic | |
jne .exit | |
.checkBootSignature: | |
; | |
; Check for boot block signature 0xAA55 | |
; | |
cmp WORD [kBoot0LoadAddr + kSectorBytes - 2], kBootSignature | |
.exit: | |
popa | |
ret | |
;-------------------------------------------------------------------------- | |
; load - Load one or more sectors from a partition. | |
; | |
; Arguments: | |
; AL = number of 512-byte sectors to read. | |
; ES:BX = pointer to where the sectors should be stored. | |
; DL = drive number (0x80 + unit number) | |
; [my_lba] = starting LBA. | |
; | |
; Returns: | |
; CF = 0 success | |
; 1 error | |
; | |
load: | |
push cx | |
.ebios: | |
mov cx, 5 ; load retry count | |
.ebios_loop: | |
call read_lba ; use INT13/F42 | |
jnc .exit | |
loop .ebios_loop | |
.exit: | |
pop cx | |
ret | |
;-------------------------------------------------------------------------- | |
; read_lba - Read sectors from a partition using LBA addressing. | |
; | |
; Arguments: | |
; AL = number of 512-byte sectors to read (valid from 1-127). | |
; ES:BX = pointer to where the sectors should be stored. | |
; DL = drive number (0x80 + unit number) | |
; [my_lba] = starting LBA. | |
; | |
; Returns: | |
; CF = 0 success | |
; 1 error | |
; | |
read_lba: | |
pushad ; save all registers | |
mov bp, sp ; save current SP | |
; | |
; Create the Disk Address Packet structure for the | |
; INT13/F42 (Extended Read Sectors) on the stack. | |
; | |
; push DWORD 0 ; offset 12, upper 32-bit LBA | |
push ds ; For sake of saving memory, | |
push ds ; push DS register, which is 0. | |
mov ecx, [my_lba] ; offset 8, lower 32-bit LBA | |
push ecx | |
push es ; offset 6, memory segment | |
push bx ; offset 4, memory offset | |
xor ah, ah ; offset 3, must be 0 | |
push ax ; offset 2, number of sectors | |
; It pushes 2 bytes with a smaller opcode than if WORD was used | |
push BYTE 16 ; offset 0-1, packet size | |
DebugChar('<') | |
%if DEBUG | |
mov eax, ecx | |
call print_hex | |
%endif | |
; | |
; INT13 Func 42 - Extended Read Sectors | |
; | |
; Arguments: | |
; AH = 0x42 | |
; DL = drive number (80h + drive unit) | |
; DS:SI = pointer to Disk Address Packet | |
; | |
; Returns: | |
; AH = return status (sucess is 0) | |
; carry = 0 success | |
; 1 error | |
; | |
; Packet offset 2 indicates the number of sectors read | |
; successfully. | |
; | |
mov si, sp | |
mov ah, 0x42 | |
int 0x13 | |
jnc .exit | |
DebugChar('R') ; indicate INT13/F42 error | |
; | |
; Issue a disk reset on error. | |
; Should this be changed to Func 0xD to skip the diskette controller | |
; reset? | |
; | |
xor ax, ax ; Func 0 | |
int 0x13 ; INT 13 | |
stc ; set carry to indicate error | |
.exit: | |
mov sp, bp ; restore SP | |
popad | |
ret | |
;-------------------------------------------------------------------------- | |
; Write a string with 'boot0: ' prefix to the console. | |
; | |
; Arguments: | |
; ES:DI pointer to a NULL terminated string. | |
; | |
; Clobber list: | |
; DI | |
; | |
log_string: | |
pusha | |
push di | |
mov si, log_title_str | |
call print_string | |
pop si | |
call print_string | |
popa | |
ret | |
;-------------------------------------------------------------------------- | |
; Write a string to the console. | |
; | |
; Arguments: | |
; DS:SI pointer to a NULL terminated string. | |
; | |
; Clobber list: | |
; AX, BX, SI | |
; | |
print_string: | |
mov bx, 1 ; BH=0, BL=1 (blue) | |
cld ; increment SI after each lodsb call | |
.loop: | |
lodsb ; load a byte from DS:SI into AL | |
cmp al, 0 ; Is it a NULL? | |
je .exit ; yes, all done | |
mov ah, 0xE ; INT10 Func 0xE | |
int 0x10 ; display byte in tty mode | |
jmp short .loop | |
.exit: | |
ret | |
%if DEBUG | |
;-------------------------------------------------------------------------- | |
; Write a ASCII character to the console. | |
; | |
; Arguments: | |
; AL = ASCII character. | |
; | |
print_char: | |
pusha | |
mov bx, 1 ; BH=0, BL=1 (blue) | |
mov ah, 0x0e ; bios INT 10, Function 0xE | |
int 0x10 ; display byte in tty mode | |
popa | |
ret | |
;-------------------------------------------------------------------------- | |
; Write the 4-byte value to the console in hex. | |
; | |
; Arguments: | |
; EAX = Value to be displayed in hex. | |
; | |
print_hex: | |
pushad | |
mov cx, WORD 4 | |
bswap eax | |
.loop: | |
push ax | |
ror al, 4 | |
call print_nibble ; display upper nibble | |
pop ax | |
call print_nibble ; display lower nibble | |
ror eax, 8 | |
loop .loop | |
mov al, 10 ; carriage return | |
call print_char | |
mov al, 13 | |
call print_char | |
popad | |
ret | |
print_nibble: | |
and al, 0x0f | |
add al, '0' | |
cmp al, '9' | |
jna .print_ascii | |
add al, 'A' - '9' - 1 | |
.print_ascii: | |
call print_char | |
ret | |
getc: | |
pusha | |
mov ah, 0 | |
int 0x16 | |
popa | |
ret | |
%endif ;DEBUG | |
;-------------------------------------------------------------------------- | |
; NULL terminated strings. | |
; | |
%if VERBOSE | |
gpt_str db 'GPT', 0 | |
test_str db 'test', 0 | |
done_str db 'done', 0 | |
%endif | |
boot_error_str db 'error', 0 | |
;-------------------------------------------------------------------------- | |
; Pad the rest of the 512 byte sized booter with zeroes. The last | |
; two bytes is the mandatory boot sector signature. | |
; | |
; If the booter code becomes too large, then nasm will complain | |
; that the 'times' argument is negative. | |
; | |
; According to EFI specification, maximum boot code size is 440 bytes | |
; | |
pad_boot: | |
times 428-($-$$) db 0 ; 428 = 440 - len(log_title_str) | |
log_title_str: | |
%ifdef ACTIVEFIRST | |
db 10, 13, 'boot0af: ', 0 ; can be use as signature | |
%else | |
db 10, 13, 'boot0ss: ', 0 ; can be use as signature | |
%endif | |
pad_table_and_sig: | |
times 510-($-$$) db 0 | |
dw kBootSignature | |
ABSOLUTE 0xE400 | |
; | |
; In memory variables. | |
; | |
my_lba resd 1 ; Starting LBA for read_lba function | |
; END https://github.com/Clover-EFI-Bootloader/clover/blob/master/BootHFS/boot0.s |
Friday, October 27, 2017
talking about "bad rabbit" or "petya" or MTR fake bootloader, here's the code
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment