一:背景
1. 讲故事
周五下午运营反馈了一个紧急bug,说客户那边一个信息列表打不开,急需解决,附带的日志文件也发过来了,看了下日志大概是这样的:
日期:2020-11-13 12:25:45,923 线程ID:[3924] 日志级别:INFO 错误类:xxx property:[(null)] - 错误描述:应用程序出现了未捕获的异常,Message:该字符串未被识别为有效的 DateTime。;StackTrace: 在 System.DateTimeParse.Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles)在 System.Data.ConstNode..ctor(DataTable table, ValueType type, Object constant, Boolean fParseQuotes)在 System.Data.ExpressionParser.Parse()在 System.Data.DataExpression..ctor(DataTable table, String expression, Type type)在 System.Data.Select..ctor(DataTable table, String filterExpression, String sort, DataViewRowState recordStates)在 System.Data.DataTable.Select(String filterExpression)
从异常信息可以看到,大概就是 DataTable.Select 的时候抛出了异常,通过调用堆栈追查了下代码大概是这样的。
public Task<DataTable> QueryDataTable(){var dt = new DataTable();dt.Columns.Add(new DataColumn("SendTime"));dt.Rows.Add(dt.NewRow()["SendTime"] = "2020/11/14");var where = $" SendTime < #{DateTime.Now.ToString()}#";var query = dt.Select(where).CopyToDataTable();}
大坑就在这里,绝大多数时候过滤 DataTable 可以采用这样的写法 : SendTime < #2020/11/5#
,但是客户在新加坡,全英文操作系统,而且时间格式也不知道设置成啥样了,我估计时间格式包含了类似的 #,正好又遇到了前后缀 #
,拆分上就出错了,导致了经典的 该字符串未被识别为有效的 DateTime
异常被抛出。
这个 bug 改起来还是很简单的,将 # 换成 ' 即可,也就是: SendTime < '2020/11/5'
,如果一切顺利的话,文章就应该到此为止了,可恰恰上天捉弄,因为是紧急bug,研发老大 & 项目实施 都请假了,我一个人还真搞不定,也不知道给了客户哪一个 release 版,不想节外生枝,为了先解决这个问题,我想到了一个好办法,反编译修改,这是代价最小的,也能最快的搞定。
二:使用 dnspy 反编译修改代码
1. 使用 dnspy 的 编辑方法 模式
为了更好的理解通过 dnspy 修改,先来聊一聊 dnspy 最便捷的修改 dll 的方式:编辑方法
,这种方式非常方便,无需理解 IL 代码,为了演示,我举一个简单的加法运算。
static void Main(string[] args){var i = 10;var j = 20;Console.WriteLine($"{i}+{j}={i + j}");Console.ReadLine();}
接下来将 var i= 10
改成 var i=100
的步骤为:
右键
编辑方法
弹框修改
var i=10
->var i=100
点击右下角
编译
Ctrl + Shift + S 全部保存
弹出框中 选择
确定
截图大概如下:
最后 bin 目录下的 exe 就被成功修改了,双击之后就能看到你的成果啦!
????????,果然搞定了,是不是太简单了?感觉做这种反编译一点门槛都没有, 哈哈,真的没有门槛吗?
不信的话,我举一个异步方法的例子:
class Program{static void Main(string[] args){var query = QueryDataTable().Result;Console.WriteLine(JsonConvert.SerializeObject(query));Console.ReadLine();}static async Task<DataTable> QueryDataTable(){var dt = new DataTable();dt.Columns.Add(new DataColumn("SendTime"));dt.Rows.Add(dt.NewRow()["SendTime"] = "2020/11/14");var where = $" SendTime < #{DateTime.Now.ToString()}#";Console.Write(where + "\t");var task = await Task.Run(() => { return dt.Select(where).CopyToDataTable(); });return task;}}
接下来反编译一下:
我去,麻烦了,从图中可以看出两点信息:
异步方法会生成状态机,用 C# 模式看反编译的代码,那些自动生成的状态机类看都看不到,谈何修改???比如你能找到:
var where = $" SendTime < #{DateTime.Now.ToString()}#";
吗?从
<QueryDataTable>d__1
类的命名格式:<QueryDataTable>d__
来看,你点击编译
按钮肯定是过不了编译器的。
而恰恰我遇到的就是这种情况,太坑爹了。。。所以说,不碰 IL 在实际反编译中是不可能的。
2. 使用 dnspy 的 编辑IL指令 模式
dnspy 中除了编辑方法
外,还可以使用 编辑IL指令
,这功能就强大了,接下来看看怎么处理呢?操作步骤如下:
在 dnspy 中将 C# 切换到 IL 视图
找到需要修改的类的 IL 代码处,右键选择
编辑IL指令
编辑完成之后,点击
确定
大概截图如下:
然后双击执行 exe ,可以看到已经修改成功了。
不过这里有一个吐糟的地方就是,这次bug我需要修改的地方有多处,而 编辑IL指令
的窗口中并没有 搜索功能,这就尴尬了,处理起来非常麻烦!
三:使用 ildasm & ilasm 反编译修改代码
1. 介绍
这一对还是蛮有意思的,ildasm 用于查看 dll 中的 il 代码, ilasm 用于将 il 编译成 dll,所以两者配合使用挺好的。
ildasm 路径:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe
ilasm 路径:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ilasm.exe
2. 使用 ildasm 导出 il文件
打开 ildasm, 点击 '文件' -> '转储' 生成 il 文件,这里我指定名称为 my.il,接下来就可以在 my.il 中将 # 改成 ' ,如下图:
3. 使用 ilasm 编译 il 文件
使用 ilasm 将 my.il 重新生成 ConsoleApp2.exe 即可。
E:\net5\ConsoleApp2\ConsoleApp2\bin\Debug>ilasm my.il my.res /output=ConsoleApp2.exe /exeMicrosoft (R) .NET Framework IL Assembler. Version 4.8.3752.0
Copyright (c) Microsoft Corporation. All rights reserved.
Assembling 'my.il' to EXE --> 'ConsoleApp2.exe'
Source file is UTF-8Assembled method ConsoleApp2.Program?<>c__DisplayClass1_0::.ctor
Assembled method ConsoleApp2.Program?<>c__DisplayClass1_0::<QueryDataTable>b__0
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::.ctor
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::MoveNext
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::SetStateMachine
Assembled method ConsoleApp2.Program::Main
Assembled method ConsoleApp2.Program::QueryDataTable
Assembled method ConsoleApp2.Program::.ctorAssembling 'my.res' to EXE --> 'ConsoleApp2.exe'
Source file is UNICODECreating PE fileEmitting classes:
Class 1: ConsoleApp2.Program
Class 2: ConsoleApp2.Program?>c__DisplayClass1_0
Class 3: ConsoleApp2.Program?QueryDataTable>d__1Emitting fields and methods:
Global
Class 1 Methods: 3;
Class 2 Fields: 2; Methods: 2;
Class 3 Fields: 6; Methods: 3;
Resolving local member refs: 44 -> 44 defs, 0 refs, 0 unresolvedEmitting events and properties:
Global
Class 1
Class 2
Class 3
Method Implementations (total): 2
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfullyE:\net5\ConsoleApp2\ConsoleApp2\bin\Debug>ilasm my.il my.res /output=ConsoleApp2.exe /exeMicrosoft (R) .NET Framework IL Assembler. Version 4.8.3752.0
Copyright (c) Microsoft Corporation. All rights reserved.
Assembling 'my.il' to EXE --> 'ConsoleApp2.exe'
Source file is UTF-8Assembled method ConsoleApp2.Program?<>c__DisplayClass1_0::.ctor
Assembled method ConsoleApp2.Program?<>c__DisplayClass1_0::<QueryDataTable>b__0
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::.ctor
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::MoveNext
Assembled method ConsoleApp2.Program?<QueryDataTable>d__1::SetStateMachine
Assembled method ConsoleApp2.Program::Main
Assembled method ConsoleApp2.Program::QueryDataTable
Assembled method ConsoleApp2.Program::.ctorAssembling 'my.res' to EXE --> 'ConsoleApp2.exe'
Source file is UNICODECreating PE fileEmitting classes:
Class 1: ConsoleApp2.Program
Class 2: ConsoleApp2.Program?>c__DisplayClass1_0
Class 3: ConsoleApp2.Program?QueryDataTable>d__1Emitting fields and methods:
Global
Class 1 Methods: 3;
Class 2 Fields: 2; Methods: 2;
Class 3 Fields: 6; Methods: 3;
Resolving local member refs: 44 -> 44 defs, 0 refs, 0 unresolvedEmitting events and properties:
Global
Class 1
Class 2
Class 3
Method Implementations (total): 2
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully
可以看到,最后编译成 exe 成功,双击 ConsoleApp2.exe 可以看到最新的成果。
四:总结
本篇介绍了两种修改 dll 的方式,其实实操起来我感觉 ildasm & ilasm
的方式更灵活一点,如果大家有更好的反编译修改的方式,欢迎留言讨论哈!