iOS系统分析(二)Mach-O二进制文件分析

  

➠再度多技术干货请戳:听云博客

图片 1

0x01
 Mach-O格式简单介绍

前言

本篇文章基于Java开发小技巧(二):自定义Maven依赖遭到创造的父工程project-monitor实现,运用我们由定义之靠包进行多工程依赖型的开发。

下面为多但尽Jar包项目之开销也例,进行Maven多工程依赖型支出之教。

Mach-O文件格式是
OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE 文件 与
Linux(其他 Unix like)的 ELF
文件,如果不清为清楚Mach-O的格式和有关内容,那么透彻钻研 xnu
内核就无从谈起。

求分析

首先来拘禁一下封面图,我们只要贯彻之效能是:
1.多个类型共享一个项目的类似与方齐通用内容
2.每个子项目单独导入所要依靠,以及分级填充父项目安排文件被的占据位符
3.子项目仍指定目录结构进行包装
4.所有子项目并打包到一个集合之目,共享依赖包以及布局文件等内容
5.由包后目录结构:

  • bin:存放脚本文件,用来装环境变量,执行相应的jar包
  • lib:依赖、项目jar包
  • etc:配置文件
  • ……

吓哪,目标明确的,接下去要进行具体支出实现了

Mach-O文件之格式如下图所示:

实现

图片 2

装进配置

以达成一样首文章被一度创办好了一个自定义的Maven依赖,相当给一个父项目,接下去我们新建一个子项目,命名也project-onepom.xml文件与老子项目基本相同,所有因包都要独立引入,不同之是大半矣对爸爸项目的依赖:

<dependency>
    <groupId>com.demo</groupId>
    <artifactId>project-monitor</artifactId>
    <version>0.0.1</version>
</dependency>

以及Maven插件maven-assembly-plugin的引入,用来促成依靠之自包以及包装后的目结构:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptors>
            <descriptor>package.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

目结构布局在项目根本目录下package.xml配置文件中:

<assembly xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/assembly-1.0.0.xsd">
    <id>package</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>true</includeBaseDirectory>

    <fileSets>
        <fileSet>
            <directory>src/main/resources</directory>
            <includes>
                <include>*.properties</include>
                <include>*.xml</include>
            </includes>
            <outputDirectory>etc</outputDirectory>
        </fileSet>
    </fileSets>
    <dependencySets>
        <dependencySet>
            <outputDirectory>lib</outputDirectory>
            <scope>runtime</scope>
        </dependencySet>
    </dependencySets>
</assembly>

以上就兑现了类别打包的布工作,实现流程图中最后的花色结构,打包结果也zip文件

产生如下几只有组成:

填充配置文件占位符

事先我们于大项目之spring上下文中定义了一个数量源bean,配置信息应用了占位符填充,所以只要我们怀念如果使用这个bean,就得替换其中的占位符信息,要怎么开吧?
先是当然是创立布局文件了,在路面临开创jdbc.propertiesJDBC配置文件,如:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localdb:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=UTC
jdbc.username=root
jdbc.password=5EF28C5A9A0CE86C2D231A526ED5B388

下一场我们用借助PropertyPlaceholderConfigurer以此看似来实现,在品种spring上下文中定义bean:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:jdbc.properties</value>
    </property>
    <property name="ignoreResourceNotFound" value="false"/>
</bean>

Bingo,占位符修改了,别忘了引入父项目的上下文,毕竟我们如果填写充占位符的bean是于大项目中的:

<import resource="classpath*:project-monitor.xml"/>

1.
Header:保存了Mach-O的有的着力信息,包括了平台、文件类型、LoadCommands的个数等等。

测试Demo

哼了,接下来测试项目的趋向,主要实现采用大项目概念之多少源来对MySql进行询问。
事先创造一个DemoService好像,内容如下:

package com.demo.project.one.service;

import org.apache.log4j.Logger;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DemoService {
    private static final Logger logger = Logger.getLogger(DemoService.class);
    private DataSource dataSource;

    public void queryDb(){
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = dataSource.getConnection();
            stmt = conn.createStatement();
            rs = stmt.executeQuery("select * from movie limit 1");
            while(rs.next()){
                logger.info(String.format("Movie[Id=%d,Title=%s,Director=%s,Genre=%s,Language=%s]",
                        rs.getInt(1),
                        rs.getString(2),
                        rs.getString(3),
                        rs.getString(4),
                        rs.getString(5)
                        ));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if(rs != null){
                    rs.close();
                }
                if(stmt != null){
                    stmt.close();
                }
                if(conn != null){
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

于内外文中为那流dataSource定义:

<bean id="demoService" class="com.demo.project.one.service.DemoService">
    <property name="dataSource" ref="dataSource"/>
</bean>

变化忘了丰富log4j.properties部署文件,这里就是未贴发文件内容了。
OK,新建一个入口类来推行DemoService的测试吧:

package com.demo.project.one;

import com.demo.project.one.service.DemoService;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {
    public static void main(String[] args){
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("/project-one.xml");
        DemoService demoService = (DemoService)context.getBean("demoService");
        demoService.queryDb();
    }
}

说到底的门类组织使图
图片 3

2.
LoadCommands:这同一截紧跟Header,加载Mach-O文件时见面以此的数目来规定内存的遍布。

打包

整套准备妥当,轮至品种自包了,在品种根本目录执行以下Maven命令即可

mvn clean package

推行到位后若晤面发现target目录的构造如图所示
图片 4
project-one.zip就是咱若之自包结果,解压后你会意识,依赖包跟配置文件已在了各自的目录下
图片 5

3.
Data:每一个segment的求实数量还封存于这边,这里带有了现实的代码、数据等等。

执行文书

品种开形成,接下便行顺序了,以Windows平台下吧条例,首先创建bin目,目录中开创批处理文件setenv.batproject-one.bat
以jar包与布局文件的分手,所以我们用指定执行jar包时的classpath,批处理文件内容如下:

0x02
FAT二前进制数据 ,数据结构定义在 \<mach-o/fat.h\>

setenv.bat

@echo off
set SRC_DIR=%cd%

cd %~dp0
cd ..
set MAIN_HOME=%cd%

cd %SRC_DIR%

set BIN_HOME=%MAIN_HOME%\bin
set ETC_HOME=%MAIN_HOME%\etc
set LIB_HOME=%MAIN_HOME%\lib
set CLASSPATH=%ETC_HOME%;%JAVA_HOME%\lib\tools.jar

for %%i in (%LIB_HOME%\*.jar) do set CLASSPATH=!CLASSPATH!;%%i

图片 6

project-one.bat

@echo off
@setlocal enableextensions enabledelayedexpansion
call setenv.bat

chcp 65001
java -cp %CLASSPATH% com.demo.project.one.Application
pause

bat文件之始末即不详细分解,有趣味可以百度了解一下,主要是为拼接出总体的CLASSPATH,执行project-one-0.0.1.jar可执行jar包

图片 7

运行

点击project-one.bat运行程序
图片 8

1.
第一段子也magic 魔数,这里注意大小端,读出来以后需要看下是0xCAFEBABE还是
0xBEBAFECA(否则就算为thin),需要基于此来改变后续读取的字节的配节序。
 可以拘留出来 前4byte 也 0xBEBAFECA ,说明也fat。

结语

种终于开发示范了,这里只是创建一个打而实行jar包的子项目作为例子,你还可创建多个子项目,最后打包的时光合并相同的依和部署文件,然后于bin遇创造相应类别之行文书即可,各个子项目之间为只是进展交互借重。
以上开发技巧不仅适用于可尽jar包的开支,同样适用于WEB项目之开销,具体或由项目要求决定。

章项目源码已披露到Github:https://github.com/ZKHDEV/MultDependPjo

本文为笔者kMacro原创,转载请注明来源:http://www.jianshu.com/p/3fa98dd52520。

2.
亚截也arch
count,也就是该App或dSYM中寓如何CPU架构,比如armv7、arm64当,这个事例中吗2(后4byte
 0x 00 00 00 02),表示包含了点儿种cpu架构。  

  `sizeof(struct fat-header) = 8byte`

3.
延续段落被带有cputype(0x  0C 00  00 01)、cpusubtype (0x 00 00 00
00)、offset (0x 00 10 00  00)、size(0x 00  F0 27
00)等数据,根据fat中的组织定义,依次读取,这里要验证的是,如果只有含有一栽CPU架构的话,是绝非立刻段fat头定义的,可以跨了就一部分,直接读取Arch数据。

   `sizeof(struct fat-arch) = 20byte`

4.
基于fat头中读取的offset数据,我们可过到文件对应之arch数据的位置,当然如果仅来平等栽架构的话语就是无欲计算偏移量了。
下图给出分析的函数

图片 9

0x03
Mach Header二进制数据

经magic我们好分出是32-bit还是64-bit,64-bit多矣4只字节的保留字段,这里同样要小心字节序的问题,也即是判断magic,来规定是不是要换字节序。
 

`sizeof(struct mach-header-64) = 32byte`  ; `sizeof(struct mach-header) = 28byte`

图片 10

根据mach-header与mach-header_64的定义,很显著得视,Headers的第一作用就是是拉系统迅速的定点Mach-O文件之运行条件,文件类型。

图片 11

FileType 

坐Mach-O文件不仅用来实现可执行文件,同时还因此来兑现了其它情节

1.
根本扩展

2.
库文件

3.
CoreDump

4.
 其它

图片 12

脚是有的脍炙人口用到之文件类型

1.
MH-OBJECT    编译过程遭到起的  obj文件 (gcc -c xxx.c
生成xxx.o文件)

2.
MH-EXECUTABLE  可实施二进制文件 (/usr/bin/ls)

3.
MH-CORE      CoreDump (崩溃时之Dump文件)

4.
MH-DYLIB  动态库(/usr/lib/里面的那些共享库文件)

5.
MH-DYLINKER  连接器linker(/usr/lib/dyld文件)

6.
MH-KEXT-BUNDLE   内核扩展文件 (自己付出之简约内核模块)

flags

Mach-O
headers还蕴藏了一些充分要紧之dyld的加载参数。

图片 13

1.
MH-NOUNDEFS   目标没有不定义之符号,不存链接依赖

2.
MH-DYLDLINK     该目标文件是dyld的输入文件,无法被另行的静态链接

3.
MH-PIE      允许擅自的地址空间(开启ASLR  -\>Address Space Layout
Randomization)

4.
MH-ALLOW-STACK-EXECUTION   栈内存可执行代码,一般是默认关闭的。

5.
MH-NO-HEAP-EXECUTION   堆内存无法实施代码

图片 14

0x04
LoadCommands

Load
Commands 直接就同当Header后面,所有command占用内存的总数在Mach-O
Header里面已经让有了。在加载了Header之后即经解析LoadCommand来加载接下去的数了。定义如下:

图片 15

cmd字段

据悉cmd字段的档次不同,使用了不同的函数来加载。简单的排有一致摆放表看无异扣押在基础代码中不同之command类型都起安作用。

1.
LC-SEGMENT;LC-SEGMENT-64   在本中由于load-segment
函数处理(将segment中之数码加载并映射到过程的内存空间去)

2.
LC-LOAD-DYLINKER    在基本中由于load-dylinker
函数处理(调用/usr/lib/dyld程序)

3.
LC-UUID 以基础中出于load-uuid 函数处理 (加载128-bit的唯一ID)

4.
LC-THREAD  于本中出于load-thread 函数处理
(开启一个MACH线程,但是不分配栈空间)

5.
LC-UNIXTHREAD 以根本中出于load-unixthread 函数处理 (开启一个UNIX
posix线程)

6.
LC-CODE-SIGNATURE 于基础中由load-code-signature 函数处理
(进行数字签名)

7.
LC-ENCRYPTION-INFO 以基本中由于 set-code-unprotect 函数处理
(加密二进制文件)

UUID
二进制数据    128byte

UUID是16个字节(128bit)的相同截数据,是文本之绝无仅有标识,前面提到的符号化时,这个UUID必须使跟App二进制文件中之UUID一致,才能够吃正确的符号化。dwarfdump查看的UUID就是马上段数据。读博就片数量时通过Command结构读取的,也就是是率先段(0x0000001B)表示接下去的数据类型,第二段落(0x00000018)数据的轻重(包含Command数据)。 

SymTab
二前行制数据

1.
符号表数据块结构,前亚段子还是是Command数据。后边4段分别吗标记在文书被的偏移量(0x001DF5E0)、符号个数(0x001DF5E0)、字符串在文书中之偏移量(0x0020C3A0)、字符串表大小(0x000729A8)。 

2.
接入下去就是读取Segment和Section数据块了,和方面读取数据块结构同样是依据Command结构读取,下图展示的Segment数据以及Section数据,它们在二进制文件被它是连续的,也就是是各个一样条Segment数据后会尾随多久对应之Section数据,Section的数量总数是经Segment结构中之nsects决定的。 

3.
此自己写了一个简易地Mach-O解析工具 [https://github.com/liutianshx2012/Tmacho\](https://github.com/liutianshx2012/Tmacho)

图片 16

Segment数据

加载数据时,主要加载的就是LC-SEGMET活在LC-SEGMENT_64。其他的Segment的用途在这边不做探索。

LCSEGMENT以及LC-SEGMENT-64
定义如下图。

 

图片 17

图片 18

可观看,这里大部分底数码是因此来增援内核将Segment映射到虚拟内存的。

nsects
字段,标示了Segment中生出微secetion
,section是现实性有因此之数量存放的地方。

TEXT的vmaddr也就是是程序的加载地址;
—DWARF中表明了DWARF数据块的消息,表示dSYM是DWARF格式的数据结构。 

` sizeof(struct segment-command) = 56byte   ;   sizeof(struct segment-command-64) = 72byte`

Section数据

图片 19

由Section数据遭到,我们得以找到—debug-info、—debug-pubnames,
—debug-line等调试信息,通过这些调试信息我们得找到程序中符号的开场地址、变量类型等信息。如果我们而符号化的话,就可以通过分析这些数据获得我们纪念使的音信。

Symbol
数据

由此SymTab中之数足以获Symbol在文件被的位置及个数,Symbol块数据遭到隐含了符号的起始地址、字符串的偏移量等数码,这有些数据结构可以参考\<nlist.h\>

\<stabl.h\>。在马上片数目全读取后,就好读取所有的符号数据了,也即是搭下去的数码。 

Symbol
String 数据

1.
经过SymTab和Symbo中之多少足以得每个符号字符串在文件被的偏移量和大小,每个符号数据是以0末尾的字符串。 

2.
咱们透过以上两有的数据的组成就好拿走每个symbo在次中的加载地址了。这些数据对随后做标记工作还充分的来帮衬。

3.
到者,关于dSYM文件中头部数据读取就完事了。头部数还生照应的数据结构定义,读取时相对会较便于些,解析数据经常要留心字节序的题材,32-bit和64-bit数据结构的歧异、字节长度的差距,DWARF版本的差距,每个数据块之间还是紧密联系的,一个字节的读取偏差就会见造成后续数据的读取错误,正所谓差的毫厘,失之千里。

 

初稿链接:http://blog.tingyun.com/web/article/detail/1341