MonoTouch 二三事(一)
題外話: 最近工作室打算接個iPhone + iPad + Android Phone + Android Pad 雜誌客戶端的活,以前只只儲備了Android的開發知識, 因為個人原因,很討厭Apple的作風,所以從沒打算做Apple的開發,也就沒有儲備相關知識,臨時只好抱佛腳,看看能否用自己 會的知識來減少iOS開發的學習成本與時間,看了Web App的AppCan和PhoneGap,沒細看,因這兩個都是用html做開發,最後找到了 MonoTouch,Mono園子裡的應該不陌生,我自己也鼓搗很長時間,MonoTouch是Mono被從Novell 轉移到新東家xamarin後的一款商業軟體, 基於Mono,以前在Novell 的時候是開源的,轉給xamarin後從Mono和MonoDevelop的原始碼樹中被移除了,成了商業軟體。
對MonoTouch主要關心授權價格、效能、以及編譯後生成檔案的大小,第一個官網上有,個人一年399刀,第二個,根據我對其進行的 簡單分析來看,其運用了AOT編譯技術及llvm優化技術,生成經過裁剪的native程式,繞過ios平臺上禁止jit的限制,效能應該是不錯的, 但沒有真機實驗,第三個問題就比較難以解答了,我經過google+baidu後沒找到一個對這方面進行評論的,於是決定自己操刀。
環境:Win7 x64 + VM9 + Mac OS X Lion 10.4
好了,看到.NET 的程式就順手傳送到ILSPY,結果就找到了
那個啥。。我真不是故意的,完整版的下載url就出來了。。。你說我不去下載也。。。於是乎。下載後
此處省略X千字針對5.2.12版本的[處理]過程,因為我要介紹的是6.0.6版本,最新的。
話說,我把5.2.12都[處理]完了,能真機編譯瞭然後有次啟動MonoDevelop時,居然問我更新不,我更新後就變成了6.0.6版本,把我的[處理]過的檔案給替換了,又不能用了。
既然他自動更新了,肯定有個臨時檔案,把新版的安裝包弄了下來,於是乎,我在並不熟悉的Mac os x裡一頓大搜索啊,其中各種google+百度一言難盡啊。。最終找到了
/Users/binsys/Library/Caches/MonoDevelop-3.0/TempDownload/4569c276-1397-4adb-9485-82a7696df22e-2060006000.pkg
/Users/binsys/Library/Caches/MonoDevelop-3.0/TempDownload/index.xml
開啟xml檔案你會發現一個url:
習慣讓我把他放入google裡搜尋了一下,結果又有意外發現
看看,這個應該是最新版的官方用來更新的地址,裡面應該永遠是最新的。沒說的,裡面三個檔案都下來,
於是乎在windows裡右鍵7z 開啟monotouch-6.0.6.pkg解壓到目錄,進入解壓後的目錄的monotouch.pkg目錄,發現4個無後綴的檔案Bom,PackageInfo,Payload,Scripts,根據百度+google告訴我,這是一個用Apple的PackageMaker打包的應用程式,支援啟動前指令碼,執行後腳本,Payload這個檔案可用7z開啟裡面幾層都能用7z開啟,裡面是這個安裝包的主要內容,Payload解壓後最終得到
存著,備用,接下來Scripts這個也能用7z在解壓了幾層後開啟,Scripts解壓後最終得到postinstall,preinstall,兩個檔案,都是bash指令碼,顧名思義,一個是安裝前執行的,一個是安裝後執行的,右鍵,notepad2開啟preinstall,檔案較大啊,根據內容是一個叫Makeself 2.1.5生成的,具體功能和在windows裡把exe轉成bat差不多,他把一個程式轉成了sh指令碼,嗯,根據本檔案內容是解壓後執行解壓縮後的user-driver(其實就是個自解壓~),我從本檔案後面的二進位制資料提出了取名為InstallationCheck.gz的檔案,好吧,偉大的7z再次。然後得到client,driver,user-driver,libMonoPosixHelper.dylib,開啟user-driver一看,是段sh指令碼呼叫了client這個程式,也就是說這個程式就是我們要[處理]的檔案,7z開啟之。。
好了,偉大的7z告訴我們這是個Mac OS X平臺的可執行檔案,Mach0型別,其和exe檔案在windows上是一個意義。嗯這下不是.NET的了,ILSPY不管用了,好吧,拿出我的靜態反彙編神器-IDA6.1,載入本檔案,等待IDA分析完畢,
看見我選中那個函式名沒?我老親切了,因為我以前閱讀過mono打包、綠化、移植相關的程式碼。。知道這個程式是使用mono的一個附屬工具mkbundle生成的,開源的,看其原始碼,發現就是把mono寫的.NET程式以及其依賴項打包到一個mac os x的可執行檔案,打包是壓縮了那些被打包的東西,具體可以自己看程式碼
咱關注的是mkbundle原始碼template_z.c這個檔案裡的mono_mkbundle_init函式,用於解壓並載入應用程式程式集並建立程式域在記憶體,
ptr = (CompressedAssembly **) compressed;
這句中的compressed指向壓縮程式集的表,根據mkbundle.cs內容來看就是那些程式集被打包後存的位置,好了我們要在client這個程式裡找到這個表位置。
在左側函式列表裡雙擊,IDA的由此反彙編視窗就顯示這函式的彙編程式碼,嗯,輕輕地摁下F5,執行hex-rays的反編譯外掛,這時我們看見了上圖的程式碼。。。明顯的是標黃的
雙擊進去,位置找到了,那他是什麼結構呢?
typedef struct { const char *name; const unsigned char *data; const unsigned int size; } MonoBundledAssembly; typedef struct _compressed_data { MonoBundledAssembly assembly; int compressed_size; } CompressedAssembly;
經過檢視程式以及其標頭檔案,我們發現了此結構,把他儲存成.h檔案,開啟ida的選單:File -> Load file -> Parse C header file,找到儲存的標頭檔案,開啟它,然後ida提示解析成功,IDA選單:View -> Open subviews->Local types,可以看到我們剛才解析的標頭檔案的結構體
選中MonoBundledAssembly右鍵選擇Synchronize to idb,CompressedAssembly也同樣,然後回到雙擊_compressed後的反彙編位置
因為是結構體指標的指標,所以這裡應該是一堆指標組成的陣列,也就是4位元組為一個指標,N個4位元組,每個指向某個資料區域,是個偏移值,在_compressed上選中,然後快捷鍵ctrl+o,將其轉成偏移,
下一個__data:002C0884也快速轉成offset,知道4位元組都是0,表索引結束。看見ida自動生成的offset名稱,_assembly_bundle_client_exe,雙擊,跳到
嗯,有點結構體的樣子了,我們在下圖裡黃色處點選右鍵
看見那個結構體名稱沒,選中,下邊一堆類似的如_data:002C08EC _assembly_bundle_mscorlib_dll,也這麼操作,看結果,
有點兒多,哈,熟悉不?寫段程式碼來吧,根據mkbundle把過程逆向,提取出一堆託管程式集
View Codeusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using ICSharpCode.SharpZipLib.Zip.Compression.Streams; namespace unmkbundle { class Program { public class MonoBundledAssembly { public UInt32 name_pos; public string name; public UInt32 data_compressed_pos; public byte[] data_compressed; public byte[] data; public UInt32 size; } public class CompressedAssembly { public MonoBundledAssembly assembly; public UInt32 compressed_size; } public class FileItem { public string FileName; public UInt32 Pos; } static FileItem[] Files = new FileItem[] { //new FileItem() //{ // FileName = "client", // Pos = 0x00396a00 //}, //new FileItem() //{ // FileName = "driver", // Pos = 0x00396a00 //}, //new FileItem() //{ // FileName = "mtouch", // Pos = 0x003a4a80 //}, //new FileItem() //{ // FileName = "mmp", // Pos = 0x0039850c //}, new FileItem() { FileName = "client6", Pos = 0x002c0880 }, new FileItem() { FileName = "driver6", Pos = 0x002c0880 }, new FileItem() { FileName = "mtouch6", Pos = 0x003aa8c0 }, }; static int base_offset = 0x1000; static List<UInt32> CompressedAssemblyItemPosList = new List<UInt32>(); static List<CompressedAssembly> CompressedAssemblyList = new List<CompressedAssembly>(); static byte[] Bytes_client; static string currentTime = DateTime.Now.ToString("yyyyMMddHHmmss"); static void Main(string[] args) { foreach (FileItem fi in Files) { CompressedAssemblyItemPosList = new List<uint>(); CompressedAssemblyList = new List<CompressedAssembly>(); Bytes_client = null; string curr_dir = fi.FileName + "_" + currentTime; Bytes_client = File.ReadAllBytes(fi.FileName); using (MemoryStream ms = new MemoryStream(Bytes_client)) { using (BinaryReader br = new BinaryReader(ms)) { br.BaseStream.Seek(fi.Pos - base_offset, SeekOrigin.Begin); UInt32 pos = 0; do { pos = br.ReadUInt32(); CompressedAssemblyItemPosList.Add(pos); } while (pos != 0); br.Close(); } ms.Close(); } foreach (UInt32 CompressedAssemblyItemPos in CompressedAssemblyItemPosList) { if (CompressedAssemblyItemPos == 0) continue; using (MemoryStream ms = new MemoryStream(Bytes_client)) { ms.Seek(CompressedAssemblyItemPos - base_offset, SeekOrigin.Begin); using (BinaryReader br = new BinaryReader(ms)) { UInt32 name_pos = br.ReadUInt32(); UInt32 data_pos = br.ReadUInt32(); UInt32 size = br.ReadUInt32(); UInt32 compressed_size = br.ReadUInt32(); MonoBundledAssembly mba = new MonoBundledAssembly() { name_pos = name_pos, data_compressed_pos = data_pos, size = size, name = ReadName(name_pos), data_compressed = ReadData(data_pos, compressed_size) }; Console.WriteLine(string.Format("從 [{0}] 解壓 [{1}] 到 [{2}]", fi.FileName, mba.name, curr_dir)); mba.data = DeCompress(mba.data_compressed); if (!Directory.Exists(curr_dir)) { Directory.CreateDirectory(curr_dir); } File.WriteAllBytes(Path.Combine(curr_dir, mba.name), mba.data); //SaveMonoBundledAssembly(mba); CompressedAssembly ca = new CompressedAssembly() { assembly = mba, compressed_size = compressed_size }; CompressedAssemblyList.Add(ca); br.Close(); } ms.Close(); } } } Console.ReadKey(true); } static string ReadName(UInt32 pos) { string a = string.Empty; using (MemoryStream ms = new MemoryStream(Bytes_client)) { using (BinaryReader br = new BinaryReader(ms)) { br.BaseStream.Seek((long)pos - base_offset, SeekOrigin.Begin); //a = br.reads byte d = 0; int len = 0; while ((d = br.ReadByte()) != 0) { len++; } br.BaseStream.Seek((long)pos - base_offset, SeekOrigin.Begin); byte[] namebytes = br.ReadBytes(len); a = Encoding.ASCII.GetString(namebytes); br.Close(); } ms.Close(); } return a; } static byte[] ReadData(UInt32 pos, UInt32 compressed_size) { byte[] ret; using (MemoryStream ms = new MemoryStream(Bytes_client)) { using (BinaryReader br = new BinaryReader(ms)) { br.BaseStream.Seek((long)pos - base_offset, SeekOrigin.Begin); ret = br.ReadBytes((int)compressed_size); br.Close(); } ms.Close(); } return ret; } public static byte[] DeCompress(byte[] pBytes) { ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream mStream = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(new MemoryStream(pBytes)); MemoryStream mMemory = new MemoryStream(); Int32 mSize; byte[] mWriteData = new byte[4096]; while (true) { mSize = mStream.Read(mWriteData, 0, mWriteData.Length); if (mSize > 0) { mMemory.Write(mWriteData, 0, mSize); } else { break; } } mStream.Close(); return mMemory.ToArray(); } } }
結果就是
程式碼裡那個Pos是__data:002C0880 _compressed 的002C0880,用這段程式碼能解壓很多MonoTouch裡的被打包的程式,
盡情的把client扔到ilspy裡吧。。。
有點兒晚了。下篇繼續拔MonoTouch,處理其。。。。