MonoTouch 二三事(三)mono mkbundle 打包程序的解包支持

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

玩过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 = 0;

    newargs = (char **) malloc (sizeof (char *) * (argc + 2 + count_mono_options_args ()));

    newargs [k++] = argv [0];

    if (mono_options != NULL) {
        i = 0;
        while (mono_options[i] != NULL)
            newargs[k++] = mono_options[i++];
    }

    newargs [k++] = image_name;

    for (i = 1; 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 = 0;
    while (*ptr++ != NULL)
        nbundles++;

    bundled = (MonoBundledAssembly **) malloc (sizeof (MonoBundledAssembly *) * (nbundles + 1));
    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 != 0) {
            fprintf (stderr, "mkbundle: Error %d decompressing data for %s\n", result, (*ptr)->assembly.name);
            exit (1);
        }
        (*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()

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

时间: 2024-11-05 20:45:46

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

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

InstallShield产品,是安装工具领域事实上的标准.InstallShield 软件是软件安装.配置软件包和升级解决方案领域内公认的标准.InstallShield已经成为安全安装软件的标准解决方案,涉及全球6.9万多个开发组织和5亿台电脑.公司提供广泛的产品和服务,为软件供应商.系统管理员以及最终用户提供成功的销售.管理和应用安装.本文将以InstallShield10.5 Premier Edition为例详述打包的过程.使用工程助手(Project assistant)设计    

安装kali 开机登陆 Ping 主机可达 Ctrl+c键来强行终止 ifconfig 查看网络状态 创建一个用户,并将其纳入root组,然后将用户删除 创建用户, 创建文件夹,文件夹中创建一些文件,将文件夹打包,最后解包到另一个文件中 创建一个文件。

安装linus操作系统 直接按回车确定 Live(amd64),就可以直接自动安装进入操作系统 安装完成后,进入界面. 创建用户zfh,密码123456 将用户zfh删除 利用ifconfig查看网络状态 创建zfh17文件夹,在文件夹里面创建17.txt,和18.txt. 利用rm,删除17.txt 利用ps-ef显示进程 查看磁盘容量 显示目录中所有文件的大小 在zfh200里面创建两个文件夹zfh201和zfh202,将zfh201压缩到zfh202中 解压缩 windows操作系统命令使

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

原文网址:http://blog.csdn.net/wh_19910525/article/details/8200372 Android 产品中,内核格式是Linux标准的zImage,根文件系统采用ramdisk格式.这两者在Android下是直接合并在一起取名为boot.img,会放在一个独立分区当中.这个分区格式是Android自行制定的格式. Android开发时,最标准的做法是重新编译于内核和根文件系统,然后调用Android给的命令行文件mkbootimg(out/host/lin

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

最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现了单个NAL单元包和FU_A分片单元包.对于丢包处理,采用简单的策略:丢弃随后的所有数据包,直到收到关键帧.测试效果还不错,代码贴上来,若能为同道中人借鉴一二,足矣.两个类的使用说明如下(省略了错误处理过程): DWORD H264SSRC ; CH264_RTP_PACK pack ( H264S

压缩/打包/解包 命令详解

压缩&解压 gzip gzip 压缩级别1-9 1 压缩级别最低 9压缩级别最高 [默认级别6] 压缩文件类型.gz 压缩:gzip 压缩文件名 [修改压缩级别方法:gzip -1 压缩文件名称] 解压:gzip -d 解压文件名称/gunzip 解压文件名 查看压缩文件:zcat压缩文件名 gzip 只支持文件,不支持目录 bzip2 压缩文件类型.bz2 [也是无法压缩目录.只能压缩文件] 压缩:bzip2 文件名称 解压:bzip2 -d 文件名称/bunzip2 文件名 查看压缩文件:b

程序猿二三事之Java基础--Java SE 5增加的特性--语法篇(一)

程序猿二三事之Java基础–Java SE 5增加的特性–语法篇(一) [ TOC ] 为什么是Java SE 5? 目前已经到了JDK-8u74了,JDK7的主版本已经于2015年4月停止公开更新. 那为什么还要来说Java/JDK5呢? Java SE在1.4(2002)趋于成熟,随着越来越多应用于开发企业应用,许多框架诞生于这个时期或走向成熟. Java SE 5.0的发布(2004)在语法层面增加了很多特性,让开发更高效,代码更整洁. 自动装箱/拆箱.泛型.注解.for循环增强.枚举.可

程序员英语二三事(4) - 应聘外企常用英语(1) - 从投简历开始

这是<程序员英语二三事>系列文章之一. 应聘外企会有如下几个环节: 投递简历 电话通知或电话面试 现场面试. 拿到Offer入职. 先从投简历开始说起. Resume:你的简历 CV / Cover Letter: 很久很久以前,外国在没有互联网的时候,因为地广人稀,是用传统的信件来应聘.那时候会针对具体每个职位写一封应聘信,附在简历上面.这就是Cover Letter,简称CV.这个传统在互联网时代延续下来了. 那么Resume和CV的区别是什么呢?Resume是通用的简历,每个职位你都可以

攻城狮在路上(叁)Linux(二十七)--- 压缩与打包之常见的压缩命令

前面讲到,linux中的后缀名没有特殊的意义,一般是作为不同文件类型的标识而已.下面是与打包.压缩相关的后缀名介绍: *.z:compress程序压缩文件 *.gz:gzip程序压缩文件 *.bz2:bzip2压缩文件 *.tar:tar打包,未压缩 *.tar.gz:tar打包,经过gzip压缩 *.tar.bz2:tar打包,经过bzip2压缩 一般经过打包或者压缩处理的文件通过后缀名来进行区别. 一.compress:该命令比较古老,centos默认未安装,可以使用gzip代替. 二.gz

转手项目二三事

程序员下班之余除了自己充电学习,一般都期待搞个私活做做,这样的方式给自己加班仿佛有了一层光环:自动回血又加经验.我也是今年才开始做一些个人的小案子,都是朋友的朋友介绍的,没什么价.只是这样的过程还是比较好的:慢慢建立自己的客户,提高自己的代码效率,驱动你去研究一些你不熟悉东西,了解别的行业,积累人脉关系.但遇到转手项目这种案子 营养不多,麻烦却不少. 上次一个朋友介绍一个接口的小差,大概一共就是二十几个方法,还没开始做就打了1000元给我.当时心想:写几个方法给这么多,太多了吧.按照他说的 一两