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
MonoTouch从官方网站下载的是一个web安装程序,在填写信息点击下载后下载的是 http://download.xamarin.com/Installer/MonoTouch/monotouch-eval.dmg 这个文件,免费试用版,无法编译真机程序。 下载后比较好奇,直接右键用7z打开一级一级目录找下去最后找到了
好了,看到.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:
http://download.xamarin.com/priv/1edf75dc763a09195fbdfb81cf367/MonoTouch/Mac/monotouch-6.0.6.pkg
习惯让我把他放入google里搜索了一下,结果又有意外发现
http://xamarin.com/download/installer/Mac/MonoTouch/full/InstallationManifest.xml
看看,这个应该是最新版的官方用来更新的地址,里面应该永远是最新的。没说的,里面三个文件都下来,
http://download.xamarin.com/monodevelop/Mac/MonoDevelop-3.0.5.dmg 这个安装正常
http://download.xamarin.com/priv/1edf75dc763a09195fbdfb81cf367/MonoTouch/Mac/monotouch-6.0.6.pkg 这个嘛。。。安装时得激活。所以我们接下来主要手工处理它。
于是乎在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把过程逆向,提取出一堆托管程序集
using 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,处理其。。。。