逆向 COM dll

 

关键词

ASP Server.CreateObject, COM reverse engineering

本文附件

PwdInfo.dll

COMView

火绒剑 / Procmon

IDA Pro

x64dbg

File for Windows

New Bing (有Windows API问题直接问就行)

开始之前

首先注册一下dll:regsvr32 PwdInfo.dll

准备好Visual Studio 2022,选中“使用C++的桌面开发”,并选择MSVC v143 - VS2022 C++ x64/x86 生成工具Windows 11 SDK适用于最新 v143 生成工具的 C++ ATL (x86 和 x64)

开启Developer PowerShell for VS 2022,注意用Hostx86\x86\cl.exe或者Hostx86\x64\cl.exe,这样产生的32位exe才可以调用32位的dll。具体可以用file查看:

/mnt/e$ file PwdInfo.dll test.exe
PwdInfo.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
test.exe:    PE32 executable (console) Intel 80386, for MS Windows

正文

COMView

File -> Load Type Library, 选择PwdInfo.dll,可以找到包含的函数、各个函数参数及返回值。

调用

注意先安装相关库。具体名称搜索ATL把看着像的装了就行。

如果无法加载COM dll,可以用火绒剑或Procmon看一下注册表访问,看看是不是没有注册或者参数打错了。

// cl /Zi /Od /DEBUG:FULL -IE:\VisualStudio\2022\BuildTools\VC\Tools\MSVC\14.16.27023\atlmfc\lib\x86 -IE:\VisualStudio\2022\BuildTools\VC\Tools\MSVC\14.38.33130\atlmfc\include /EHsc .\test.cpp /link /libpath:"E:\VisualStudio\2022\BuildTools\VC\Tools\MSVC\14.16.27023\atlmfc\lib\x86" atls.lib /DEBUG:FULL ; .\test.exe
#pragma comment(lib, "Ole32.lib")
#include <Windows.h>
#include <objbase.h>
#include <combaseapi.h>
#include <comdef.h>
#include <iostream>
#include <atlbase.h>

using std::cin;
using std::cout;
using std::endl;

// failfast macro with a function name if hr failed
#define FAIL_FAST(name)                                             \
    if (FAILED(hr))                                                 \
    {                                                               \
        cout << "FAIL_FAST: " << #name << " failed " << hr << endl; \
        return 0;                                                   \
    }                                                               \
    else                                                            \
    {                                                               \
        cout << #name << " success" << endl;                        \
    }

#define FAIL_FAST_IF_NULL(name)                                     \
    if (name == NULL)                                               \
    {                                                               \
        cout << "FAIL_FAST: " << #name << " is NULL" << endl;       \
        return 0;                                                   \
    }                                                               \
    else                                                            \
    {                                                               \
        cout << #name << " success" << endl;                        \
    }

#define WAIT_FOR_ENTER                        \
    {                                         \
        cout << "press enter to continue..."; \
        std::getchar();                       \
    }

struct CoInitHelper
{
    CoInitHelper() { CoInitialize(NULL); }
    ~CoInitHelper() { CoUninitialize(); }
};

int main()
{
    CoInitHelper coInitGuard;
    {
        HRESULT hr;
        CLSID clsid;
        // hr = CLSIDFromString(L"{410C6850-4C6F-11D4-8654-0000E8E6E355}", &clsid);
        // FAIL_FAST(CLSIDFromString);
        hr = CLSIDFromProgID(L"PwdInfo.Password", &clsid);
        FAIL_FAST(CLSIDFromProgID);

        IDispatch *pOR;
        hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (void **)&pOR);
        FAIL_FAST(CoCreateInstance);

        DISPID PropertyID[1] = {0};
        BSTR PropName[1];

        PropName[0] = SysAllocString(L"UnLockPwd");
        hr = pOR->GetIDsOfNames(IID_NULL, PropName, 1, LOCALE_USER_DEFAULT, PropertyID);
        FAIL_FAST(GetIDsOfNames);

        // unlockpwd
        BSTR account = SysAllocString(L"1234567");
        BSTR pwd = SysAllocString(L"123456");

        DISPPARAMS dp = {NULL, NULL, 0, 0};
        VARIANT vResult;
        EXCEPINFO ei;
        UINT uArgErr;

        // Allocate memory for the arguments array
        dp.rgvarg = new VARIANT[2];
        if (dp.rgvarg == NULL)
            return E_OUTOFMEMORY;

        // Set the number of arguments
        dp.cArgs = 2;

        // Initialize the arguments as empty variants
        VariantInit(&dp.rgvarg[0]);
        VariantInit(&dp.rgvarg[1]);

        // Set the arguments as BSTRs
        dp.rgvarg[0].vt = VT_BSTR;
        dp.rgvarg[0].bstrVal = pwd;
        dp.rgvarg[1].vt = VT_BSTR;
        dp.rgvarg[1].bstrVal = account;

        // Initialize the result as an empty variant
        VariantInit(&vResult);

        // Call the function using Invoke
        WAIT_FOR_ENTER
        hr = pOR->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dp, &vResult, &ei, &uArgErr);
        FAIL_FAST(Invoke);
        WAIT_FOR_ENTER

        // Free the memory for the arguments array
        delete[] dp.rgvarg;

        // Convert the BSTR to a char*
        char *strResult;

        // hr = VariantChangeType(&vResult, &vResult, 0, VT_BSTR);
        // FAIL_FAST(VariantChangeType);

        strResult = _com_util::ConvertBSTRToString(vResult.bstrVal);
        FAIL_FAST_IF_NULL(strResult)

        // Use the char* result
        cout << "result: " << strResult << endl;  // 812121

        // Free the memory for the BSTR and the char*
        delete[] strResult;
        VariantClear(&vResult);
    }
}

Python调用

出现了,胶水语言!

# pip install pywin32
import win32com.client
# com_error: (-2147221164, '没有注册类', None, None)  说明你的 Python 位数不对,本文提供的是 32 位的,则 Python 也应该是 32 位
# com_error: (-2147221005, '无效的类字符串', None, None)  则是 32 位也没有注册,需要用 regsvr32 注册一下
PwdInfo = win32com.client.DispatchEx("PwdInfo.Password")
# win32com.client.gencache.EnsureDispatch
# 上面这种写法会报:TypeError: This COM object can not automate the makepy process - please run makepy manually for this object
# 换成下面这个就好了
assert PwdInfo.UnLockPwd("1234567", "123456") == PwdInfo.UnLockPwd(1234567, "123456") == "812121"
# 入参类型貌似会自动转换,没有找到相关文档

动态调试

直接在Invoke前打断点,然后把传入参数所在内存打读写断点,一直跟踪就行。

参考资料

windows - What are general guide lines for reversing COM objects - Reverse Engineering Stack Exchange

Category:COM Tools - Collaborative RCE Tool Library