zoukankan      html  css  js  c++  java
  • 批处理工具 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位模式下传参方式的理解

  • 相关阅读:
    前端基础之CSS
    前端基础之HTML(三)
    前端基础之HTML(二)
    前端基础之HTML(一)
    面向对象总结
    内置函数总结
    函数部分总结
    文件操作总结
    基础数据类型总结
    python基础知识总结
  • 原文地址:https://www.cnblogs.com/algonote/p/13282204.html
Copyright © 2011-2022 走看看