基于JIT定位.Net软件的代码位置
本帖最后由 吾爱破解1111 于 2021-2-18 23:26 编辑## 普通的程序定位
界面如下
代码如下图
### VS中调试
在按钮点击事件处下断调试,查看堆栈
此时可以知道Button点击消息的传递过程
堆栈如下
System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e)
同时进去查看MessageBox.Show的代码定义
可以看到来自于System.Windows.Forms的MessageBox类
此时针对该程序将有两种定位
### 通过按钮消息定位
首先找到System.Windows.Forms模块
之后一层层往下找找到Button类的OnClick方法
之后下断调试将会断下,调试时进入base.Onclick内
此时来到Control类的OnClick方法,继续步入到eventHandle函数内
此时将来到我们自己写的函数了
### 通过信息框定位
信息框可以去对应的类定位,同上,此处不赘述。
也可以使用"F12断点法"定位
弹出信息框以后,点击暂停,之后查看堆栈
第三个就是我们写的函数,直接转过去即可
## 通过JIT定位
### 前期铺垫
.Net的程序执行时每个方法需要经过JIT编译,且仅编译一次
可以理解成最初是下面的,均指向JIT内
| 函数|地址|
| --- ---| --- |
| 函数1 |JIT|
| 函数2 | JIT |
函数1执行JIT,之后经过JIT编译成本地代码执行,同时表格内地址变更如下
| 函数 |地址|
| --- ----| --- |
| 函数1 |0xXXXXXXXX|
| 函数2 | JIT |
后续函数1执行的时候就不经过JIT了,直接调用本地代码,IL代码也不需要二次JIT编译了
### 查看JIT代码
CLRJIT继承自ICorJitCompile
注意是stdcall调用约定,关注第二个参数与第四个参数第五个参数
ICorJitCompile内如下,注意四个函数都是虚函数
### 使用X64dbg调试
首先运行程序,先不要点击按钮,防止JIT编译
查看clrjit.dll,下载符号(**后面有不下载符号的方法**)
直接搜索compilemethod,下断。点击按钮将会断下
如下图,因为是stdcall,所以堆栈中第三个参数CORINFO_METHOD_INFO*
第五个参数是返回的本地代码的二级指针
查看该结构体定义,第三个参数执行了IL代码,第四个参数是IL代码的长度
此时复制对应长度的ILCode的代码使用解码工具解码如下
对比dnspy中显示的代码,确定了是按钮单击事件的
#### clrjit模块无符号定位CompileMethod地址
因有些网络环境无法下载符号或有些公司不能连接外网(😭)
clrjit模块导出函数如下,没有符号的时候如何定位CompileMethod
代码中查看getJit定义发现返回了ICorJitCompiler*
查看getJit代码
0x71373420存储ICorJitCompiler*
0x7136A3FC存储ICorJitCompiler* 的虚表指针
第一个函数就是CompileMethod的函数指针
如果该地址被HOOK了,没有符号的时候可以下dll断点,这样dll刚加载就断下了,之后在getJit下断,可以提前知道CompileMethod的地址,防止启动后没符号不好找了
## 基于JIT地址定位代码位置
场景:
有些软件很大,关注的功能不在按钮下。无法通过字符串,按钮消息,或者操作文件等"API"等直接定位代码。或者定位比较麻烦
使用条件,软件启动后该功能首次使用,被JIT后就不行了
由于被编译所以会提供IL代码,此时我们把IL代码复制出来去主模块搜索
定位到位置了
0x40206A该地址处有该段IL代码
模块基址是0x400000,RVA是0x206A
用CFF打开程序,可以发现button1_Click的RVA是2069,看起来差1字节
.Net的方法体的前面有个方法头,分为tiny和fat,tiny是代码少于64字节,没有异常处理表,堆栈深度小于8的时候是tiny,tiny是方法体前面有一字节,所以该例子中相差一个字节。一般都是fat,fat前面多12字节
### 自动查找方法位置
上述Method中只有几个,所以知道是button1_Click方法
借助于Dnlib遍历所有的方法
var filename = "Csharp.exe";
int RVA = 0x206A;
var mod = ModuleDefMD.Load(filename);
foreach (var type in mod.GetTypes())
{
foreach (var method in type.Methods)
{
if ((int)method.RVA + 1 == RVA || (int)method.RVA + 12 == RVA)
{
Console.WriteLine("Type:{0}", type.FullName);
if (type.BaseType != null)
{
Console.WriteLine("Base type :{0}", type.BaseType.FullName);
}
Console.WriteLine(method.Name);
}
}
}
可以直接得到方法的名字与类,可以快速定位到代码
### 全自动化
可以写X64dbg插件,Hook CompileMethod,之后获取到IL代码,进行特征查找,找到后通过dnlib代码输出函数地址和方法名
## 实战论坛的一个DNG软件爆破
软件地址
https://www.52pojie.cn/forum.php?mod=viewthread&tid=952601&highlight=dng
该软件有弹出信息框可以作为突破口,此处以JIT处入手进行演示
X64dbg调试
通过getjit方法查看CompileMethod函数地址
发现CompileMethod被Hook了,所以没有符号时需要用上述方法提前知道地址
此处做爆破所以需要找到本地代码的地址
留意返回地址这个参数
之后转过去打个断点
如下有个跳转,根据call的地址符号名判断是这里
修改跳转后爆破
### 关于自动化
这类软件有些只是简单的判断,跳过了跳转就爆破
1. 可以写dll导入表注入,HOOK 真实的CompileMethod,判断IL代码是这个函数的IL代码时,获取出返回的本地代码位置,之后根据偏移之类的动态修改机器码达到爆破目的
2. 将这个函数的本地代码做个特征码,每次打开软件先执行一次,提示错误后,此时代码已经Jit,根据特征码定位到位置,修改机器码破解,每次都需要点击两次才能进入,比较麻烦
本帖最后由 无闻无问 于 2021-2-19 15:34 编辑
暂停,回溯,即可解决…不过,原理性的爆破还是牛逼…
所以compilemethod为万能断点…加解密二者争夺之处… 好教程 非常感谢 学习了 为啥不直接用dnspy? 学习到了 lyghost 发表于 2021-2-19 07:40
为啥不直接用dnspy?
加壳的你用dnspy毛都看不到… 看起来好高端的亚子
好教程 非常感谢 学习了 谢谢分享,学习到了。 感谢大佬分享
页:
[1]
2