; *** UWOT.ASM *** ; Ultimate "waveOut" test / Ynsane sound PLAYER ; (CL) Copyleft 2014-07-01 by DOS386 ; Public Domain - ABUSE AT YOUR OWN RISK !!! ; Tested with interesting (but not necessarily audible) results on ; MS-DOG 1.0, Windaube 3.11, ME, XP, Wi$ta, Windows 9.1, W[h]ine, and HX ; WARNING: This tool does one arguably exotic task very well !!!!!!!!!!!!!!!! ; ASS'ume: really nothing AKA BUGGER all ;-D ; Compile with FASM 1.72 or better ; ---------------------------------------------------------------------- define pope pop define popef popf macro movntq mdst, msrc { if mdst in & msrc eq 0 xor mdst, mdst else mov mdst, msrc end if } ; endmacro macro cmpntq minp, mref { if minp in & mref eq 0 test minp, minp else cmp minp, mref end if } ; endmacro ; ---------------------------------------------------------------------- format binary as "EXE" use32 org 0 ; ---------------------------------------------------------------------- ccbase = $0040'0000 ; 4 Mi for EXE ; ccbase = $1000'0000 ; 1 Gi for DLL ccstartrva = $1000 ; 4 Ki (increase if you have a big MZ stub !!!) ; ---------------------------------------------------------------------- ; *** MZ-Header, stub (0,$60) *** ccstacktop = $1040 ; MZ header $20 ; Program $0040 ; Heap 0 ; Stack $1000 db "MZ" ; Mark's Sigi dw $60 ; Octet's in last block (0 is full, includes header size) dw 1 ; Blocks per 512 Octet's (includes header size) db 0,0 ; No relox db 2,0 ; Header size dw $1000 ; Min (Heap + Stack) 4 KiO dw $1000 ; Max (Heap + Stack) 4 KiO dw 0 ; Stack segment: SS=CS !!! dw ccstacktop ; Stack size (almost 4 KiO) / pointer in Octet's !!! db 0,0,0,0,0,0 ; CHK, IP, CS dw $1C ; Useless relox offset db 0,0 ; Useless "overlay" db 0,0,0,0 ; Padding / empty relox ; org = $20 , should be 0 but irrelevant :-D db $0E,$1F ; PUSH CS | POPE DS db $B4,9,$BA,$20,0 ; MOV AH, 9 | MOV DX, $0020 db $CD,$21 ; INT $21 db $B8,1,$4C ; MOV AX, $4C01 db $CD,$21 ; INT $21 db 0,0 ; Wasting 2 Octet's ; org = $30 db 0,0,0,0,0,0,0,0,0,0,0,0 ; Wasting 12 Octet's db $60,0,0,0 ; PE begins here assert ($=$40) ; Otherwise "ERROR: corrupt MZ stub" ; org = $40, should be $20 db "Need HX-DOS Extender to run !",$0D,$0A,$24 ; 29 chars + 3 special assert ($=$60) ; Otherwise "ERROR: corrupt MZ stub" ; org = $60, content moved by + $20 Octet's ; *** PE-Header ($60,$18) *** db "PE", 0, 0 ; Signature dw $014C ; CPU: 80386 or compatible (nobody seems use 80486 or Pentium) dw 1 ; Number of sections dd 0 ; Timestamp ; dd %t ; Timestamp dd 0, 0 ; Useless "symbols" :-( dw $00E0 ; Size of Optional header (const ???) ; dw $010E ; Characteristics : "image", 32-bit, low junk, relox ON dw $010F ; Characteristics : "image", 32-bit, low junk, relox OFF ; dw $210E ; Characteristics : "image", 32-bit, low junk, relox ON, DLL ; *** The "portable" Optional header ($78,$1C) *** dw $010B ; Great "magic" for PE (PE32) dw 0 ; Useless "linker version" :-( dd 0, 0, 0 ; Useless .code/.idata/.data sizes :-( dd (llentry - ccbase) ; Entry point RVA dd 0, 0 ; Useless .code/.data offsets :-( ; *** NT-specific Optional header extension ($94,$44) *** dd ccbase ; Base address dd $1000, $0200 ; Section alignment in memory and file: 4 KiO / 512 O dd 1 ; OS version ( "NT 1.0" !!!??? ) dd 0 ; Image version dd 4 ; Great "subsystem" version, bump to 5 -> NT4 and ME refuse dd 0 ; Reserved dd ((llveryend - ccbase + $0FFF) shr 12) shl 12 ; Image size ; Total size from base to end of sections, all aligned to $1000 ; This includes all headers and stub !!! dd $0200 ; Header size (MS-DOG + PE + Optional + Sectional), section offset dd 0 ; Useless "checksum" :-( dw 3 ; Critical: "subsystem": 2:GUI 3:Console (but for a DLL ... ???) dw 0 ; Useless DLL stuff (???) dd $1000, $1000, $1000, 0 ; Bloat: stack/heap reserve/commit ; dd 0, 0, 0, 0 ; Bloat: ignored for a DLL ;-) dd 0 ; Obsolete/reserved :-( dd 16 ; "directories" : 16 entries per 8 Octet's : 128 = $80 Octet's ; *** Directories ($D8,$80) *** dq 0 ; dd (llexpbeg - ccbase), xxexpsize ; Useful exports ;-) dd (llimpbeg - ccbase), xximpsize ; Useful imports ;-) dq 3 dup (0) ; 3 empty entries :-D dq 0 ; dd (llrelbeg - ccbase), 8 ; 8 Octet's of empty PE-relox if PIC dq 10 dup (0) ; 10 empty entries :-D ; *** Sectional header ($0158,$28) *** db ".myvirus" ; Section name, 8 Octet's, but nobody cares about it dd (llveryend - llverybeg) ; Exact section size dd ccstartrva ; Our "rva" of section begin dd (llpadend - llverybeg) ; Size rounded up to $0200 file alignment dd $0200, 0, 0, 0 ; File offset, no COFF-specific relox/linenumber crap dd $6000'0020 ; Attr: b30 readable, b29 & b5 executable ; dd $E000'0020 ; Attr: b31 writable, b30 readable, b29 & b5 executable assert ($=$0180) ; Otherwise "ERROR: corrupt PE header" ; *** Header padding ($0180,$80) *** db $80 dup (0) ; Proceed to $0200/$1000 org (ccbase + ccstartrva) ; ---------------------------------------------------------------------- ; ######## Our main section begins here ######## ; ---------------------------------------------------------------------- llverybeg: llentry: ; Boast call @f db 118, 1, '### Ultimate "waveOut" test / Ynsane sound PLAYER ###' db 1, '### (CL) 2014-07-01 by DOS386 | ABUSE at your own risk !! ###' db 1, 1 @@: pope esi call ssprintesiaps ; Check for available soundcards with working driver installed call sswavegetn ; Clamped to 255 push eax call @f db 45, 'Amount of soundcards ("waveOutGetNumDevs"): $' @@: pope esi call ssprintesiaps pope eax push eax call sshex8 call sseol pope eax cmp al, 0 jz short do_eof ; No soundcard cmp al, 10 ja short do_eof ; We have an interesting de-luxe problem ; Hog mov eax, $0002'0000 ; 128 KiO call sslocalhog jz short do_eof ; No memory | Forgot to bring your brain? ; Brew sound data push eax ; Address of 128 KiO block xchg edi, eax mov ecx, $0002'0000 ; 128 KiO call @f db 0,1,2,4,8,16,32,64,128,192,224,240,248,252,254,255 db 255,254,252,248,240,224,192,128,64,32,16,8,4,2,1,0 @@: pope esi brew_sin: push ecx mov eax, ecx shr ecx, 14 ; Now ZERO ... 7 only (problem $0002'0000) shl eax, 3 shr eax, cl and eax, 31 mov al, [esi+eax] stosb ; Brew single 8-bit sample pope ecx loop short brew_sin pope edx ; Address of 128 KiO block now in EDX ; Play it mov ecx, $0002'0000 ; Data size 128 KiO call ssplay ; IN: EDX and ECX ; EOF | YES we CAN suck, we forgot to unhog the 128 KiO buffer :-( do_eof: call @f db 11, 1, "EOF, dude", 1 @@: pope esi call ssprintesiaps jmp ssofckoff ;--------------- ; ---------------------------------------------------------------------- ; ######## Conversion SUB's ######## ; ---------------------------------------------------------------------- ; SUB's SSHEX8 and SSHEX4 sshex8: push eax shr al, 4 call sshex4 pope eax ; and pass sshex4: and al, $0F cmp al, 10 ; Decimal "10" !!! sbb al, $69 das ; Digital Attack System jmp short ssoonecharalnocheck ;-------------------------------- ; ---------------------------------------------------------------------- ; ######## Low level SUB's ######## ; ---------------------------------------------------------------------- ; SUB SSLCLAMPFF ; EAX clamped, ECX trashed sslclampff: movntq ecx, 0 dec cl ; MOVNTQ ECX, $FF cmp eax, ecx jb short @f mov eax, ecx ; Clamp to max 255 @@: ret ;---- ; ---------------------------------------------------------------------- ; ######## Console SUB's ######## ; ---------------------------------------------------------------------- ; SUB SSPRINTESIAPS - prints size-prefixed string AKA "PString" :-) ssprintesiaps: push ebx movntq eax, 0 lodsb xchg eax, ebx ; MOVNTQ BL, AL | Up to 255 Octet's only !!! silly_ps_loo_p: dec ebx ; NO "DEC BL" | We do need > 8 bits !!! js short pri_end ; Stealing a RET / RAT lodsb cmp al, 1 jne short @f call sseol jmp short silly_ps_loo_p ;-------------------------- @@: call ssoonecharalnocheck jmp short silly_ps_loo_p ;-------------------------- pri_end: pope ebx ret ;---- ; ---------------------------------------------------------------------- ; SUB SSEOL sseol: mov al, 13 call ssoonecharalnocheck mov al, 10 ; and pass ; ---------------------------------------------------------------------- ; ######## OS access SUB's ######## ; ---------------------------------------------------------------------- ; SUB SSOONECHARALNOCHECK ssoonecharalnocheck: push eax ; Preserve EAX, also serve as our data to write pushd $FFFF'FFF5 ; "-11" means "stdout" call dword [GetStdHandle] ; $FFFF'FFFF is invalid (if no console ?) inc eax ; CMPNTQ EAX, $FFFF'FFFF jz short console_sucks ; Console got shot by some gun geek ??? dec eax ; Is ZERO valid or not ??? movntq edx, 0 push edx ; PUSHD 0 | Reserved UINT32 for output mov ecx, esp ; Now points to the reserved UINT32 push edx ; Useless ZERO :-( push ecx ; Output: number of chars written !!! :-D inc edx ; MOVNTQ EDX, 1 | Size of text push edx ; Size of text, no "$" no ZERO at the end ;-) add ecx, 4 ; Now points to our "text", upper 24 bits are junk push ecx ; Addr of our "text" push eax ; Hendaye call dword [WriteFile] ; We can "WriteFile" or "WriteConsoleA" pope ecx ; Discard return value with written amount console_sucks: pope eax ret ;---- ; ---------------------------------------------------------------------- ; SUB SSLOCALHOG sslocalhog: push eax ; Size to hog pushd $40 ; "GMEM_ZEROINIT" type of memory call dword [LocalAlloc] cmpntq eax, 0 ; Use JZ or JNZ after RET ret ;---- ; ---------------------------------------------------------------------- ; SUB SSWAVEGETN sswavegetn: call dword [waveOutGetNumDevs] ; No param jmp sslclampff ; Clamp result to max 255 soundcards ;-) ;---------------- ; ---------------------------------------------------------------------- ; SUB SSPLAY ; IN: EDX buffer address | ECX buffer size ssplay: push ebp push edx ; Buffer address ... will be [EBP+8] push ecx ; Buffer size ... will be [EBP+4] movntq eax, 0 push eax ; Hendaye ("waveOutOpen" will generate it) mov ebp, esp ; EBP <- ESP | Hendaye lands at [EBP] push eax ; UINT16 "wavefx.cbSize" 0 UINT16 padding mov eax, $0008'0001 push eax ; UINT16 "wBitsPerSample" 8 "nBlockAlign" 1 mov eax, 22'050 push eax ; "nAvgBytesPerSec" - not always same !!! push eax ; "nSamplesPerSec" mov eax, $0001'0001 push eax ; UINT16 "nChannels" UINT16 "wFormatTag" 1 mov edx, esp ; EDX | Our "wavefx" 18 -> 20 Octet's movntq eax, 0 push eax ; Junk ZERO for the call push eax ; Junk ZERO for the call push eax ; Junk ZERO for the call push edx ; "&wavefx" dec eax ; MOVNTQ EAX, $FFFF'FFFF push eax ; "-1" AKA "WAVE_MAPPER" for the call push ebp ; Here our hendaye comes out at [EBP] call dword [waveOutOpen] ; Only 6 params add esp, 20 ; Destroy "&wavefx" junk, keep 4 values call sslclampff ; Clamp error code to max 255 push eax call @f db 23, 'Result "waveOutOpen": $' @@: pope esi call ssprintesiaps pope eax push eax call sshex8 call sseol pope eax cmpntq eax, 0 jne play_crap ; No short | Use EBP to restore the stack movntq eax, 0 ; Now fill "wavehdr" help buffer 32 Octet's push eax ; Junk ZERO push eax ; Junk ZERO push eax ; Junk ZERO push eax ; "wavehdr.dwFlags" at + 16 push eax ; Junk ZERO at + 12 push eax ; Junk ZERO at + 8 mov eax, [ebp+4] push eax ; Buffer size at + 4 mov eax, [ebp+8] push eax ; Buffer address | Now done PUSH'ing buffer mov ecx, esp ; ECX | Our "wavehdr" 32 Octet's movntq eax, 0 mov al, 32 ; Al-Quaeda push eax ; Size of the "wavehdr" help buffer push ecx ; "&wavehdr" mov eax, [ebp] push eax ; Hendaye | Didn't see it call dword [waveOutPrepareHeader] ; Only 3 params call sslclampff ; Clamp error code to max 255 push eax call @f db 32, 'Result "waveOutPrepareHeader": $' @@: pope esi call ssprintesiaps pope eax push eax call sshex8 call sseol pope eax cmpntq eax, 0 jne play_crap ; No short | Use EBP to restore the stack mov ecx, esp ; ECX | Our "wavehdr" 32 Octet's movntq eax, 0 mov al, 32 ; Al-Quaeda push eax ; Size of the "wavehdr" help buffer push ecx ; "&wavehdr" mov eax, [ebp] push eax ; Hendaye | Didn't see it call dword [waveOutWrite] ; Only 3 params call sslclampff ; Clamp error code to max 255 push eax call @f db 24, 'Result "waveOutWrite": $' @@: pope esi call ssprintesiaps pope eax push eax call sshex8 call sseol pope eax cmpntq eax, 0 jne play_crap ; No short | Use EBP to restore the stack call @f db 24, 1, 'Now playing (WOW!!!) ..' @@: pope esi call ssprintesiaps mov esi, 60 ; 60 x 100 ms -> timeout 6 s play_loo_p: pushd 100 ; 100 ms call dword [KernelSleep] mov al, 46 ; Dot "." call ssoonecharalnocheck mov eax, [esp+16] ; "wavehdr.dwFlags" shr eax, 1 ; Check b0 "WHDR_DONE" jc short play_completed dec esi jnz short play_loo_p call play_done db 20, '. Timeout!!! (F**K)', 1 ;------------------------------ play_completed: call play_done db 16, '. Completed !!!', 1 ;-------------------------- play_done: pope esi call ssprintesiaps mov ecx, esp ; ECX | Our "wavehdr" 32 Octet's movntq eax, 0 mov al, 32 ; Al-Quaeda push eax ; Size of the "wavehdr" help buffer push ecx ; "&wavehdr" mov eax, [ebp] push eax ; Hendaye | Didn't see it call dword [waveOutUnprepareHeader] ; Only 3 params play_crap: mov esp, ebp ; ESP <- EBP | Kill "wavehdr" pope edx ; Hendaye pope eax pope eax pope ebp ; Now stack neutral again and EBP preserved inc edx ; CMPNTQ EDX, $FFFF'FFFF je short @f dec edx ; CMPNTQ EDX, ZERO je short @f push edx ; Hendaye used the very last times call dword [waveOutClose] ; Only 1 param | Close it if maybe open @@: call @f db 13, 1, 'Extra delay', 1 @@: pope esi call ssprintesiaps pushd 2000 ; 2 s call dword [KernelSleep] ; Extra delay - does it still play ??? ret ;---- ; ---------------------------------------------------------------------- ; QUASI-SUB SSOFCKOFF ssofckoff: pushd 0 call dword [ExitProcess] ;------------------------- ; ---------------------------------------------------------------------- if ( ($ mod 4) > 0 ) db ( (3) - ($+3) mod 4 ) dup (0) ; Align to UINT32 AKA 4 O | This ^^^ line fails if already aligned end if ; ---------------------------------------------------------------------- ; ##### Export Import Relox blocks just follow, no private section ##### ; Aligned : Export Descriptor, Import Descriptor, Relox, ; Export Name Address Table, Export Function Address Table, ; Import Functions ; Semi-aligned : Export Zordinals ; Not aligned : Export DLL Name, Import DLL Names, Export Functions, ; Import Functions With Ordinals ; ---------------------------------------------------------------------- ; Export Descriptor - Cool 40 Octet's long Export Descriptor - 10 UINT32's ; llexpbeg: ; N/A Export Descriptor ; Import Descriptor - there must be a 20 Octet's long Import Descriptor ; (5 UINT32 values) for every DLL and an empty one as list termination ; (line with 5 (or just 4 ;-) ...) UINT32 ZERO's llimpbeg: dd 0, 0, 0, (dgjpp_kernel_name-ccbase), (dgjpp_kernel_table-ccbase) dd 0, 0, 0, (dgjpp_winmom_name-ccbase), (dgjpp_winmom_table-ccbase) dd 0, 0, 0, 0, 0 ; Relox - trivial only if code is PIC ; llrelbeg: ; N/A Relox ; N/A Export Name Address Table ; N/A Export Function Address Table ; Import Functions - there must be a table for every DLL, and every ; table consists of UINT32 values, every value pointing to the name ; of function, thus there is no sorting requirement, and every table ; must have an UINT32 ZERO as termination, but no need to terminate ; the list of DLL's here. dgjpp_kernel_table: ExitProcess: dd (_ExitProcess - ccbase) GetStdHandle: dd (_GetStdHandle - ccbase) LocalAlloc: dd (_LocalAlloc - ccbase) KernelSleep: dd (_KernelSleep - ccbase) WriteFile: dd (_WriteFile - ccbase) dd 0 dgjpp_winmom_table: waveOutGetNumDevs: dd (_waveOutGetNumDevs - ccbase) waveOutOpen: dd (_waveOutOpen - ccbase) waveOutPrepareHeader: dd (_waveOutPrepareHeader - ccbase) waveOutWrite: dd (_waveOutWrite - ccbase) waveOutUnprepareHeader: dd (_waveOutUnprepareHeader - ccbase) waveOutClose: dd (_waveOutClose - ccbase) dd 0 ; N/A Export Zordinals ; Now we get misaligned ;-) ; There is no obligation to sort import names, but there must be ; an additional UINT16 "ordinal hint" value (hint: ZERO is safe ; to use) preceding every piece. Export names MUST be sorted, as ; spec requires this, and otherwise risk that some name will not get ; found despite it is in there. Both export and import function names ; must be ZERO-terminated, as well as the DLL file names. There is no ; need to terminate the name lists because the amount is already stored ; elsewhere, but in our optimized PE we do need an extra ZERO in the ; import function lists due to the "double-use" trick. ; N/A Export DLL Name ; Import DLL Names dgjpp_kernel_name: db "KERNEL32.DLL" ; ZERO is below _ExitProcess: db 0, 0, "ExitProcess" _GetStdHandle: db 0, 0, "GetStdHandle" _LocalAlloc: db 0, 0, "LocalAlloc" _KernelSleep: db 0, 0, "Sleep" ; !!! _WriteFile: db 0, 0, "WriteFile" db 0 dgjpp_winmom_name: db "WINMM.DLL" ; ZERO is below _waveOutGetNumDevs: db 0, 0, "waveOutGetNumDevs" _waveOutOpen: db 0, 0, "waveOutOpen" _waveOutPrepareHeader: db 0, 0, "waveOutPrepareHeader" _waveOutWrite: db 0, 0, "waveOutWrite" _waveOutUnprepareHeader: db 0, 0, "waveOutUnprepareHeader" _waveOutClose: db 0, 0, "waveOutClose" db 0 ; Get the sizes ; xxexpsize = ($ - llexpbeg) xximpsize = ($ - llimpbeg) ; ---------------------------------------------------------------------- llveryend: if ( ($ mod $0200) > 0 ) db ( ($01FF) - ($+$01FF) mod $0200 ) dup (0) ; Align to UINT32 AKA 4 O | This ^^^ line fails if already aligned end if llpadend: ; ---------------------------------------------------------------------- ; END.