安全矩阵

 找回密码
 立即注册
搜索
查看: 3309|回复: 0

C# 逆向入门

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2022-3-17 01:12:54 | 显示全部楼层 |阅读模式
原文链接:C# 逆向入门 (qq.com)



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

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

           
  •         第二章 讲解CLR机制

           
  •         第三章 讲解IL语言



第一章

准备工作:需要安装Windows的VisualStudio用于写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代码到底是什么样式:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;

  10. namespace WindowsFormsApp1
  11. {
  12.     public partial class Form1 : Form
  13.     {
  14.         public Form1()
  15.         {
  16.             InitializeComponent();
  17.         }

  18.         private void button1_Click(object sender, EventArgs e)
  19.         {
  20.             MessageBox.Show(this,"确认提交么","提示");
  21.         }
  22.     }
  23. }
复制代码

il的main代码:

  1. .method private hidebysig static void  Main() cil managed
  2. {
  3.   .entrypoint
  4.   .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
  5.   // 代码大小       26 (0x1a)
  6.   .maxstack  8
  7.   IL_0000:  nop
  8.   IL_0001:  call       void [System.Windows.Forms]System.Windows.Forms.Application::EnableVisualStyles()
  9.   IL_0006:  nop
  10.   IL_0007:  ldc.i4.0
  11.   IL_0008:  call       void [System.Windows.Forms]System.Windows.Forms.Application::SetCompatibleTextRenderingDefault(bool)
  12.   IL_000d:  nop
  13.   IL_000e:  newobj     instance void WindowsFormsApp1.Form1::.ctor()
  14.   IL_0013:  call       void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)
  15.   IL_0018:  nop
  16.   IL_0019:  ret
  17. } // end of method Program::Main
复制代码

onclick的代码:
  1. .method private hidebysig instance void  button1_Click(object sender,
  2.                                                        class [mscorlib]System.EventArgs e) cil managed
  3. {
  4.   // 代码大小       19 (0x13)
  5.   .maxstack  8
  6.   IL_0000:  nop
  7.   IL_0001:  ldarg.0
  8.   IL_0002:  ldstr      bytearray (6E 78 A4 8B D0 63 A4 4E 48 4E )                   // nx...c.NHN
  9.   IL_0007:  ldstr      bytearray (D0 63 3A 79 )                                     // .c:y
  10.   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,
  11.                                                                                                                                                      string,
  12.                                                                                                                                                      string)
  13.   IL_0011:  pop
  14.   IL_0012:  ret
  15. } // end of method Form1::button1_Click
复制代码

参考来源:微软msdn资料库
https://docs.microsoft.com/en-us ... 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代码到底是什么样式:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;

  6. namespace ConsoleApp2
  7. {
  8.     internal class Program
  9.     {
  10.         static void Main(string[] args)
  11.         {
  12.             string inputStr = Console.ReadLine();
  13.             if (inputStr == "12243")
  14.             {
  15.                 Console.WriteLine("成功");
  16.             }
  17.             else {
  18.                 Console.WriteLine("失败");
  19.             }
  20.             string inputStr2 = Console.ReadLine();
  21.         }
  22.     }
  23. }
复制代码

第三章

1、IL简介

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

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

运行时流程如下:


2、ildasm使用

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





3、程序il执行样例演示

C#源码

  1. namespace ConsoleApp2
  2. {
  3.     internal class Program
  4.     {
  5.         static void Main(string[] args)
  6.         {
  7.             string inputStr = Console.ReadLine();
  8.             if (inputStr == "12243")
  9.             {
  10.                 Console.WriteLine("成功");
  11.             }
  12.             else {
  13.                 Console.WriteLine("失败");
  14.             }
  15.             string inputStr2 = Console.ReadLine();
  16.         }
  17.     }
  18. }
复制代码

IL源码
  1. .method private hidebysig static void  Main(string[] args) cil managed
  2. {
  3.   .entrypoint
  4.   // 代码大小       46 (0x2e)
  5.   .maxstack  8
  6.   IL_0000:  call       string [mscorlib]System.Console::ReadLine()
  7.   IL_0005:  ldstr      "12243"
  8.   IL_000a:  call       bool [mscorlib]System.String::op_Equality(string,
  9.                                                                  string)
  10.   IL_000f:  brtrue.s   IL_001d
  11.   IL_0011:  ldstr      bytearray (10 62 9F 52 )                                     // .b.R
  12.   IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)
  13.   IL_001b:  br.s       IL_0027
  14.   IL_001d:  ldstr      bytearray (31 59 25 8D )                                     // 1Y%.
  15.   IL_0022:  call       void [mscorlib]System.Console::WriteLine(string)
  16.   IL_0027:  call       string [mscorlib]System.Console::ReadLine()
  17.   IL_002c:  pop
  18.   IL_002d:  ret
  19. } // 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:p_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函数返回

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-24 08:29 , Processed in 0.021212 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表