C# 逆向入门

百家 作者:平安安全应急响应中心 2022-03-15 23:48:25


作者简介 /Profile/

耿建航,平安科技银河实验室高级经理,曾在多家安全公司任职,擅长逆向。



  • 本系列文章会分成以下三个部分进行讲解:

  • 第一章 介绍C#逆向基础知识、C#编译原理、常用工具等

  • 第二章 讲解CLR机制

  • 第三章 讲解IL语言



第一章


准备工作:需要安装WindowsVisualStudio用于写C#程序对照,同时VS提供命令行环境,能够提供ildasm,ilasm等工具。单纯依靠dnspy、net reflector等自动化工具,遇到壳就得变sb了,基本功还是要学好。


本章需要了解C#的编译运行原理。在逆向过程中会遇到的很多技术点,就不一一展开,感兴趣的同学可根据关键字自行检索学习。


C#编程使用的Windows库主要就是.net平台库,常用版本2.5-4.5,对C#程序的逆向主要也是对.net平台库的熟悉与逆向,所以先让我们看一下这个平台。

 

1、.net平台简介

 

.net 是微软提供的用于Windows系统上软件开发的平台。常与VisualStudio组合进行含有gui界面app的开发。


.net 对于微软自定义的各种网络认证协议都有很好的支持,是在Windows系统上编程的省事之选。可以用于Web服务的开发,但是用户集中在北美,应用在微软自家的Azure云上。

 

支持语言:

  • C#(单机app,IIS-web服务)

  • F#(使用量少)

  • Visual Basic(多用于病毒)

 

2、C#逆向常见目的


最常见的情况有三种:

  • .net app 逆向,原理分析或修改,功能增加(目标exe,dll)

  • com组件dll,原理分析或修改,功能增加(目标dll)

  • .net visual basic 脚本分析(病毒行为检测)

 

3、C#程序特征

 

C#程序基本为Windows系统使用,方便写界面或使用Windows系统接口,所以我们见到的大多是dll或者exe,其中dll主要用在com组件服务,Web服务上,exe就是常见的Windows软件。

 

使用常用逆向工具ghidra打开我们自己编写的一个C#exe程序,得到如下信息:


 

这里红框标注的编译器cli就是C#的程序特。

 

4、C#编译流程

 

C#编译的过程中会进行两次编译:


第一次是使用的IDE,比如VScode自带的编译器进行编译,生成exe或dll文件。


源代码被编译为一种符合 CLI 规范的中间语言 (IL)。IL 代码与资源(例如位图和字符串)一起作为一种称为程序集的可执行文件存储在磁盘上,通常具有的扩展名为 .exe 或 .dll。程序集包含清单,它提供有关程序集的类型、版本、区域性和安全要求等信息。

 

第二次是JIT编译,把exe或dll文件编译成二进制代码。


执行 C# 程序时,程序集将加载到 CLR 中,这可能会根据清单中的信息执行不同的操作。然后,如果符合安全要求,CLR 就会执行实时 (JIT) 编译以将 IL 代码转换为本机机器指令。CLR 还提供与自动垃圾回收、异常处理和资源管理有关的其他服务。


由 CLR 执行的代码有时称为“托管代码”,它与编译为面向特定系统的本机机器语言的“非托管代码”相对应。下图阐释了C#源代码文件、.NET Framework 类库、程序集和 CLR 的编译时与运行时的关系。

 

 

5、C#反编译流程-手工

 

1).使用编译完成的exe、dll进行逆向推导,获取c#源代码。


2).ILDASM工具 在安装了visualStudio之后就会出现在 vs2022的程序文件夹里面:

 


直接运行ildasm 会出现对话框



3)把编译好的exe拖进去就能看到代码结构和IL源码:

 

 

6、自动化逆向工具

 

1) .Net Reflector

 

.Net Reflector具有良好的用户体验、强大的插件功能和反编译能力,使用它不仅能看到反编译后的IL源码甚至能直接反编译出C#源码,而且和编写时的代码几无二致。此外还可以直接另存为工程文件用Visual Studio打开,方便二次开发。

 

官方网址:http://www.red-gate.com/products/dotnet-development/reflector/

 

2) ILSpy/dnSpy


  • 开源免费

  • 无Visual Studio集成

  • 从ILSpy 产生的dnSpy工具更加方便逆向与编译工程

 

官方网址:https://github.com/0xd4d/dnSpy/releases

 

3)JetBrains dotPeek

 

  • 尝试到源代码服务器上抓取代码

  • 导航功能和快捷键非常便捷

  • 精确查找符号的使用,同时支持插件

 

官方网址:http://www.jetbrains.com/decompiler/

 

4)Telerik JustDecompile

 

  • 无Visual Studio集成

  • 可以为反编译程序集得到的代码创建一个项目

  • 提供了健壮的查找功能,能够支持全文查找和符号使用查找

 

官方网址:http://www.telerik.com/products/decompiler.aspx

 

推荐使用dnSpy 工具作为主逆向工具使用,其优点如下:

  • 开源免费,自己编译没有小礼物

  • 界面友好,反编译代码质量高

  • 可重新打包,方便二次开发


7、相关实验室材料


本章中写了一个简单的C# WindowsFormsApp1源码如下,大家可以看下这个程序经过ildasm后出现的代码作为对照,了解下C#编译成功的il代码到底是什么样式:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;
namespace WindowsFormsApp1{ public partial class Form1 : Form { public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e) { MessageBox.Show(this,"确认提交么","提示"); } }}


il的main代码:

.method private hidebysig static void  Main() cil managed{  .entrypoint  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )  // 代码大小       26 (0x1a)  .maxstack  8  IL_0000:  nop  IL_0001:  call       void [System.Windows.Forms]System.Windows.Forms.Application::EnableVisualStyles()  IL_0006:  nop  IL_0007:  ldc.i4.0  IL_0008:  call       void [System.Windows.Forms]System.Windows.Forms.Application::SetCompatibleTextRenderingDefault(bool)  IL_000d:  nop  IL_000e:  newobj     instance void WindowsFormsApp1.Form1::.ctor()  IL_0013:  call       void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)  IL_0018:  nop  IL_0019:  ret} // end of method Program::Main


onclick的代码:

.method private hidebysig instance void  button1_Click(object sender,                                                       class [mscorlib]System.EventArgs e) cil managed{  // 代码大小       19 (0x13)  .maxstack  8  IL_0000:  nop  IL_0001:  ldarg.0  IL_0002:  ldstr      bytearray (6E 78 A4 8B D0 63 A4 4E 48 4E )                   // nx...c.NHN  IL_0007:  ldstr      bytearray (D0 63 3A 79 )                                     // .c:y  IL_000c:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class [System.Windows.Forms]System.Windows.Forms.IWin32Window,                                                                                                                                                     string,                                                                                                                                                     string)  IL_0011:  pop  IL_0012:  ret} // end of method Form1::button1_Click


参考来源:

微软msdn资料库

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/



第二章


第二章节需要了解C#的运行原理、过程,编译1产生文件的结构与内容。

 

1、CLR简介

 

CLR是一个可以由多编程语言使用的运行时,CLR的核心功能:内存管理,程序集加载,安全性,异常处理,线程同步等等。可以被很多属于微软系列的开发语言使用。

 

事实上,在运行时,CLR根本不关心开发运用什么语言编写源代码,这意味着选择编程语言的时候,应该选择最容易表达自己意图的语言。只要编译器是面向CLR的即可。

 

2、CLR流程

 


从源码到应用程序执行CLR 流程:

1)源码编译成托管模块

2)托管模块合并成程序集

3)加载CLR

 

3、源码第一次编译-托管模块


使用C#,vb,il等语言编写代码后,可以使用对应语言的编译器将代码编译成托管模块。

 

4、托管模块结构

 

托管模块由四部分组成:PE32头、CLR头、元数据(Metadata)、IL代码

1) PE32头用来决定托管模块运行的系统环境(32位、64位)

2) CLR头用来描述CLR版本等信息

3) 元数据:元数据其实是一些用来描述:

程序集、托管模块、类型、类型的成员之间的关系的表(tables)。我们可以将这些表分为三类:定义表、引用表、Manifest,我们通常看到的托管模块不包含Manifest。

4)IL代码:中间语言代码,提供给JIT执行的脚本,类似JAVA 编译后的class

 

四个结构中,元数据、IL代码是逆向需要重点关注的点。

 

元数据结构-定义表


 

元数据结构-引用表


 

元数据结构-引用表-遍历流程

  • 通过模块的入口 找到所有的类型

  • 通过类型的入口 找到对应类型的所有的成员的入口

  • 成员方法的入口会有指向IL代码的索引,只要有模块的入口就可以拿到入口中所有元素了。


成员方法和成员属性以外其他的元素都是用元数据描述出来的,只有这两者是有IL代码的描述的。

 

元数据结构-Manifest


模块结构明确后,通过链接托管模块,可以获取到程序集,程序集有多种形态:

  • 单文件程序集:只包含一个物理文件

  • 多文件程序集:包含多个物理文件

 

使用VS创建的项目都是单文件程序集,这个程序集与一般托管模块不同,会包含Manifest类型的表。


Manifest表:描述程序集中托管模块的分布,将托管模块从逻辑上关联成为一个程序集。

 

元数据结构-Manifest表


  

5、程序集生成方式


程序集文件的Manifest 中 包含一些引用表,是用来描述程序集中所有模块引用的程序集的入口的,这样在我们加载程序集的时候,就可以根据这个表知道有哪些程序集被引用了。程序集结构大概有如下几种:

或者下面这种形式:


清单元数据放到一个空的托管模块中。最终的dll文件本身没有内容,但是其中包含了两个.netmodule托管模块。


6、CLR加载执行程序集

 

1)初次调用


这里指的是CLR在本次启动中第一次调用程序,程序集中的被调用函数没有初始化的情况。



2)再次调用

 


7、相关实验材料


本章中写了一个简单的C#ConsoleApp2源码如下,大家可以看下这个程序经过ildasm后出现的代码作为对照,了解下c#编译成功的il代码到底是什么样式:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;
namespace ConsoleApp2{ internal class Program { static void Main(string[] args) { string inputStr = Console.ReadLine(); if (inputStr == "12243") { Console.WriteLine("成功"); } else { Console.WriteLine("失败"); } string inputStr2 = Console.ReadLine(); } }}



第三章


1、IL简介


IL是.NET框架中中间语言(Intermediate Language)的缩写。


使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码。

 

运行时流程如下:


2、ildasm使用

 

可以使用vs提供的ildasm进行代码获取。



 

3、程序il执行样例演示

 

C#源码

namespace ConsoleApp2{    internal class Program    {        static void Main(string[] args)        {            string inputStr = Console.ReadLine();            if (inputStr == "12243")            {                Console.WriteLine("成功");            }            else {                Console.WriteLine("失败");            }            string inputStr2 = Console.ReadLine();        }    }}


IL源码

.method private hidebysig static void  Main(string[] args) cil managed{  .entrypoint  // 代码大小       46 (0x2e)  .maxstack  8  IL_0000:  call       string [mscorlib]System.Console::ReadLine()  IL_0005:  ldstr      "12243"  IL_000a:  call       bool [mscorlib]System.String::op_Equality(string,                                                                 string)  IL_000f:  brtrue.s   IL_001d  IL_0011:  ldstr      bytearray (10 62 9F 52 )                                     // .b.R  IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)  IL_001b:  br.s       IL_0027  IL_001d:  ldstr      bytearray (31 59 25 8D )                                     // 1Y%.  IL_0022:  call       void [mscorlib]System.Console::WriteLine(string)  IL_0027:  call       string [mscorlib]System.Console::ReadLine()  IL_002c:  pop  IL_002d:  ret} // end of method Program::Main


执行流程1:


语句执行之后 在终端输入 “12243”,评估栈的情况如下:

解释:

 

IL语言运行时在内存中会一直保存一个区域 :评估栈(evaluation stack )
作为函数调用的参数保存。


评估栈的开辟会在函数开始进行 .maxstack 8 代表了这个main函数中会最多保存8个数据。


然后是程序的第一条语句

IL_0000: call string [mscorlib]System.Console::ReadLine()


这条语句表示程序调用库mscorlib 里的 System.Console类型ReadLine函数,这个函数的返回值是 string,这条语句的地址是 IL_0000。

 

执行流程2:

 

(1) IL_0005: ldstr "12243"
把用于比较的字符串“12243”加载到评估栈上


(2) IL_000a: call bool [mscorlib]System.String::op_Equality(string, string)
调用对比函数,如果字符串相等,在栈上放入bool值 true


(3) IL_000f: brtrue.s IL_001d 根据评估栈上的值判断是否需要跳到指令流程IL_001d。


执行流程3:

(1) IL_0011: ldstr bytearray (10 62 9F 52 ) // .b.R
加载数据到评估栈上

(2) IL_0016: call void [mscorlib]System.Console::WriteLine(string)

调用系统函数,打印数据

(3) IL_001b: br.s IL_0027 直接跳转到 IL_0027

(4) IL_001d: ldstr bytearray (31 59 25 8D ) // 1Y%

(5) IL_0022: call void [mscorlib]System.Console::WriteLine(string)

(6) IL_0027: call string [mscorlib]System.Console::ReadLine()

(7) IL_002c: pop

(8) IL_002d: ret main函数返回


银河实验室

银河实验室(GalaxyLab)是平安集团信息安全部下一个相对独立的安全实验室,主要从事安全技术研究和安全测试工作。团队内现在覆盖逆向、物联网、Web、Android、iOS、云平台区块链安全等多个安全方向。
官网:http://galaxylab.pingan.com.cn/



往期回顾


技术

利用DNS 反向解析执行shellcode

技术

利用DNS A记录执行Shellcode

技术

利用图片隐写执行shellcode

技术

【干货】cobaltstrike通信协议研究


长按识别二维码关注我们

微信号:PSRC_Team



球分享

球点赞

球在看

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接