2014.10.06 更新 编写了 IDA Pro 的插件,用来解包和打包 mkbundle程序,请参见 https://github.com/binsys/MKBundleManager

许久以后,这个续上这个系列的第三篇。

玩过mono的可能知道mono有一个工具mkbundle ,可以把mono的运行时与类库与你的程序的依赖程序集都打包成一个可执行文件,在win下为exe文件,例如mandroid.exe,mtouch.exe,在mac下的Mach-O文件,例如mtouch,mtouch-64。

根据他的源代码 https://github.com/mono/mono/tree/master/mcs/tools/mkbundle,我们得到:

template_main.c

int main (int argc, char* argv[])
{
char **newargs;
int i, k = ; newargs = (char **) malloc (sizeof (char *) * (argc + + count_mono_options_args ())); newargs [k++] = argv []; if (mono_options != NULL) {
i = ;
while (mono_options[i] != NULL)
newargs[k++] = mono_options[i++];
} newargs [k++] = image_name; for (i = ; i < argc; i++) {
newargs [k++] = argv [i];
}
newargs [k] = NULL; if (config_dir != NULL && getenv ("MONO_CFG_DIR") == NULL)
mono_set_dirs (getenv ("MONO_PATH"), config_dir); mono_mkbundle_init(); return mono_main (k, newargs);
}

看调用了函数mono_mkbundle_init,而这个函数有两个实现,分别位于:

https://github.com/mono/mono/blob/master/mcs/tools/mkbundle/template.c

https://github.com/mono/mono/blob/master/mcs/tools/mkbundle/template_z.c

工具根据运行选项 -z 是否压缩程序集选择使用template.c或template_z.c内的mono_mkbundle_init函数实现,我们使用时通常是选择压缩的,所以通常使用的是后者的实现。

看 https://github.com/mono/mono/blob/master/mcs/tools/mkbundle/template_z.c,:

void mono_mkbundle_init ()
{
CompressedAssembly **ptr;
MonoBundledAssembly **bundled_ptr;
Bytef *buffer;
int nbundles; install_dll_config_files (); ptr = (CompressedAssembly **) compressed;
nbundles = ;
while (*ptr++ != NULL)
nbundles++; bundled = (MonoBundledAssembly **) malloc (sizeof (MonoBundledAssembly *) * (nbundles + ));
bundled_ptr = bundled;
ptr = (CompressedAssembly **) compressed;
while (*ptr != NULL) {
uLong real_size;
uLongf zsize;
int result;
MonoBundledAssembly *current; real_size = (*ptr)->assembly.size;
zsize = (*ptr)->compressed_size;
buffer = (Bytef *) malloc (real_size);
result = my_inflate ((*ptr)->assembly.data, zsize, buffer, real_size);
if (result != ) {
fprintf (stderr, "mkbundle: Error %d decompressing data for %s\n", result, (*ptr)->assembly.name);
exit ();
}
(*ptr)->assembly.data = buffer;
current = (MonoBundledAssembly *) malloc (sizeof (MonoBundledAssembly));
memcpy (current, *ptr, sizeof (MonoBundledAssembly));
current->name = (*ptr)->assembly.name;
*bundled_ptr = current;
bundled_ptr++;
ptr++;
}
*bundled_ptr = NULL;
mono_register_bundled_assemblies((const MonoBundledAssembly **) bundled);
}

我们看到解压时使用了compressed这个本文件未定义的变量。通过工具源代码我们得知其是一个类型为如下结构体指针的数组:

typedef struct {
const char *name;
const unsigned char *data;
const unsigned int size;
} MonoBundledAssembly; typedef struct _compressed_data {
MonoBundledAssembly assembly;
int compressed_size;
} CompressedAssembly;

也就是说我们找到被打包后的程序的函数mono_mkbundle_init ,并找到对compressed这个数据的引用操作,就可以找到一个程序集个数的int32(64位打包target为int64)数组,每个数组为一个指向CompressedAssembly结构体的指针。(不好描述,继续看我给的代码吧~)

因为compressed指向的是常量数据,一般位于执行文件的类似名为.data或.const等段。

因为被打包后的程序如 mandroid.exe 往往无任何符号,定位mono_mkbundle_init 以及 compressed并不容易,往往需要靠人工判断,这个想自动化完成。通过对各个版本的Xa*****程序集分析得到结果是,再无c语言级别上的代码大改动的情况下,同一语句生成的汇编的对数据引用的偏移量可能会变更,但如果不看数据引用的话,汇编语句的语义序列以及顺序往往固定,也就是说我们可以根据此特征定位位于函数mono_mkbundle_init 内对compressed变量引用时compressed变量在可执行文件的虚拟地址(VA)。

下面我们就得请出伟大的泄漏版IDA Pro 6.5 (没有的自己百度吧~pediy的资源区有)。

我们得知函数内有常量 [mkbundle: Error %d decompressing data for %s\n]这个字符串(根据win或mac的编译器不同,前面的mkbundle: 有时会没有),而往往整个程序只有一个函数对此有引用,由此我们得到mono_mkbundle_init 函数,这个通过IDAPython脚本可以得到,然后找到函数内第一次对数据段的引用这个引用的就是compressed变量,上代码:

#!/usr/bin/env python
# coding=gbk # 支持 mtouch mtouch-64 mtouch.exe mandroid.exe 解包
# 用IDA打开待分析文件,等待分析完毕,运行此脚本,将会在待分析文件同目录下生成临时文件夹并解压文件
# by BinSys import urllib2, httplib
import zlib
import StringIO, gzip
import struct
import io
import sys import idaapi
import idc
import idautils
from struct import * import time
import datetime
from datetime import datetime, date, time InputFileType_EXE = 11
InputFileType_MachO = 25
InputFileType = -1 Is64Bit = False string_type_map = {
0 : "ASCSTR_C", # C-string, zero terminated
1 : "ASCSTR_PASCAL", # Pascal-style ASCII string (length byte)
2 : "ASCSTR_LEN2", # Pascal-style, length is 2 bytes
3 : "ASCSTR_UNICODE", # Unicode string
4 : "ASCSTR_LEN4", # Delphi string, length is 4 bytes
5 : "ASCSTR_ULEN2", # Pascal-style Unicode, length is 2 bytes
6 : "ASCSTR_ULEN4", # Pascal-style Unicode, length is 4 bytes
} filetype_t_map = {
0 : "f_EXE_old", # MS DOS EXE File
1 : "f_COM_old", # MS DOS COM File
2 : "f_BIN", # Binary File
3 : "f_DRV", # MS DOS Driver
4 : "f_WIN", # New Executable (NE)
5 : "f_HEX", # Intel Hex Object File
6 : "f_MEX", # MOS Technology Hex Object File
7 : "f_LX", # Linear Executable (LX)
8 : "f_LE", # Linear Executable (LE)
9 : "f_NLM", # Netware Loadable Module (NLM)
10 : "f_COFF", # Common Object File Format (COFF)
11 : "f_PE", # Portable Executable (PE)
12 : "f_OMF", # Object Module Format
13 : "f_SREC", # R-records
14 : "f_ZIP", # ZIP file (this file is never loaded to IDA database)
15 : "f_OMFLIB", # Library of OMF Modules
16 : "f_AR", # ar library
17 : "f_LOADER", # file is loaded using LOADER DLL
18 : "f_ELF", # Executable and Linkable Format (ELF)
19 : "f_W32RUN", # Watcom DOS32 Extender (W32RUN)
20 : "f_AOUT", # Linux a.out (AOUT)
21 : "f_PRC", # PalmPilot program file
22 : "f_EXE", # MS DOS EXE File
23 : "f_COM", # MS DOS COM File
24 : "f_AIXAR", # AIX ar library
25 : "f_MACHO", # Max OS X
} def FindStringEA():
searchstr = str("mkbundle: Error %d decompressing data for %s\n")
searchstr2 = str("Error %d decompresing data for %s\n") #Do not use default set up, we'll call setup().
s = idautils.Strings(default_setup = False)
# we want C & Unicode strings, and *only* existing strings.
s.setup(strtypes=Strings.STR_C | Strings.STR_UNICODE, ignore_instructions = True, display_only_existing_strings = True) #loop through strings
for i, v in enumerate(s):
if not v:
#print("Failed to retrieve string at index {}".format(i))
return -1
else:
#print("[{}] ea: {:#x} ; length: {}; type: {}; '{}'".format(i, v.ea, v.length, string_type_map.get(v.type, None), str(v)))
if str(v) == searchstr or str(v) == searchstr2:
return v.ea return -1 def FindUnFunction(StringEA):
for ref in DataRefsTo(StringEA):
f = idaapi.get_func(ref) if f:
return f
return None def FindDataOffset(FuncEA):
for funcitem in FuncItems(FuncEA):
#print hex(funcitem)
for dataref in DataRefsFrom(funcitem):
return dataref
#print " " + hex(dataref)
return None def GetStructOffsetList(DataOffset): global Is64Bit if Is64Bit == True:
addv = 8
mf=MakeQword
vf=Qword
else:
mf=MakeDword
addv = 4
vf=Dword AsmListStructListOffset = DataOffset currentoffset = AsmListStructListOffset mf(currentoffset)
currentvalue = vf(currentoffset)
currentoffset+=addv AsmListStructListOffsetList = []
AsmListStructListOffsetList.append(currentvalue) while currentvalue!= 0: mf(currentoffset)
currentvalue = vf(currentoffset) if currentvalue!=0:
AsmListStructListOffsetList.append(currentvalue)
currentoffset+=addv return AsmListStructListOffsetList #print len(AsmListStructListOffsetList) #for vv in AsmListStructListOffsetList:
#print hex(vv) def MakeFileItemStruct(FileItemStructOffset): global Is64Bit if Is64Bit == True:
addv = 8
mf=MakeQword
vf=Qword
else:
mf=MakeDword
addv = 4
vf=Dword offset = FileItemStructOffset mf(offset)
FileNameOffset = vf(offset)
FileName = idc.GetString(FileNameOffset)
offset+=addv mf(offset)
FileDataOffset = vf(offset)
offset+=addv mf(offset)
FileSize = vf(offset)
FileSizeOffset = offset
offset+=addv mf(offset)
FileCompressedSize = vf(offset)
FileCompressedSizeOffset = offset
offset+=addv IsGZip = 0 FileDataCompressed = idc.GetManyBytes(FileDataOffset,FileCompressedSize) b1,b2,b3 = struct.unpack('ccc', FileDataCompressed[0:3])
if b1 == '\x1f' and b2 == '\x8b' and b3 == '\x08':
IsGZip = 1
else:
IsGZip = 0 return {\
"FileItemStructOffset":FileItemStructOffset, \
"FileNameOffset":FileNameOffset,\
"FileName":FileName,\
"FileDataOffset":FileDataOffset,\
"FileSize":FileSize,\
"FileSizeOffset":FileSizeOffset,\
"FileCompressedSizeOffset":FileCompressedSizeOffset,\
"FileCompressedSize":FileCompressedSize,\
"IsGZip":IsGZip,\
"FileDataCompressed":FileDataCompressed\
} #Python语言: Python Cookbook: 比系统自带的更加友好的makedir函数
#from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/82465
def _mkdir(newdir):
"""works the way a good mkdir should :)
- already exists, silently complete
- regular file in the way, raise an exception
- parent directory(ies) does not exist, make them as well
"""
if os.path.isdir(newdir):
pass
elif os.path.isfile(newdir):
raise OSError("a file with the same name as the desired " \
"dir, '%s', already exists." % newdir)
else:
head, tail = os.path.split(newdir)
if head and not os.path.isdir(head):
_mkdir(head)
#print "_mkdir %s" % repr(newdir)
if tail:
os.mkdir(newdir)
def DecompressZLib(Data,Path): #compressedstream = StringIO.StringIO(Data)
data2 = zlib.decompress(Data)
f = open(Path, 'wb')
f.write(data2)
f.close()
pass def DecompressGzipTo(Data,Path): compressedstream = StringIO.StringIO(Data)
gziper = gzip.GzipFile(fileobj=compressedstream)
data2 = gziper.read() # 读取解压缩后数据 f = open(Path, 'wb')
f.write(data2)
f.close()
pass def DecompressFileTo(FileItem,OutputDir):
newpath = '{}\\{}'.format(OutputDir, FileItem["FileName"])
#print newpath if FileItem["IsGZip"] == 1:
DecompressGzipTo(FileItem["FileDataCompressed"],newpath)
pass
else:
DecompressZLib(FileItem["FileDataCompressed"],newpath)
pass pass def main():
global Is64Bit
global InputFileType
print("Input File:{}".format(GetInputFile()))
print("Input File Path:{}".format(GetInputFilePath()))
print("Idb File Path:{}".format(GetIdbPath()))
print("cpu_name:{}".format(idc.GetShortPrm(idc.INF_PROCNAME).lower())) InputFileType = idc.GetShortPrm(idc.INF_FILETYPE)
#ida.hpp filetype_t f_PE=11 f_MACHO=25 print("InputFileType:{}".format(filetype_t_map.get(InputFileType, None))) if InputFileType != InputFileType_EXE and InputFileType != InputFileType_MachO:
print "Error,Input file type must is PE or MachO!"
return if (idc.GetShortPrm(idc.INF_LFLAGS) & idc.LFLG_64BIT) == idc.LFLG_64BIT:
Is64Bit = True
else:
Is64Bit = False print("Is64Bit:{}".format(Is64Bit)) OutputDir = '{}_{:%Y%m%d%H%M%S%f}'.format(GetInputFilePath(), datetime.now())
_mkdir(OutputDir)
print("OutputDir:{}".format(OutputDir)) StringEA = FindStringEA()
if StringEA == -1:
print "Can't find StringEA!"
return Func = FindUnFunction(StringEA) if not Func:
print "Can't find Func!"
return FuncName = idc.GetFunctionName(Func.startEA) print "Found Data Function:" + FuncName DataOffset = FindDataOffset(Func.startEA)
if not DataOffset:
print "Can't find DataOffset!"
return print("DataOffset:0x{:016X}".format(DataOffset)); StructOffsetList = GetStructOffsetList(DataOffset) if len(StructOffsetList) == 0:
print "Can't find StructOffsetList!"
return FileItems = [] for StructOffsetItem in StructOffsetList:
FileItemStruct = MakeFileItemStruct(StructOffsetItem)
FileItems.append(FileItemStruct) for FileItem in FileItems: print("FileItemStructOffset:{:016X} FileNameOffset:{:016X} FileDataOffset:{:016X} FileSize:{:016X} FileCompressedSize:{:016X} IsGZip:{} FileName:{}"\
.format( \
FileItem["FileItemStructOffset"] , \
FileItem["FileNameOffset"],\
FileItem["FileDataOffset"],\
FileItem["FileSize"],\
FileItem["FileCompressedSize"],\
FileItem["IsGZip"],\
FileItem["FileName"])) DecompressFileTo(FileItem,OutputDir) if __name__ == "__main__":
main()

被压缩的数据有两种格式,新版和旧版不一样,根据数据的头部几个字节可以判断压缩格式。

MonoTouch 二三事(三)mono mkbundle 打包程序的解包支持的更多相关文章

  1. linux下如何打包压缩?解包解压?.tar文件.gz文件

    ===文件打包.压缩 ==打包 tar [root@521478.com]# tar -cvf etc1.tar /etc //c创建 v详细 f打包后文件名 [root@521478.com]# t ...

  2. nstallShield制作打包程序详解(图)

    InstallShield产品,是安装工具领域事实上的标准.InstallShield 软件是软件安装.配置软件包和升级解决方案领域内公认的标准.InstallShield已经成为安全安装软件的标准解 ...

  3. VS2013(InstallShield2015LimitedEdition)打包程序详解

    VS2012没有自带打包工具,所以要先下载并安装一个打包工具.我采用微软提供的打包工具:  InstallShield2015LimitedEdition.下载地址:https://msdn.micr ...

  4. 【转】 boot.img的解包与打包

    原文网址:http://blog.csdn.net/wh_19910525/article/details/8200372 Android 产品中,内核格式是Linux标准的zImage,根文件系统采 ...

  5. apk 解包 打包

    APK应用程序的解包.修改.编辑.汉化.打包及应用 前两讲主要讲玩机的最基本的知识,集中在如何刷机.本讲是进级的内容,来谈谈与apk应用程序有关的知识,内容包括akp文件的解包.打包.反编辑.解析.汉 ...

  6. 基于RTP的H264视频数据打包解包类

    from:http://blog.csdn.net/dengzikun/article/details/5807694 最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打 ...

  7. Android ROM包定制(解包,增删模块,打包)

    1. 解包 假设有一个名为update.zip的ROM包,我们要在Ubuntu下对它进行定制.首先把system.transfer.list和system.new.dat.br(有些旧版的系统的镜像可 ...

  8. (转)基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

  9. 【FFMPEG】基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

随机推荐

  1. Windows Server 2012 NIC Teaming介绍及注意事项

    Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...

  2. 10个最好用的HTML/CSS 工具、插件和资料库

    大家在使用HTML/CSS开发项目的过程中,有使用过哪些工具,插件和库?下面介绍的10种HTML/CSS工具,插件和资料库,是国外程序员经常用到的. Firebug Lite FirebugLite ...

  3. CSS HTML元素布局及Display属性

    本篇文章主要介绍HTML的内联元素.块级元素的分类与布局,以及dispaly属性对布局的影响. 目录 1. HTML 元素分类:介绍内联元素.块级元素的分类. 2. HTML 元素布局:介绍内联元素. ...

  4. PowerDesigner-VBSrcipt-自动设置主键,外键名等(SQL Server)

    在PowerDesigner中的设计SQL Server 数据表时,要求通过vbScript脚本实现下面的功能: 主键:pk_TableName 外键:fk_TableName_ForeignKeyC ...

  5. MVC还是MVVM?或许VMVC更适合WinForm客户端

    最近开始重构一个稍嫌古老的C/S项目,原先采用的技术栈是『WinForm』+『WCF』+『EF』.相对于现在铺天盖地的B/S架构来说,看上去似乎和Win95一样古老,很多新入行的,可能就没有见过经典的 ...

  6. JS鼠标事件大全 推荐收藏

    一般事件 事件 浏览器支持 描述 onClick HTML: 2 | 3 | 3.2 | 4 Browser: IE3 | N2 | O3 鼠标点击事件,多用在某个对象控制的范围内的鼠标点击 onDb ...

  7. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  8. 【C#】获取网页内容及HTML解析器HtmlAgilityPack的使用

    最近经常需要下载一些东西,而这个下载地址又会经过层层跳转,每个页面上都有很多广告,烦不胜烦,所以做了一个一键获得最终下载地址的小工具.使用C#,来获取网页内容,然后通过HtmlAgilityPack获 ...

  9. qt5中信号和槽的新语法

    qt5中的连接 有下列几种方式可以连接到信号上 旧语法 qt5将继续支持旧的语法去连接,在QObject对象上定义信号和槽函数,及任何继承QObjec的对象(包含QWidget). connect(s ...

  10. Vim新手入门资料和一些Vim实用小技巧

    一些网络上质量较高的Vim资料 从我07年接触Vim以来,已经过去了8个年头,期间看过很多的Vim文章,我自己觉得非常不错,而且创作时间也比较近的文章有如下这些. Vim入门 目前为阿里巴巴高级技术专 ...