; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; ; Description: Heroic attempt to disassemble the Windows 95 Boot Sector. ; Date: 16. Aug. 1998 ; Author: Mr. X ; Email: unknown ; Comment: This boot code is messy. ; Status: PART I, II and III are now pretty much done. ; Important: "SHLD EDX,EAX,16" This is a Microsoft Patent. ; Also take a look at the "list near EOF" ; ; ---> CUT HERE IF YOU LIKE TO LISTEN TO ME <--- ; ; This file will assemble into a fully functional (I hope) Win95B boot code. ; ; tasm win95 /m ; tlink win95,win95.bin /t ; ; Ask someone for the proper dropper code... ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; ; AFTER DISASSEMBLY I have this impression: ; ; This is what Bill Gates said... when Win95 was going to be released: ; ; "Gates: OK, then we're ready to press the CD's and start shipping out ; this new load of shit, but of course nobody will notice... harr harr. ; Employee: Hey, Mr. Gates what about the Boot Sector?? We haven't ; written the FAT32 support routines there yet... ; Gates: Ah, that's right... anybody?? We have just 45 minutes... ; Employee #2: Well, I think I can hack some shit together... ; Gates: Fine, go for it... remember you have only 44 minutes... ; Employee #2: I think I can do it. ; Gates: Fine, then I'll just go home... We've made it!!" ; ; FUNNY? ; ; There is some really strange code in this boot record.... ; ; I bet Bill Gates hired some crazy mother fucker to write this shit. ; It seems like he had really tried to make the code fit within one sector. ; But when it didn't hid just decided to use three instead... ; ; This is a typical microsoft solution, they mix stupid 8086 code... with ; cheap solutions and then suddenly they use 386 code... ; ; And then there is the new FAT32 data structures where they have moved ; the volume label, FileSystem ID and serial number down to make room for ; some extended FAT32 variables... it sucks. Why not rearange the whole ; structure... An OS would not try to interpret the shit anyway, because ; the Partitioni Table SYSID has changed with FAT32. ; ; As I said... crazy mother fucker... ; ; Well, well... here's some of the stuff... with a mix of mine and sourcer's ; comments... ; ; Another thing about TASM, which I use, of course I didn't buy it... I'm ; have a shareware version ;) on a 30 year trial period. ; ; Back to what I was about to say again... When I use the brXXXX variables ; in indexing with BP, TASM generates 16-bit offset in operands even when ; they are less than 128... the Win95 code uses byte offsets (I'm not sure ; if I'm expressing myself clear here). When I changed the code from: ; ; mov AX,[bp+brHPC] to mov AX,[bp+128], TASM did use the byte offset form... ; This made my code a little less readable... but the comments should give ; you an idea of what data is being accessed. ; ; Basically this boot sector code is 32 bit extension for a 16 bit patch to ; an 8 bit boot sector originally coded for a 4 bit microprocessor, written ; by a 2 bit company, that can't stand 1 bit of competition. ; ; ---> CUT HERE IF YOU DOES NOT LIKE TO LISTEN TO ME <--- .386C CODE SEGMENT USE16 ASSUME CS:CODE, DS:CODE, SS:NOTHING ; BOOT RECORD - PART I - MAIN BOOT SECTOR CODE ; Just so I've said it - ASM opcodes are only readable when capitalized, ; but I forgot to set the option in sourcer... so I wrote a small TP program ; that capitalized everything but what's after the semicolon... Win95b PROC FAR JMP SkipData ; 0000h brINT13Flag DB 90H ; 0002h - 0EH for INT13 AH=42 READ brOEM DB 'MSWIN4.1' ; 0003h - OEM ID - Windows 95B brBPS DW 512 ; 000Bh - Bytes per sector brSPC DB 8 ; 000Dh - Sector per cluster brResCount DW 32 ; 000Eh - Reserved sectors brFATs DB 2 ; 0010h - FAT copies brRootEntries DW 0 ; 0011h - Root directory entries brSectorCount DW 0 ; 0013h - Sectors in volume, < 32MB brMedia DB 0F8H ; 0015h - Media descriptor brSPF DW 0 ; 0016h - Sectors per FAT brSPH DW 63 ; 0018h - Sectors per head/track brHPC DW 128 ; 001Ah - Heads per cylinder brHidden DD 63 ; 001Ch - Hidden sectors brSectors DD 6305985 ; 0020h - Total number of sectors brSPF32 DD 6153 ; 0024h - Sector per FAT (FAT32) brFlags DW 0 ; 0028h - Flags (FAT32) brVersion DW 0 ; 002Ah - FS Version (FAT32) brRootCluster DD 2 ; 002Ch - Root start cluster (FAT32) brFSInfoSector DW 1 ; 0030h - FS Info Sector (FAT32) brBackupBoot DW 6 ; 0032h - Backup Boot Record brReserved DB 6 DUP (0) ; 0038h - Reserved brShitter DB 6 DUP (0) ; 003Bh - Unused filler?? brDrive DB 80H ; 0040h - BIOS drive number brHeadTemp DB 00H ; 0041h - Head/temp number???? brSignature DB 29H ; 0042h - Extended Boot Record sig. brSerialNum DD 404418EAH ; 0043h - Volume serial number brLabel DB 'HARDDISK ' ; 0047h - Volume label brFSID DB 'FAT32 ' ; 0052h - File System ID SkipData: CLI XOR CX,CX MOV SS,CX ; SS=CX=0 ; Set up stack 8 bytes below us, do you know why??? ; Yes, it's because somewhere in this code, the shitter who ; wrote this... save the start of data area DWORD at 7C00H - 4 and ; the value -1 at 7C00H - 8... cool? MOV SP,7C00H - 8 MOV ES,CX ; ES=CX=0 MOV BP,78H ; Point DS:SI to INT 1E - DISKETTE PARAMS structure... LDS SI,DWORD PTR SS:[BP] PUSH DS PUSH SI PUSH SS PUSH BP MOV DI,522H MOV SS:[BP],DI ; setup our INT 1E MOV SS:[BP+2],CX ; copy 11 bytes from old diskette parameter table into ; es:522h, that is 0000:0522 or 0050:0022 - into the PrtScr/DOS area. ; I assume that 0001-0021 is used for something else 0050:0000 I know ; is the PrtScr flag byte. MOV CL,11 CLD REP MOVSB MOV DS,CX ; DS=CX=0 MOV BP,7C00H ; point BP to start of us MOV BYTE PTR [DI-2],0FH ; modify head settle time MOV AX,SS:[BP+18H] MOV [DI-7],AL ; modify sectors per track ; compare drive number with 0... ; if greater than or equal... go to MBRReadError ; I guess that lower than zero... must be -1 .. -128 (80H..FFH) ; Which would mean Harddisk boot... CMP SS:[BP+40H],CL ; Boot from diskette? JGE MBRReadError MOV AX,CX ; AX=CX=0 CWD ; DX=AX[15]-> (ZerO) MOV BX,0700H ; Use 0000:0700 for sector ; read buffer CALL ReadSector ; load Master Boot Record JC MBRReadError ; error? SUB BX,58 ; BX = 08C6h (700h - 3Ah) ; point to "start" field MOV EAX,DS:[7C1CH] ; load hidden sectors CheckMBR: CMP EAX,[BX] ; Is this our entry?? MOV DL,[BX-4] ; Put System ID in DL JNZ NotOurs ; Jump if not our entry ; If system ID or "partition type", is 0Ch or 0Eh, ReadSector ; will use INT13/42H... OR DL,2 ; set bit 1, to allow for ; types 0Ch or 0Eh to be ; thought of as both 0Eh. MOV SS:[BP+2],DL ; set brINT13Flag NotOurs: ADD BL,16 ; skip to next entry... JNB CheckMBR ; More entries? MBRReadError: MOV DI,2 ; FAT32 - Is sector per FAT zero? CMP WORD PTR SS:[BP+16H],0 JNE ShowErrMsg1 ; Put number of hidden sectors in DX:AX MOV AX,WORD PTR SS:[BP+1CH] MOV DX,WORD PTR SS:[BP+1EH] MOV CX,3 ; Boot Record is 3 sectors... ; Start loading reminder of Boot Record for FAT32 LoadIt: DEC CX INC AX ; next Boot Record sector JNZ Skipper ; AX wrap-around? INC DX ; Yes, inc DX too Skipper: MOV BX,7E00H ; into 0000:7E00 CALL ReadSectorX ; Read Sector JNC ReadOK ; no error? MOV AL,0F8H ; what's this???? DEC DI JZ NoMore ; Jump if no more sectors MOV AX,SS:[BP+32H] ; get backup boot sector XOR DX,DX MOV CX,3 CMP CX,AX ; compare backup BS num JA ShowErrMsg1 ; with 3 (or vice versa) ; if 3 is higher than ; backup sector number, ; Bill's oooutta here... MOV SI,SS:[BP+0EH] ; SI = # of reserved sectors CMP CX,SI JAE ShowErrMsg1 ; same thing here... if 3 is ; higher then the number of ; reserved sectors... Bill's ; gone SUB SI,CX ; get number reserved sectors ; excluding the three boot ; sectors... ; add number of hidden sectors to DX:AX ADD AX,WORD PTR SS:[BP+1CH] ADC DX,WORD PTR SS:[BP+1EH] JMP LoadIt NoMore: JNC ShowErrMsg1 ; Jump if carry=0 JMP ShowErrMsg2 ReadOK: CMP WORD PTR SS:[BP+2AH],0 JA ShowErrMsg1 ; Jump if not version 0.0? JMP GOFAT32 ShowErrMsg1: MOV SI,OFFSET ErrMsg1 + 7C00H PrintMessage: LODSB ; get msg Skip length CBW ADD SI,AX ; Skip control data NextChar: LODSB ; get chacacter TEST AL,AL JZ LastChar ; End of string? CMP AL,-1 JE SkipChar ; End of first part? MOV AH,0EH ; TTY write character MOV BX,7 INT 10H JMP NextChar ; repeat write... SkipChar: MOV SI,OFFSET ErrMsg4 + 7C00H ; point to tail message JMP PrintMessage ShowErrMsg2: MOV SI,OFFSET ErrMsg2 + 7C00H JMP PrintMessage LastChar: CBW ; Ah, clever... save one byte, take ; advantage of the fact that LODSB INT 16H ; returns the null-terminator. POP SI ; restore the stack... why??? POP DS ; the stack is killed at startup... POP DWORD PTR [SI] INT 19H ; BIOS bootstrap loader... Win95b ENDP ;========================================================================== ; READ SECTOR ;========================================================================== ReadSector PROC NEAR INC CX ; increase SECTOR COUNT ReadSectorX: rsReadMore: PUSH SI PUSH DWORD PTR 0 PUSH DX PUSH AX PUSH ES PUSH BX PUSH 1 PUSH 10H MOV SI,SP ; save stack pointer ; for later use by LEA PUSHA ; Save "all" registers CMP BYTE PTR SS:[BP+2],0EH ; Use INT13 extensions? JNE rsOldINT13 MOV AH,42H ; Do ext INT13 READ JMP RSDiskIO rsOldINT13: XCHG CX,AX ; swap CX and AX XCHG DX,AX ; swap DX and AX XOR DX,DX ; clear DX DIV WORD PTR SS:[BP+18H] ; div LBA_HI by sectors/track XCHG CX,AX ; save result in CX and put ; the LBA_LO in AX DIV WORD PTR SS:[BP+18H] ; divide reminder and LBA_LO ; by sectors/track too INC DX ; make sector 1-based XCHG CX,DX ; save it in CX and get the ; result of the 1st division ; in DX DIV WORD PTR SS:[BP+1AH] ; divide this new result by ; heads per cylinder MOV DH,DL ; save Head of CHS in DH ; head was in the reminder ; after the division above MOV CH,AL ; save LO cylinder in CH ; cylinder was in the result ; after the division above ROR AH,2 ; rotate AH to make bits 8-9 ; of cylinder appear as bits ; 6-7 in AH and... OR CL,AH ; or it with the sector num MOV AX,201H ; setup for READ - 1 sector rsDiskIO: MOV DL,SS:[BP+40H] ; load drive number INT 13H ; call INT13 POPA ; Restore "all" registers ; the entry code pushed 12h bytes on the stack... ; the last word pushed was 0001h, restore SP to point to it... LEA SP,[SI+10H] ; Load effective addr ; Now, SI should contain 0001h POP SI ; was there an error from INT13? JC RSDone INC AX ; increment LBA sector num JNZ rsSkip ; wrap-around? INC DX ; yes raise high word too rsSkip: ADD BX,SS:[BP+0BH] ; increment by sector size DEC CX ; decrement SECTOR COUNT JNZ rsReadMore ; Jump if more to read rsDone: RET ReadSector ENDP ;============================================================================ ; DATA AREA FOR MESSAGES - IN "NORSK" NORWEGIAN ;============================================================================ ErrMsg1 DB 03H ; Skip counter for message1 ErrMsg2 DB 18H ; Skip counter for message2 ErrMsg3 DB 01H ; Skip counter for message3 ErrMsg4 DB 27H ; Skip counter for message4 DB 13,10,'Ugyldig systemdisk ',-1 DB 13,10,'Disk I/U-feil ',-1 DB 13,10,'Sett inn en annen disk, og trykk en tast',13,10,0 ;============================================================================ DB 0,0 ; Padding? ; ROOT file names to search for...? IO_SYS DB 'IO SYS' MSDOS_SYS DB 'MSDOS SYS' DB 7EH,1,0 ; What is this? WINBOOT_SYS DB 'WINBOOT SYS' ; When is this used? DB 0,0 ; Padding? DW 0AA55H ; 1st Boot Signature ; ; BOOT RECORD - PART II - FSINFO sector ; DB 'RRaA' ; FAT32 Extension Signature DB 480 DUP (0) ; FSINFO information... DB 'rrAa' ; FAT32 FSINFO Signature brFreeClusters DD 56990 ; I have 233431040 bytes free! brNextFree DD 466175 ; My next free cluster! DD 3 DUP (0) ; Reserved, acroding to FAT32API.HLP DW ? ; word padding DW 0AA55H ; 2nd Boot Signature ; ; BOOT RECORD - PART III - FAT32 specific code, I think? only Bill knows? ; GOFAT32: CLI ; calculate total size of FAT area MOVZX EAX,BYTE PTR SS:[BP+10H] ; number of FATs MOV ECX,SS:[BP+24H] ; sectors per FAT MUL ECX ; mul'em ; add hidden sectors ADD EAX,SS:[BP+1CH] ; add reserved sectors MOVZX EDX,WORD PTR SS:[BP+0EH] ADD EAX,EDX XOR CX,CX ; clear CX for some reason... ; By looking down the code, I can't ; seem to find out why CX is cleared ; It's set to 1 down there... ; before it's ever used... ; EAX will now point to the start of the data area (cluster 2) ; save start of data area below us at 0000:7BFC, or there around... MOV SS:[BP-4],EAX ; Save another value to... This one is checked by GetFAT32Sector MOV DWORD PTR SS:[BP-8],0FFFFFFFFH ; Oh... at Microsoft they take no chances... disable INTs again! ; This is what I call proper software writing! Hail M$ CLI ; load Root Start Cluster in EAX MOV EAX,SS:[BP+2CH] ; Is it cluster 2? CMP EAX,2 JB ShowErrMsg1 ; error if less than 2 ; Is it an EOF marker or something above? CMP EAX,0FFFFFF8H JAE ShowErrMsg1 ; error if it is ; Put upper 16-bits of cluster number into DX?? SHLD EDX,EAX,16 STI ; Puh. Safe again. GetRootCluster: PUSH DX PUSH AX CLI ; Eh? ; clear upper 16-bits of cluster number, and of course move up the ; lower bits... SHL EAX,16 ; shift lower 16-bits of cluster number back down, and at the same ; time shift in the high 16-bits in top of EAX? SHRD EAX,EDX,16 ; make cluster number 0-based... "the way it's supposed to be" SUB EAX,2 ; put Sectors Per Cluster in EBX MOVZX EBX,BYTE PTR SS:[BP+0DH] ; save it in SI too! Yippi MOV SI,BX ; calculate relative sector of first part of root... right? MUL EBX ; add the "start of data area" value we saved below us! ADD EAX,SS:[BP-4] ; Maybe now, some shitter is trying to make DX:AX what EAX is?? ; Shift upper 16-bits of EAX into DX... and AX is lower bits... SHLD EDX,EAX,10H STI ; Enable interrupts GetRootSector: ; Use 0070:0000 as a directory buffer... MOV BX,0700H MOV DI,BX ; read 1 sector MOV CX,1 CALL ReadSectorX ; this shit should be pretty JC ShowErrMsg2 ; obvious... CheckEntry: CMP [DI],CH ; is the first entry of the JE EndOfRoot ; root empty??? MOV CL,11 ; the stupid CP/M filenames ; are 11 bytes... PUSH SI MOV SI,OFFSET IO_SYS + 7C00H REPE CMPSB ; Is it IO.SYS? POP SI JZ FoundOS ; Yeah... ADD DI,CX ; add what's left after CMPSB ADD DI,15H ; and then 21 more... ; Yeah, yeah, anyway... point to the next dir entry... ; and check if it is above the last entry... INT13 increments ; BX with 512 on the sector read, so it points past the sector. CMP DI,BX JB CheckEntry ; Jump if below ; are there any more sectors in this cluster??? DEC SI JNZ GetRootSector ; yeap, read more POP AX ; restore cluster number POP DX ; Get FAT value... "GetFAT32Value" will compare the value with ; -8, and the JB below continues if below... that is, non-EOF/BAD ; the "previous cluster" value is taken from DX:AX (as restored ; above with POP). CALL GetFAT32Value JB GetRootCluster ; if not end of root... go to GetRootCluster.. EndOfRoot: ; EOF/BAD cluster... ADD SP,4 ; clean up stack... JMP ShowErrMsg1 ; and print error message FoundOS: ADD SP,4 ; clean up... ; Now... DI should point just above the IO.SYS name... ; SI would be set to DirEntry[14H] - starting cluster (HI) ; DI would be set to DirEntry[1AH] - starting cluster (LO) MOV SI,[DI+09H] MOV DI,[DI+0FH] ; copy FAT32 starting cluster upper 16-bits to AX MOV AX,SI CLI ; Disable interrupts ; shift cluster high into upper half of EAX and store lower half ; from DI into AX SHL EAX,10H MOV AX,DI ; cluster out of range?? CMP EAX,2 ; clusters start with 2 JB InvalidCluster CMP EAX,0FFFFFF8H ; cluster 0FFFFFF8 is EOF JAE InvalidCluster DEC EAX ; make it 0-based... DEC EAX ; Multiply cluster number with "sectors per cluster" MOVZX ECX,BYTE PTR SS:[BP+0DH] MUL ECX ; Add the "start of data area" value that was saved back there... ADD EAX,SS:[BP-4] ; And for the N'th time, make DX:AX same as EAX - sector number. SHLD EDX,EAX,10H STI ; aha... MOV BX,0700H ; IO.SYS loads here! PUSH BX MOV CX,4 ; load 4 IO.SYS sectors CALL ReadSectorX ; 2K is minimum FAT32 cluster POP BX ; size anyway... JC ShowErrMsg2 ; error...??? ; COMMENT: ; ; Now, there is enough code here... to read the entire IO.SYS ; file into memory. This code has code to go through the FAT, ; there is code to read cluster... bla bla. And still only 2K ; of IO.SYS is read. If the entire file was read... IO.SYS would ; not have to do this... well well. ; Is there a Mark Zibikowski in the room? CMP WORD PTR [BX],'ZM' ; EXE signature... JNE InvalidCluster ; Is there a Barabara Jones in the room? CMP WORD PTR DS:[0200H][BX],'JB' ; IO.SYS signature? JE ExecutIOSYS ; The above shit appear in the IO.SYS file at offsets 0 and 200h ; The MZ is the usual EXE signature while the "BJ" is unknown to ; me. Maybe they chose it because it translates to harmless code: ; ; INC DX - DEC DX, pretty dull if you ask me ;) ; InvalidCluster: MOV SI,OFFSET ErrMsg3 + 7C00H JMP PrintMessage ExecutIOSYS: DB 0EAH ; Jump to IO.SYS at 0070:0200 DW 0200H, 0070H ;========================================================================== ; GET FAT32 VALUE ;========================================================================== GetFAT32Value PROC NEAR ADD AX,AX ; Multiply DX:AX by 4, ADC DX,DX ADD AX,AX ; convert DX:AX from FAT32 ADC DX,DX ; index value to offset ; DX:AX is passed on as the FAT offset to lookup... CALL GetFAT32Sector ; read FAT sector ; the correct sector is returned... with DI as index...?? ; At least that's what the MOV below assumes... CLI MOV EAX,ES:[BX+DI] ; EAX = cluster value ; mask of top 4 bits of because Microsoft say it's reserved. AND EAX,0FFFFFFFH ; Make DX:AX the cluster number too... SHLD EDX,EAX,16 ; EAX[HI] into EDX[LO] ; Check for EOF/BAD CMP EAX,0FFFFFF8H ; Is it the EOF marker? STI ; return with ZF=1 if the ; last cluster was read?? RET GetFAT32Value ENDP ;========================================================================== ; GET FAT32 SECTOR ;========================================================================== ; On entry DX:AX is the FAT offset in bytes... GetFAT32Sector PROC NEAR ; When this is called 0070:0200 seems to be the buffer in ES:BX ; but, the code below uses the DI value set down under here... MOV DI,7E00H CLI ; Disable interrupts ; make EAX the sector number again... move DX into top of EAX... SHL EAX,16 SHRD EAX,EDX,16 ; move bytes per sector into ECX MOVZX ECX,WORD PTR SS:[BP+0BH] ; divide EDX:EAX by BPS... EAX = sector, EDX = offset in sector... XOR EDX,EDX DIV ECX ; Check FAT sector number agains... saved value on stack... ; This one is initially -1 (also known as 0FFFFFFFFH) CMP EAX,SS:[BP-8] JE LOC_30 ; If sector is <> from -1, save this sector at 0000:7BF8 MOV SS:[BP-8],EAX ; add hidden sectors... ADD EAX,SS:[BP+1CH] ; add reserved sectors too... MOVZX ECX,WORD PTR SS:[BP+0EH] ADD EAX,ECX ; get FAT32 flags into EBX MOVZX EBX,WORD PTR SS:[BP+28H] ; keep "Active FAT" bits 0-3 AND BX,0FH ; If zero, we're at the correct FAT JZ CorrectFAT ; compare active FAT with number of FATs... CMP BL,SS:[BP+10H] JAE ShowErrMsg1 ; oops... invalid active FAT PUSH DX ; save DX for a while... ; save FAT sector in ECX MOV ECX,EAX ; Put sectors per fat in EAX MOV EAX,SS:[BP+24H] ; Multiply active FAT number with sectors per FAT MUL EBX ; Add to first FAT sector number we already had... ADD EAX,ECX ; NOW, EAX contains the correct FAT sector number. POP DX CorrectFAT: PUSH DX ; And for the N'th time, make DX:AX same as EAX - sector number. SHLD EDX,EAX,16 STI ; Enable interrupts MOV BX,DI ; read FAT sector into ; 0000:7E00 ; They sucker who wrote this could have saved 1 byte by ; saying XOR CX,CX instead of MOV CX,1 and called ReadSector ; instead of ReadSectorX, because there is an INC CX at ; ReadSector... haha... MOV CX,1 ; 1 sector CALL ReadSectorX POP DX JC ShowErrMsg2 LOC_30: STI ; Enable interrupts MOV BX,DX RET GetFAT32Sector ENDP ; Properly align the sector's boot signature at the end of ; the 3rd boot sect0r. ORG 512 * 3 - 2 DW 0AA55H ; 3rd Boot Signature CODE ENDS END ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; ; CONCLUSION - THE END - HASTA LA VISTA - SLUTT - DET VAR ALT FOR I DAG. ; ; OK, Folks. that was quite a bit of work. It got pretty simple after some ; hours. ; ; I would like to thank the following people... ; ; * V Communications for Sourcer. ; * Ralf Brown for the Interrupt List. ; * Uriah Heep, The Who and Blind Guardian, for providing music. ; ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -