.NET 7 的 AOT 到底能不能扛反編譯?

来源:https://www.cnblogs.com/huangxincheng/archive/2022/11/23/16917197.html
-Advertisement-
Play Games

一:背景 1.講故事 在B站,公眾號上發了一篇 AOT 的文章後,沒想到反響還是挺大的,都稱贊這個東西能抗反編譯,可以讓破解難度極大提高,可能有很多朋友對逆向不瞭解,以為用 ILSpy,Reflector,DnSpy 這些工具打不開就覺得很安全,其實不然,在 OllyDbg,IDA,WinDBG 這 ...


一:背景

1.講故事

在B站,公眾號上發了一篇 AOT 的文章後,沒想到反響還是挺大的,都稱贊這個東西能抗反編譯,可以讓破解難度極大提高,可能有很多朋友對逆向不瞭解,以為用 ILSpy,Reflector,DnSpy 這些工具打不開就覺得很安全,其實不然,在 OllyDbg,IDA,WinDBG 這些逆向工具面前一樣是裸奔。

既然大家都很感興趣,那這篇就和大家聊一聊。

二:幾個例子

1. 動態修改程式數據

修改程式數據在逆向中再正常不過了,由於目前的 AOT 只能發佈成 x64 ,這裡就用 WinDbg 做下演示,首先看下例子。


    internal class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("hello world!");
                Thread.Sleep(1000);
            }
        }
    }

程式在不斷的輸出,接下來我們將 hello world 中的 world 給去掉,操作手法非常簡單,先記憶體搜索找到 hello world,然後修改 length=5 即可。


0:005> lm
start             end                 module name
00007ff7`95b70000 00007ff7`95e5d000   ConsoleApp1 C (private pdb symbols)  

0:005> s-u 00007ff7`95b70000 L?0x00007ff7`95e5d000 hello
00007ff7`95e1c41c  0068 0065 006c 006c 006f 0020 0077 006f  h.e.l.l.o. .w.o.

0:000> dp 00007ff795e1c41c-0x4 L1
00007ff7`95e1c418  00650068`0000000c

0:000> eq 00007ff7`95e1c418 00650068`00000005
0:000> g

2. 獲取程式托管堆

AOT 再怎麼牛,它終還是個托管程式,既然是托管程式自然就有托管堆,托管堆中就有所有的托管數據,玩過 SOS.dll 朋友應該知道,用 !eeheap -gc 就能把托管堆給顯示出來,比如下麵這樣。


0:022> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x000002414D891030
generation 1 starts at 0x000002414D891018
generation 2 starts at 0x000002414D891000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
000002414D890000  000002414D891000  000002414D8D1FE8  000002414D8D2000  0x40fe8(266216)  0x41000(266240)
Large object heap starts at 0x000002415D891000
         segment             begin         allocated         committed    allocated size    committed size
000002415D890000  000002415D891000  000002415D891018  000002415D892000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x0000024165891000
0000024165890000  0000024165891000  0000024165899C10  00000241658A2000  0x8c10(35856)  0x11000(69632)
Total Allocated Size:              Size: 0x49c10 (302096) bytes.
Total Committed Size:              Size: 0x42000 (270336) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0x49c10 (302096) bytes.
GC Committed Heap Size:    Size: 0x42000 (270336) bytes.

雖然目前的 AOT 不支持 SOS 擴展,無法顯示出托管堆,但一點關係都沒有,SOS 是通過 DataAccess 去挖的,大不來我手工挖一下就好了哈,接下來就是怎麼挖的問題了,熟悉 CLR 的朋友應該知道所謂的托管堆在內部用的是 generation_table[] 一維數據來維護的,以 的方式來劃分,代的落地是用 heap_segment 來表示的, 參考代碼如下:


generation gc_heap::generation_table [total_generation_count];

enum gc_generation_num
{
    // small object heap includes generations [0-2], which are "generations" in the general sense.
    soh_gen0 = 0,
    soh_gen1 = 1,
    soh_gen2 = 2,
    max_generation = soh_gen2,

    // large object heap, technically not a generation, but it is convenient to represent it as such
    loh_generation = 3,

    // pinned heap, a separate generation for the same reasons as loh
    poh_generation = 4,

    uoh_start_generation = loh_generation,

    // number of ephemeral generations
    ephemeral_generation_count = max_generation,

    // number of all generations
    total_generation_count = poh_generation + 1
};

接下來用 x 命令看下數組內容,代碼如下:


0:000> x ConsoleApp1!WKS::gc_heap::generation_table
00007ff7`95e25010 ConsoleApp1!WKS::gc_heap::generation_table = class WKS::generation [5]

0:000> dx -r1 (*((ConsoleApp1!WKS::generation (*)[5])0x7ff795e25010))
(*((ConsoleApp1!WKS::generation (*)[5])0x7ff795e25010))                 [Type: WKS::generation [5]]
    [0]              [Type: WKS::generation]
    ...
    [4]              [Type: WKS::generation]

0:000> dx -r1 (*((ConsoleApp1!WKS::generation *)0x7ff795e25010))
(*((ConsoleApp1!WKS::generation *)0x7ff795e25010))                 [Type: WKS::generation]
    [+0x038] start_segment    : 0x25100000000 [Type: WKS::heap_segment *]
    [+0x040] allocation_start : 0x25100001030 : 0x38 [Type: unsigned char *]
    [+0x048] allocation_segment : 0x25100000000 [Type: WKS::heap_segment *]
    [+0x0d0] allocation_size  : 0x0 [Type: unsigned __int64]
    [+0x100] gen_num          : 0 [Type: int]
    ...

0:000> dx -r1 ((ConsoleApp1!WKS::heap_segment *)0x25100000000)
((ConsoleApp1!WKS::heap_segment *)0x25100000000)                 : 0x25100000000 [Type: WKS::heap_segment *]
    [+0x000] allocated        : 0x25100001048 : 0x90 [Type: unsigned char *]
    [+0x008] committed        : 0x25100012000 : Unable to read memory at Address 0x25100012000 [Type: unsigned char *]
    [+0x010] reserved         : 0x25110000000 : 0x18 [Type: unsigned char *]
    [+0x018] used             : 0x25100009fe0 : 0x0 [Type: unsigned char *]
    [+0x020] mem              : 0x25100001000 : 0x38 [Type: unsigned char *]
    [+0x028] flags            : 0x0 [Type: unsigned __int64]
    [+0x030] next             : 0x0 [Type: WKS::heap_segment *]
    ...

上面的這些欄位就描述出了 !eeheap -gc 的結果,接下來想挖什麼,提取什麼我就不過多介紹了。

3. 提取托管線程列表

提取 托管線程 列表也是非常重要的, 它能指示出很多信息,一般用 !t 命令就能顯示,輸出如下:


0:022> !t
ThreadCount:      17
UnstartedThread:  0
BackgroundThread: 6
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     4128 000002414BDB8C70    2a020 Preemptive  000002414D8C6108:000002414D8C8000 000002414bdaf8f0 -00001 MTA 
   6    2     4458 000002414BDE5EB0    2b220 Preemptive  0000000000000000:0000000000000000 000002414bdaf8f0 -00001 MTA (Finalizer) 
   7    4     23e8 000002416DDB15C0  102b220 Preemptive  000002414D8C9250:000002414D8CA000 000002414bdaf8f0 -00001 MTA (Threadpool Worker) 
   ...
  20   17     50a8 000002416DE43DD0  102b220 Preemptive  000002414D8BC2D0:000002414D8BDFD0 000002414bdaf8f0 -00001 MTA (Threadpool Worker) 
  21   18     57d4 000002416DE628E0  8029220 Preemptive  000002414D8CC2A8:000002414D8CE000 000002414bdaf8f0 -00001 MTA (Threadpool Completion Port) 

既然目前的 SOS 不支持,同樣可以手工到 CLR 中去挖,熟悉的朋友應該知道 !t 的數據源來自於 ThreadStore::s_pThreadStore 下的 m_ThreadList 集合,它以鏈表的形式串聯了每個線程的 LinkPtr 欄位,但可惜的是,在 AOT 中,這一塊已經重寫了,由 g_pTheRuntimeInstance 全局變數下的 m_ThreadList 來維護了。

為了方便觀察,多生成幾個 Thread。


        static void Main(string[] args)
        {
            Debugger.Break();

            var tasks = Enumerable.Range(0, 10).Select(m => new Thread(() =>
            {
                Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId} 已執行!");
                Console.ReadLine();
            }));

            foreach (var item in tasks)
            {
                item.Start();
            }

            Console.ReadLine();
        }

程式跑起來後,深挖 g_pTheRuntimeInstance 全局變數即可。


0:015> x ConsoleApp1!g_pTheRuntimeInstance
00007ff7`0155ee20 ConsoleApp1!g_pTheRuntimeInstance = 0x00000291`cb5b9300
0:015> dx -r1 ((ConsoleApp1!RuntimeInstance *)0x291cb5b9300)
((ConsoleApp1!RuntimeInstance *)0x291cb5b9300)                 : 0x291cb5b9300 [Type: RuntimeInstance *]
    [+0x000] m_pThreadStore   : 0x291cb5b9390 [Type: ThreadStore *]
    ...
0:015> dx -r1 ((ConsoleApp1!ThreadStore *)0x291cb5b9390)
((ConsoleApp1!ThreadStore *)0x291cb5b9390)                 : 0x291cb5b9390 [Type: ThreadStore *]
    [+0x000] m_ThreadList     [Type: SList<Thread,DefaultSListTraits<Thread,DoNothingFailFastPolicy> >]
    [+0x008] m_pRuntimeInstance : 0x291cb5b9300 [Type: RuntimeInstance *]
    [+0x010] m_Lock           [Type: ReaderWriterLock]
0:015> dx -r1 (*((ConsoleApp1!SList<Thread,DefaultSListTraits<Thread,DoNothingFailFastPolicy> > *)0x291cb5b9390))
(*((ConsoleApp1!SList<Thread,DefaultSListTraits<Thread,DoNothingFailFastPolicy> > *)0x291cb5b9390))                 [Type: SList<Thread,DefaultSListTraits<Thread,DoNothingFailFastPolicy> >]
    [+0x000] m_pHead          : 0x291ed366240 [Type: Thread *]
0:015> dx -r1 ((ConsoleApp1!Thread *)0x291ed366240)
((ConsoleApp1!Thread *)0x291ed366240)                 : 0x291ed366240 [Type: Thread *]
    ...
    [+0x058] m_pNext          : 0x291cb6aeb60 [Type: Thread *]
    [+0x060] m_hPalThread     : 0x204 [Type: void *]
    [+0x068] m_ppvHijackedReturnAddressLocation : 0x0 [Type: void * *]
    [+0x070] m_pvHijackedReturnAddress : 0x0 [Type: void *]
    [+0x078] m_uHijackedReturnValueFlags : 0x0 [Type: unsigned __int64]
    [+0x080] m_pExInfoStackHead : 0x0 [Type: ExInfo *]
    [+0x088] m_threadAbortException : 0x0 [Type: Object *]
    [+0x090] m_pThreadLocalModuleStatics : 0x291cb6aee90 [Type: void * *]
    [+0x098] m_numThreadLocalModuleStatics : 0x1 [Type: unsigned int]
    [+0x0a0] m_pGCFrameRegistrations : 0x0 [Type: GCFrameRegistration *]
    [+0x0a8] m_pStackLow      : 0xf754100000 [Type: void *]
    [+0x0b0] m_pStackHigh     : 0xf754200000 [Type: void *]
    [+0x0b8] m_pTEB           : 0xf7533ba000 : 0x0 [Type: unsigned char *]
    [+0x0c0] m_uPalThreadIdForLogging : 0x2044 [Type: unsigned __int64]
    [+0x0c8] m_threadId       [Type: EEThreadId]
    [+0x0d0] m_pThreadStressLog : 0x0 [Type: void *]
    [+0x0d8] m_interruptedContext : 0x0 [Type: _CONTEXT *]
    [+0x0e0] m_redirectionContextBuffer : 0x0 [Type: unsigned char *]
0:015> dx -r1 (*((ConsoleApp1!EEThreadId *)0x291ed366308))
(*((ConsoleApp1!EEThreadId *)0x291ed366308))                 [Type: EEThreadId]
    [+0x000] m_uiId           : 0x2044 [Type: unsigned __int64]

從CLR 的 Thread 維護的信息來看,這個結構體已經很小了,也說明 AOT 在Thread信息維護上做了很多的精簡。

三:總結

總的來說,AOT 確實能加速程式的初始啟動,一體化的打包機制也非常方便部署,但怎麼變終究還是一個托管程式,需要底層的 C++ 托著它,扛 反編譯 無從談起,所以防小人的話,該加殼的加殼,該混淆的混淆。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上篇文章談到BlockingQueue的使用場景,並重點分析了ArrayBlockingQueue的實現原理,瞭解到ArrayBlockingQueue底層是基於數組實現的阻塞隊列。 但是BlockingQueue的實現類中,有一種阻塞隊列比較特殊,就是SynchronousQueue(同步移交隊... ...
  • 1、const修飾變數 被const修飾過的變數相當於常量,它的值不能被賦值改變,在整個作用域內保持固定。所以說它定義的是只讀變數,在定義的時候需要給它賦初值。 1 const int a = 1; 2 a = 2; /*錯誤,常量的值不能改變*/ 3 const int a; /*錯誤,常量定義時 ...
  • 性能檢測分析方法 - 時間、空間衡量方法 Python 自帶模塊 import time 點擊查看代碼 # 僅僅是示範 time 模塊的用法,此段不能直接運行,運行請看測試項目源碼 import time def measure_runtime(func): time_start = time.ti ...
  • 我們寫東西的時候總會遇到lua中要調用java代碼,當然這個用JNI肯定是可以做到的,但是有更加方便的辦法—LuaJavaBridge。 一、luaj 主要特征 * 可以從 Lua 調用 Java Class Static Method * 調用 Java 方法時,支持 int/float/bool ...
  • Python安裝完成之後,我們的電腦都多出了哪些東西? 我們在電腦搜索框中搜索“python”,會顯示出python相關的程式。可以看到,我們的電腦會多出4個應用程式,如下: 接下來介紹下這4個程式的作用。 IDLE (Python 3.11 64-bit) IDLE是Python官方的集成開 ...
  • 這個問題是最近更新.NET 7 進行資料庫遷移發現的,操作步驟很簡單,先看一下SQL Server中的解決方法: 錯誤信息: 解決方案: 在登錄時,更改選項的連接屬性,解決方案、信任伺服器證書選項都選擇或者都不選擇,不能只選一個 這是我們SQL Server的寫法,在我們資料庫遷移中.NET 7 也 ...
  • 環境 伺服器:centos6.5 客戶端:Windows 前言 項目中有一個exe,安裝在客戶端,其中有一個功能是將本地產生的文件上傳至伺服器,這個功能是以服務的方式安裝在客戶端上。之前一切好使,文件能正常上傳到伺服器。但最近發現產生的文件沒有被上傳到伺服器,查看文件上傳服務的事件日誌提示密碼錯誤。 ...
  • ASP.NET Core是啥 ASP.NET Core 是一個跨平臺的高性能開源框架,用於生成啟用雲且連接 Internet 的新式應用。 ASP.NET Core 可以幹啥 生成 Web 應用和服務、物聯網 (IoT) 應用和移動後端。 在 Windows、macOS 和 Linux 上使用喜愛的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...