Windows提供了大量的API接口,这极大地提高了程序员软件开发的效率,但是通过逆向分析及API的提示下就可获取到大量信息,从而定位到目标程序的关键代码段,大大减少了逆向分析的工作难度。

所以隐藏对API 的调用可以有效地提高程序的抗分析能力,同时也增加了软件破解的难度。下面逐一介绍隐藏API调用的方法。

首先,看一下最简单的情况:

MessageBox(NULL, "Hello World!", "Test", MB_OK);
用IDA查看:
.text:004113C0                 push    0               ; uType
.text:004113C2                 push    offset Caption  ; "Test"
.text:004113C7                 push    offset Text     ; "Hello World!"
.text:004113CC                 push    0               ; hWnd
.text:004113CE                 call    ds:__imp__MessageBoxW@16 ; MessageBoxW(x,x,x,x)


这是我们最常用的形式,IDA等反汇编工具会轻易地对他们进行标识。

好了,我们来看一下稍微复杂点的情况:
HMODULE hDll = LoadLibrary(L"user32.dll"); 
PFUN pMsg = (PFUN)GetProcAddress(hDll,"MessageBoxW");
pMsg(NULL, L"Hello World!", L"Test", MB_OK);

对应的汇编代码如下:
.text:00411A30                 push    offset LibFileName ; "user32.dll"
.text:00411A35                 call    ds:__imp__LoadLibraryW@4 ; LoadLibraryW(x)
.text:00411A3B                 cmp     esi, esp
.text:00411A3D                 call    j__RTC_CheckEsp
.text:00411A42                 mov     [ebp+hModule], eax
.text:00411A45                 mov     esi, esp
.text:00411A47                 push    offset ProcName ; "MessageBoxW"
.text:00411A4C                 mov     eax, [ebp+hModule]
.text:00411A4F                 push    eax             ; hModule
.text:00411A50                 call    ds:__imp__GetProcAddress@8 ; GetProcAddress(x,x)
.text:00411A56                 cmp     esi, esp
.text:00411A58                 call    j__RTC_CheckEsp
.text:00411A5D                 mov     [ebp+var_14], eax
.text:00411A60                 mov     esi, esp
.text:00411A62                 push    0
.text:00411A64                 push    offset aTest    ; "Test"
.text:00411A69                 push    offset aHelloWorld ; "Hello World!"
.text:00411A6E                 push    0
.text:00411A70                 call    [ebp+var_14]  //调用MessageBoxW


可以看到,IDA 没有识别我们 的MessageBox(),这是值得庆贺的,但它识别除了LoadlLibrary(),GetProcAddress().

这在 某种程度上也揭示我们将要调用的API ,所以我们需要寻找更好的方法。

请看以下代码段:
  _asm
  {
        push MB_OK
      push StringAddress
      mov eax,0
      push eax
      push eax
     
      mov eax,offset Label001
      push eax
      jmp [dwFunctionAddress]    //call MessageBox()

Label002:

     
Label001:
    nop
    nop
  }

以上代码段中的
      mov eax,offset Label001
      push eax
      jmp [dwFunctionAddress] 

实现了CALL 指令的功能,且功能有所增强,如果使用CALL 指令,API函数的返回地址必定是紧接CALL 指令的下一条指令的地址。
而此代码段可返回到任意指定地址,形成更具迷惑性的代码。


  _asm
  {
    push MB_OK
    push StringAddress
    mov eax,0
    push eax
    push eax

    mov eax,offset Label001    ;;返回到Label001
    push eax
    jmp [dwFunctionAddress]    ;;call MessageBox()

Label002:
    //Insert invalid instructions here to confuse the code analysiser
    //Instructions between the Label001 and Label002 will never be executed.
    nop
    nop
    call dwFunctionAddress

Label001:
    nop
    nop
  }

由IDA 得到的汇编代码如下:

.text:00401175                 push    0
.text:00401177                 push    [ebp+var_318]
.text:0040117D                 mov     eax, 0
.text:00401182                 push    eax
.text:00401183                 push    eax
.text:00401184                 mov     eax, offset loc_401198
.text:00401189                 push    eax
.text:0040118A                 jmp     [ebp+var_314]
.text:0040118A main            endp
.text:0040118A
.text:0040118A ; ---------------------------------------------------------------------------
.text:00401190                 db 2 dup(90h)    ;;我们的NOP,
                        ;;可以插入任意数据
.text:00401192 ; ---------------------------------------------------------------------------
.text:00401192                 call    dword ptr [ebp-314h]
.text:00401198
.text:00401198 loc_401198:                             ; DATA XREF: main+174 o

在这,IDA 已经不能正确分析,说明此方法非常有效。<>
的作者对这种思想有以下描述:

It can be very difficult to identify such functions (especially if they have no prolog);

 a context search gives no result because the body of any program contains plenty

 of JMP instructions used for near jumps.

How, then, can we analyze all of them? If we don't identify the functions, two of them will drop

out of sight — the called function and the function to which control is passed just upon returning.

Unfortunately, there is no quick and easy solution to this problem;

 the only hook here is that the calling JMP practically always goes beyond the boundaries of the function

 in whose body it's located.

 We can determine the boundaries of a function by using an epilog.
翻译为中文大意如下:

识别这样的函数是非常困难的(特别是当这些函数没有 prolog的时候);因为任何程序体都包含大量 的用来实现近跳转的 JMP 指令,

所以联系上下文进行搜索不会有结果。那我们怎样识别这些函数呢?如果我们不识别出这些函数

,被调用的函数以及函数的返回地址将会逃出我们的视线。不幸的是,这个问题没有快速且简单的解决方法。这里唯一的线索是:
调用跳转总是会跳转到调用跳转指令所在的函数体的外部。我们可以通过使用epilog来检测函数的边界。


好了,下面我们回到LoadLibrary(),GetProcAddress().
如果我们使用这种方法来获取 API 函数地址,在程序中必定会出现字符串,
如user32.dll,kernel32.dll,MessageBoxA…等。这些信息可以通过分析工具轻易地得到,
这对于增强程序的抗分析能力 是无益的。
所以,加密字符串并在代码使用他们是进行动态解密是十分必要的。
我第一次看到这样的示例是在一个Downloader 中:

PS______:0040361C ; LPCSTR lpString2
PS______:0040361C lpString2       dd offset dword_403620  ; DATA XREF: CreateBOLE_INI:loc_403B5B r
PS______:0040361C                                         ; CreateBOLE_INI+83 r ...
PS______:00403620 dword_403620    dd 1D151517h, 16081708h, 5050505h, 5551514Dh, 4C0A0A1Fh
PS______:00403620                                         ; DATA XREF: PS______:lpString2 o
PS______:00403620                 dd 460B4C4Ch, 554C564Dh, 51404B0Bh, 564C490Ah, 5D510B51h
PS______:00403620                 dd 5050551h, 8 dup(5050505h), 37h dup(20202020h), 0


PS______:00403B51                 xor     ecx, ecx        ; 0
PS______:00403B53                 cmp     dword_403618, ebx ; compare 4CH with 0
PS______:00403B59                 jbe     short loc_403B7D
PS______:00403B5B
PS______:00403B5B loc_403B5B:                             ; CODE XREF: CreateBOLE_INI+98 j
PS______:00403B5B                 mov     eax, lpString2
PS______:00403B60                 xor     byte ptr [eax+ecx], 25h
PS______:00403B64                 add     eax, ecx        ; useless
PS______:00403B66                 mov     eax, lpString2
PS______:00403B6B                 add     eax, ecx
PS______:00403B6D                 cmp     byte ptr [eax], 20h
PS______:00403B70                 jnz     short loc_403B74
PS______:00403B72                 mov     [eax], bl    ;;BL==0
PS______:00403B74
PS______:00403B74 loc_403B74:                             ; CODE XREF: CreateBOLE_INI+8D j
PS______:00403B74                 inc     ecx
PS______:00403B75                 cmp     ecx, dword_403618
PS______:00403B7B                 jb      short loc_403B5B
PS______:00403B7D

此代码段的功能非常简单,循环将一长度为4ch的加密字符串解密,使用 XOR ,如果与25H 异或后得到20H,则用0替换,
0010 0101                       ;;(0010 0101)=25h
0000 0101    XOR               ;;(0000 0101)=5h
0010 0000                       ;;(0010 0000)=20h

也就是说,原字符串中数值为05H的字节均被0替换
为了生动地将这一过程展献给读者,有了以下程序:
#include
#include

void main()
{
  int i=0;
  DWORD String[13]={
                  0x1D151517, 0x16081708, 0x5050505, 0x5551514D, 0x4C0A0A1F,
                  0x460B4C4C, 0x554C564D, 0x51404B0B, 0x564C490A, 0x5D510B51,
                  0x5050551,0x5050505,0x5050505};
    char *pString=NULL;
  char DestinationString[0x4c];


//////////////////////////////////////////////////////////////////////////////////////////
  CopyMemory(DestinationString,String,0x4c);
  for(i=0;i<0x4c;i++)
  {
    *(DestinationString+i)=*(DestinationString+i)^0x25;
    if(*(DestinationString+i)==0x20)
    {
      *(DestinationString+i)=0;
    }
    if(*(DestinationString+i)=='\0')
    {
      printf("\n");
    }
    else
    {
      printf("%c",*(DestinationString+i));
    }
   
  }
  printf("\n\n");
  //////////////////////////////////////////////////////////////////////////////////////////
  CopyMemory(DestinationString,String,0x4c);
  for(i=0;i<0x4c;i++)
  {
    *(DestinationString+i)=*(DestinationString+i)^0x25;
    if(*(DestinationString+i)==0x20)
    {
      *(DestinationString+i)=0;
    }
    if(*(DestinationString+i)=='\0')
    {
      printf("\n");
      printf("%d\n",i);
    }
    else
    {
      printf("%c",*(DestinationString+i));
    }
   
  }
}

此程序有如下输出:
2008-2-3
8

9

10

11
http://iii.chsip.net/list.txt
41

42

43

44

45

46

47

48

49

50

51

其中,http://iii.chsip.net/list.txt 是Downloader要用到的列表文件,他会将解密生成的字符串写入BOLE.INI文件中。
好了,不要扯的太远,我们要调用的API 名称字符串也可以用相同的方法来操作。

事实上,在Win32病毒技术中,kernel32.dll 在进程地址空间中的基址,GetProcAddress()函数地址都是通过搜索kernel32.dll 的内存空间得到的。
所以病毒体中的API调用相对比较隐蔽,但也完全可以应用calling jmp 及字符串动态解密技术增强他们的抗分析能力。
 这种方法在壳中是非常常见的,有人甚至连LoadLibrary和GetProcAddress这两个函数完全重写,让你在用IDA分析时,看不到一个API的调用。