Board logo

标题: [其他] 批处理工具CAPI逆向分析之API Call [打印本页]

作者: Byaidu    时间: 2020-7-11 14:43     标题: 批处理工具CAPI逆向分析之API Call

本帖最后由 Byaidu 于 2020-7-11 15:05 编辑

CAPI简介

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

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

逆向分析

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



与API Call相关的主要伪代码如下
  1. if ( !v28(*v27, aApi) && !v28(v27[1], aCall) && pNumArgs >= 4 )// API Call
  2.   {
  3.     v40 = v27[2];
  4.     Value = 0;
  5.     v41 = GetModuleHandleW(v40);
  6.     if ( !v41 )
  7.       v41 = LoadLibraryW(v27[2]);               // Module
  8.     subcmd = (void *)GetLastError();
  9.     if ( v41 )
  10.     {
  11.       v42 = ToA(v27[3]);
  12.       func = GetProcAddress(v41, v42);          // Function
  13.       subcmd = (void *)GetLastError();
  14.       operator delete(v42);
  15.       if ( func )
  16.       {
  17.         v43 = operator new(4 * pNumArgs - 12);
  18.         v44 = pNumArgs;
  19.         v45 = pNumArgs - 1;
  20.         if ( pNumArgs - 1 > 3 )
  21.         {
  22.           v46 = &v27[v45];
  23.           while ( '\x01' )                      // Arg List
  24.           {
  25.             v43[v44 - v45 - 1] = 0;
  26.             v47 = *v46;
  27.             switch ( **v46 )
  28.             {
  29.               case '#':
  30.                 prepush = (LPCWSTR)ToA(v47 + 1);
  31.                 v43[pNumArgs - v45 - 1] = prepush;
  32.                 stacktop = (void *)prepush;
  33.                 break;
  34.               case '$':
  35.                 prepush = v47 + 1;
  36.                 stacktop = (void *)(v47 + 1);
  37.                 break;
  38.               case '*':
  39.                 prepush = GetVar(v47 + 1);
  40.                 v43[pNumArgs - v45 - 1] = prepush;
  41.                 stacktop = (void *)prepush;
  42.                 break;
  43.               case '.':
  44.                 LOBYTE(v66) = ::wtoi(v47 + 1);
  45.                 stacktop = v66;
  46.                 break;
  47.               case ';':
  48.                 prepush = (LPCWSTR)::wtoi(v47 + 1);
  49.                 stacktop = (void *)prepush;
  50.                 break;
  51.               default:
  52.                 break;
  53.             }
  54.             --v45;
  55.             --v46;
  56.             if ( v45 <= 3 )
  57.               break;
  58.             v44 = pNumArgs;
  59.           }
  60.         }
  61.         Value = ((int (__cdecl *)(void *))func)(stacktop);// Call
  62.         v48 = (void *)GetLastError();
  63.         v27 = cmd;
  64.         subcmd = v48;
  65.         v49 = pNumArgs - 1;
  66.         if ( pNumArgs - 1 > 3 )
  67.         {
  68.           prepush = (LPCWSTR)&cmd[v49];
  69.           do
  70.           {
  71.             if ( v43[pNumArgs - v49 - 1] )
  72.             {
  73.               if ( **(_WORD **)prepush == 42 )
  74.                 dword_1000321C(*(_DWORD *)prepush + 2, v43[pNumArgs - v49 - 1]);
  75.               operator delete((void *)v43[pNumArgs - v49 - 1]);
  76.             }
  77.             --v49;
  78.             prepush -= 2;
  79.           }
  80.           while ( v49 > 3 );
  81.         }
  82.         operator delete(v43);
  83.       }
  84.     }
  85.     itow((int)aCapiRet, Value);
  86.     itow((int)aCapiErr, (int)subcmd);
  87.     v28 = wcsicmp;
  88.   }
复制代码
加载模块

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

压入参数

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

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

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



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

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

调用函数

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



内存释放

在调用完成后,进行相关内存的释放
  1. operator delete((void *)v43[pNumArgs - v49 - 1]);
复制代码
返回值

最后通过itow函数来设置返回值CapiRet和CapiErr
  1. itow((int)aCapiRet, Value);
  2. itow((int)aCapiErr, (int)subcmd);
复制代码
对比CAPIx

与CAPI不同的是,CAPIx使用汇编语句写了一个模块来完成压栈的工作
  1. CAPI_Ret* APIStdCall(void *hProc, int *arr, int len, short type)
  2. {
  3.     //int _high;
  4.     int _low;
  5.     double _double ;
  6.     __asm
  7.     {
  8.         mov ebx, dword ptr [arr]  ;//把arr指向的地址(参数列表的尾地址)放入ebx
  9.         mov ecx, dword ptr [len]  ;//把len的值放入ecx,作为循环控制变量
  10.         dec ecx                   ;//递减ecx
  11. LOOP1:
  12.         mov eax, dword ptr [ebx]  ;//倒序把数组arr(ebx指向的内容)的内容加载到eax
  13.         sub ebx, 4                ;//把ebx的内容递减4(ebx指向的前移一位)
  14.         push eax                  ;//把eax压栈
  15.         dec ecx                   ;//递减ecx
  16.         jns LOOP1                 ;//如果ecx不为负值,则跳转到LOOP1:
  17.         call dword ptr [hProc]    ;//调用API
  18.         fstp _double;
  19.         mov _low, eax              ;//返回值存入result
  20.         //mov _high, edx             ;
  21.         mov ebx, dword ptr [len]  ;//把len的值放入ebx
  22.         SHL ebx, 2                ;//左移两位,这是可变参数的大小
  23.         //add esp, ebx              ;//恢复堆栈指针 //API use __stdcall  needn't to add esp
  24.         xor eax, eax              ;//清空eax
  25.     }
  26.    
  27.     CAPI_Ret *ret = (CAPI_Ret *)malloc(sizeof(CAPI_Ret));;
  28.     if (type == INT_FUNCTION) {
  29.         ret->_int[0] = _low;
  30.     } else {
  31.         ret->_double = _double;
  32.     }
  33.     return ret;
  34. }
复制代码
总结

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

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

原文链接:https://www.cnblogs.com/algonote/p/13282204.html
作者: slimay    时间: 2021-9-12 22:42

本帖最后由 slimay 于 2021-9-12 22:43 编辑

capi都没法在64位下运行, vs的64位汇编比较特殊, 指令有所不兼容, 国外论坛的思路是包装成com可以64位调用32位, 另外那个capi.exe注入也得改成load 64位的dll
或者简单粗暴, 直接把32位的cmd弄过去.
作者: Byaidu    时间: 2021-9-14 03:52

回复 2# slimay


64位好像只有传参方式不一样,这个实现起来应该不难

用COM实现的话确实优雅,是个比较好的思路

顺便想请教一下国外有哪些论坛在搞这些呀,我看好像基本都在写powershell了




欢迎光临 批处理之家 (http://www.bathome.net/) Powered by Discuz! 7.2