.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 Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...