标题: [其他] 批处理工具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相关的主要伪代码如下- 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
作者: 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 |