简介 C# 是微软公司发布的一种由 C 和 C++ 衍生出来的面向对象的编程语言,运行于 .NET Framework和 .NET Core(完全开源)之上的高级程序设计语言。
C#程序特征 使用 .NET 框架提供的编译器开源直接将源程序编译为 .exe 或 .dll 文件,但此时编译出来的程序代码并部署 CPU 可以直接执行的机器代码,而是一种中级语言 IL 的代码。
我们把这样的 exe 拖进 IDA 里,能看到一个没见过的加载项pe64.dll
:
而且反汇编窗口里只能看到 IL 的代码(类似 pyc 字节码)
C#基础 C# 语法和 Java 很像,仅仅搞逆向的话如果有 C++ 以及 Java 基础就没必要太过深入的 C# 的语法。
就算一个方法你不懂它的功能,但是仅仅通过名字也能大概猜出来。
不过简单的基础我们还是要了解下的。
开发 C# 程序一般我都用 MSVC 编译器进行开发。
C# 程序文件是以cs
为后缀名结尾的。
创建一个名称为 RE 的 C# 项目
基本的 C# 代码结构:
1 2 3 4 5 6 7 8 9 10 11 12 using System; namespace RE { class Program { static void Main (string [] args ) { Console.WriteLine("Hello, World!" ); } } }
C#中常用的类和库:
Write
方法:输出文本。
WriteLine
方法:输出文本并换行ReadLine
方法:读取一行文本。
1 Console.WriteLine("hello" );
String
类:字符串类型类,提供了许多方法来处理字符串。
连接字符串:
1 string demo="hello" +" " +"world"
字符串格式化:
1 2 string formatted = string .Format("Hello, {0}!" , "Alice" ); Console.WriteLine(formatted);
字符串替换
1 2 3 string str = "Hello, World!" ;string replaced = str.Replace("World" , "C#" ); Console.WriteLine(replaced);
子串提取(类似于切片):
1 2 string sub = "Hello, World!" .Substring(7 , 5 ); Console.WriteLine(sub);
判断字符串包含:
1 bool contains = "Hello, World!" .Contains("World" );
字符串拆分:
1 2 string sentence = "apple,banana,cherry" ;string [] fruits = sentence.Split(',' );
C#逆向工具 dnspy 工具是专门用于逆向 C# 程序的工具。
我们首先根据程序位数选择 dnSpy32 或 dnSpy64 打开程序。
展开命名空间查看代码
dnSpy同样也支持交叉引用
C#打包
在项目命名空间下可以看到我们编写的代码。
Form1图形开发
调试:调试程序的话程序必须是 debug 编译的才行
打补丁:支持源码级别编辑类属性,类方法,并保存成新文件。
分析:交叉引用
脱壳与去混淆 去混淆工具:de4dot (.NET程序脱壳,反混淆工具)
程序并没有发布版本,所以要我们自己进行编译。
通过 Visual Studio 进行编译。
下载的包中有两个解决方案,一个是基于 .NET Core 的,一个是基于 .NET Frameword 的。
如果使用 .net core版本,需要安装 netcoreapp3.1 和 netcoreapp2.1。
如果使用 .net framework 版本,需要安装 net 35和 net45。
通过 Visual Studio 打开解决方案,然后右键解决方案选择重新生成解决方案即可进行编译。
de4dot 支持对于以下工具混淆的代码的去混淆:
Agile.NET (aka CliSecure)
Babel.NET
CodeFort
CodeVeil
CodeWall
CryptoObfuscator
DeepSea Obfuscator
Dotfuscator
.NET Reactor
Eazfuscator.NET
Goliath.NET
ILProtector
MaxtoCode
MPRESS
Rummage
Skater.NET
SmartAssembly
Spices.Net
Xenocode
常见混淆的特征:
这里对符号进行了随机字符串混淆,那些以\u
开头的都是 Unicode 编码。
直接去混淆
1 ./de4dot/bin/Debug/de4dot.exe myApp.dll
指定混淆类型去混淆
1 ./de4dot/bin/Debug/de4dot.exe myApp.dll -P sa
Unity应用 Unity3D 是一款强大的游戏引擎,它的核心优势之一就是能够让开发者一次开发,然后将游戏部署到多个平台。Mono 是 Unity3D 实现这一跨平台能力的关键,它允许 Unity 在多种平台上运行,特别是在支持 .NET 的平台上,Mono 提供了兼容的运行时和类库。
然而,Mono 并不是完全安全的,它会将源代码编译成 IL(中间语言)代码,这使得逆向工程变得相对容易。为了增强安全性,Unity 从 2014 年开始引入 IL2CPP(Intermediate Language to C++),这一技术将中间语言(IL)代码转化为 C++ 代码,然后再编译成机器码,从而提高了代码的安全性和性能。
Mono、IL2CPP的逆向
CE Mono功能
uniref框架
例题 [CFI-CTF 2018]IntroToPE Exeinfo 查看程序发现为 C# 程序。
dnSpy打开反编译
进入程序命名空间,打开ValidatePasswd
类查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using System;using System.Text;namespace IntroToPe { internal class ValidatePasswd { public ValidatePasswd (string passwd ) { this .passwd = passwd; } public bool verifyPasswd () { bool result = false ; bool flag = Convert.ToBase64String(Encoding.UTF8.GetBytes(this .passwd)) == "Q0ZJey5OZXRDI18xc19AdzNzMG0zfQ==" ; if (flag) { result = true ; } return result; } private string passwd; } }
分析发现程序将输入通过Convert.ToBase64String
方法编码为 base64,然后与一串密文进行比较。
很明显那串密文就是 base64 编码。
如果输入内容与密文比较相等,则result
为true
即返回相等。
直接那密文 base64 解密拿到flag
[ISC+2016]Classical+CrackMe dnSpy 打开发现程序被加了混淆。
我们通过上面讲过的 de4dot 工具来去混淆
去混淆后的主要逻辑代码
直接解密 base64 密文拿到 flag
1 PCTF{Ea5y_Do_Net_Cr4ck3r}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 using System;using System.ComponentModel;using System.Drawing;using System.Text;using System.Windows.Forms;public class Form1 : Form {public Form1 () {this .InitializeComponent(); }private void Form1_Load (object sender, EventArgs e ) { }private void button1_Click (object sender, EventArgs e ) {string s = this .textBox1.Text.ToString();byte [] bytes = Encoding.Default.GetBytes(s);string a = Convert.ToBase64String(bytes);string b = "UENURntFYTV5X0RvX05ldF9DcjRjazNyfQ==" ;if (a == b) { MessageBox.Show("注册成功!" , "提示" , MessageBoxButtons.OK); }else { MessageBox.Show("注册失败!" , "提示" , MessageBoxButtons.OK, MessageBoxIcon.Hand); } }private void textBox1_TextChanged (object sender, EventArgs e ) { }private void button2_Click (object sender, EventArgs e ) {base .Close(); }protected virtual void Dispose (bool disposing ) {if (disposing && this .icontainer_0 != null ) {this .icontainer_0.Dispose(); }base .Dispose(disposing); }
2024Moectf dotNet 下载附件拿到一个dll
文件,根据题目名称判定为 C# 逆向。
我们用 dnSpy 打开可执行文件。
在项目的命名空间中我们可以看到Main
函数,进入查看。
程序的主要逻辑就在Main
函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private static void <Main>$(string [] args) { byte [] array = new byte [] { 173 , 146 , 161 , 174 , 132 , 179 , 187 , 234 , 231 , 244 , 177 , 161 , 65 , 13 , 18 , 12 , 166 , 247 , 229 , 207 , 125 , 109 , 67 , 180 , 230 , 156 , 125 , 127 , 182 , 236 , 105 , 21 , 215 , 148 , 92 , 18 , 199 , 137 , 124 , 38 , 228 , 55 , 62 , 164 }; Console.WriteLine("Input Your Flag:" ); string text = Console.ReadLine(); if (text.Length != array.Length) { Console.WriteLine("Flag is WRONG!!!" ); return ; } int num = 1 ; for (int i = 0 ; i < array.Length; i++) { if ((byte )((int )((byte )text[i] + 114 ^ 114 ) ^ i * i) != array[i]) { num &= 0 ; } } if (num == 1 ) { Console.WriteLine("Correct Flag!!!" ); return ; } Console.WriteLine("Flag is WRONG!!!" ); }
这里对输入的内容进行了操作,如果操作后的内容与密文相等则输入的内容就是正确的。
1 2 3 4 5 6 7 8 for (int i = 0 ; i < array.Length; i++) { if ((byte )((int )((byte )text[i] + 114 ^ 114 ) ^ i * i) != array[i]) { num &= 0 ; } }
根据加密逻辑编写解密脚本
将加密逻辑逆过来就是:先与i*i
异或,然后再与114异或,最后减去114
1 2 3 4 5 6 7 8 9 10 11 12 13 enc = [ 173 , 146 , 161 , 174 , 132 , 179 , 187 , 234 , 231 , 244 , 177 , 161 , 65 , 13 , 18 , 12 , 166 , 247 , 229 , 207 , 125 , 109 , 67 , 180 , 230 , 156 , 125 , 127 , 182 , 236 , 105 , 21 , 215 , 148 , 92 , 18 , 199 , 137 , 124 , 38 , 228 , 55 , 62 , 164 ] flag = "" for i in range (len (enc)): flag += chr (((enc[i] ^ (i * i)) ^ 114 ) - 114 & 0xff )print (flag)
[2024极客大挑战] 玩就行了 [FlareOn5]Ultimate Minesweeper
题目描述:You hacked your way into the Minesweeper Championship, good job. Now its time to compete. Here is the Ultimate Minesweeper binary. Beat it, win the championship, and we’ll move you on to greater challenges. Hint:本题解出相应字符串后请用flag{}包裹,形如:flag{123456@flare-on.com }
题目描述中文翻译:你闯入了扫雷锦标赛,干得好。现在是比赛的时候了。这是终极扫雷二进制文件。打败它,赢得冠军,我们将带你去迎接更大的挑战。
运行程序发现为扫雷游戏。
Exeinfo查看发现程序为 C# 编写
接下来我们通过 dnSpy 打开反编译
查看发现程序命名空间中很多类名我们通过名字就能猜出其作用。
比如:FailurePopup
(失败后弹出)、SucessPopup
(成功后弹出)、MainForm
(主窗口)、MineField
(雷区)、MineFieldControl
(雷场控制)、Program
(程序)。
我们先进入Program
类查看,发现其中创建了MainForm
的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System; using System.Windows.Forms; namespace UltimateMinesweeper { internal static class Program { [STAThread ] private static void Main () { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false ); Application.Run(new MainForm()); } } }
我们点击MainForm
进入查看
分析其逻辑,加上注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public MainForm () { this .InitializeComponent(); this .MineField = new MineField(MainForm.VALLOC_NODE_LIMIT); this .AllocateMemory(this .MineField); this .mineFieldControl.DataSource = this .MineField; this .mineFieldControl.SquareRevealed += this .SquareRevealedCallback; this .mineFieldControl.FirstClick += this .FirstClickCallback; this .stopwatch = new Stopwatch(); this .FlagsRemaining = this .MineField.TotalMines; this .mineFieldControl.MineFlagged += this .MineFlaggedCallback; this .RevealedCells = new List<uint >(); }
进入AllocateMemory
函数分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void AllocateMemory (MineField mf ) { for (uint num = 0U ; num < MainForm.VALLOC_NODE_LIMIT; num += 1U ) { for (uint num2 = 0U ; num2 < MainForm.VALLOC_NODE_LIMIT; num2 += 1U ) { bool flag = true ; uint r = num + 1U ; uint c = num2 + 1U ; if (this .VALLOC_TYPES.Contains(this .DeriveVallocType(r, c))) { flag = false ; } mf.GarbageCollect[(int )num2, (int )num] = flag; } } }
进入SquareRevealedCallback
函数分析
第一部分的if
程序是触发地雷的显示。
第二部分的if
程序中存在GetKey
函数,猜测是成功后的显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void SquareRevealedCallback (uint column, uint row ) { if (this .MineField.BombRevealed) { this .stopwatch.Stop(); Application.DoEvents(); Thread.Sleep(1000 ); new FailurePopup().ShowDialog(); Application.Exit(); } this .RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column); if (this .MineField.TotalUnrevealedEmptySquares == 0 ) { this .stopwatch.Stop(); Application.DoEvents(); Thread.Sleep(1000 ); new SuccessPopup(this .GetKey(this .RevealedCells)).ShowDialog(); Application.Exit(); } }
GetKey
函数
利用revealedCells
前三个元素作为种子,之后将array
和array2
的值进行异或运算,最后将结果转化为 ASCII 字符的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private string GetKey (List<uint > revealedCells ) { revealedCells.Sort(); Random random = new Random(Convert.ToInt32(revealedCells[0 ] << 20 | revealedCells[1 ] << 10 | revealedCells[2 ])); byte [] array = new byte [32 ]; byte [] array2 = new byte [] { 245 , 75 , 65 , 142 , 68 , 71 , 100 , 185 , 74 , 127 , 62 , 130 , 231 , 129 , 254 , 243 , 28 , 58 , 103 , 179 , 60 , 91 , 195 , 215 , 102 , 145 , 154 , 27 , 57 , 231 , 241 , 86 }; random.NextBytes(array); uint num = 0U ; while ((ulong )num < (ulong )((long )array2.Length)) { byte [] array3 = array2; uint num2 = num; array3[(int )num2] = (array3[(int )num2] ^ array[(int )num]); num += 1U ; } return Encoding.ASCII.GetString(array2); }
解题思路:
[2019红帽杯]Snake