Search: Home Bugtraq Vulnerabilities Mailing Lists Jobs Tools Vista
      Digg this story   Add to del.icio.us   (page 2 of 3 ) previous  next 
Fighting EPO Viruses
Piotr Bania 2005-06-29

Article continued from Page 1

The EPO technique used in Win32.CTX.Phage

The Phage virus doesn't modify the entry-point of an infected file, instead it scans all over the host code section and searches for API calls generated by Borland or the Microsoft linker. When such code is found, the virus checks that the destination address points somewhere inside the IMPORT section. If the call is really an import call, Phage gets a random number which tells the virus to patch the current processed instruction or to find next one. Figures 1, 2, 3, and 4 below show a few example schemas.

Figure 1.
Figure 1. Original application (ENTRYPOINT: 0x1000 – LINKER: BORLAND).

Figure 2.
Figure 2. Infected application (ENTRYPOINT: 0x1000 – LINKER: BORLAND).

Figure 3.
Figure 3. Original application (ENTRYPOINT: 0x1039 – LINKER: MICROSOFT).

Figure 4.
Figure 4. Infected application (ENTRYPOINT: 0x1039 – LINKER: MICROSOFT).

The above schemas show how the CTX.Phage EPO virus works. As mentioned before, the virus injects the call instruction by overwriting it with a randomly found call. As the application size grows (and also the injected call range from the entry-point), it becomes increasingly difficult to find the injection of the virus. On the other hand, while using this EPO technique reduces the risk of virus execution, there are also some cases when the "call-to-virus" will not be executed at all.

At this point, let's find a way to detect such injections such that it does not cause false alarms.

Finding the virus injection

How difficult is it to find CTX.Phage injections? First of all, the virus inserts a call instruction as follows:

E8 ?? ?? ?? ??CALL XXXXXXXX

Where:

  • E8 is the CALL instruction opcode
  • ?? ?? ?? ?? is the instruction operands (destination)

Before we go any further, let's summarize all the information we know about the current EPO:

  1. The injection is always done somewhere behind the entry-point.
  2. The injected call executes the virus code which is stored always in last section (this bit of information is really helpful).

As the reader probably knows, we could simply search for 0xE8 bytes (call opcodes) but there is large possibility that we might find some "suspicious" call that thands in non-call instruction, for example:

68 332211E8PUSH E8112233

As you can see, this is the push instruction, but the scanner finds the E8 byte and could consider it as a call. Unless we don't want to build up our disassembler engine (which is very long and hard work) we need to find another way. Yes, you guessed it: we need to add a condition for the E8 byte scanning routine, remembering that the call always executes code that resides in last section! Now that everything is clear, here are the conditions we require:

temp_loc = (DWORD)((DWORD)pSHC->VirtualAddress + i + (*(DWORD*)loc)) + 5;
if (temp_loc >= pSH->VirtualAddress && temp_loc <= pSH->VirtualAddress + pSH-
>Misc.VirtualSize) BAD_CALL = 1;

Where:

  • temp_loc is the calculated destination of found call (E8 opcode)
  • pSH is the header of last section
  • + 5 is the size of call instruction (opcode + destination)

A sample temp_loc calculation might look as follows:

Scanned instruction:
00401025  \. E8 58270000    CALL  

Calculation:
temp_loc = 1025 (virtual address) + 00002758 (call destination) + 5 (size of call instruction)

If the temp_loc address resides somewhere between last section's virtual address (start) and the last section's virtual address + its virtual size, the call is marked as suspicious. Here is the short snippet from the author's scanner:

(searches for call and jump instructions and checks theirs destinations):
// --- snip of scanner code ------------------------------------------------
...(snip)...
    
printf("[+] Starting from offset: 0x%.08x\n",pPE->OptionalHeader.ImageBase + 
    pSHC->VirtualAddress);

    for (i = 0; (i != pSHC->SizeOfRawData); i++) 
    {
        loc = (DWORD)((DWORD)mymap + pSHC->PointerToRawData) + i;
        
        if ((*(BYTE*)loc) == O_CALL || (*(BYTE*)loc) == O_JMP ) 
        {
            loc++;
            temp_loc = (DWORD)((DWORD)pSHC->VirtualAddress + i + (*(DWORD*)loc)) + 5;
        
            if (temp_loc >= pSH->VirtualAddress && temp_loc <= pSH->VirtualAddress + \ 
                          pSH->Misc.VirtualSize) 
            {
                printf("[!] Alert: Detected request to %s(0x%.08x) section at: 0x%.08x\n",
				    pSH->Name,pPE->OptionalHeader.ImageBase + temp_loc, \ 
                    pSHC->VirtualAddress + pPE->OptionalHeader.ImageBase + i);

                if (where_ctx == NULL) 
                {
                    where_ctx = (DWORD)(pPE->OptionalHeader.ImageBase + temp_loc);
                    caller = (DWORD)(pSHC->VirtualAddress + \ 
                                              pPE->OptionalHeader.ImageBase + i);

                    upa = (DWORD)(pSH->VirtualAddress + pPE->OptionalHeader.ImageBase);

                    sv = loc - 1;
                    
                }
                count++;
            }
            loc--;
        }

    }
    
    printf("[+] Scan finished, %d suspected instruction(s) found.\n",count);

...(snip)...
// --- snip of scanner code ------------------------------------------------

While scanning files with this code, I haven't seen any false alarms, so it is probably one of the best solutions or techniques one can use to find such virus injections.

Article continued on Page 3 



SecurityFocus accepts Infocus article submissions from members of the security community. Articles are published based on outstanding merit and level of technical detail. Full submission guidelines can be found at http://www.securityfocus.com/static/submissions.html.
    Digg this story   Add to del.icio.us   (page 2 of 3 ) previous  next 
Comments Mode:







 

Privacy Statement
Copyright 2008, SecurityFocus