%if 0 Loader test payload by C. Masloch, 2017 Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. %endif %include "lmacros2.mac" numdef LARGE, 1 numdef PADDING, 0 struc BS bsJump: resb 3 bsOEM: resb 8 bsBPB: endstruc struc EBPB ; BPB sec bpbBytesPerSector: resw 1 ; offset 00h 0Bh bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh bpbReservedSectors: resw 1 ; offset 03h 0Eh bpbNumFATs: resb 1 ; offset 05h 10h bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32 bpbTotalSectors: resw 1 ; offset 08h 13h bpbMediaID: resb 1 ; offset 0Ah 15h bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32 bpbCHSSectors: resw 1 ; offset 0Dh 18h bpbCHSHeads: resw 1 ; offset 0Fh 1Ah bpbHiddenSectors: resd 1 ; offset 11h 1Ch bpbTotalSectorsLarge: resd 1 ; offset 15h 20h bpbNew: ; offset 19h 24h ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h ebpbFSFlags: resw 1 ; offset 1Dh 28h ebpbFSVersion: resw 1 ; offset 1Fh 2Ah ebpbRootCluster: resd 1 ; offset 21h 2Ch ebpbFSINFOSector: resw 1 ; offset 25h 30h ebpbBackupSector: resw 1 ; offset 27h 32h ebpbReserved: resb 12 ; offset 29h 34h ebpbNew: ; offset 35h 40h endstruc struc BPBN ; ofs B16 S16 B32 S32 bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h resb 1 ; 01h 1Ah 25h 36h 41h bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h endstruc ; 1Ah 33h 3Eh 4Fh 5Ah struc LOADSTACKVARS, -10h lsvFirstCluster: resd 1 lsvFATSector: resd 1 lsvFATSeg: resw 1 lsvLoadSeg: resw 1 lsvDataStart: resd 1 endstruc lsvclSignature equ "CL" lsvclBufferLength equ 256 struc LOADDATA, LOADSTACKVARS - 10h ldMemoryTop: resw 1 ldLoadTop: resw 1 ldSectorSeg: resw 1 ldFATType: resb 1 ldHasLBA: resb 1 ldClusterSize: resw 1 ldParaPerSector:resw 1 ldLoadingSeg: ; word lsvCommandLine: ; word .start: equ $ - lsvclBufferLength .signature: resw 1 ldLoadUntilSeg: ; word lsvExtra: ; word .partition: resb 1 ; byte .flags: resb 1 ; byte endstruc lsvefNoDataStart equ 1 lsvefPartitionNumber equ 2 struc LOADCMDLINE, LOADDATA - lsvclBufferLength ldCommandLine: .start: resb lsvclBufferLength endstruc %ifndef _MAP %elifempty _MAP %else ; defined non-empty, str or non-str [map all _MAP] %endif cpu 8086 org 0 payload: ; The device header is of a fixed format. ; For our purposes, the 4-byte code for ; each the strategy entry and the ; interrupt entry is part of this format. ; (DOS may read the attributes or entrypoint ; offsets before calling either, so the ; inicomp stage needs to recreate in its ; entrypoints part exactly what we have here.) device_header: .next: fill 2, -1, jmp strict short j_zero_entrypoint dw -1 .attributes: dw 8000h ; character device .strategy: dw .strategy_entry ; -> strategy entry .interrupt: dw .interrupt_entry ; -> interrupt entry .name: fill 8, 32, db "TESTPL$$" ; character device name .strategy_entry: fill 4, 90h, jmp device_entrypoint .interrupt_entry: fill 4, 90h, retf j_zero_entrypoint: jmp zero_entrypoint nop align 32, nop kernel_entrypoint: ; cs:ip = load seg : 32 here %if ($ - $$) != 32 %error Wrong kernel mode entrypoint %endif ; S0 +28 pushf ; +26 push ax ; +24 push cs ; +22 call .push_ip .push_ip: pop ax sub ax, .push_ip - kernel_entrypoint push ax ; +20 IP common_entrypoint: push es ; +18 ES push bx ; +16 BX cmp word [cs:signature], 2638h je sig1_valid jmp sig_invalid align 64, nop dos_exe_entrypoint: ; cs:ip = PSP : 256 + 64 here ; ; Code must be position independent enough. %if ($ - $$) != 64 %error Wrong EXE mode entrypoint %endif ; S0 +28 pushf ; +26 push ax ; +24 push cs ; +22 call .push_ip .push_ip: pop ax sub ax, .push_ip - dos_exe_entrypoint push ax ; +20 IP dos_com_entrypoint: mov byte [cs:100h + loadmode], 1 mov ax, cs add ax, 10h ; simulate kernel loading push ax jump_common_entrypoint: mov ax, common_entrypoint push ax retf ; jump to cs + 10h : common_entrypoint zero_entrypoint: ; S0 +28 pushf ; +26 push ax ; +24 push cs ; +22 call .push_ip .push_ip: pop ax sub ax, .push_ip push ax ; +20 IP cmp word [cs:0], 20CDh jne @F cmp ax, 100h je dos_com_entrypoint @@: push cx mov cl, 4 shr ax, cl mov cx, cs add ax, cx pop cx push ax jmp jump_common_entrypoint device_entrypoint: pushf push ax push cs call .push_ip .push_ip: pop ax sub ax, .push_ip - device_header.strategy_entry push ax mov byte [cs:loadmode], 2 mov word [cs:dev_sp], sp mov word [cs:dev_exit], dev_exit.1 jmp common_entrypoint sig_invalid: call error db "Signature invalid.", 0 sig1_valid: push ds push bx mov bx, cs add bx, (signature2 -$$+0) >> 4 mov ds, bx cmp word [(signature2 -$$+0) & 0Fh], 2638h pop bx pop ds jne sig_invalid jmp sig_valid msg: .error: db "Test payload error: ", 0 .test: db "Test payload loaded.", 13, 10, 0 .psp_and_size_before: asciz "PSP at " .psp_and_size_between: asciz "h, size of memory block is " .psp_and_size_after: asciz "h paragraphs.",13,10 .cmdline.kern.none: asciz "No kernel command line given!",13,10 .cmdline_before.kern: asciz "Kernel command line = ",'"' .cmdline_before.app: asciz "Application command line = ",'"' .cmdline_before.device: asciz "Device command line = ",'"' .cmdline_after: asciz '"',13,10 align 4 .foundname: times 8+1+3+1 db 0 ; buffer for base name (8) + dot (1) + ext (3) + NUL (1) align 2 .foundname_none: asciz "(None)" .foundname_none_size: equ $ - .foundname_none align 2 .names: dw .name_first, 0 dw .name_second, 0 dw .name_third, 0 dw .name_fourth, 0 dw 0 .name_first: asciz "1st name" .name_second: asciz "2nd name" .name_third: asciz "3rd name" .name_fourth: asciz "4th name" .name_before: asciz ": " .name_quote: asciz '"' .name_after: asciz 13,10 align 4 dev_request_header: dd 0 loadmode: dw 0 ; 0 = loaded as boot payload, ; 1 = loaded as DOS application, ; 2 = loaded as DOS device driver dev_sp: dw 0 dev_exit: dw 0 .1: mov sp, word [cs:dev_sp] jmp .common_1 .2: mov sp, word [cs:dev_sp] jmp .common_2 .common_2: pop ax ; ss pop ds pop ax ; sp pop bp pop di pop si pop dx pop cx .common_1: pop bx ; bx pop es ; es pop ax ; (IP) pop ax ; (CS) pop ax ; ax mov word [es:bx + 3], 8103h ; error, done, error code: unknown command popf ; flags retf ; far return to DOS error: push cs pop ds mov si, msg.error call disp_msg pop si call disp_msg test byte [cs:loadmode], 2 jz .dos_or_bios jmp near [cs:dev_exit] .dos_or_bios: test byte [cs:loadmode], 1 jz .bios mov ax, 4C01h int 21h .bios: xor ax, ax int 16h int 19h disp_msg_asciz: push ds push si push ax push cs pop ds mov si, dx call disp_msg pop ax pop si pop ds retn disp_msg: @@: lodsb test al, al jz @F call disp_al jmp short @B disp_al: push ax push bx push dx push bp test byte [cs:loadmode], 1 | 2 jz .bios mov dl, al mov ah, 02h int 21h jmp .common .bios: mov ah, 0Eh mov bx, 7 int 10h .common: pop bp pop dx pop bx pop ax @@: retn disp_msg_length: push cx jcxz .ret .loop: lodsb call disp_al loop .loop .ret: pop cx retn sig_valid: ; S0 +28 ; pushf ; +26 ; push ax ; +24 ; push cs ; +22 ; call .push_ip ;.push_ip: ; pop ax ; sub ax, .push_ip ; push ax ; +20 IP ; push es ; +18 ; push bx ; +16 sti cld push cx ; +14 push dx ; +12 push si ; +10 push di ; +8 push bp ; +6 mov ax, sp add ax, 22 push ax ; +4 SP push ds ; +2 push ss ; +0 test byte [cs:loadmode], 2 jz @F mov word [cs:dev_sp], sp mov word [cs:dev_exit], dev_exit.2 mov word [cs:dev_request_header], bx mov word [cs:dev_request_header + 2], es cmp byte [es:bx + 2], 0 ; command code 0 (init) ? jne dev_exit.2 ; else immediately return --> mov byte [es:bx + 13], 0 ; number of units = 0 mov word [es:bx + 14 + 2], cs and word [es:bx + 14], 0 ; -> after end of memory to allocate or word [cs:device_header.next], -1 ; fill in offset of device header link @@: mov si, sp push ss pop ds mov di, table push cs pop es loop_table: mov bx, [es:di + 0] mov al, 32 call disp_al mov ax, [es:di + 2] call disp_al xchg al, ah call disp_al cmp bx, -1 je @F mov al, '=' call disp_al mov ax, [si + bx] call disp_ax_hex @@: add di, 4 cmp di, table.end jb loop_table listnames: test byte [cs:loadmode], 1 | 2 jnz .skip mov bx, msg.names push ss pop ds lea si, [bp + bsBPB + ebpbNew + BPBN_size] mov cx, (512 - (bsBPB + ebpbNew + BPBN_size)) - 2 ; -2 = AA55h sig cmp word [bp + bsBPB + bpbSectorsPerFAT], 0 je @F mov cx, (512 + (ebpbNew - bpbNew) - (bsBPB + ebpbNew + BPBN_size)) - 2 @@: .nextname: call findname lahf mov dx, [cs:bx] call disp_msg_asciz mov dx, msg.name_before call disp_msg_asciz sahf jc @F ; skip quote if no name --> mov dx, msg.name_quote call disp_msg_asciz @@: mov dx, msg.foundname call disp_msg_asciz sahf jc @F ; skip quote if no name --> mov dx, msg.name_quote call disp_msg_asciz @@: mov dx, msg.name_after call disp_msg_asciz sahf mov ax, 0 jc @F ; set to zero if no name --> lea ax, [si - 11] ; -> name in buffer @@: mov word [cs:bx + 2], ax ; -> name in buffer, or 0 add bx, 4 cmp word [cs:bx], 0 jne .nextname .skip: push cs pop ds mov si, msg.test call disp_msg test byte [cs:loadmode], 1 jz .skip_psp_dos mov si, msg.psp_and_size_before call disp_msg mov ah, 51h int 21h mov ax, bx call disp_ax_hex mov si, msg.psp_and_size_between call disp_msg dec bx mov es, bx mov ax, word [es:3] call disp_ax_hex mov si, msg.psp_and_size_after call disp_msg mov si, msg.cmdline_before.app call disp_msg mov ah, 51h int 21h mov ds, bx mov si, 81h xor cx, cx mov cl, byte [si - 1] call disp_msg_length push cs pop ds mov si, msg.cmdline_after call disp_msg jmp .after_cmdline .skip_psp_dos: test byte [cs:loadmode], 2 jz .skip_device mov si, msg.cmdline_before.device call disp_msg les bx, [cs:dev_request_header] les di, [es:bx + 18] push es pop ds mov si, di ; Writing MS-DOS Device Drivers, second edition, page 349 ; specifies the following as to the command line termination: ; "Note that the DEVICE= command string is terminated by an ; Ah when there are no arguments. When there are arguments, ; the string is terminated with the following sequence: ; 0h, Dh, Ah." db __TEST_IMM8 @@: inc di cmp byte [di], 0 je @F cmp byte [di], 13 je @F cmp byte [di], 10 jne @B @@: ; di -> at terminator sub di, si mov cx, di call disp_msg_length push cs pop ds mov si, msg.cmdline_after call disp_msg jmp .after_cmdline .skip_device: mov si, msg.cmdline.kern.none cmp word [bp + ldCommandLine], 0FF00h je .no_kernel_cmdline mov si, msg.cmdline_before.kern call disp_msg push ss pop ds lea si, [bp + ldCommandLine] call disp_msg push cs pop ds mov si, msg.cmdline_after .no_kernel_cmdline: call disp_msg .after_cmdline: int3 test byte [cs:loadmode], 2 jz .dos_or_bios jmp near [cs:dev_exit] .dos_or_bios: test byte [cs:loadmode], 1 jz .bios mov ax, 4C00h int 21h .bios: xor ax, ax int 16h int 19h disp_ax_hex: ; ax xchg al,ah call disp_al_hex ; display former ah xchg al,ah ; and fall trough for al disp_al_hex: ; al push cx mov cl,4 ror al,cl call disp_al_lownibble_hex ; display former high-nibble rol al,cl pop cx ; and fall trough for low-nibble disp_al_lownibble_hex: push ax ; save ax for call return and al,00001111b ; high nibble must be zero add al,'0' ; if number is 0-9, now it's the correct character cmp al,'9' jna .decimalnum ; if we get decimal number with this, ok --> add al,7 ; otherwise, add 7 and we are inside our alphabet .decimalnum: call disp_al pop ax retn ; INP: ds:si -> first byte to check for name ; cx = number of bytes left ; OUT: (8+1+3+1)bytes[es:msg.foundname] = found name, ; converted to 8.3 ASCIZ format, ; "(None)" if none ; CY if no filename found, ; si = INP:si + INP:cx ; cx = 0 ; NC if filename found, ; ds:si -> byte behind the name, thus ds:(si-11)-> name ; cx = number of bytes left ; CHG: di, ax findname: .: cmp cx, 11 ; enough for another name ? jb .none ; no --> ; (cx == 0 jumps here too) .check: push cx push si mov cx, 11 lodsb mov ah, al ; check for same char in all 11 places cmp al, 32 ; first character must not be blank je .check_fail ; if it is --> ; cmp al, 5 ; first character may be 05h to indicate 0E5h ; je .check_pass db __TEST_IMM8 ; (skip lodsb) .check_loop_same: lodsb cmp ah, al jne .check_loop_differs call .check_character jc .check_fail loop .check_loop_same ; if we arrive here, all characters (while valid) are the ; same character repeated 11 times. we disallow this in case ; that the padding character is an allowed one (eg '&' 26h). .check_fail: pop si pop cx dec cx ; lessen the counter inc si ; -> next position to check jmp . .check_character: cmp al, 32 jb .check_character_fail cmp al, 127 ; je .check_character_fail jae .check_character_fail ; note: with all characters >= 128 allowed, ; we get false positives in our sectors. cmp al, '.' je .check_character_fail cmp al, '/' je .check_character_fail cmp al, '\' je .check_character_fail cmp al, 'a' jb .check_character_pass cmp al, 'z' ja .check_character_pass .check_character_fail: stc retn .check_character_pass: clc retn .check_loop: lodsb .check_loop_differs: call .check_character jc .check_fail .check_pass: loop .check_loop pop ax ; (discard si) sub si, 11 ; -> at name call convert_name_to_asciz ; si -> behind name pop cx sub cx, 11 ; lessen the counter clc retn .none: add si, cx mov di, msg.foundname push si push ds push cs pop ds mov si, msg.foundname_none mov cx, (msg.foundname_none_size + 1) >> 1 rep movsw pop ds pop si xor cx, cx stc retn ; INP: ds:si -> 11-byte blank-padded name ; es:msg.foundname -> (8+1+3+1)-byte buffer ; OUT: ds:si -> behind 11-byte blank-padded name ; es:msg.foundname filled ; CHG: cx, di, ax convert_name_to_asciz: mov di, msg.foundname mov cx, 8 rep movsb ; copy over base name, si -> extension cmp byte [es:di - 8], 05h ; is it 05h ? jne @F ; no --> mov byte [es:di - 8], 0E5h ; yes, convert to 0E5h @@: db __TEST_IMM8 ; (skip dec) @@: dec di ; decrement -> at previous trailing blank cmp byte [es:di - 1], 32 ; trailing blank ? je @B ; yes --> mov al, '.' stosb ; store dot (if needed) mov cl, 3 rep movsb ; copy over extension, si -> behind name db __TEST_IMM8 ; (skip dec) @@: dec di ; decrement -> at previous trailing blank cmp byte [es:di - 1], 32 ; trailing blank ? je @B ; yes --> cmp byte [es:di - 1], '.' ; trailing dot ? (only occurs if all-blank ext) jne @F ; no --> dec di ; -> at the dot @@: mov al, 0 stosb ; store filename terminator retn align 4 table: dw +0, "SS" dw +6, "BP" dw +4, "SP" dw +22, "CS" dw +20, "IP" dw +26, "FL" db -1, -1, 13,10 dw +2, "DS" dw +10, "SI" dw +18, "ES" dw +8, "DI" db -1, -1, 13,10 dw +24, "AX" dw +16, "BX" dw +14, "CX" dw +12, "DX" db -1, -1, 13,10 dw +28, "S0" dw +30, "S1" dw +32, "S2" dw +34, "S3" dw +36, "S4" dw +38, "S5" dw +40, "S6" dw +42, "S7" db -1, -1, 13,10 dw +44, "S8" dw +46, "S9" dw +48, "SA" dw +50, "SB" dw +52, "SC" dw +54, "SD" dw +56, "SE" dw +58, "SF" db -1, -1, 13,10 .end: signature: dw 2638h align 16, db 0 %if _LARGE times 64 * 1024 db 0 %endif signature2: dw 2638h %if _PADDING %if ($ - $$) > _PADDING %warning No padding needed %else times _PADDING - ($ - $$) db 0 %endif %endif