.NET 7 的 AOT 到底能不能扛反编译?
创始人
2024-02-09 08:08:28

一:背景

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`0000000c0: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: nonesegment             begin         allocated         committed    allocated size    committed size
000002414D890000  000002414D891000  000002414D8D1FE8  000002414D8D2000  0x40fe8(266216)  0x41000(266240)
Large object heap starts at 0x000002415D891000segment             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 suchloh_generation = 3,// pinned heap, a separate generation for the same reasons as lohpoh_generation = 4,uoh_start_generation = loh_generation,// number of ephemeral generationsephemeral_generation_count = max_generation,// number of all generationstotal_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:   noLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    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 >][+0x008] m_pRuntimeInstance : 0x291cb5b9300 [Type: RuntimeInstance *][+0x010] m_Lock           [Type: ReaderWriterLock]
0:015> dx -r1 (*((ConsoleApp1!SList > *)0x291cb5b9390))
(*((ConsoleApp1!SList > *)0x291cb5b9390))                 [Type: SList >][+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++ 托着它,扛 反编译 无从谈起,所以防小人的话,该加壳的加壳,该混淆的混淆。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...