.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
  • 1.部署歷史 猿友們好,作為初來實習的我,已經遭受社會的“毒打”,所以請容許我在下麵環節適當吐槽,3Q! 傳統部署 ​ 回顧以往在伺服器部署webapi項目(非獨立發佈),dotnet環境、守護進程兩個逃都逃不掉,正常情況下還得來個nginx代理。不僅僅這仨,可能牽扯到yum或npm。node等都要 ...
  • 隨著技術的進步,跨平臺開發已經成為了標配,在此大背景下,ASP.NET Core也應運而生。本文主要基於ASP.NET Core+Element+Sql Server開發一個校園圖書管理系統為例,簡述基於MVC三層架構開發的常見知識點,前一篇文章,已經簡單介紹瞭如何搭建開發框架,和登錄功能實現,本篇... ...
  • 這道題只要會自定義cmp恰當地進行排序,其他部分沒有什麼大問題。 上代碼: 1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,s,h1,h2,cnt; 4 struct apple{ 5 int height,ns;//height為蘋 ...
  • 這篇文章主要描述RPC的路由策略,包括為什麼需要請求隔離,為什麼不在註冊中心中實現請求隔離以及不同粒度的路由策略。 ...
  • 簡介: 中介者模式,屬於行為型的設計模式。用一個中介對象來封裝一系列的對象交互。中介者是各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變他們之間的交互。 適用場景: 如果平行對象間的依賴複雜,可以使用中介者解耦。 優點: 符合迪米特法則,減少成員間的依賴。 缺點: 不適用於系統出現對 ...
  • 【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
  • 簡介: 享元模式,屬於結構型的設計模式。運用共用技術有效地支持大量細粒度的對象。 適用場景: 具有相同抽象但是細節不同的場景中。 優點: 把公共的部分分離為抽象,細節依賴於抽象,符合依賴倒轉原則。 缺點: 增加複雜性。 代碼: //用戶類 class User { private $name; fu ...
  • 這次設計一個通用的多位元組SPI介面模塊,特點如下: 可以設置為1-128位元組的SPI通信模塊 可以修改CPOL、CPHA來進行不同的通信模式 可以設置輸出的時鐘 狀態轉移圖和思路與多位元組串口發送模塊一樣,這裡就不給出了,具體可看該隨筆。 一、模塊代碼 1、需要的模塊 通用8位SPI介面模塊 `tim ...
  • AOP-03 7.AOP-切入表達式 7.1切入表達式的具體使用 1.切入表達式的作用: 通過表達式的方式定義一個或多個具體的連接點。 2.語法細節: (1)切入表達式的語法格式: execution([許可權修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]) 若目標類、介面與 ...
  • 測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...