批处理工具 CAPI 逆向分析之 API Call

CAPI简介

CAPI是由defanive开发的批处理工具,实现了在批处理脚本内进行内存操作以及调用动态库函数的功能

同类工具还有由Aloxaf开发的CAPIx和由happy886rr开发的ICMD

逆向分析

下面通过逆向工具IDA来对CAPI进行分析,并与由Aloxaf开源实现的CAPIx进行对比

与API Call相关的主要伪代码如下

 if ( !v28(*v27, aApi) && !v28(v27[1], aCall) && pNumArgs >= 4 )// API Call
  {
    v40 = v27[2];
    Value = 0;
    v41 = GetModuleHandleW(v40);
    if ( !v41 )
      v41 = LoadLibraryW(v27[2]);               // Module
    subcmd = (void *)GetLastError();
    if ( v41 )
    {
      v42 = ToA(v27[3]);
      func = GetProcAddress(v41, v42);          // Function
      subcmd = (void *)GetLastError();
      operator delete(v42);
      if ( func )
      {
        v43 = operator new(4 * pNumArgs - 12);
        v44 = pNumArgs;
        v45 = pNumArgs - 1;
        if ( pNumArgs - 1 > 3 )
        {
          v46 = &v27[v45];
          while ( 'x01' )                      // Arg List
          {
            v43[v44 - v45 - 1] = 0;
            v47 = *v46;
            switch ( **v46 )
            {
              case '#':
                prepush = (LPCWSTR)ToA(v47 + 1);
                v43[pNumArgs - v45 - 1] = prepush;
                stacktop = (void *)prepush;
                break;
              case '$':
                prepush = v47 + 1;
                stacktop = (void *)(v47 + 1);
                break;
              case '*':
                prepush = GetVar(v47 + 1);
                v43[pNumArgs - v45 - 1] = prepush;
                stacktop = (void *)prepush;
                break;
              case '.':
                LOBYTE(v66) = ::wtoi(v47 + 1);
                stacktop = v66;
                break;
              case ';':
                prepush = (LPCWSTR)::wtoi(v47 + 1);
                stacktop = (void *)prepush;
                break;
              default:
                break;
            }
            --v45;
            --v46;
            if ( v45 <= 3 )
              break;
            v44 = pNumArgs;
          }
        }
        Value = ((int (__cdecl *)(void *))func)(stacktop);// Call
        v48 = (void *)GetLastError();
        v27 = cmd;
        subcmd = v48;
        v49 = pNumArgs - 1;
        if ( pNumArgs - 1 > 3 )
        {
          prepush = (LPCWSTR)&cmd[v49];
          do
          {
            if ( v43[pNumArgs - v49 - 1] )
            {
              if ( **(_WORD **)prepush == 42 )
                dword_1000321C(*(_DWORD *)prepush + 2, v43[pNumArgs - v49 - 1]);
              operator delete((void *)v43[pNumArgs - v49 - 1]);
            }
            --v49;
            prepush -= 2;
          }
          while ( v49 > 3 );
        }
        operator delete(v43);
      }
    }
    itow((int)aCapiRet, Value);
    itow((int)aCapiErr, (int)subcmd);
    v28 = wcsicmp;
  }

加载模块

CAPI首先通过LoadLibraryW加载动态库,然后调用GetProcAddress获取动态库导出函数的地址

压入参数

接下来对以*$;.*开头的参数进行不同的处理,并将解析的参数值压入到栈中

注意这里IDA没有正确处理push指令,故猜测作者在这里可能内联了汇编语句,需要手动进一步分析

下面以整形传值功能$为例进行分析,切换到汇编模式

可以看到switch分支在jmp离开前,使用了push指令将参数压入栈中

对于其他分支也是用相同的方法压入参数

调用函数

压栈完成后再通过call [ebp+func]来调用指定的函数,如下图所示

内存释放

在调用完成后,进行相关内存的释放

operator delete((void *)v43[pNumArgs - v49 - 1]);

返回值

最后通过itow函数来设置返回值CapiRet和CapiErr

itow((int)aCapiRet, Value);
itow((int)aCapiErr, (int)subcmd);

对比CAPIx

与CAPI不同的是,CAPIx使用汇编语句写了一个模块来完成压栈的工作

CAPI_Ret* APIStdCall(void *hProc, int *arr, int len, short type)
{
    //int _high;
    int _low;
    double _double ;
    __asm
    {
        mov ebx, dword ptr [arr]  ;//把arr指向的地址(参数列表的尾地址)放入ebx
        mov ecx, dword ptr [len]  ;//把len的值放入ecx,作为循环控制变量
        dec ecx                   ;//递减ecx

LOOP1: 

        mov eax, dword ptr [ebx]  ;//倒序把数组arr(ebx指向的内容)的内容加载到eax
        sub ebx, 4                ;//把ebx的内容递减4(ebx指向的前移一位)
        push eax                  ;//把eax压栈
        dec ecx                   ;//递减ecx

        jns LOOP1                 ;//如果ecx不为负值,则跳转到LOOP1:

        call dword ptr [hProc]    ;//调用API
        fstp _double;
        mov _low, eax              ;//返回值存入result
        //mov _high, edx             ;

        mov ebx, dword ptr [len]  ;//把len的值放入ebx
        SHL ebx, 2                ;//左移两位,这是可变参数的大小
        //add esp, ebx              ;//恢复堆栈指针 //API use __stdcall  needn't to add esp
        xor eax, eax              ;//清空eax
    }
    
    CAPI_Ret *ret = (CAPI_Ret *)malloc(sizeof(CAPI_Ret));;
    if (type == INT_FUNCTION) {
        ret->_int[0] = _low;
    } else {
        ret->_double = _double;
    }
    return ret;
}

总结

对于不定长参数问题,CAPI和CAPIx的解决方式有所不同,但都涉及到使用汇编语句进行压栈操作

通过对CAPI和CAPIx的分析,可以加深对32位模式下传参方式的理解

原文地址:https://www.cnblogs.com/algonote/p/13282204.html