首页
社区
课程
招聘
[原创]给.net程序打内存补丁(3) (完结,继续含操作录像)
发表于: 2006-9-2 18:16 15530

[原创]给.net程序打内存补丁(3) (完结,继续含操作录像)

2006-9-2 18:16
15530

给.net程序打内存补丁(3)
by:tankaiha[NE365][FCG]
2006-9-2

    这算是本系列最后一篇了,因为偶要学习另一个新课题。本系列的文章主要来源是《Modifying IL at runtime》,代码主要来源是MSDN上的《在 .NET Framework 2.0 中,没有任何代码能够逃避 Profiling API 的分析》。虽然是很久前的文章了,但对于像我一样刚接触这方面的人应该还是很有帮助的。废话少说,进入正题。
一、修改目标
    先来看看这次修改目标text.exe的代码,有一点接近实战,要求输入用户名和密码,正确和错误均会提示。

主要代码如下,可以看到,程序读取“用户名”框中的用户输入,调用cryptcal函数进行计算,然后和用户输入的注册码比较,判断结果的正误。
        private void button1_Click(object sender, EventArgs e)
        {
            if(textBox1.Text.Length==0)
            {
                MessageBox.Show("请输入用户名!");
            }
            else if (textBox2.Text.Length == 0)
            {
                MessageBox.Show("请输入注册码!");
            }
            else if(textBox2.Text==cryptcal(textBox1.Text))
            {
                MessageBox.Show("你怎么猜到的!");
            }
            else
            {
                MessageBox.Show("猜错了!");
            }
        }

        private string cryptcal(string textToCal)
        {
            byte[] encData_byte = new byte[textToCal.Length];
            encData_byte = System.Text.Encoding.UTF8.GetBytes(textToCal);
            string encodedData = Convert.ToBase64String(encData_byte);
            return encodedData;
        }
    修改的目标,另写一个inject.dll,其中调用MessageBox,动态修改程序让它自己弹出正确的注册码。来看一下inject.dll的源程序,也就是要在test中调用injectClass.injectMsg方法,并把正确的注册码当作参数传递给该方法。
using System;
using System.Windows.Forms;

namespace injectcode
{
  public class injectClass
  {
   public static void injectMsg(string str)
   {
     MessageBox.Show("正确的注册码是:"+str,"插入代码提示");
   }
  }
}
   inject.dll的编译方法,首先生成一个强命名文件,然后编译,这样inject就有了个PublicKeyToken。没有这个标志时,载入会出错。(原参考文献中并没有,不知道为什么总出错。)全部命令行如下:
sn ?k injectkey.snk
csc /target:library /key:injectkey.snk inject.cs
用Reflector可以看到inject的信息,这在下面的代码中要用。


二、修改方法
    看一下tmp.exe的反编译代码,来到button1_Click,找一下入手点。
  .method /*06000004*/ private hidebysig
          instance void  button1_Click(object sender,
…..
IL_0049:/*7B | (04)000002*/ ldfld class System.Windows.Forms.TextBox  tmp.Form1::textBox1
IL_004e:/*6F | (0A)00002B*/callvirt instance string System.Windows.Forms.Control::get_Text()
IL_0053:/*28 | (06)000005*/call instance string tmp.Form1/*02000002*/::cryptcal(string)
IL_0058:/*28 | (0A)00002E*/call bool System.String::op_Equality(string, string)
IL_005d:/*2C | 0C */ brfalse.s  IL_006b
IL_005f:/* 72 | (70)000091*/ ldstr bytearray (60 4F 0E 60 48 4E 1C 73 30 52 84 76 01 FF )
IL_0064:/*28 | (0A)00002D*/ call System.Windows.Forms.MessageBox/*01000027*/::Show(string)
IL_0069: /* 26 | */ pop
IL_006a: /* 2A | */ ret

IL_006b: /*72 | (70)0000A1*/ ldstr      bytearray (1C 73 19 95 86 4E 01 FF )
IL_0070: /*28 | (0A)00002D*/ call       System.Windows.Forms.MessageBox/*01000027*/::Show(string)
IL_0075:  /* 26 | */ pop
IL_0076:  /* 2A | */ ret
  } // end of method Form1::button1_Click

    红色体显示的就是进行字符串比较,从堆栈上取两个参数,其中堆栈顶的就是正确的注册码。我们就把它当作参数,直接传递给injectMsg。由于要平衡堆栈,还应该在调用后执行一个pop。插入代码形如
Call injectClass.injectMsg(string);
Pop;
这两句代码共6个字节,为了保证程序正常运行,我们将从IL_005D行开始的0x0C,到IL_006a处的代码全部nop掉。记住,nop在.net中的代码是00,不是0x90,别搞错了。

三、几个概念
    介绍几个基本概念。首先,.net中的程序无论是传入参数还是返回值,都存储在堆栈中,因此修改程序时要注意堆栈的平衡。第二,.net一个进程中的token是唯一的,即使我们要调用的是另一个assembly中的方法,也只需要在本assembly中调用call token既可。Assembly、Class和Method的token结构不同,以Method为例,如下图


我们在代码中的定义就是(具体参考Tool Development Guide中的文档)
        // 为injectMsg方法建立token
        COR_SIGNATURE Sig_void_String[] = {
                0, // IMAGE_CEE_CS_CALLCONV_DEFAULT
                0x1, // argument count
                ELEMENT_TYPE_VOID, // ret = ELEMENT_TYPE_VOID
                ELEMENT_TYPE_STRING// parameter
        };

其中
• HASTHIS for IMAGE_CEE_CS_CALLCONV_HASTHIS
• EXPLICITTHIS for IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS
• DEFAULT for IMAGE_CEE_CS_CALLCONV_DEFAULT
• VARARG for IMAGE_CEE_CS_CALLCONV_VARARG
我们用的是第三项,并直接跳过了前两项。

四、Profiler的代码
    Profile的代码仍然是在JITCompilationStarted中修改。下面分步讲解。

GetFullMethodName (functionId, wszMethod, NAME_BUFFER_SIZE);
//如果不是我们要找的方法就返回
if (lstrcmpW(wszMethod,wszTarget)!=0)
{
        goto exit;
}
        //取得函数体
        hr = m_pICorProfilerInfo->GetFunctionInfo(functionId, &classId, &moduleId, &tkMethod );
        if (FAILED(hr))
        { goto exit; }
        hr = m_pICorProfilerInfo->GetILFunctionBody(moduleId, tkMethod, &pMethodHeader, &iMethodSize);
        if (FAILED(hr))
        { goto exit; }
        //取得Metadata Import
        IMetaDataImport* pMetaDataImport = NULL;
        hr = m_pICorProfilerInfo->GetModuleMetaData(moduleId, ofRead, IID_IMetaDataImport,(IUnknown** )&pMetaDataImport);

        if (FAILED(hr))
        { goto exit; }

        //开始修改Metadata
        //首先取得必需的接口
        IMetaDataEmit* pMetaDataEmit = NULL;
        IMetaDataAssemblyEmit* pMetaDataAssemblyEmit = NULL;
        mdAssemblyRef tkInsertLib;
        hr = m_pICorProfilerInfo->GetModuleMetaData(moduleId, ofRead | ofWrite, IID_IMetaDataEmit,(IUnknown** )&pMetaDataEmit);
        if (FAILED(hr)) { goto exit; }
        hr = pMetaDataEmit->QueryInterface(IID_IMetaDataAssemblyEmit,(void**)&pMetaDataAssemblyEmit);
        if (FAILED(hr)) { goto exit; }

下面的要注意,是关键代码开始。主要是分别为Assembly,Class和Method建立token,还记得原来讲的token是某个东西在.net程序中的唯一标识。
mdTypeDef tkInertClass = 0;
        mdMethodDef tkInsertMethod = 0;

        // 为inject.dll创立一个token
        ASSEMBLYMETADATA amd;
        ZeroMemory(&amd, sizeof(amd));
        amd.usMajorVersion = 0;
        amd.usMinorVersion = 0;
        amd.usBuildNumber = 0;
        amd.usRevisionNumber = 0;
        byte assemblyPublicKeyToken[]={0x1e,0xf0,0xec,0x8e,0x40,0x91,0xde,0x2c};

这里的token就是刚才用Reflector看到的值。代码继续
        hr = pMetaDataAssemblyEmit->DefineAssemblyRef(
                &assemblyPublicKeyToken, sizeof(assemblyPublicKeyToken),
                L"inject",
                &amd, NULL, 0, 0,
                &tkInsertLib);
        if (FAILED(hr)) { goto exit; }
这样就已经为inject这个Assembly建立了token,下面再为class和method建立。
        // 为injectClass建立token
        hr = pMetaDataEmit->DefineTypeRefByName(tkInsertLib,L"injectcode.injectClass", &tkInertClass);
        if (FAILED(hr)) { goto exit; }

        // 为injectMsg方法建立token
        COR_SIGNATURE Sig_void_String[] = {
                0, // IMAGE_CEE_CS_CALLCONV_DEFAULT
                0x1, // argument count
                ELEMENT_TYPE_VOID, // ret = ELEMENT_TYPE_VOID
                ELEMENT_TYPE_STRING// parameter
        };

        hr = pMetaDataEmit->DefineMemberRef(tkInertClass,
                L"injectMsg",Sig_void_String, sizeof(Sig_void_String),
                &tkInsertMethod);
        if (FAILED(hr)) { goto exit; }

下面开始修改代码了,同前两篇一样,先定义代码,再分配新的方法块并修改。简单起见,我们这里只考虑fat头的修改了,由于是在原代码上修改,因此代码块大小没变。被修改的指令位于第89个字节处。
        //这里开始修改代码
        //首先定义我们要插入的代码,注意改变默认的对齐方式
#pragma pack(1)
        struct
        {
                BYTE insertcall;
                DWORD method_token;
                BYTE insertpop;
        } InsertCode;
#pragma pack()
        InsertCode.insertcall=0x28;//call指令
        InsertCode.method_token=tkInsertMethod;//插入方法的token
        InsertCode.insertpop=0x26;//pop指令

        //下面先取得已有的il
        hr = m_pICorProfilerInfo->GetILFunctionBody(moduleId, tkMethod, &pMethodHeader, &iMethodSize);
        if (FAILED(hr))
        { goto exit; }

        IMAGE_COR_ILMETHOD* pMethod = (IMAGE_COR_ILMETHOD*)pMethodHeader;
        if(IsTinyHeader(pMethod)) //小头就不处理了
        {
                goto exit;
        }

        //分配新的空间
        IMethodMalloc* pIMethodMalloc = NULL;
        IMAGE_COR_ILMETHOD* pNewMethod = NULL;
        hr = m_pICorProfilerInfo->GetILFunctionBodyAllocator(moduleId, &pIMethodMalloc);
        if (FAILED(hr))
        { goto exit; }
        pNewMethod = (IMAGE_COR_ILMETHOD*) pIMethodMalloc->Alloc(iMethodSize);//这里的size没变
        if (pNewMethod == NULL)
        { goto exit; }
        memcpy((void*)pNewMethod, (void*)pMethod, iMethodSize);

                COR_ILMETHOD_FAT* newfatImage = (COR_ILMETHOD_FAT*)&pNewMethod->Fat;
                LogEntry("enter fat code\n");
                //Handle Fat method
                LogEntry("Flags: %X\n", newfatImage->Flags);
                LogEntry("MaxStack: %X\n", newfatImage->MaxStack);
                LogEntry("NewCodeSize: %X\n", newfatImage->CodeSize);
                LogEntry("LocalVarSigTok: %X\n", newfatImage->LocalVarSigTok);

                codeBytes = newfatImage->GetCode();
                ULONG codeSize = newfatImage->GetCodeSize();//方法大小不变

                //这里更改
                memcpy(codeBytes+88,&InsertCode,sizeof(InsertCode));//从第89个字节开始改
                ZeroMemory(codeBytes+94,13);

                for(ULONG i = 0; i < codeSize; i++)
                {
                        if(codeBytes[i] > 0x0F)
                        {
                                LogEntry("codeBytes[%u] = 0x%X;\n", i, codeBytes[i]);
                        }
                        else
                        {
                                LogEntry("codeBytes[%u] = 0x0%X;\n", i, codeBytes[i]);
                        }
                }

        hr = m_pICorProfilerInfo->SetILFunctionBody(moduleId, tkMethod, (LPCBYTE) pNewMethod);
        if (FAILED(hr))
        { goto exit; }

        pIMethodMalloc->Release();

        LogEntry("modify exit");

五、测试
    为方便新手,仍然做了一个动画。最终可以看到程序弹出的MessageBox中显示当用户名是ne365时,正确的注册码为bmUzNjU=。

    好了,本系列告一段落。只希望本系列对新手能有些帮助,让更多的人进入.net内核这个有趣的世界。

录像在二楼


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 7
支持
分享
最新回复 (14)
雪    币: 5275
活跃值: (456)
能力值: (RANK:1170 )
在线值:
发帖
回帖
粉丝
2
占位先
上传的附件:
2006-9-2 18:16
0
雪    币: 109
活跃值: (498)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
3
啊?
我算不算  沙发啊?
2006-9-2 20:33
0
雪    币: 175
活跃值: (2531)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
系列教学讲的真好。
2006-9-2 20:57
0
雪    币: 263
活跃值: (10)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
5
完全不懂,只有学习
2006-9-2 21:50
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
顶,支持,.net是很神秘的。
2006-9-4 18:53
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
.net的,大家要加油钻研,,net软件的影响越来越多了。
2007-2-28 13:06
0
雪    币: 671
活跃值: (723)
能力值: ( LV9,RANK:1060 )
在线值:
发帖
回帖
粉丝
8
对 .net 知道甚少,楼主多来两片
2007-2-28 15:04
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
真是不懂呀??? 完全不懂 要好好学习
2007-2-28 18:19
0
雪    币: 208
活跃值: (51)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
确实看不懂......
2007-3-1 08:40
0
雪    币: 716
活跃值: (162)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
11
强帖留名,
学习.......
2007-3-1 14:03
0
雪    币: 5
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
这个太方便我们新手了。谢谢
2009-5-3 18:34
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
不错的讲解,受益方浅。
2009-5-6 21:03
0
雪    币: 211
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
发帖占位!
关注中……
2009-5-6 21:18
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
你好,版主。你的《给.net程序打内存补丁(3)》的源码贴错了,请上传过吧,源码不把在CLR运行时动态载入DLL函数的!
2015-12-17 09:54
0
游客
登录 | 注册 方可回帖
返回
//