THE ART OF COMPUTER VIRUS RESEARCH AND DEFENSE [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

THE ART OF COMPUTER VIRUS RESEARCH AND DEFENSE [Electronic resources] - نسخه متنی

Peter Szor

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید











  • 11.4. Code Emulation


    Code emulation is an extremely powerful virus detection technique. A virtual machine is implemented to simulate the CPU and memory management systems to mimic the code execution. Thus malicious code is simulated in the virtual machine of the scanner, and no actual virus code is executed by the real processor.

    Some early methods of "code-emulation" used debugger interfaces to trace the code using the processor. However, such a solution is not safe enough because the virus code can jump out of the "emulated" environment during analysis.

    Among the first antivirus programs was Skulason's F-PROT, which used software-based emulation for heuristic analysis. The third generation of F-PROT integrated the emulator and the scanning components to apply emulation to all computer virusesparticularly the difficult polymorphic viruses.

    As an example, the registers and flags of a 16-bit Intel CPU can be defined with the following structures in C language:


    Typedef struct
    { byte ah,al,bh,bl,ch,cl,dh,dl;
    word si,di,sp,bp,cs,ds,es,ss,ip;
    } Emulator_Registers_t;
    typedef struct {
    byte c,z,p,s,o,d,i,t,a;
    } Emulator_Flags_t;

    The point of the code emulation is to mimic the instruction set of the CPU using virtual registers and flags. It is also important to define memory access functions to fetch 8-bit, 16-bit, and 32-bit data (and so on). Furthermore, the functionality of the operating system must be emulated to create a virtualized system that supports system APIs, file and memory management, and so on.

    To mimic the execution of programs, the data from executable files is first fetched into memory buffers. Then a giant switch() statement of the emulator can analyze each instruction opcode, one by one. The current instruction, pointed by the virtual register IP (instruction pointer) is decoded, and the related virtual function for each instruction is executed. This changes the content of the virtual machine's memory, as well as the virtual registers and flags. The instruction pointer register IP is incremented after each executed instruction, and the iterations are counted.

    Consider the code snippet of a 16-bit CPU emulator shown in Listing 11.8. First, the code selects the next instruction for execution with an internal read_mem() function that will access the already fetched buffers according to CS (code segment). Next, a while loop executes instructions according to preset conditions, such as the number of iterations. The execution also stops if the emulator experiences a fatal emulation error.

    Listing 11.8. A Sample Snippet of a 16-Bit Intel CPU Emulator



    opcode=read_mem(absadr(CPU->reg.ip,SEGM_CS,0));
    while( condition(opcode) && (!CpuError) )
    {
    switch(code)
    {
    // All opcodes listed here one by one
    // Only two examples are shown here
    :
    :
    case 0x90: /* NOP instruction */
    my_ip++;
    break;
    :
    :
    case 0xCD: /* INT instruction execute an interrupt */
    emulator_init_interrupt_stack();
    emu_int(code,read_mem(absadr(CPU->reg.ip+1,SEGM_CS,0)));
    my_ip++=2;
    break;
    :
    :
    }
    CPU->reg.ip+=my_ip;
    CPU->iterations++++;
    opcode=read_mem(absadr(CPU->reg.ip,SEGM_CS,0));
    }
    /* Emulate Interrupts */
    void emu_int(byte opcode, byte opcode2)
    {
    // DOS Version check?
    if( opcode==0xcd && opcode2==0x21 && CPU->reg.ah==0x30)
    {
    CPU->reg.al=3; CPU->reg.ah=38; // DOS 3.38, why not?
    return;
    }
    :
    :
    }

    This example illustrates how the CPU emulator encounters a NOP and INT instruction during emulation. When a NOP (no operation) is executed, the IP register needs to be incremented. When an INT instruction is executed, the code in Listing 11.8 demonstrates what the emulator does when the DOS get version call is executed.

    First, the state of the CPU stack is set, given that the INT instruction sets a return address on the top of the stack. The emu_int() function should normally handle most interrupt calls, but in this example only the DOS version check is handled and the false 3.38 DOS version is returned to the caller of the interrupt. As a result, a program executed in the virtual machine will receive the false version numbers when running in the virtual system. This illustrates that everything is under the emulator's control. Depending how well the emulator can mimic the real system functionality, the code has more or fewer chances to detect the fact that it is running in a virtual environment. Of course, the preceding code is over-simplified, but it demonstrates the typical structure of a generic CPU emulator. The 32-bit emulators differ only in complexity.

    Polymorphic virus detection can be done by examining the content of the virtual machine's memory after a predefined number of maximum iterations, or whenever other stop conditions are met. Because polymorphic viruses decrypt themselves, the virus will readily present itself in the virtual machine's memory if emulated long enough. The question arises of how to decide when to stop the emulator. The following common methods are used:

    • Tracking of active instructions:
      Active instructions are those instructions that change an 8-bit, 16-bit, or 32-bit value in the virtual machine's memory. The instruction becomes active when two memory modifications occur next to each other in memory. This is a typical decryption pattern in memory. Although not all decryptors can be profiled with this technique, it covers the most common cases. The emulator can execute instructions up to a predefined number of iterations, such as a quarter of a million, half a million, or even a million iterations, but only if the code continuously generates active instructions. Short decryptors typically generate a lot of active instructions, whereas longer decryptors with a lot of inserted junk will use active instructions less frequently. For example, this technique was used in the IBM Antivirus.

    • Tracking of decryptor using profiles:
      This method takes advantage of the exact profile of each polymorphic decryptor. Most polymorphic engines use only a few instructions in their decryptor. Thus the first time an instruction is executed that is not in the profile, the first decrypted instruction is executed. This moment can be used to stop the emulator and attempt virus detection.

    • Stopping with break points:
      Several predefined break points can be set for the emulator as conditions. For example, an instruction or a hash of a few instructions can be used from each polymorphic virus to stop the execution of the emulator whenever the decrypted virus body is likely to be executed. Other conditions can include the first interrupt or API call because polymorphic viruses typically do not use them in their decryptors (but some antiemulation viruses do use them).


    First, the location of emulations need to be identified. For example, each known entry point of a program can be emulated. Moreover, each possible decryptor location is identified (this method can assume false decryptor detection because the detection of the decryptor itself will not produce a virus warning). Then the decryptor is executed for an efficient number of iterations, and the virus code is identified in the virtual machine of the scanner by checking for search strings (or by using other, previously discussed methods) in the "dirty" pages of the virtual machine's memory.

    Note

    A memory page becomes dirty when it is modified. Each modified page has a dirty flag, which is set the first time a change occurs in the page.

    Such detection can be much faster than X-RAY-based scanning. However, it depends on the actual iterations of the decryptor loop. With short decryptors, the method will be fast enough to be useful. In the case of longer decryption loops (which have a lot of garbage instructions), even partial decryption of the virus code might not be fast enough because the number of necessary iterations can be extremely high, so the decryption of the virus would take more than several minutes in the virtual machine. This problem is also the greatest challenge for emulator-based virus heuristics.

    11.4.1. Encrypted and Polymorphic Virus Detection Using Emulation


    Consider the example in Listing 11.9, which shows an emulation of an encrypted virus { W95,W97M} /Fabi.9608 in a PE file. This virus places itself at the entry point of infected files. Emulation of the entry-point code will result in a quick decryption of virus code in the virtual machine's memory. Although this virus is not polymorphic, the basic principle of polymorphic virus detection is the same.

    Fabi initializes the ESI pointer to the start of the encrypted virus body. The decryption loop decrypts each 32-bit word of the body with a 32-bit key. This key is set randomly but is also shifted in each decryption loop. At iteration 12, the virus generates an active instruction because two 32-bit words in memory are changed next to each other as the XOR instruction decrypts them. This can signal an emulator to continue emulation of the decryption loop, which will stop after about 38,000 iterations.

    Note

    Detection of the virus is possible before the constant virus body is completely decrypted. However, exact identification of this virus using emulation would require this many iterations to be executed in the virtual machine.

    Listing 11.9. The Emulation of the W95/Fabi Virus



    Iteration Number Flags
    Registers
    Opcode Instruction
    Iteration: 1, IP=00405200
    AX>00000000 BX>00000000 CX>00000000 DX>00000000
    SI>00000000 DI>00000000 BP>0070FF87 SP>0070FE38
    FC cld
    Iteration: 2, IP=00405201
    AX>00000000 BX>00000000 CX>00000000 DX>00000000
    SI>00000000 DI>00000000 BP>0070FF87 SP>0070FE38
    E800000000 call 00405206h
    Iteration: 3, IP=00405206
    AX>00000000 BX>00000000 CX>00000000 DX>00000000
    SI>00000000 DI>00000000 BP>0070FF87 SP>0070FE34
    5D pop ebp
    Iteration: 4, IP=00405207
    AX>0000000 BX>00000000 CX>000000000 DX>00000000
    SI>0000000 DI>00000000 BP>00405206 SP>0070FE38
    81ED06104000 sub ebp,00401006h
    Iteration: 5, IP=0040520D
    AX>00000000 BX>00000000 CX>00000000 DX>00000000
    SI>00000000 DI>00000000 BP>00004200 SP>0070FE38
    8DB52A104000 lea esi,[ebp+0040102A]
    Iteration: 6, IP=00405213
    AX>00000000 BX>00000000 CX>00000000 DX>00000000
    SI>0040522A DI>00000000 BP>00004200 SP>0070FE38
    B95E250000 mov ecx,255Eh
    Iteration: 7, IP=00405218
    AX>00000000 BX>00000000 CX>0000255E DX>00000000
    SI>0040522A DI>00000000 BP>00004200 SP>0070FE38
    BB72FD597A mov ebx,7A59FD72h
    Iteration: 8, IP=0040521D
    AX>00000000 BX>7A59FD72 CX>0000255E DX>00000000
    SI>0040522A DI>00000000 BP>00004200 SP>0070FE38
    311E xor [esi],ebx
    Iteration: 9, IP=0040521F
    AX>00000000 BX>7A59FD72 CX>0000255E DX>00000000
    SI>0040522A DI>00000000 BP>00004200 SP>0070FE38
    AD lodsd
    AX>03247C80 BX>7A59FD72 CX>0000255E DX>00000000
    SI>0040522E DI>00000000 BP>00004200 SP>0070FE38
    81C3C3D5B57B add ebx,7BB5D5C3h
    Iteration: 11, IP=00405226
    AX>03247C80 BX>F60FD335 CX>0000255E DX>00000000
    SI>0040522E DI>00000000 BP>00004200 SP>0070FE38 O S
    E2F5 loop 0040521Dh
    Iteration: 12, IP=0040521D
    AX>03247C80 BX>F60FD335 CX>0000255D DX>00000000
    SI>0040522E DI>00000000 BP>00004200 SP>0070FE38 O S
    311E xor [esi],ebx

    When this particular instance of the virus is loaded for emulation, the virus is still encrypted in the memory of the virtual machine, as shown in Listing 11.10.

    Note

    The decryptor is in front of the encrypted virus body, and the decryption begins at virtual address 40522A (in this example), pointed by ESI.

    Listing 11.10. The Front of W95/Fabi.9608 in Encrypted Form



    Address Encrypted virus body snippet Text (encrypted)
    405200 FCE8000000005D81-ED061040008DB52A ................
    405210 104000B95E250000-BB72FD597A311EAD ................
    405220 81C3C3D5B57BE2F5-EB00F2817D798ABB ................
    405230 0FE6B8A8B14A7856-18C45E02540A2F4B ................
    405240 EAEE4520F009A9BD-33FCFAC46D2B24E0 ................
    405250 9EB9B0771A89BC0C-5EAEFB2294232CF8 ................
    405260 FBAD71CB6510F18E-0B6EB1AE08482F2D ................

    After a few thousand iterations, the virus is decrypted. The scanner can use any of the previously mentioned techniques to detect or identify the virus easily in the virtual machine's dirty pages. String scans are typically done periodically after a number of predefined iterations during emulation, so the complete decryption of the virus does not need to happen. Then the emulation can be further extended for exact identification.

    The emulation of Fabi reveals the name of its creator, Vecna, with a Portuguese message that translates to "My poetry" (see Listing 11.11).

    Listing 11.11. The Front of W95/Fabi.9608 in Decrypted Form



    Address Decrypted virus body snippet Text
    405200 FCE8000000005D81-ED061040008DB52A ................
    405210 104000B95E250000-BB72FD597A311EAD ................
    405220 81C3C3D5B57BE2F5-EB00807C2403BF68 ................
    405230 00104000743BC328-6329205665636E61 .......(c) Vecna
    405240 0D0A41206D696E68-6120706F65736961 ..A minha poesia
    405250 206AA0206EC66F20-74656D2074657520 j. .n.o tem teu
    405260 6E6F6D652E2E2E0D-0AD413F7BF7D4A02 nome............

    This technique is the most powerful detection method for polymorphic viruses. It can be applied to metamorphic viruses that use encryption layers. An antivirus product that does not support code emulation is ineffective against most polymorphic viruses because response time is seriously affected by complex polymorphic viruses. This is a danger to those scanners that do not implement emulation because polymorphic computer worms leave little room for prolonged response. Indeed, there are wildly adopted scanners that do not support emulation technology that will not be able to respond to complex threats.

    The key idea in using the emulator relies on the trial-and-error detection approach. A computer file might be emulated from more than a hundred possible entry points, one after another, attempting to find viruses. Such detections are not cost effective in the long run, and they can only survive if the average computer's CPU performance can keep up with the increasing performance hunger of the code emulation. As scanners evolve, the effectiveness of the scanner on old platforms decreases. For example, emulation of Pentium CPUs on 8086 or even 286 processors is too slow to be effective nowadays. In addition, handheld devices have very limited CPU power, and thus complex mobile threats will be more challenging to detect and repair on the native platform. (For example, imagine a polymorphic virus running on a Pocket PC using an ARM processor. Such a virus would be slow, but the antivirus would be even slower.)

    11.4.2. Dynamic Decryptor Detection


    A relatively new scanning technique uses a combination of emulation and decryptor detection. For viruses with longer loops, such as W32/Dengue, virus emulation cannot perform very fast. The possible entry point of the virus decryptor can be identified in a virus-specific manner. During emulation, for example, specific algorithmic detection can check which areas of the virtual machine's memory have been changed. If there is a suspicious change, additional scanning code can check which instructions were executed during a limited number of iterations. Furthermore, the executed instructions can be profiled and the essential set of decryptor instructions can be identified. For example, a virus that always uses XOR decryption will execute a lot of XOR instructions in its decryptor. At the same time, certain instructions will never be executed, which can be used as exclusions. The combination of inclusions and exclusions will yield an excellent profile of polymorphic decryptors. This can be used by itself to detect the virus with enough filtering in place to keep false positives to the minimum, making the decryptor detection more fully proved and quick enough.16 have a chance to detect such viruses because the decryptor of the virus is often represented at or near the entry point. Therefore, the decryptor will often be reached via emulation.17.

    A relatively new dynamic technique to detect polymorphic viruses is done by using code optimalization techniques18 in an attempt to reduce the polymorphic decryptor to a certain core set of instructions, removing the junk. This technique works very effectively with simple polymorphic viruses. Suppose that emulation is used to detect a virus that has many jump instructions inserted into its polymorphic decryptor. Suppose further that the jump instructions are the only "garbage" instructions in the polymorphic decryptor. The code flow of the decryptor can be optimized in each loop of the decryptor as the emulator executes it. Each jump that points to another jump is identified as nonessential in the code, so the jumps can be removed one by one. Chapter 7) cannot be optimized effectively. Other problems appear when multiple encryptions are used on top of each other, dependent on one another.


    • / 191