測試下VMP加密.NET的強度,選了最新的.Net8+AOT編譯,用VMP給它加殼。最後逆向下,簡單的分析,本篇看下。
一.前奏
首先一段簡單的C#程式碼:
namespace Test_{
internal class Program{
static void Main(string[] args) {
Console.WriteLine("hello, World!");
Console.ReadLine();
}
}
}
把這段程式碼編譯成AOT:
1.csproj裡面新增 <PublishAot>true</PublishAot>
2.dotnet publish -r win-x64 -c Release
編譯完成之後在路徑
bin\release\net8.0\win-x64\publish
找到Test_.exe,然後對它進行VMP加密。這裡用的是VMP3.7.1。為了最大強度加密,把它所有加密全選上,如下圖:
最後得到一個獨立的Exe檔案Test_.vmp.exe。把這個Exe執行之後,拍攝一個記憶體快照,它的堆疊如下
00 00000000`0014fad8 00007fff`00cc65cb ntdll!NtReadFile+0x14
01 00000000`0014fae0 00000001`40140e2b KERNELBASE!ReadFile+0x7b
02 00000000`0014fb50 00000001`401419d9 Test__vmp+0x140e2b
03 00000000`0014fc10 00000001`401418d1 Test__vmp+0x1419d9
04 00000000`0014fc60 00000001`40143bed Test__vmp+0x1418d1
05 00000000`0014fca0 00000001`400e0796 Test__vmp+0x143bed
06 00000000`0014fd00 00000001`400e0857 Test__vmp+0xe0796
07 00000000`0014fd50 00000001`401440ce Test__vmp+0xe0857
08 00000000`0014fda0 00000001`401413d2 Test__vmp+0x1440ce
09 00000000`0014fde0 00000001`4007b455 Test__vmp+0x1413d2
0a 00000000`0014fe10 00000001`4016bc0b Test__vmp+0x7b455
0b 00000000`0014fe40 00000001`4006e15e Test__vmp+0x16bc0b
0c 00000000`0014fea0 00000001`400694b4 Test__vmp+0x6e15e
0d 00000000`0014fef0 00007fff`029426ad Test__vmp+0x694b4
0e 00000000`0014ff30 00007fff`0370aa68 kernel32!BaseThreadInitThunk+0x1d
0f 00000000`0014ff60 00000000`00000000 ntdll!RtlUserThreadStart+0x28
因為它停在了ReadLine()這個託管函數上面,又因為受控程式碼進行了AOT,所以它這裡的堆疊其實就是非託管棧。
注意這個堆疊裡面的第10行,也即編號09哪一行
09 00000000`0014fde0 00000001`4007b455 Test__vmp+0x1413d2
這裡就是.Net8預程式設計式碼AOT的託管Main入口的非託管表現。
Test__Test__Program__Main
看下它的內容:
00000001`4007b440 4883ec28 sub rsp,28h
00000001`4007b444 488d0d6dc52200 lea rcx,[Test__vmp+0x2a79b8 (00000001`402a79b8)]
00000001`4007b44b e8905f0c00 call Test__vmp+0x1413e0 (00000001`401413e0)
00000001`4007b450 e86b5f0c00 call Test__vmp+0x1413c0 (00000001`401413c0)
00000001`4007b455 90 nop
00000001`4007b456 4883c428 add rsp,28h
00000001`4007b45a c3 ret
這裡剛好的是託管範例的機器碼呼叫,注意它只是記憶體裡面的表現
二:虛擬機器器原理
這裡需要簡單瞭解下虛擬機器器的原理,虛擬機器器會根據傳遞進來的程式碼,對這些程式碼進行,解析,組合,變形等等之後,組合成新的機器碼在機器上執行。這裡的VMP本身就是個虛擬機器器,它把AOT的組合程式碼,進行解析,組合,變形之後形成組合程式碼執行。瞭解了這一點,我們就知道上面的記憶體快照只不過是它執行時候的表現,而實際上Exe裡面的程式碼未必會是這種表現。
這裡看下,hello World字串
00000001`4007b444 488d0d6dc52200 lea rcx,[Test__vmp+0x2a79b8 (00000001`402a79b8)]
它指向的就是字串範例的MethodTable,hello world字串清晰可見:
00000001`402a79b8 88 41 20 40 01 00 00 00-0d 00 00 00 48 00 68 00 .A @........h.e.
00000001`402a79c8 6c 00 6c 00 6f 00 2c 00-20 00 57 00 6f 00 72 00 l.l.o.,. .W.o.r.
00000001`402a79d8 6c 00 64 00 21 00 00 00-00 00 00 00 00 00 00 00 l.d.!...........
但是這些程式碼只是記憶體的表現,而實際上儲存在Exe裡面可能是加密過的字串,這裡是解析之後,最後執行的結果。如果想要修改掉這串字元,那麼得找到這串字元ASCII單個byte加密的地方。
三:特徵碼
我們可以看到字串的操作取地址lea指令後面是立即數:
00000001`4007b444 488d0d6dc52200 lea rcx,[Test__vmp+0x2a79b8 (00000001`402a79b8)]
一般來說,這種取地址不會混淆,所以這裡在Test_.vmp.exe的整個組合裡面搜尋一下特徵
lea rcx 後面跟立即數的
結果如下:
000000014067AA6D: 48 8D 0C 06 lea rcx,[000000014067B101]
地址:000000014067AAE4如下:
000000014067B101: 93 xchg eax,ebx
000000014067B102: 32 C0 xor al,al
000000014067B104: CB retf
000000014067B105: 02 68 B0 add ch,byte ptr [rax-50h]
可以看到地址:000000014067B106有個0x68,還記得上面的hello World字串嘛,首字母h的ASCII就是0x68。
OK,要找的就是這個地方了,在十六進位制編輯器裡面把它改成0x67,然後儲存執行下Test_.vmp.exe。字串hello World變成了gello World,如下圖。
非常簡單的一個例子,如果你對以上感興趣,可以掃描下面卡片關注我。帶你玩好玩的技術。