标 题: 【原创】给.net程序打内存补丁(1)
作 者: tankaiha
时 间: 2006-08-23,22:37:54
链 接: http://bbs.pediy.com/showthread.php?t=30940

给.net程序打内存补丁(1)
by:tankaiha[NE365]
2006.8.23
any problem: visit http://vxer.cn

内存补丁在破解中的作用不多说了,Win32平台下的内存补丁技术大家也都很熟悉,这里主要讲.Net平台下可执行程序的内存补丁。换一个说法,叫动态地改变正在执行的.Net可执行程序的指令(或数据)。传统的技术在.Net下不能用了吗?也不是,但是给JIT即时编译MSIL代码生成的asm代码打补丁,难度有点大。我们需要的,是直接在MSIL的基础上进行修改。此技术我也在学习中,学一点写一点,有错误请大家指正。

前置知识及参考文献(文献都可在google中搜到,MSDN中还有很多相关文档):
1、MSIL基本知识

2、Rewrite MSIL Code on the Fly with the .NET Framework Profiling API (Aleksandr Mikunov)  
3、Modifying IL at runtime (Julien Couvreur's) 
4、The .NET Profiling API and the DNProfiler Tool (Matt Pietrek) 
5、.Net FrameWork SDK中的相关文档
    如果有可能,在阅读本文之前先阅读上面的文献,这样会轻松一些。

一、.Net Profiling API
    什么是.Net Profiling API,从字面上看就是这种API可以提供.Net运行情况的概况。而实际情况是它提供的功能远远不只如此,动态修改MSIL代码就是用的这些API。如果你已经看了上面的参考文献,应该已经知道它是干吗的。
    通俗的说,Profiling API是.Net平台提供的底层接口之一(还有Debug和Metadata相关的,待续),通过该接口提供的方法,我们可以得到(及控制)以下过程的相关信息:
Application的开始/结束
Assembly的载入/卸载
Methods的开始/结束
Module的载入/卸载
Class的类的载入/卸载
线程
与COM接口的互操作
托管/非托管代码的即时编译
等等……
    基本上.Net的核心操作都可以通过Profiling API来得到信息。

二、使用Profiling API
    这里结合一个例子说,因为该接口太复杂,不可能一下学完全部的功能。我们结合这篇文章(在 .NET Framework 2.0 中,没有任何代码能够逃避 Profiling API 的分析)中的例子,在其基础上修改,加入我们需要的功能。修改的方法应用了参考文献2的方法。
    先去网上搜索上面的文章,把它的附件下来,解压。有四个目录,我们关心的就是Profiler目录中的文件。
    Profiling API是通过编译成dll文件,注册为系统的com服务而实现的。(对com我不熟悉,不多说),因此,你见到的profiler的例子通常都是三个文件:ProfilerCallback.h,ProfilerCallback.cpp,Profiler.cpp。前两个是实现Profiler的核心功能代码,最后一个只是实现了dll的基本构架。我们需要的就是在ProfilerCallback.cpp添加代码实现功能。
    首先找到CProfilerCallback::JITCompilationStarted,顾名思义,当JIT引擎开始编译MSIL代码的时候,会执行这里的代码。CProfilerCallback是什么?看一下定义:
class CProfilerCallback : public ICorProfilerCallback2
它就是作者实现的类,继承自.Net的核心接口ICorProfilerCallback2。这里的2应该代表是针对.Net 2.0的。(我试过,.net v1.1和v2.0下分别生成的Profiler,对不同版本的执行程序有时存在兼容性问题。)ICorProfilerCallback2接口很复杂,但琐碎的工作都由作者做好了,我们只是使用现成的类。

三、修改的目标
    目标程序是一个简单的程序,如图:
 
代码如下(在VS2005中编译,运行于.Net FrameWork 2.0):

代码:
namespace tmp{    public partial class Form1 : Form    {        public Form1()        {            InitializeComponent();        }        private void button1_Click(object sender, EventArgs e)        {            if(textBox1.Text!="tankaiha")            {                MessageBox.Show("Wrong!");            }            else            {                MessageBox.Show("You get it");            }        }    }}

也就是如果文本框中输入了“tankaiha”,则显示You get it。输入其它则显示错误。用ildasm可以查看这段代码的IL:
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [System.Windows.Forms]System.Windows.Forms.TextBox tmp.Form1::textBox1
  IL_0006:  callvirt   instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
  IL_000b:  ldstr      "tankaiha"
  IL_0010:  call       bool [mscorlib]System.String::op_Inequality(string,
                                                                   string)
  IL_0015:  brfalse.s  IL_0023
  IL_0017:  ldstr      "Wrong!"
  IL_001c:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
  IL_0021:  pop
  IL_0022:  ret
  IL_0023:  ldstr      "You get it"
  IL_0028:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
  IL_002d:  pop
  IL_002e:  ret

我们的目标,把IL_0015处的brfalse.s(0x2C)改为brtrue.s(0x2D)。

四、修改ProfilerCallback.cpp
    下面要跟着源码看了。
GetFullMethodName():取得即将编译的方法的名称。我们需要调用该方法来比较是不是到了tmp.Form1.button1_Click。

代码:
  GetFullMethodName (functionId, wszMethod, NAME_BUFFER_SIZE);  if (lstrcmpW(wszMethod,wszTarget)!=0)  {    goto exit;  }

假如比较结果OK,我们已经进入了button1_Click,则接着获得该方法的详细信息。

代码:
  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; }IMAGE_COR_ILMETHOD* pMethod = (IMAGE_COR_ILMETHOD*)pMethodHeader;

.Net中的方法分为fat和tiny,具体区别见参考文献。因此先判断到底是fat还是tiny,然后分别处理。我们是入门,只考虑最简单的情况。

代码:
if(IsTinyHeader(pMethod))   {    COR_ILMETHOD_TINY* tinyImage = (COR_ILMETHOD_TINY*)&pMethod->Tiny;    //Handle Tiny method    codeBytes = tinyImage->GetCode();    ULONG codeSize = tinyImage->GetCodeSize();      }  else   {    COR_ILMETHOD_FAT* fatImage = (COR_ILMETHOD_FAT*)&pMethod->Fat;    codeBytes = fatImage->GetCode();    ULONG codeSize = fatImage->GetCodeSize();  }

在取得了相应的代码块信息后,开始修改。首先分配新的代码块,将老的复制过去,再修改相应的字节,最后将新的代码块分配给Click方法。

代码:
//这里开始修改代码  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);  if (pNewMethod == NULL)  { goto exit; }  memcpy((void*)pNewMethod, (void*)pMethod, iMethodSize);  if(IsTinyHeader(pNewMethod))   {    COR_ILMETHOD_TINY* newtinyImage = (COR_ILMETHOD_TINY*)&pNewMethod->Tiny;    codeBytes = newtinyImage->GetCode();    ULONG codeSize = newtinyImage->GetCodeSize();    codeBytes[21]=0x2D;//就是这里修改  }  else   {    COR_ILMETHOD_FAT* newfatImage = (COR_ILMETHOD_FAT*)&pNewMethod->Fat;    codeBytes = newfatImage->GetCode();    ULONG codeSize = newfatImage->GetCodeSize();    codeBytes[21]=0x2D;  }   hr = m_pICorProfilerInfo->SetILFunctionBody(moduleId, tkMethod, (LPCBYTE) pNewMethod);   if (FAILED(hr))    { goto exit; }   pIMethodMalloc->Release();

这就是主要添加的代码。源文件中还有很多LogEntry()的代码,这是调试时可以显示调试信息,用DebugTrack就可以看到了。

源代码中还有几处要修改。一处是CProfilerCallback::GetEventMask()。把源代码屏蔽掉,然后m_dwEventMask=COR_PRF_MONITOR_JIT_COMPILATION,这样就可以只接收JIT编译的相关信息了。第二处是有一个sleep及int 3,这是原作者方便调试的,删除之。具体修改后的ProfilerCallback.cpp见附件。

五、实测
    编译生成了Profiler.dll后怎么用呢?主要是以下5步(注意,在同一个cmd窗口中操作):
1、regsvr32 Profiler.dll(注册服务)
2、设置环境变量,主要是两个
SET COR_PROFILER={18884ADE-B15B-4af8-BE6C-FE5117BA4B32}
SET COR_ENABLE_PROFILING=1
3、运行tmp.exe(我们的试验程序)。点击check后,你会看到结果。
4、关闭Profiler,set Cor_Enable_Profiling=0x0。
5、regsvr32 /u Profiler.dll。

我打开了所有的调试信息输出,随便输入一些字符串,运行结果如图:

    红圈处清楚地看到,2C已经被改为2D了。
    你还可以继续作试验,多次点击check,已经不再显示JIT信息了,因为.net已经将这些代码存储在缓冲区中了,而且不管你输入什么,都显示you get it。

六、结语
    这篇文章讲得太少了,很多具体的信息都在相应的参考文献中,如果光看这篇文章会摸不着头脑的。本文的主要目的就是告诉你怎么使用现成的代码,修改为自已的Profiler,更深的东东还要继续摸索。
To be continued

DownLoad

给.net程序打内存补丁(2)
tankaiha[NE365][FCG]
2006-8-25

接上文。上次讲了个最简单的动态修改代码的方法,讲得比较简约。今天介绍个复杂点的代码修改,顺便多介绍一些基本概念。

一、修改目标
    先看今天修改的目标。这一次tmp.cs的代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace tmp
{
    //新增的类
    public class userClass1
    {
        public static void showMsg()
        {
            MessageBox.Show("You get it", "^_^");
        }
    };

public partial class Form1 : Form
    {
        bool bRetVal;
        
        //定义一个变量,表示是否输入正确的字符串
        public Form1()
        {
            InitializeComponent();
        }

private void button1_Click(object sender, EventArgs e)
        {
            string cmpText = textBox1.Text;
            if(cmpText=="tankaiha")
            {
                this.bRetVal = true;
            }
            else
            {
                this.bRetVal = false;
            }            
            
        }

//永远不会执行这个
        private void neverUsed()
        {
            MessageBox.Show("You never get this");
        }
    }
}
    我们在代码新增了一个userClass1,里面有一个静态方法showMsg()。我们要在button1_Click返回前执行这个方法。先对比一下这次的修

改和上次的不同

上次       本次
代码块大小      不变       改变
代码块类型    小型(tiny)   大型(fat)
    
其实这次难度也不大,更难的在以后介绍。来看一下反汇编代码(部分):

……..
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 7B   | (04)000003       */ ldfld      class [System.Windows.Forms/*23000001*/]

System.Windows.Forms.TextBox/*01000006*/ tmp.Form1/*02000002*/::textBox1 /* 04000003 */
    IL_0006:  /* 6F   | (0A)000028       */ callvirt   instance string [System.Windows.Forms/*23000001*/]System.Windows.Forms.Control/*0100001B*/::get_Text() /* 0A000028 */
    IL_000b:  /* 0A   |                  */ stloc.0
    IL_000c:  /* 06   |                  */ ldloc.0
    IL_000d:  /* 72   | (70)00003B       */ ldstr      "tankaiha" /* 7000003B */
    IL_0012:  /* 28   | (0A)000029       */ call       bool [mscorlib/*23000002*/]System.String/*01000024*/::op_Equality(string,string) /* 0A000029 */
    IL_0017:  /* 2C   | 08               */ brfalse.s  IL_0021

IL_0019:  /* 02   |                  */ ldarg.0
    IL_001a:  /* 17   |                  */ ldc.i4.1
    IL_001b:  /* 7D   | (04)000004       */ stfld      bool tmp.Form1/*02000002*/::bRetVal /* 04000004 */
    IL_0020:  /* 2A   |                  */ ret

IL_0021:  /* 02   |                  */ ldarg.0
    IL_0022:  /* 16   |                  */ ldc.i4.0
    IL_0023:  /* 7D   | (04)000004       */ stfld      bool tmp.Form1/*02000002*/::bRetVal /* 04000004 */
    IL_0028:  /* 2A   |                  */ ret
  } // end of method Form1::button1_Click

我们要在最后IL_0028 ret之前插入一个call。

二、相关基本概念
    先来看看.net里的call。如果你用ildasm比较多,会比较熟悉。MSIL里的call是0x28,后面接了个(XX)YYYYYY,这是它的操作数,合起来XXYYYYYY就是你要call的方法的token。Token就是一个代码该方法的唯一值。不只是方法,.net中的任何东东都有个token。反汇编后发现userClass1.showMsg()的token是0x06000006。XX是分类(06),YYYYYY(000006)是序号。用工具打开tmp.exe可以看得更清楚些。
 
 
    从上图中看到,00是module,01是TypeRef,02是TypeDef,当然,还有06代表Method。而Method中,000001是Form1::Dispose(),000002是Form1::InitializeComponent,还有我们要调用的userClass1::showMsg(),排在第6位。这些信息都是存储在#~流中,还有其它的流,如#Strings、#US和#Blog等。这些流分别存储不同的信息,具体见MSDN,与本文关系不大。
    .Net中的token还有个方法,就是在一个Module中他是不变的,不管是否在一个类里,都可以直接用token值调用。因此,我们只需要在ret前插入28 06 00 00 06。

第二个要介绍的概念是tiny和fat的区别。下图是方法在内存中的布局。
 

说白了,方法体就是一块内存。很明显,tiny比fat少了SEH处理块。一般来说,有SEH肯定是fat的,二是代码超过64字节也是fat。我们这次处理的就是不含SEH的fat Method。(下次再说对SEH块的处理)
    基本概念先介绍这两个,下面看代码。

三、修改
    下面开始修改代码,仍然在JITCompilationStarted中,首先定义我们要插入的代码:
#pragma pack(1)
  struct 
  {
    BYTE insertcall; 
    DWORD method_token;
  } InsertCode;
#pragma pack()
  InsertCode.insertcall=0x28;
  InsertCode.method_token=0x06000006;
    
    这样,我们的代码就比原代码大了5字节,所以在分配空间时要加上:

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+sizeof(InsertCode)+1);//注意新空间

的size要改
  if (pNewMethod == NULL)
  { goto exit; }
  memcpy((void*)pNewMethod, (void*)pMethod, iMethodSize);
下面是对fat方法头的处理和修改
if(IsTinyHeader(pNewMethod)) 
  {
        ……
}
  else 
  {
    COR_ILMETHOD_FAT* newfatImage = (COR_ILMETHOD_FAT*)&pNewMethod->Fat;
    codeBytes = newfatImage->GetCode();
    ULONG codeSize = newfatImage->GetCodeSize()+sizeof(InsertCode);

//这里更改,注意位置的选择
    memcpy(codeBytes+codeSize-sizeof(InsertCode)-1,&InsertCode,sizeof(InsertCode));
    codeBytes[codeSize-1]=0x2A;
    newfatImage->SetCodeSize(codeSize);
  }

最后是将修改过的代码分配给新的方法,并释放空间。
   hr = m_pICorProfilerInfo->SetILFunctionBody(moduleId, tkMethod, (LPCBYTE) pNewMethod);
   if (FAILED(hr)) 
   { goto exit; }

pIMethodMalloc->Release();

四、测试
    测试方法不变,不过这次给新手做了个动画。下面是前后结果对比,注意看红体字的codeSize前后对比和从第40个字节开始的更改。

1  17:59:04:078  516: tmp  funcitonId is a75930
2  17:59:04:078  516: tmp  JITCompilationStarted: ::tmp.Form1.button1_Click
3  17:59:04:078  516: tmp  target string is: tmp.Form1.button1_Click
4  17:59:04:078  516: tmp  enter fat code
5  17:59:04:078  516: tmp  Flags: 13
6  17:59:04:093  516: tmp  MaxStack: 2
7  17:59:04:093  516: tmp  CodeSize: 29
8  17:59:04:093  516: tmp  LocalVarSigTok: 11000001
……
44  19:44:59:062  3984: tmp  codeBytes[35] = 0x7D;
45  19:44:59:062  3984: tmp  codeBytes[36] = 0x04;
46  19:44:59:062  3984: tmp  codeBytes[37] = 0x00;
47  19:44:59:062  3984: tmp  codeBytes[38] = 0x00;
48  19:44:59:062  3984: tmp  codeBytes[39] = 0x04;
49  19:44:59:078  3984: tmp  codeBytes[40] = 0x2A;

50  17:59:04:250  516: tmp  enter fat code again
51  17:59:04:265  516: tmp  Flags: 13
52  17:59:04:265  516: tmp  MaxStack: 2
53  17:59:04:281  516: tmp  NewCodeSize: 2E
54  17:59:04:281  516: tmp  LocalVarSigTok: 11000001
……
90  19:44:59:343  3984: tmp  codeBytes[35] = 0x7D;
91  19:44:59:359  3984: tmp  codeBytes[36] = 0x04;
92  19:44:59:359  3984: tmp  codeBytes[37] = 0x00;
93  19:44:59:375  3984: tmp  codeBytes[38] = 0x00;
94  19:44:59:375  3984: tmp  codeBytes[39] = 0x04;
95  19:44:59:390  3984: tmp  codeBytes[40] = 0x28;
96  19:44:59:390  3984: tmp  codeBytes[41] = 0x06;
97  19:44:59:390  3984: tmp  codeBytes[42] = 0x00;
98  19:44:59:406  3984: tmp  codeBytes[43] = 0x00;
99  19:44:59:406  3984: tmp  codeBytes[44] = 0x06;
100  19:44:59:421  3984: tmp  codeBytes[45] = 0x2A;

打完收功,下一次会介绍更复杂更有实战性的修改。附件里是测试文件和测试的动画,专为新手准备。

DownLoad

给.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内核这个有趣的世界。

DownLoad

转载于:https://www.cnblogs.com/xioxu/archive/2010/05/20/1739769.html

给.net程序打内存补丁-转相关推荐

  1. C语音和易语言实现内存补丁

    前言 当程序加了壳,我们就不能在OD里直接修改指令了,因为壳会打乱程序代码,由壳负责恢复,所以我们在OD里修改指令是没有意义的.为了解决这个问题,我们可以使用内存补丁,实际上就是在程序正常运行起来后, ...

  2. 打造自己的游戏修改器和内存补丁

    相信很多人打游戏的时候都用修改器,这里我介绍怎样用VB编写修改器. 1.其实修改器原理很简单,一般来说,在游戏运行的时候我们对游戏内存空间中必要的数据进行修改就可以了.举个例子来说,一款拳皇模拟器里游 ...

  3. java代码耗尽内存_windows server 2008 环境下,运行java程序,内存耗尽问题

    经历的几天的分析,希望把自己学到的知识总结一下. 系统版本:Windows Server 2008 R2 Standard 系统类型:64bit 内存:32GB 程序:在系统上部署了solr,然后写5 ...

  4. Identifying Patch Correctness in Test-Based Program Repair--基于测试的程序修复中补丁正确性的识别

    Identifying Patch Correctness in Test-Based Program Repair–基于测试的程序修复中补丁正确性的识别 摘要 近年来,基于测试的程序自动修复引起了广 ...

  5. [检测] CRC 校验内存补丁

    背景 通常程序中至少包括了代码段,数据段,而数据段中所存储的数据是经常会发生变动的,例如我们的全局变量,静态变量等都会默认存储在数据段,而代码段则不会发生变化,我们在检验时只需要注重.text内存段中 ...

  6. 程序在内存中运行的奥秘

    简介 当丰富多彩的应用程序在计算机上运行,为你每天的工作和生活带来便利时,你是否知道它们是如何在计算机中工作呢?本文用形象的图表与生动的解释,揭示了程序在计算机中运行的奥秘. 内存管理是操作系统的核心 ...

  7. 如何优化cocos2d程序的内存使用和程序大小:第一部分

    译者: 在我完成第一个游戏项目的时候,我深切地意识到"使用cocos2d来制作游戏的开发者们,他们大多会被cocos2d的内存问题所困扰".而我刚开始接触cocos2d的时候,社区 ...

  8. Linux下的十个好用的命令工具:查看系统版本,显示目录的大小,查看硬盘HDD/SSD,硬盘测速,ssh时自动输入密码,查看程序的内存使用情况,查看I/O的速度,查看ssh密码错误日志,查找文件

    文章目录 1.查看系统版本 2.显示目录的大小 3.查看硬盘是HDD还是SSD 4.硬盘测速 5.在ssh的时候自动输入密码 6.查看程序的内存使用情况 7.查看I/O的速度 8.查看ssh密码错误日 ...

  9. iis多进程下的全局变量_Linux下c程序的内存映像

    前言   -----今天开始分享C语言里面的存储类型.作用域.生命周期.链接属性等知识点,我们写完一个程序,不只说知其,更要知其所以然.    概念简介: - 存储类 - (1)存储类就是存储类型,也 ...

最新文章

  1. 业务逻辑组件化android,AppJoint 极简 Android 组件化方案
  2. 用Sketchup和Vray学习室内设计
  3. koa2+vue实现登陆以及是否登陆控制
  4. 深入剖析OkHttp系列(五) 来自官方的事件机制
  5. 入行php 四年多了,写点自评.
  6. JVM运行时区域详解
  7. 发短信接口获取验证码
  8. html支持的脚本语言,能不能让日志内容在支持html语言的同时支持一下脚本语言,拜托!拜托!...
  9. 产品壁垒_打破人员,流程和产品之间的壁垒
  10. oracle 11g 静默安装
  11. 1128. 等价多米诺骨牌对的数量
  12. UnityShader24:最简单的屏幕后处理例子
  13. Hello Word!
  14. MDP马尔可夫决策过程
  15. java swing浏览器_浏览器控件JxBrowser Swing开发者快速入门指南
  16. STM32F072单片机的低功耗实验/STOP模式低功耗调试
  17. Blake2b算法 php,Blake2b算法是什么?Blake2b算法币种盘点
  18. 如何成为嵌入式软件工程师_为什么要成为软件工程师
  19. TaxoNN: ensemble of neural networks on stratified microbiome data for disease prediction阅读报告
  20. 升级glibc经验谈!!!

热门文章

  1. 8086CPU的出栈(pop)和入栈(push) 都是以字为单位进行的
  2. JDK动态代理小例子
  3. Git 使用规范流程
  4. 容器 vector :为何要有reserve
  5. PostgreSQL操作问题(转载)
  6. Mybatis-Plus一个新的报错:数据库表名与SQL的关键字冲突!!!
  7. python字典数据类型笔记_Python学习笔记整理(六)Python中的字典
  8. 单域名多php,php多域名单站点路由
  9. 改变路径但是不让它跳转_Vue实战047:Breadcrumb面包屑实现导航路径
  10. ajax用https请求不了_Chrome滚动事件概率性Block Ajax请求