Android逆向之旅---SO(ELF)文件格式詳解

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/jiangwei0910410003/article/details/49336613

第一、前言

從今天開始我們正式開始Android的逆向之旅,關於逆向的相關知識,想必大家都不陌生了,逆向領域是一個充滿挑戰和神祕的領域。作爲一名Android開發者,每個人都想去探索這個領域,因爲一旦你破解了別人的內容,成就感肯定爆棚,不過相反的是,我們不僅要研究破解之道,也要研究加密之道,因爲加密和破解是相生相剋的。但是我們在破解的過程中可能最頭疼的是native層,也就是so文件的破解。所以我們先來詳細瞭解一下so文件的內容下面就來看看我們今天所要介紹的內容。今天我們先來介紹一下elf文件的格式,因爲我們知道Android中的so文件就是elf文件,所以需要了解so文件,必須先來了解一下elf文件的格式,對於如何詳細瞭解一個elf文件,就是手動的寫一個工具類來解析一個elf文件。

 

第二、準備資料

我們需要了解elf文件的格式,關於elf文件格式詳解,網上已經有很多介紹資料了。這裏我也不做太多的解釋了。不過有兩個資料還是需要介紹一下的,因爲網上的內容真的很多,很雜。這兩個資料是最全的,也是最好的。我就是看這兩個資料來操作的:

第一個資料是非蟲大哥的經典之作:

看吧,是不是超級詳細?後面我們用Java代碼來解析elf文件的時候,就是按照這張圖來的。但是這張圖有些數據結構解釋的還不是很清楚,所以第二個資料來了。

第二個資料:北京大學實驗室出的標準版

http://download.csdn.net/detail/jiangwei0910410003/9204051

這裏就不對這個文件做詳細解釋了,後面在做解析工作的時候,會截圖說明。

關於上面的這兩個資料,這裏還是多數兩句:一定要仔細認真的閱讀。這個是經典之作。也是後面工作的基礎。

 

第三、工具

當然這裏還需要介紹一個工具,因爲這個工具在我們下面解析elf文件的時候,也非常有用,而且是檢查我們解析elf文件的模板。

就是很出名的:readelf命令

不過Window下這個命令不能用,因爲這個命令是Linux的,所以我們還得做個工作就是安裝Cygwin。關於這個工具的安裝,大家可以看看這篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/17710243

不過在下載的過程中,我擔心小朋友們會遇到挫折,所以很貼心的,放到的雲盤裏面:

http://pan.baidu.com/s/1C1Zci

下載下來之後,需要改一個東西才能用:

該一下這個文件:

這個路徑要改成你本地cygwin64中的bin目錄的路徑,不然運行錯誤的。改好之後,直接運行Cygwin.bat就可以了。

關於readelf工具我們這裏不做太詳細的介紹,只介紹我們要用到的命令:

1、readelf -h xxx.so

查看so文件的頭部信息

 

2、readelf -S xxx.so

查看so文件的段(Section)頭的信息

 

3、readelf -l xxx.so

查看so文件的程序段頭信息(Program)

 

4、readelf -a xxx.so

查看so文件的全部內容

 

還有很多命令用法,這裏就不在細說了,網上有很多介紹的~~

 

第四、實際操作解析Elf文件(Java代碼&C++代碼)

上面我們介紹了elf文件格式資料,elf文件的工具,那麼下面我們就來實際操作一下,來用Java代碼手把手的解析一個libhello-jni.so文件。關於這個libhello-jni.so文件的下載地址:

http://download.csdn.net/detail/jiangwei0910410003/9204087

1、首先定義elf文件中各個結構體內容

這個我們需要參考elf.h這個頭文件的格式了。這個文件網上也是有的,這裏還是給個下載鏈接吧:

http://download.csdn.net/detail/jiangwei0910410003/9204081

我們看看Java中定義的elf文件的數據結構類:

package com.demo.parseso;
 
import java.util.ArrayList;
 
public class ElfType32 {
	
	public elf32_rel rel;
	public elf32_rela rela;
	public ArrayList<Elf32_Sym> symList = new ArrayList<Elf32_Sym>();
	public elf32_hdr hdr;//elf頭部信息
	public ArrayList<elf32_phdr> phdrList = new ArrayList<elf32_phdr>();//可能會有多個程序頭
	public ArrayList<elf32_shdr> shdrList = new ArrayList<elf32_shdr>();//可能會有多個段頭
	public ArrayList<elf32_strtb> strtbList = new ArrayList<elf32_strtb>();//可能會有多個字符串值
	
	public ElfType32() {
		rel = new elf32_rel();
		rela = new elf32_rela();
		hdr = new elf32_hdr();
	}
	
	/**
	 *  typedef struct elf32_rel {
		  Elf32_Addr	r_offset;
		  Elf32_Word	r_info;
		} Elf32_Rel;
	 *
	 */
	public class elf32_rel {
		public byte[] r_offset = new byte[4];
		public byte[] r_info = new byte[4];
		
		@Override
		public String toString(){
			return "r_offset:"+Utils.bytes2HexString(r_offset)+";r_info:"+Utils.bytes2HexString(r_info);
		}
	}
	
	/**
	 *  typedef struct elf32_rela{
		  Elf32_Addr	r_offset;
		  Elf32_Word	r_info;
		  Elf32_Sword	r_addend;
		} Elf32_Rela;
	 */
	public class elf32_rela{
		public byte[] r_offset = new byte[4];
		public byte[] r_info = new byte[4];
		public byte[] r_addend = new byte[4];
		
		@Override
		public String toString(){
			return "r_offset:"+Utils.bytes2HexString(r_offset)+";r_info:"+Utils.bytes2HexString(r_info)+";r_addend:"+Utils.bytes2HexString(r_info);
		}
	}
	
	/**
	 * typedef struct elf32_sym{
		  Elf32_Word	st_name;
		  Elf32_Addr	st_value;
		  Elf32_Word	st_size;
		  unsigned char	st_info;
		  unsigned char	st_other;
		  Elf32_Half	st_shndx;
		} Elf32_Sym;
	 */
	public static class Elf32_Sym{
		public byte[] st_name = new byte[4];
		public byte[] st_value = new byte[4];
		public byte[] st_size = new byte[4];
		public byte st_info;
		public byte st_other;
		public byte[] st_shndx = new byte[2];
		
		@Override
		public String toString(){
			return "st_name:"+Utils.bytes2HexString(st_name)
					+"\nst_value:"+Utils.bytes2HexString(st_value)
					+"\nst_size:"+Utils.bytes2HexString(st_size)
					+"\nst_info:"+(st_info/16)
					+"\nst_other:"+(((short)st_other) & 0xF)
					+"\nst_shndx:"+Utils.bytes2HexString(st_shndx);
		}
	}
	
	public void printSymList(){
		for(int i=0;i<symList.size();i++){
			System.out.println();
			System.out.println("The "+(i+1)+" Symbol Table:");
			System.out.println(symList.get(i).toString());
		}
	}
	
	//Bind字段==》st_info
	public static final int STB_LOCAL = 0;
	public static final int STB_GLOBAL = 1;
	public static final int STB_WEAK = 2;
	//Type字段==》st_other
	public static final int STT_NOTYPE = 0;
	public static final int STT_OBJECT = 1;
	public static final int STT_FUNC = 2;
	public static final int STT_SECTION = 3;
	public static final int STT_FILE = 4;
	/**
	 * 這裏需要注意的是還需要做一次轉化
	 *  #define ELF_ST_BIND(x)	((x) >> 4)
		#define ELF_ST_TYPE(x)	(((unsigned int) x) & 0xf)
	 */
	
	/**
	 * typedef struct elf32_hdr{
		  unsigned char	e_ident[EI_NIDENT];
		  Elf32_Half	e_type;
		  Elf32_Half	e_machine;
		  Elf32_Word	e_version;
		  Elf32_Addr	e_entry;  // Entry point
		  Elf32_Off	e_phoff;
		  Elf32_Off	e_shoff;
		  Elf32_Word	e_flags;
		  Elf32_Half	e_ehsize;
		  Elf32_Half	e_phentsize;
		  Elf32_Half	e_phnum;
		  Elf32_Half	e_shentsize;
		  Elf32_Half	e_shnum;
		  Elf32_Half	e_shstrndx;
		} Elf32_Ehdr;
	 */
	public class elf32_hdr{
		public byte[] e_ident = new byte[16];
		public byte[] e_type = new byte[2];
		public byte[] e_machine = new byte[2];
		public byte[] e_version = new byte[4];
		public byte[] e_entry = new byte[4];
		public byte[] e_phoff = new byte[4];
		public byte[] e_shoff = new byte[4];
		public byte[] e_flags = new byte[4];
		public byte[] e_ehsize = new byte[2];
		public byte[] e_phentsize = new byte[2];
		public byte[] e_phnum = new byte[2];
		public byte[] e_shentsize = new byte[2];
		public byte[] e_shnum = new byte[2];
		public byte[] e_shstrndx = new byte[2];
		
		@Override
		public String toString(){
			return  "magic:"+ Utils.bytes2HexString(e_ident) 
					+"\ne_type:"+Utils.bytes2HexString(e_type)
					+"\ne_machine:"+Utils.bytes2HexString(e_machine)
					+"\ne_version:"+Utils.bytes2HexString(e_version)
					+"\ne_entry:"+Utils.bytes2HexString(e_entry)
					+"\ne_phoff:"+Utils.bytes2HexString(e_phoff)
					+"\ne_shoff:"+Utils.bytes2HexString(e_shoff)
					+"\ne_flags:"+Utils.bytes2HexString(e_flags)
					+"\ne_ehsize:"+Utils.bytes2HexString(e_ehsize)
					+"\ne_phentsize:"+Utils.bytes2HexString(e_phentsize)
					+"\ne_phnum:"+Utils.bytes2HexString(e_phnum)
					+"\ne_shentsize:"+Utils.bytes2HexString(e_shentsize)
					+"\ne_shnum:"+Utils.bytes2HexString(e_shnum)
					+"\ne_shstrndx:"+Utils.bytes2HexString(e_shstrndx);
		}
	}
	
	/**
	 * typedef struct elf32_phdr{
		  Elf32_Word	p_type;
		  Elf32_Off	p_offset;
		  Elf32_Addr	p_vaddr;
		  Elf32_Addr	p_paddr;
		  Elf32_Word	p_filesz;
		  Elf32_Word	p_memsz;
		  Elf32_Word	p_flags;
		  Elf32_Word	p_align;
		} Elf32_Phdr;
	 */
	public static class elf32_phdr{
		public byte[] p_type = new byte[4];
		public byte[] p_offset = new byte[4];
		public byte[] p_vaddr = new byte[4];
		public byte[] p_paddr = new byte[4];
		public byte[] p_filesz = new byte[4];
		public byte[] p_memsz = new byte[4];
		public byte[] p_flags = new byte[4];
		public byte[] p_align = new byte[4];
		
		@Override
		public String toString(){
			return "p_type:"+ Utils.bytes2HexString(p_type)
					+"\np_offset:"+Utils.bytes2HexString(p_offset)
					+"\np_vaddr:"+Utils.bytes2HexString(p_vaddr)
					+"\np_paddr:"+Utils.bytes2HexString(p_paddr)
					+"\np_filesz:"+Utils.bytes2HexString(p_filesz)
					+"\np_memsz:"+Utils.bytes2HexString(p_memsz)
					+"\np_flags:"+Utils.bytes2HexString(p_flags)
					+"\np_align:"+Utils.bytes2HexString(p_align);
		}
	}
	
	public void printPhdrList(){
		for(int i=0;i<phdrList.size();i++){
			System.out.println();
			System.out.println("The "+(i+1)+" Program Header:");
			System.out.println(phdrList.get(i).toString());
		}
	}
	
	/**
	 * typedef struct elf32_shdr {
		  Elf32_Word	sh_name;
		  Elf32_Word	sh_type;
		  Elf32_Word	sh_flags;
		  Elf32_Addr	sh_addr;
		  Elf32_Off	sh_offset;
		  Elf32_Word	sh_size;
		  Elf32_Word	sh_link;
		  Elf32_Word	sh_info;
		  Elf32_Word	sh_addralign;
		  Elf32_Word	sh_entsize;
		} Elf32_Shdr;
	 */
	public static class elf32_shdr{
		public byte[] sh_name = new byte[4];
		public byte[] sh_type = new byte[4];
		public byte[] sh_flags = new byte[4];
		public byte[] sh_addr = new byte[4];
		public byte[] sh_offset = new byte[4];
		public byte[] sh_size = new byte[4];
		public byte[] sh_link = new byte[4];
		public byte[] sh_info = new byte[4];
		public byte[] sh_addralign = new byte[4];
		public byte[] sh_entsize = new byte[4];
		
		@Override
		public String toString(){
			return "sh_name:"+Utils.bytes2HexString(sh_name)/*Utils.byte2Int(sh_name)*/
					+"\nsh_type:"+Utils.bytes2HexString(sh_type)
					+"\nsh_flags:"+Utils.bytes2HexString(sh_flags)
					+"\nsh_add:"+Utils.bytes2HexString(sh_addr)
					+"\nsh_offset:"+Utils.bytes2HexString(sh_offset)
					+"\nsh_size:"+Utils.bytes2HexString(sh_size)
					+"\nsh_link:"+Utils.bytes2HexString(sh_link)
					+"\nsh_info:"+Utils.bytes2HexString(sh_info)
					+"\nsh_addralign:"+Utils.bytes2HexString(sh_addralign)
					+"\nsh_entsize:"+ Utils.bytes2HexString(sh_entsize);
		}
	}
	
	/****************sh_type********************/
	public static final int SHT_NULL = 0;
	public static final int SHT_PROGBITS = 1;
	public static final int SHT_SYMTAB = 2;
	public static final int SHT_STRTAB = 3;
	public static final int SHT_RELA = 4;
	public static final int SHT_HASH = 5;
	public static final int SHT_DYNAMIC = 6;
	public static final int SHT_NOTE = 7;
	public static final int SHT_NOBITS = 8;
	public static final int SHT_REL = 9;
	public static final int SHT_SHLIB = 10;
	public static final int SHT_DYNSYM = 11;
	public static final int SHT_NUM = 12;
	public static final int SHT_LOPROC = 0x70000000;
	public static final int SHT_HIPROC = 0x7fffffff;
	public static final int SHT_LOUSER = 0x80000000;
	public static final int SHT_HIUSER = 0xffffffff;
	public static final int SHT_MIPS_LIST = 0x70000000;
	public static final int SHT_MIPS_CONFLICT = 0x70000002;
	public static final int SHT_MIPS_GPTAB = 0x70000003;
	public static final int SHT_MIPS_UCODE = 0x70000004;
	
	/*****************sh_flag***********************/
	public static final int SHF_WRITE = 0x1;
	public static final int SHF_ALLOC = 0x2;
	public static final int SHF_EXECINSTR = 0x4;
	public static final int SHF_MASKPROC = 0xf0000000;
	public static final int SHF_MIPS_GPREL = 0x10000000;
	
	public void printShdrList(){
		for(int i=0;i<shdrList.size();i++){
			System.out.println();
			System.out.println("The "+(i+1)+" Section Header:");
			System.out.println(shdrList.get(i));
		}
	}
	
	
	public static class elf32_strtb{
		public byte[] str_name;
		public int len;
		
		@Override
		public String toString(){
			return "str_name:"+str_name
					+"len:"+len;
		}
	}
}

這個沒什麼問題,也沒難度,就是在看elf.h文件中定義的數據結構的時候,要記得每個字段的佔用字節數就可以了。

 

有了結構定義,下面就來看看如何解析吧。

在解析之前我們需要將so文件讀取到byte[]中,定義一個數據結構類型

public static ElfType32 type_32 = new ElfType32();
 
byte[] fileByteArys = Utils.readFile("so/libhello-jni.so");
if(fileByteArys == null){
	System.out.println("read file byte failed...");
	return;
}

2、解析elf文件的頭部信息

關於這些字段的解釋,要看上面提到的那個pdf文件中的描述

這裏我們介紹幾個重要的字段,也是我們後面修改so文件的時候也會用到:

1)、e_phoff

這個字段是程序頭(Program Header)內容在整個文件的偏移值,我們可以用這個偏移值來定位程序頭的開始位置,用於解析程序頭信息

2)、e_shoff

這個字段是段頭(Section Header)內容在這個文件的偏移值,我們可以用這個偏移值來定位段頭的開始位置,用於解析段頭信息

3)、e_phnum

這個字段是程序頭的個數,用於解析程序頭信息

4)、e_shnum

這個字段是段頭的個數,用於解析段頭信息

5)、e_shstrndx

這個字段是String段在整個段列表中的索引值,這個用於後面定位String段的位置

 

按照上面的圖我們就可以很容易的解析

/**
 * 解析Elf的頭部信息
 * @param header
 */
private static void  parseHeader(byte[] header, int offset){
	if(header == null){
		System.out.println("header is null");
		return;
	}
	/**
	 *  public byte[] e_ident = new byte[16];
			public short e_type;
			public short e_machine;
			public int e_version;
			public int e_entry;
			public int e_phoff;
			public int e_shoff;
			public int e_flags;
			public short e_ehsize;
			public short e_phentsize;
			public short e_phnum;
			public short e_shentsize;
			public short e_shnum;
			public short e_shstrndx;
	 */
	type_32.hdr.e_ident = Utils.copyBytes(header, 0, 16);//魔數
	type_32.hdr.e_type = Utils.copyBytes(header, 16, 2);
	type_32.hdr.e_machine = Utils.copyBytes(header, 18, 2);
	type_32.hdr.e_version = Utils.copyBytes(header, 20, 4);
	type_32.hdr.e_entry = Utils.copyBytes(header, 24, 4);
	type_32.hdr.e_phoff = Utils.copyBytes(header, 28, 4);
	type_32.hdr.e_shoff = Utils.copyBytes(header, 32, 4);
	type_32.hdr.e_flags = Utils.copyBytes(header, 36, 4);
	type_32.hdr.e_ehsize = Utils.copyBytes(header, 40, 2);
	type_32.hdr.e_phentsize = Utils.copyBytes(header, 42, 2);
	type_32.hdr.e_phnum = Utils.copyBytes(header, 44,2);
	type_32.hdr.e_shentsize = Utils.copyBytes(header, 46,2);
	type_32.hdr.e_shnum = Utils.copyBytes(header, 48, 2);
	type_32.hdr.e_shstrndx = Utils.copyBytes(header, 50, 2);
}

按照對應的每個字段的字節個數,讀取byte就可以了。

 

 

3、解析段頭(Section Header)信息



這個結構中字段見pdf中的描述吧,這裏就不做解釋了。後面我們會手動的構造這樣的一個數據結構,到時候在詳細說明每個字段含義。

按照這個結構。我們解析也簡單了:

 

這裏需要注意的是,我們看到的Section Header一般都是多個的,這裏用一個List來保存

/**
 * 解析段頭信息內容
 */
public static void parseSectionHeaderList(byte[] header, int offset){
	int header_size = 40;//40個字節
	int header_count = Utils.byte2Short(type_32.hdr.e_shnum);//頭部的個數
	byte[] des = new byte[header_size];
	for(int i=0;i<header_count;i++){
		System.arraycopy(header, i*header_size + offset, des, 0, header_size);
		type_32.shdrList.add(parseSectionHeader(des));
	}
}
 
private static elf32_shdr parseSectionHeader(byte[] header){
	ElfType32.elf32_shdr shdr = new ElfType32.elf32_shdr();
	/**
	 *  public byte[] sh_name = new byte[4];
			public byte[] sh_type = new byte[4];
			public byte[] sh_flags = new byte[4];
			public byte[] sh_addr = new byte[4];
			public byte[] sh_offset = new byte[4];
			public byte[] sh_size = new byte[4];
			public byte[] sh_link = new byte[4];
			public byte[] sh_info = new byte[4];
			public byte[] sh_addralign = new byte[4];
			public byte[] sh_entsize = new byte[4];
	 */
	shdr.sh_name = Utils.copyBytes(header, 0, 4);
	shdr.sh_type = Utils.copyBytes(header, 4, 4);
	shdr.sh_flags = Utils.copyBytes(header, 8, 4);
	shdr.sh_addr = Utils.copyBytes(header, 12, 4);
	shdr.sh_offset = Utils.copyBytes(header, 16, 4);
	shdr.sh_size = Utils.copyBytes(header, 20, 4);
	shdr.sh_link = Utils.copyBytes(header, 24, 4);
	shdr.sh_info = Utils.copyBytes(header, 28, 4);
	shdr.sh_addralign = Utils.copyBytes(header, 32, 4);
	shdr.sh_entsize = Utils.copyBytes(header, 36, 4);
	return shdr;
}

 

4、解析程序頭(Program Header)信息


這裏的字段,這裏也不做解釋了,看pdf文檔。

我們按照這個結構來進行解析:

/**
 * 解析程序頭信息
 * @param header
 */
public static void parseProgramHeaderList(byte[] header, int offset){
	int header_size = 32;//32個字節
	int header_count = Utils.byte2Short(type_32.hdr.e_phnum);//頭部的個數
	byte[] des = new byte[header_size];
	for(int i=0;i<header_count;i++){
		System.arraycopy(header, i*header_size + offset, des, 0, header_size);
		type_32.phdrList.add(parseProgramHeader(des));
	}
}
 
private static elf32_phdr parseProgramHeader(byte[] header){
	/**
	 *  public int p_type;
			public int p_offset;
			public int p_vaddr;
			public int p_paddr;
			public int p_filesz;
			public int p_memsz;
			public int p_flags;
			public int p_align;
	 */
	ElfType32.elf32_phdr phdr = new ElfType32.elf32_phdr();
	phdr.p_type = Utils.copyBytes(header, 0, 4);
	phdr.p_offset = Utils.copyBytes(header, 4, 4);
	phdr.p_vaddr = Utils.copyBytes(header, 8, 4);
	phdr.p_paddr = Utils.copyBytes(header, 12, 4);
	phdr.p_filesz = Utils.copyBytes(header, 16, 4);
	phdr.p_memsz = Utils.copyBytes(header, 20, 4);
	phdr.p_flags = Utils.copyBytes(header, 24, 4);
	phdr.p_align = Utils.copyBytes(header, 28, 4);
	return phdr;
 
}

當然還有其他結構的解析工作,這裏就不在一一介紹了,因爲這些結構我們在後面的介紹中不會用到,但是也是需要了解的,詳細參見pdf文檔。

 

5、驗證解析結果

那麼上面我們的解析工作做完了,爲了驗證我們的解析工作是否正確,我們需要給每個結構定義個打印函數,也就是從寫toString方法即可。

然後我們在使用readelf工具來查看so文件的各個結構內容,對比就可以知道解析的是否成功了。

 

解析代碼下載地址:https://github.com/fourbrother/parse_androidso

 

上面我們用的是Java代碼來進行解析的,爲了照顧廣大程序猿,所以給出一個C++版本的解析類:
 

#include<iostream.h>
#include<string.h>
#include<stdio.h>
#include "elf.h"
 
/**
	非常重要的一個宏,功能很簡單:
	P:需要對其的段地址
	ALIGNBYTES:對其的字節數
	功能:將P值補充到時ALIGNBYTES的整數倍
	這個函數也叫:頁面對其函數
	eg: 0x3e45/0x1000 == >0x4000
	
*/
#define ALIGN(P, ALIGNBYTES)  ( ((unsigned long)P + ALIGNBYTES -1)&~(ALIGNBYTES-1) )
 
int addSectionFun(char*, char*, unsigned int);
 
int main()
{
	addSectionFun("D:\libhello-jni.so", ".jiangwei", 0x1000);
	return 0;
}
 
int addSectionFun(char *lpPath, char *szSecname, unsigned int nNewSecSize)
{
	char name[50];
	FILE *fdr, *fdw;
	char *base = NULL;
	Elf32_Ehdr *ehdr;
	Elf32_Phdr *t_phdr, *load1, *load2, *dynamic;
	Elf32_Shdr *s_hdr;
	int flag = 0;
	int i = 0;
	unsigned mapSZ = 0;
	unsigned nLoop = 0;
	unsigned int nAddInitFun = 0;
	unsigned int nNewSecAddr = 0;
	unsigned int nModuleBase = 0;
	memset(name, 0, sizeof(name));
	if(nNewSecSize == 0)
	{
		return 0;
	}
	fdr = fopen(lpPath, "rb");
	strcpy(name, lpPath);
	if(strchr(name, '.'))
	{
		strcpy(strchr(name, '.'), "_new.so");
	}
	else
	{
		strcat(name, "_new");
	}
	fdw = fopen(name, "wb");
	if(fdr == NULL || fdw == NULL)
	{
		printf("Open file failed");
		return 1;
	}
	fseek(fdr, 0, SEEK_END);
	mapSZ = ftell(fdr);//源文件的長度大小
	printf("mapSZ:0x%x\n", mapSZ);
 
	base = (char*)malloc(mapSZ * 2 + nNewSecSize);//2*源文件大小+新加的Section size
	printf("base 0x%x \n", base);
 
	memset(base, 0, mapSZ * 2 + nNewSecSize);
	fseek(fdr, 0, SEEK_SET);
	fread(base, 1, mapSZ, fdr);//拷貝源文件內容到base
	if(base == (void*) -1)
	{
		printf("fread fd failed");
		return 2;
	}
 
	//判斷Program Header
	ehdr = (Elf32_Ehdr*) base;
	t_phdr = (Elf32_Phdr*)(base + sizeof(Elf32_Ehdr));
	for(i=0;i<ehdr->e_phnum;i++)
	{
		if(t_phdr->p_type == PT_LOAD)
		{
			//這裏的flag只是一個標誌位,去除第一個LOAD的Segment的值
			if(flag == 0)
			{
				load1 = t_phdr;
				flag = 1;
				nModuleBase = load1->p_vaddr;
				printf("load1 = %p, offset = 0x%x \n", load1, load1->p_offset);
 
			}
			else
			{
				load2 = t_phdr;
				printf("load2 = %p, offset = 0x%x \n", load2, load2->p_offset);
			}
		}
		if(t_phdr->p_type == PT_DYNAMIC)
		{
			dynamic = t_phdr;
			printf("dynamic = %p, offset = 0x%x \n", dynamic, dynamic->p_offset);
		}
		t_phdr ++;
	}
 
	//section header
	s_hdr = (Elf32_Shdr*)(base + ehdr->e_shoff);
	//獲取到新加section的位置,這個是重點,需要進行頁面對其操作
	printf("addr:0x%x\n",load2->p_paddr);
	nNewSecAddr = ALIGN(load2->p_paddr + load2->p_memsz - nModuleBase, load2->p_align);
	printf("new section add:%x \n", nNewSecAddr);
 
	if(load1->p_filesz < ALIGN(load2->p_paddr + load2->p_memsz, load2->p_align) )
	{
		printf("offset:%x\n",(ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shnum));
		//注意這裏的代碼的執行條件,這裏其實就是判斷section header是不是在文件的末尾
		if( (ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shnum) != mapSZ)
		{
			if(mapSZ + sizeof(Elf32_Shdr) * (ehdr->e_shnum + 1) > nNewSecAddr)
			{
				printf("無法添加節\n");
				return 3;
			}
			else
			{
				memcpy(base + mapSZ, base + ehdr->e_shoff, sizeof(Elf32_Shdr) * ehdr->e_shnum);//將Section Header拷貝到原來文件的末尾
				ehdr->e_shoff = mapSZ;
				mapSZ += sizeof(Elf32_Shdr) * ehdr->e_shnum;//加上Section Header的長度
				s_hdr = (Elf32_Shdr*)(base + ehdr->e_shoff);
				printf("ehdr_offset:%x",ehdr->e_shoff);
			}
		}
	}
	else
	{
		nNewSecAddr = load1->p_filesz;
	}
	printf("還可添加 %d 個節\n", (nNewSecAddr - ehdr->e_shoff) / sizeof(Elf32_Shdr) - ehdr->e_shnum - 1);
 
	int nWriteLen = nNewSecAddr + ALIGN(strlen(szSecname) + 1, 0x10) + nNewSecSize;//添加section之後的文件總長度:原來的長度 + section name + section size
	printf("write len %x\n",nWriteLen);
 
	char *lpWriteBuf = (char *)malloc(nWriteLen);//nWriteLen :最後文件的總大小
	memset(lpWriteBuf, 0, nWriteLen);
	//ehdr->e_shstrndx是section name的string表在section表頭中的偏移值,修改string段的大小
	s_hdr[ehdr->e_shstrndx].sh_size = nNewSecAddr - s_hdr[ehdr->e_shstrndx].sh_offset + strlen(szSecname) + 1;
	strcpy(lpWriteBuf + nNewSecAddr, szSecname);//添加section name
	
	//以下代碼是構建一個Section Header
	Elf32_Shdr newSecShdr = {0};
	newSecShdr.sh_name = nNewSecAddr - s_hdr[ehdr->e_shstrndx].sh_offset;
	newSecShdr.sh_type = SHT_PROGBITS;
	newSecShdr.sh_flags = SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR;
	nNewSecAddr += ALIGN(strlen(szSecname) + 1, 0x10);
	newSecShdr.sh_size = nNewSecSize;
	newSecShdr.sh_offset = nNewSecAddr;
	newSecShdr.sh_addr = nNewSecAddr + nModuleBase;
	newSecShdr.sh_addralign = 4;
 
	//修改Program Header信息
	load1->p_filesz = nWriteLen;
	load1->p_memsz = nNewSecAddr + nNewSecSize;
	load1->p_flags = 7;		//可讀 可寫 可執行
 
	//修改Elf header中的section的count值
	ehdr->e_shnum++;
	memcpy(lpWriteBuf, base, mapSZ);//從base中拷貝mapSZ長度的字節到lpWriteBuf
	memcpy(lpWriteBuf + mapSZ, &newSecShdr, sizeof(Elf32_Shdr));//將新加的Section Header追加到lpWriteBuf末尾
	
	//寫文件
	fseek(fdw, 0, SEEK_SET);
	fwrite(lpWriteBuf, 1, nWriteLen, fdw);
	fclose(fdw);
	fclose(fdr);
	free(base);
	free(lpWriteBuf);
	return 0;
}

看了C++代碼解析之後,這裏不得不多說兩句了,看看C++中的代碼多麼簡單,原因很簡單:在做文件字節操作的時候,C++中的指針真的很牛逼的,這個也是Java望成莫及的。。

 

C++代碼下載:http://download.csdn.net/detail/jiangwei0910410003/9204139
 

第五、總結

關於Elf文件的格式,就介紹到這裏,通過自己寫一個解析類的話,可以很深刻的瞭解elf文件的格式,所以我們在以後遇到一個文件格式的瞭解過程中,最好的方式就是手動的寫一個工具類就好了。那麼這篇文章是逆向之旅的第一篇,也是以後篇章的基礎,下面一篇文章我們會介紹如何來手動的在elf中添加一個段數據結構,盡情期待~~

 

轉:https://www.jianshu.com/p/a2d87d9467cc

So文件添加Section段詳細說明

簡介

在逆向Android底層時,一般都或多或少的接觸so文件,需要逆向so文件,一般的方法是往so文件植入我們的調試的代碼;而通常都是通過添加section段來植入代碼;查看本篇文章之前你需要先了解elf文件格式,so文件就是採用這種格式的

簡單介紹elf文件格式

elf文件一般是由elf文件頭、program header頭(多個)、section段(多個)和section header頭(多個組成)。

program header

程序運行時,定位文件中各個段的位置,提供創建程序映像的具體信息;一個program header指向一個segment(運行時的稱呼),而一個segement可能包含多個section 段

1.png

section header

執行之前描述文件結構的,理論上來說,Linux運行期間不需要section header頭的,可以刪掉;一個section header頭對應一個section 段,所以添加一個section段時還要添加一個section header頭

section段

section段是elf文件的主題,很多內容都保存在裏面,包括我們熟悉的.text、.rodata、.data等等,還有我們不熟悉的.got(全局偏移表)、.plt(過程鏈接表)等等,這兩個表主要用於函數和全局變量的調用,可參考這個鏈接理解這兩個表,下面是展示部分的section header頭:

1.png

進入主題 -- 添加section字段

當ELF文件被加載到內存中後,系統會將多個具有相同權限(flg值)section合併一個segment。操作系統往往以頁爲基本單位來管理內存分配,一般頁的大小爲4096B,即4KB的大小。同時,內存的權限管理的粒度也是以頁爲單位,頁內的內存是具有同樣的權限等屬性,並且操作系統對內存的管理往往追求高效和高利用率這樣的目標。ELF文件在被映射時,是以系統的頁長度爲單位的,那麼每個section在映射時的長度都是系統頁長度的整數倍,如果section的長度不是其整數倍,則導致多餘部分也將佔用一個頁。所以section段的位置和長度不是隨便添加的,要根據program header頭的align對齊方式來添加

確定添加位置

新加入的section一般是在文件末尾,但是這裏不是真正的文件末尾,而是將文件加載到內存映像中的末尾,一般等於虛擬地址vaddr+佔用空間大小memsiz,但是這樣還不行,還要做字節對其,我們知道文件末尾不等於映像末尾,一般這個映像末尾比文件末尾大,因爲操作系統對內存都是以頁的粒度來操作的,所以我們添加section段要找到真正的映像末尾,操作系統會把program header的type爲LOAD的段加入內存映像中,而LOAD類型的program header一般都是做升序排列,我們只需要取最後一個LOAD就可以,讓vaddr+memsiz在和align做對齊操作就能夠得到映像末尾,算法如下:

/**
     * 對其算法 --- 保證addr能被align整除,系統按align字節對齊的訪問方式
     * 返回一個addr是align的整數倍的值
     * @param addr:vaddr + memsi
     * @param align:對其的字節數
     * @return
     */
    public static int align(int addr, int align){
        if(align > addr){    //這種情況不好弄,我也不知道怎麼辦
            return addr;
        }
        int offset = addr % align;
        return addr + (align-offset);
    }

1.png

添加section header頭

添加section header頭的目的是把5000H的表名和5010H的內容聯繫起來;先看section header的結構:

typedef struct{
    Elf32_Word sh_name;
    Elf32_Word sh_type;
    Elf32_Word sh_flags;
    Elf32_Addr sh_addr;
    Elf32_Off  sh_offset;
    Elf32_Word sh_size;
    Elf32_Word sh_link;
    Elf32_Word sh_info;
    Elf32_Word sh_addralign;
    Elf32_Word sh_entsize;
}Elf32_Shdr;

sh_name : 是一個字符串表的索引偏移(5000H - 第一個字符串表的位置,第一個字符串表位置等於)

sh_addr和 sh_offset填上一個步驟得到的地址即可

sh_size: 就是我們填入的section長度

sh_type:SHT_PROGBITS 1 此節區包含程序定義的信息,其格式和含義都由程序來解釋。

sh_flags:

名稱 取值 意義
SHF_WRITE 0x1 節區包含進程執行過程中將可寫的數據
SHF_ALLOC 0x2 節區在進程執行過程中佔用內存。某些控制節區並不出現於目標文件的內存映像中,對於那些節區,此位應設置爲 0
SHF_EXECINSTR 0x4 節區包含可執行的機器指令
SHF_MASKPROC 0xF0000000 所有包含於此掩碼中的四位都用於處理器專用的語義

其他的幾個可以不用處理,以下是示例代碼:

public static byte[] addSectionHeader(byte[] src){
                /**
                 *  public byte[] sh_name = new byte[4];
                 public byte[] sh_type = new byte[4];
                 public byte[] sh_flags = new byte[4];
                 public byte[] sh_addr = new byte[4];
                 public byte[] sh_offset = new byte[4];
                 public byte[] sh_size = new byte[4];
                 public byte[] sh_link = new byte[4];
                 public byte[] sh_info = new byte[4];
                 public byte[] sh_addralign = new byte[4];
                 public byte[] sh_entsize = new byte[4];
                 */
                byte[] newHeader = new byte[sectionSize];

                //構建一個New Section Header
                newHeader = Utils.replaceByteAry(newHeader, 0, Utils.int2Byte(addSectionStartAddr - stringSectionOffset));
                newHeader = Utils.replaceByteAry(newHeader, 4, Utils.int2Byte(ElfType32.SHT_PROGBITS));                         //type=PROGBITS
                newHeader = Utils.replaceByteAry(newHeader, 8, Utils.int2Byte(ElfType32.SHF_ALLOC + ElfType32.SHF_WRITE));              //改字節區可以被寫入
                newHeader = Utils.replaceByteAry(newHeader, 12, Utils.int2Byte(addSectionStartAddr+0x10));                        //所代表的字節區其實地址
                newHeader = Utils.replaceByteAry(newHeader, 16, Utils.int2Byte(addSectionStartAddr+0x10));
                newHeader = Utils.replaceByteAry(newHeader, 20, Utils.int2Byte(newSectionSize));        //字節區大小
                newHeader = Utils.replaceByteAry(newHeader, 24, Utils.int2Byte(0));
                newHeader = Utils.replaceByteAry(newHeader, 28, Utils.int2Byte(0));
                newHeader = Utils.replaceByteAry(newHeader, 32, Utils.int2Byte(4));
                newHeader = Utils.replaceByteAry(newHeader, 36, Utils.int2Byte(0));

                //在末尾增加Section
                byte[] newSrc = new byte[src.length + newHeader.length];
                newSrc = Utils.replaceByteAry(newSrc, 0, src);
                newSrc = Utils.replaceByteAry(newSrc, src.length, newHeader);

                return newSrc;
        }

完善步驟

修改第一個LOAD的program header

將其filesize和memsiz改爲文件的總長度

修改section header爲.shstrta

將他的size在原有的基礎上加16即可,因爲增加了一個section name

至此,完結,如果把so文件弄回去報了這個錯誤
load segment1: p_offset (0x0) + p_filesz (0x53f8) ( = 0x53f8) past end of file (0x53f8)
這是因爲你剛剛添加的段的大小超出了文件大小,因爲我們修改program header的filesize和memsize文件總長度,這個長度包括啦文件結束標誌0x0a,我們要修改這兩個值爲文件長度減去1即可



作者:jackzhoud
鏈接:https://www.jianshu.com/p/a2d87d9467cc
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章