Android so(ELF) 文件解析

2021-03-06 l0neman未央
Android so(ELF) 文件解析

Android so(ELF) 文件解析

前言

生成 so 文件

相關工具

整體結構圖

頭部結構

段表結構

字符串表結構

程序表結構

符號表結構

重定位表結構

其他結構

解析代碼

打開 ELF 文件

檢查 ELF 文件

解析 ELF 頭部結構

解析段描述表結構

解析字符串表

列印段描述表結構

解析符號字符串表

解析程序頭表

解析段

解析符號表

解析重定位表

測試

解析源碼

參考

前言

ELF 是一種可執行文件的格式,全稱是 Executable and Linkable Format,即可執行和連結格式,它是 Unix/Linux 系統下的二進位文件的標準格式,與之對應的是 Windows 系統的 PE(Portable Executable)可執行文件格式,它們都是由 COFF(Common Object File Format,通用對象文件格式)文件格式發展而來。

so 文件是 Unix/Linux 系統中的動態庫文件,被稱為共享目標文件(Shared Object File),後綴名為 .so,它是 ELF 的一種,另外屬於 ELF 類型的還有可重定位文件(Relocatable File)以及核心轉儲文件(Core Dump File)。

ELF 文件類型說明實例可重定位文件(Relocatable File)這類文件包含了代碼和數據,可以被用來連結成可執行文件或共享目標文件,靜態連結庫也可以歸為這一類Linux 的 .o;Windows 的 .obj共享目標文件(Shared Object File)這種文件包含了代碼和數據,可以在以下兩種情況中使用,一種是連結器可以使用這種文件跟其他的可重定位文件和共享目標文件連結,產生新的目標文件,第二種是動態連接器可以將幾個這種共享目標文件與可執行文件結合,作為進程映像的一部分來運行Linux 的 .so,如 /lib/glibc-2.5.so,Windows 的 DLL核心轉儲文件(Core Dump File)當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些其他信息轉儲到核心轉儲文件Linux 下的 Core Dump

Android 是基於 Linux 內核開發的作業系統,所以 Android 平臺上的可執行文件格式和 Unix/Linux 是一致的。

下面以 Android 平臺下的 so 文件為例子對 ELF 這種文件格式進行解析。

生成 so 文件

為了對 so 文件進行解析,首先需要生成一個 so 文件。

首先建立一個最基本的 NDK 開發工程,創建 Java 類 NativeHandler:

// NativeHandler.java
package io.l0neman.nativetproject;
public class NativeHandler {
static {
// 加載 libfoo.so 庫
System.loadLibrary("foo");
}

public static native String getHello();
}

編寫 C++ 代碼文件,為了稍微顯得沒有那麼簡單,加入一些變量和簡單函數:

// foo.h
#ifndef NATIVETPROJECT_FOO_H
#define NATIVETPROJECT_FOO_H
#include <jni.h>

extern "C" {
JNIEXPORT jstring JNICALL
Java_io_l0neman_nativetproject_NativeHandler_getHello(JNIEnv *env, jclass clazz);
}

#endif //NATIVETPROJECT_FOO_H

// foo.cpp
#include "foo.h"
#include <cstdio>
#include <jni.h>

int global_init_var = 84;
int global_uninit_var;

void func1(int i) {
printf("%d", i);
}

int test() {
static int static_var = 85;
static int static_var2;

int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}

extern "C" {
jstring Java_io_l0neman_nativetproject_NativeHandler_getHello(JNIEnv *env, jclass clazz) {
test();
return env->NewStringUTF("hello");
}
}

mk 文件:

# Application.mk
APP_ABI := armeabi-v7a arm64-v8a
APP_OPTIM := release

# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo.cpp

LOCAL_CFLAGS := -g

include $(BUILD_SHARED_LIBRARY)

這些文件在 src/main/jni 目錄中,進入 jni 目錄,然後執行 ndk-build 命令,將編譯出 armeabi-v7a 和 arm64-v8a 架構的 libfoo.so 文件,它們的位置在 src/main/jni/libs/armxxx/libfoo.so。

有了文件,下面開始進行解析。

相關工具

在解析之前介紹兩個用於解析 ELF 文件的工具,它們通常是 Linux 系統中自帶的軟體,可直接使用。

如果想要在 Windows 系統中使用,推薦使用 Windows 子系統(Windows Subsystem for Linux)。

objdump

可解析目標文件的工具,可顯示 ELF 文件的概要信息。常用選項如下:

-h 顯示所有節的信息
-x 顯示所有節的內容
-d 顯示可執行節的彙編程序內容
-D --disassemble-all 顯示所有節的彙編程序內容
-s --full-contents 顯示所有節內容
-t --syms 顯示符號表的內容
-T --dynamic-syms 顯示動態符號表的內容
-r --reloc 顯示文件中的重定位條目
-R --dynamic-reloc 顯示文件中的動態重定位條目

readelf

用於解析 ELF 文件的工具,可以詳細的輸出 ELF 文件的信息。常用選項如下:

-a 等效於:-h -l -S -s -r -d -V -A -I
-h --file-header 顯示 ELF 文件頭
-l --program-headers 顯示程序頭
-s --syms 顯示符號表
--dyn-syms 顯示動態符號表
-n --notes 顯示核心注釋
-r --relocs 顯示重定位
-u --unwind 顯示展開信息
-d --dynamic 顯示動態部分

在下面的解析過程中,可使用這兩個工具對解析結果進行參考和對照。

整體結構圖

上圖反映了一個 ELF 文件的典型結構,首先是一個 ELF 文件的頭結構,根據 ELF 所支持的目標執行平臺不同,分為 32 位和 64 位的 ELF 文件,32 位 ELF 文件的頭結構使用一個 Elf32_Ehdr 結構體描述,ELF 頭結構描述了整個 ELF 文件的屬性,包括文件類型(可執行文件、可重定位、共享目標文件等)、虛擬入口地址、段表偏移、文件本身的大小等。

文件頭下面的就是 ELF 文件的主要內容了,ELF 文件由若干個段(Section)組成,它們的結構各不相同,在 ELF 文件中扮演不同的角色,有各自的分工。通常 ELF 文件包含若干遵循 ELF 結構規範的段。如上圖所示,左邊帶有 . 前綴的為段名,上面幾個深藍色的矩形為 ELF 文件標準結構,它們有明確的結構定義,在 Android 9.0 系統原始碼中,可在 art/runtime/elf.h 文件中找到它們對應的定義,在下面的分析中會一一解釋它們的含義。

除了具有標準結構的 ELF 段,還有一些常用段以及程序自定義段名的段。下面列舉一些常用段的含義:

段名說明.rodataRead only Data,存放的是只讀數據,比如字符串、全局 const 變量等.comment存放編譯器版本信息,例如 GCC:(GNU)4.2.0.debug調試信息.dynamic動態連結信息.hash符號哈希表.line調試時的行號表,即原始碼與行號與編譯後指令的對應表.notes額外的編譯器信息,比如程序的公司名、發布版本等.strtabString Table,字符串表,用於存儲 ELF 文件中用到的字符串.symtabSymbol Table,符號表,存放 ELF 文件的內部符號.shstrtabSection String Table,段名字符串表.plt
.got動態連結的跳轉表和全局入口表.init
.fini程序初始化終結代碼段.interp存放動態連結器路徑.text代碼段,存放機器指令,是 ELF 文件的主要內容.bss為未初始化的全局變量和局部靜態變量預留位置,沒有內容,不佔空間.data數據段,存全局變量和局部靜態變量.dynstr動態符號字符串表

對於任意的 ELF 文件,它的結構可能不會像上面圖中一樣完整,根據實際情況,編譯器編譯生成的 ELF 文件會根據實際代碼來增加或減少相應的段,順序也可能和上圖不同,但 ELF 文件的頭部結構和那幾個標準格式段的結構是一致的。

頭部結構

首先是 ELF 文件的頭部結構,32 位 ELF 文件的頭部結構定義如下:

typedef uint32_t Elf32_Addr; // 表示程序地址
typedef uint32_t Elf32_Off; // 表示文件偏移
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;

EI_NIDENT = 16

struct Elf32_Ehdr {
unsigned char e_ident[EI_NIDENT]; // 文件標識
Elf32_Half e_type; // 文件類型
Elf32_Half e_machine; // ELF 文件的 CPU 平臺屬性,相關常量以 EM_ 開頭
Elf32_Word e_version; // ELF 版本號,一般為常數 1
Elf32_Addr e_entry; // 入口地址,規定 ELF 程序的入口虛擬地址,作業系統在加載完該程序後從這個地址開始執行進程的指令
Elf32_Off e_phoff; // Program header 表的文件偏移字節
Elf32_Off e_shoff; // 段表在文件中的偏移
Elf32_Word e_flags; // LF 標誌位,用來標識一些 ELF 文件平臺相關的屬性。相關常量格式一般為 EF_machine_flag,machine 為平臺,flag 為標誌
Elf32_Half e_ehsize; // ELF 文件頭本身的大小
Elf32_Half e_phentsize; // Program header 表的大小
Elf32_Half e_phnum; // Program header 表的數量
Elf32_Half e_shentsize; // 段表描述符的大小,這個一般等於一節
Elf32_Half e_shnum; // 段表描述符數量。這個值等於 ELF 文件中擁有段的數量
Elf32_Half e_shstrndx; // 段表字符串表所在的段在段表中的下標
};

e_ident 是文件標識欄位,也就是魔數,32 位 ELF 文件的文件標識為 16 個字節,每個字節含義如下:

7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
^ ^ ^ ^ ^ ^
E L F | |
/ \
[ ELF 文件類型 ] [ 字節序 ]
0 無效文件 0 無效格式
1 32 位 ELF 文件 1 小端格式
2 64 位 ELF 文件 2 大端格式

e_type 成員表示 ELF 文件類型,系統通過這個常量來判斷 ELF 文件類型,而不是文件擴展名。

常量值含義ET_NONE0無類型ET_REL1可重定位文件,一般為 .o 文件ET_EXEC2可執行文件ET_DYN3共享目標文件,一般為 .so 文件ET_CORE4核心文件

ELF 文件被設計成可以在多個平臺下使用,但並不表示同一個 ELF 文件可以在不同的平臺下使用,而是表示不同平臺下的 ELF 文件都遵循同一套 ELF 標準。e_machine 成員就表示該屬性。

相關常量以「EM」開頭,例如:

常量值含義EM_M321AT&T WE 32100EM_SPARC2SPARCEM_3863Intel x86EM_68K4Motorola 68000EM_88K5Motorola 88000EM_8606Intel 80860...

完整列表可以參考 elf.h 文件中的相關常量定義。

ELF 文件頭結構中其他欄位上面的注釋中已經能夠說明對應的含義,部分欄位描述了子結構的偏移,包括 Program Header 表和 Section Header 表以及字符串表,這個在解析對應的結構時會用到。

使用 readelf 工具解析 armeabi-v7a/libfoo.so 頭結構結果如下:

ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: ARM
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 12920 (bytes into file)
Flags: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 27
Section header string table index: 26

段表結構

段表由 Elf32_Shdr 結構體數組描述,每個 Elf32_Shdr 描述一個段。

段表的描述結構體數組在文件中的偏移存放在 ELF 文件頭中的 e_shoff 欄位中,e_shentsize 和 e_shnum 字欄位分別為數組的大小和數量。

struct Elf32_Shdr {
Elf32_Word sh_name; // 段名,位於 .shstrtab 的字符串表。sh_name 是段名在其中的偏移
Elf32_Word sh_type; // 段類型(SHT_*)
Elf32_Word sh_flags; // 段標誌位(SHF_*)
Elf32_Addr sh_addr; // 段的虛擬地址,前提是該段可被加載,否則為 0
Elf32_Off sh_offset; // 段偏移,前提是該段存在於文件中,否則無意義
Elf32_Word sh_size; // 段的長度
Elf32_Word sh_link; // 段的連結信息
Elf32_Word sh_info; // 段的額外信息
Elf32_Word sh_addralign; // 段地址對齊
Elf32_Word sh_entsize; // 項的長度
};

段的名字只在編譯和連結過程中有意義,無法真正表示段的類型,決定段的屬性和類型的是段的類型(sh_type)和段的屬性(sh_flag)

段類型的相關常量以 SHT 開頭,例如:

常量值含義SHT_NULL0無效段SHT_PROGBITS1程序段、代碼段、數據段都是這種類型SHT_SYMTAB2表示該段的內容為符號表SHT_STRTAB3表示該段的內容為字符串表SHT_RELA4重定位表,該段包含了重定位信息SHT_HASH5符號表的哈希表SHT_DYNAMIC6動態連結信息SHT_NOTE7提示性信息SHT_NOBITS8表示該段在文件中沒有內容,比如 .bss 段SHT_REL9該段包含了重定位信息SHT_SHLIB10保留SHT_DNYSYM11動態連結的符號表...

完整列表可以參考 elf.h 文件中的相關常量定義。

段的標誌位表示該段在進程虛擬地址空間中的屬性,比如是否可寫,是否可執行等。

常見值如下:

常量值含義SHF_WRITE0x1表示該段在進程空間中可寫SHF_ALLOC0x2表示該段在進程空間中需要分配空間。有些包含指示或者控制信息的段不需要在進程空間中被分配空間,它們一般不會有這個標誌。像代碼段、數據段和 .bss 段一般都會有這個標誌位SHF_EXECINSTR0x4表示該段在進程空間中可以被執行,一般指代碼段...

完整列表可以參考 elf.h 文件中的相關常量定義。

系統保留段相關標誌位如下:

Namesh_typesh_flags.bssSHT_NOBITSSHF_ALLOC + SHF_WRITE.commentSHT_PROGBITSnone.dataSHT_PROGBITSSHF_ALLOC + SHF_WRITE.data1SHT_PROGBITSSHF_ALLOC + SHF_WRITE.debugSHT_PROGBITSnone.dynamicSHT_DYNAMICSHF_ALLOC + SHF_WRITE
有些系統下 .dynamic 段可能是只讀的,所以沒有 SHF_WRITE 標誌位.hashSHT_HASHSHF_ALLOC.lineSHT_PROGBITSnone.noteSHT_NOTEnone.rodataSHT_PROGBITSSHF_ALLOC.rodata1SHT_PROGBITSSHF_ALLOC.shstrtabSHT_STRTABnone.strtabSHT_STRTAB如果該 ELF 文件中有可裝載的段需要用到該字符串表,那麼字符串表也將被裝載的到內存空間,則有 SHF_ALLOC 標誌位.symtabSHT_SYMTAB同字符串表.textSHT_PROGBITSSHF_ALLOC + SHF_WRITE

段的類型必須是連結相關的(動態或靜態),比如重定位表、符號表等。否則這兩個成員無意義。

sh_typesh_linksh_infoSHT_DYNAMIC該段所使用的字符串表在段表中的下標0SHT_HASH該段所使用的符號表在段表中的下標0SHT_REL
SHT_RELA該段所使用的相應符號表在段表中的下標該重定位表所作用的段在段表中的下標SHT_SYMTAB
SHT_DYNSYM作業系統相關的作業系統相關的otherSHN_UNDEF0

使用 readelf 工具解析 libfoo.so 段表描述結果如下:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.android.ide NOTE 00000134 000134 000098 00 A 0 0 4
[ 2] .note.gnu.build-i NOTE 000001cc 0001cc 000024 00 A 0 0 4
[ 3] .dynsym DYNSYM 000001f0 0001f0 000350 10 A 4 1 4
[ 4] .dynstr STRTAB 00000540 000540 000373 00 A 0 0 1
[ 5] .gnu.hash GNU_HASH 000008b4 0008b4 00015c 04 A 3 0 4
[ 6] .hash HASH 00000a10 000a10 000170 04 A 3 0 4
[ 7] .gnu.version VERSYM 00000b80 000b80 00006a 02 A 3 0 2
[ 8] .gnu.version_d VERDEF 00000bec 000bec 00001c 00 A 4 1 4
[ 9] .gnu.version_r VERNEED 00000c08 000c08 000040 00 A 4 2 4
[10] .rel.dyn REL 00000c48 000c48 0000c8 08 A 3 0 4
[11] .rel.plt REL 00000d10 000d10 0000f0 08 AI 3 20 4
[12] .plt PROGBITS 00000e00 000e00 00017c 00 AX 0 0 4
[13] .text PROGBITS 00000f7c 000f7c 0016b4 00 AX 0 0 4
[14] .ARM.exidx ARM_EXIDX 00002630 002630 0001a0 08 AL 13 0 4
[15] .ARM.extab PROGBITS 000027d0 0027d0 000180 00 A 0 0 4
[16] .rodata PROGBITS 00002950 002950 000497 01 AMS 0 0 1
[17] .fini_array FINI_ARRAY 00003e08 002e08 000008 04 WA 0 0 4
[18] .data.rel.ro PROGBITS 00003e10 002e10 000048 00 WA 0 0 4
[19] .dynamic DYNAMIC 00003e58 002e58 000110 08 WA 4 0 4
[20] .got PROGBITS 00003f68 002f68 000098 00 WA 0 0 4
[21] .data PROGBITS 00004000 003000 00000c 00 WA 0 0 4
[22] .bss NOBITS 0000400c 00300c 000005 00 WA 0 0 4
[23] .comment PROGBITS 00000000 00300c 000109 01 MS 0 0 1
[24] .note.gnu.gold-ve NOTE 00000000 003118 00001c 00 0 0 4
[25] .ARM.attributes ARM_ATTRIBUTES 00000000 003134 000034 00 0 0 1
[26] .shstrtab STRTAB 00000000 003168 00010f 00 0 0 1

字符串表結構

字符串表存放 ELF 文件內需要被使用的字符串,它是由多個字符串首尾相連組成,是一段連續的字節。

通常一個 ELF 文件包含多個字符串表,存放段名和存放符號名的字符串表不是同一個。

其他 ELF 結構包含字符串時,只需提供一個所屬字符串表中的索引值。

下面是 libfoo.so 文件的一個字符串表一部分的 16 進位視圖,字符串表的第一個字節是 \0:

00 2E 73 68 73 74 72 74 . s h s t r t
61 62 00 2E 6E 6F 74 65 2E 61 6E 64 72 6F 69 64 a b . n o t e . a n d r o i d
2E 69 64 65 6E 74 00 2E 6E 6F 74 65 2E 67 6E 75 . i d e n t . n o t e . g n u
2E 62 75 69 6C 64 2D 69 64 00 2E 64 79 6E 73 79 . b u i l d - i d . d y n s y
6D 00 2E 64 79 6E 73 74 72 00 2E 67 6E 75 2E 68 m . d y n s t r . g n u . h
61 73 68 00 2E 67 6E 75 2E 76 65 72 73 69 6F 6E a s h . g n u . v e r s i o n
00 2E 67 6E 75 2E 76 65 72 73 69 6F 6E 5F 64 00 . g n u . v e r s i o n _ d
2E 67 6E 75 2E 76 65 72 73 69 6F 6E 5F 72 00 2E . g n u . v e r s i o n _ r .
72 65 6C 2E 64 79 6E 00 2E 72 65 6C 2E 70 6C 74 r e l . d y n . r e l . p l t

使用 readelf 工具解析 libfoo.so 的 .shstrtab 字符串表如下:

String dump of section '.shstrtab':
[ 1] .shstrtab
[ b] .note.android.ident
[ 1f] .note.gnu.build-id
[ 32] .dynsym
[ 3a] .dynstr
[ 42] .gnu.hash
[ 4c] .gnu.version
[ 59] .gnu.version_d
[ 68] .gnu.version_r
[ 77] .rel.dyn
[ 80] .rel.plt
[ 89] .text
[ 8f] .ARM.exidx
[ 9a] .ARM.extab
[ a5] .rodata
[ ad] .fini_array
[ b9] .data.rel.ro
[ c6] .dynamic
[ cf] .got
[ d4] .data
[ da] .bss
[ df] .comment
[ e8] .note.gnu.gold-version
[ ff] .ARM.attributes

程序表結構

ELF 文件的段分為兩種模式,一種是 ELF 文件被連結之前,就是被加載到內存空間之前,ELF 文件中的段使用 Section 描述,也可以稱為 『節』;另一種是 ELF 文件被連結後,整個 ELF 文件將被加載到內存中,這時 ELF 文件中的段使用 Segment 描述,程序表就是專門用於保存 Segment 信息的列表,用於初始化連結後的內存中的 Segment。

每個程序表結構使用一個 Elf32_Phdr 結構體描述:

struct Elf32_Phdr {
Elf32_Word p_type; // 段類型
Elf32_Off p_offset; // 段在文件中的偏移
Elf32_Addr p_vaddr; // 段的第一個字節在虛擬地址空間的起始位置,整個程序表頭中
Elf32_Addr p_paddr; // 段的物理裝載地址,即 LMA(Load Memory Address),一般情況下 p_paddr 和 p_vaddr 是相同的
Elf32_Word p_filesz; // 段在 ELF 文件中所佔空間的長度,可能為 0
Elf32_Word p_memsz; // 段在進程虛擬空間中所佔空間的長度,可能為 0
Elf32_Word p_flags; // 段的權限屬性,比如可讀 "R",可寫 "W" 和可執行 "X"
Elf32_Word p_align; // 段的對齊屬性,實際對齊字節等於 2 的 p_align 次方
};

使用 readelf 工具解析 libfoo.so 程序表結果如下:

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00100 0x00100 R 0x4
LOAD 0x000000 0x00000000 0x00000000 0x02de7 0x02de7 R E 0x1000
LOAD 0x002e08 0x00003e08 0x00003e08 0x00204 0x00209 RW 0x1000
DYNAMIC 0x002e58 0x00003e58 0x00003e58 0x00110 0x00110 RW 0x4
NOTE 0x000134 0x00000134 0x00000134 0x000bc 0x000bc R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
EXIDX 0x002630 0x00002630 0x00002630 0x001a0 0x001a0 R 0x4
GNU_RELRO 0x002e08 0x00003e08 0x00003e08 0x001f8 0x001f8 RW 0x4

符號表結構

ELF 文件有一個相應的符號表(Symbol Table),每一個定義的符號有一個對應的值,叫做符號值(Symbol Value),對於函數和變量來說,就是它們的地址。

符號表中的所有符號分為如下幾類:

定義在本目標文件的全局符號,可被其他目標文件引用;

定義在本目標文件的全局符號,卻沒有定義在目標文件,一般叫做外部符號(External Symbol),也就是符號引用,例如「printf」;

段名,這種符號往往由編譯器產生,它的值就是該段的起始地址;

局部符號,這種符號只在編譯單元內部可見,對於連結過程沒有作用,調試器可以使用這些符號來分析程序或崩潰時的核心轉儲文件;

行號信息,即目標文件指令與原始碼中代碼行的對應關係,它也是可選的。

符號表存在於 .symtab 段和 .dynsym 段,前者包含 ELF 文件中所有符號,後者包含動態符號(只是動態連結相關的導入導出符號,不包含 ELF 內部符號)。

在使用 NDK 編譯 so 文件時,如果以 release 模式編譯,會被 strip 工具優化,so 文件將被去除內部符號表,那麼就只留下 .dynsym 段。

符號表中每個符號使用 Elf32_Sym 結構體描述:

struct Elf32_Sym {
Elf32_Word st_name; // 符號名字,包含了該符號名在字符串表中的下標
Elf32_Addr st_value; // 符號相對應的值,是一個絕對值,或地址等。不同的符號,含義不同
Elf32_Word st_size; // 符號的大小
unsigned char st_info; // 符號的類型和綁定信息
unsigned char st_other; // 目前為 0,保留
Elf32_Half st_shndx; // 符號所在段的下標
};

該成員低 4 位表示符號的類型(Symbol Type),高 28 位表示符號綁定信息(Symbol Binding)。

符號綁定信息常見值:

宏定義名值說明STB_LOCAL0局部符號,對於目標文件的外部不可見STB_GLOBAL1全局符號,外部可見STB_WEAK2弱引用

符號類型常見值:

宏定義名值說明STT_NOTYPE0未知類型符號STT_OBJECT1該符號是一個數據對象,比如變量、數組等STT_FUNC2該符號是一個函數或其他可執行代碼STT_SECTION3該符號表示一個段,這種符號必須是 STB_LOCAL 的STT_FILE4該符號表示文件名,一般都是該目標文件所對應的源文件名,它一定是 STB_LOCAL 類型的,並且它的 st_shndx 一定是系統 SHN_ABS

如果符號定義在本目標文件中,那麼這個成員表示符號所在的段在段表中的下標,如果符號不是定義在本目標文件中,或者對於有些特殊符號,sh_shndx 值為特殊變量。

常見特殊變量如下:

宏定義名值說明SHN_ABS0xfff1表示該符號包含了一個絕對的值。比如文件名的符號類型就是這種SHN_COMMON0xff2表示該符號是一個「COMMON 塊」類型的符號,一般來說,未初始化的全局符號定義就是這種類型的,比如 SimpleSection.o 裡面的 global_uninit_varSHN_UNDEF0表示該符號未定義,這個符號表示該符號在本目標文件中被引用,但是定義在其他目標文件中

有如下幾種情況:

在目標文件中,如果是符號的定義並且該符號不是「COMMON 塊」類型的,則表示該符號在段中的偏移。即符號所對應的變量或函數位於 sh_shndx 指定的段,偏移 st_value 的位置。

在目標文件中,如果符號是「COMMON 塊」類型的,則 st_value 表示該符號的對齊屬性。

在可執行文件中,st_value 表示符號的虛擬地址。這個虛擬地址對於動態連結器十分有用。

使用 readelf 工具解析 libfoo.so 符號表結果如下:

Symbol table '.dynsym' contains 53 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND dladdr@LIBC (3)
4: 00000000 0 FUNC GLOBAL DEFAULT UND snprintf@LIBC (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2)
6: 00000000 0 OBJECT GLOBAL DEFAULT UND __sF@LIBC (2)
7: 00000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@LIBC (2)
8: 00000000 0 OBJECT GLOBAL DEFAULT UND __stack_chk_guard@LIBC (2)
9: 00000000 0 FUNC GLOBAL DEFAULT UND abort@LIBC (2)
10: 00000000 0 FUNC GLOBAL DEFAULT UND fflush@LIBC (2)
11: 00000000 0 FUNC GLOBAL DEFAULT UND fprintf@LIBC (2)
12: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memclr8
13: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memcpy
14: 00000000 0 FUNC GLOBAL DEFAULT UND __gnu_Unwind_Find_exidx
15: 00001a37 6 FUNC GLOBAL DEFAULT 13 unw_save_vfp_as_X
16: 00000ffd 80 FUNC GLOBAL DEFAULT 13 decode_eht_entry
17: 00001561 8 FUNC GLOBAL DEFAULT 13 __aeabi_unwind_cpp_pr0
18: 00000fdd 32 FUNC GLOBAL DEFAULT 13 Java_io_l0neman_nativetpr
19: 00001599 8 FUNC GLOBAL DEFAULT 13 __aeabi_unwind_cpp_pr1
20: 00001761 2 FUNC GLOBAL DEFAULT 13 _Unwind_Complete
21: 000015a1 8 FUNC GLOBAL DEFAULT 13 __aeabi_unwind_cpp_pr2
22: 00001854 0 FUNC GLOBAL DEFAULT 13 unw_getcontext
23: 00001765 104 FUNC GLOBAL DEFAULT 13 _Unwind_Resume
24: 0000104d 620 FUNC GLOBAL DEFAULT 13 _Unwind_VRS_Interpret
25: 00004004 4 OBJECT GLOBAL DEFAULT 21 global_init_var
26: 000019c3 32 FUNC GLOBAL DEFAULT 13 unw_get_proc_info
27: 00001a2b 12 FUNC GLOBAL DEFAULT 13 unw_is_signal_frame
28: 0000400c 4 OBJECT GLOBAL DEFAULT 22 global_uninit_var
29: 00001411 336 FUNC GLOBAL DEFAULT 13 _Unwind_VRS_Pop
30: 00004008 4 OBJECT GLOBAL DEFAULT 21 unw_local_addr_space
31: 00001981 60 FUNC GLOBAL DEFAULT 13 unw_set_fpreg
32: 000019bd 6 FUNC GLOBAL DEFAULT 13 unw_step
33: 000015a9 160 FUNC GLOBAL DEFAULT 13 _Unwind_RaiseException
34: 000012b9 172 FUNC GLOBAL DEFAULT 13 _Unwind_VRS_Get
35: 000019e5 20 FUNC GLOBAL DEFAULT 13 unw_resume
36: 00001841 18 FUNC GLOBAL DEFAULT 13 __gnu_unwind_frame
37: 00001885 72 FUNC GLOBAL DEFAULT 13 unw_init_local
38: 0000400c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
39: 00004011 0 NOTYPE GLOBAL DEFAULT ABS _end
40: 00001901 72 FUNC GLOBAL DEFAULT 13 unw_set_reg
41: 00000fd1 12 FUNC GLOBAL DEFAULT 13 _Z4testv
42: 000017cd 52 FUNC GLOBAL DEFAULT 13 _Unwind_GetLanguageSpecif
43: 00001949 56 FUNC GLOBAL DEFAULT 13 unw_get_fpreg
44: 00001365 172 FUNC GLOBAL DEFAULT 13 _Unwind_VRS_Set
45: 0000400c 0 NOTYPE GLOBAL DEFAULT ABS _edata
46: 00001835 12 FUNC GLOBAL DEFAULT 13 _Unwind_DeleteException
47: 00001a19 12 FUNC GLOBAL DEFAULT 13 unw_is_fpreg
48: 000018cd 52 FUNC GLOBAL DEFAULT 13 unw_get_reg
49: 00001a25 6 FUNC GLOBAL DEFAULT 13 unw_regname
50: 00000fc1 16 FUNC GLOBAL DEFAULT 13 _Z5func1i
51: 00001801 52 FUNC GLOBAL DEFAULT 13 _Unwind_GetRegionStart
52: 000019f9 32 FUNC GLOBAL DEFAULT 13 unw_get_proc_name

重定位表結構

如果 ELF 文件中有需要被重定位的地方,例如「.text」段,那麼會有一個相對應的「.rel.text」段保存「.text」段的重定位表。

重定位表的每一個元素使用 Elf32_Rel 結構體表示

struct Elf32_Rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
};

成員含義r_offset重定位入口的偏移。對於可重定位文件來說,這個值是該可重定位入口所要修正的位置的第一個字節相對於段起始的偏移;對於可執行文件或共享對象文件來說,這個值是該重定位入口所要修正的位置的第一個字節的虛擬地址。r_info重定位入口的類型和符號。這個成員的低 8 位表示重定位入口的類型,高 24 位表示重定位入口的符號在符號表中下標。
因為各個處理器的指令格式不一樣,所以重定位所修正的地址格式也不一樣,每種處理器都有自己一套重定位入口的類型,對於可執行文件和共享對象文件來說,它們的重定位入口是動態連結類型的。

使用 readelf 工具查看 libfoo.so 中的 rel.dyn 和 rel.plt 重定位表:

Relocation section '.rel.dyn' at offset 0xc48 contains 25 entries:
Offset Info Type Sym.Value Sym. Name
00003e08 00000017 R_ARM_RELATIVE
00003e0c 00000017 R_ARM_RELATIVE
00003e18 00000017 R_ARM_RELATIVE
00003e1c 00000017 R_ARM_RELATIVE
00003e20 00000017 R_ARM_RELATIVE
00003e24 00000017 R_ARM_RELATIVE
00003e28 00000017 R_ARM_RELATIVE
00003e2c 00000017 R_ARM_RELATIVE
00003e30 00000017 R_ARM_RELATIVE
00003e34 00000017 R_ARM_RELATIVE
00003e38 00000017 R_ARM_RELATIVE
00003e3c 00000017 R_ARM_RELATIVE
00003e40 00000017 R_ARM_RELATIVE
00003e44 00000017 R_ARM_RELATIVE
00003e48 00000017 R_ARM_RELATIVE
00003e4c 00000017 R_ARM_RELATIVE
00003e50 00000017 R_ARM_RELATIVE
00003e54 00000017 R_ARM_RELATIVE
00004000 00000017 R_ARM_RELATIVE
00004008 00000017 R_ARM_RELATIVE
00003f6c 00000615 R_ARM_GLOB_DAT 00000000 __sF@LIBC
00003f68 00000815 R_ARM_GLOB_DAT 00000000 __stack_chk_guard@LIBC
00003f74 00001115 R_ARM_GLOB_DAT 00001561 __aeabi_unwind_cpp_pr0
00003f70 00001315 R_ARM_GLOB_DAT 00001599 __aeabi_unwind_cpp_pr1
00003f78 00001515 R_ARM_GLOB_DAT 000015a1 __aeabi_unwind_cpp_pr2

Relocation section '.rel.plt' at offset 0xd10 contains 30 entries:
Offset Info Type Sym.Value Sym. Name
00003f88 00000216 R_ARM_JUMP_SLOT 00000000 __cxa_finalize@LIBC
00003f8c 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_atexit@LIBC
00003f90 00000516 R_ARM_JUMP_SLOT 00000000 printf@LIBC
00003f94 00003216 R_ARM_JUMP_SLOT 00000fc1 _Z5func1i
00003f98 00002916 R_ARM_JUMP_SLOT 00000fd1 _Z4testv
00003f9c 00003016 R_ARM_JUMP_SLOT 000018cd unw_get_reg
00003fa0 00001d16 R_ARM_JUMP_SLOT 00001411 _Unwind_VRS_Pop
00003fa4 00002816 R_ARM_JUMP_SLOT 00001901 unw_set_reg
00003fa8 00002216 R_ARM_JUMP_SLOT 000012b9 _Unwind_VRS_Get
00003fac 00000716 R_ARM_JUMP_SLOT 00000000 __stack_chk_fail@LIBC
00003fb0 00000f16 R_ARM_JUMP_SLOT 00001a37 unw_save_vfp_as_X
00003fb4 00002b16 R_ARM_JUMP_SLOT 00001949 unw_get_fpreg
00003fb8 00000b16 R_ARM_JUMP_SLOT 00000000 fprintf@LIBC
00003fbc 00000a16 R_ARM_JUMP_SLOT 00000000 fflush@LIBC
00003fc0 00000916 R_ARM_JUMP_SLOT 00000000 abort@LIBC
00003fc4 00001f16 R_ARM_JUMP_SLOT 00001981 unw_set_fpreg
00003fc8 00002c16 R_ARM_JUMP_SLOT 00001365 _Unwind_VRS_Set
00003fcc 00002a16 R_ARM_JUMP_SLOT 000017cd _Unwind_GetLanguageSpe
00003fd0 00002016 R_ARM_JUMP_SLOT 000019bd unw_step
00003fd4 00001616 R_ARM_JUMP_SLOT 00001854 unw_getcontext
00003fd8 00002516 R_ARM_JUMP_SLOT 00001885 unw_init_local
00003fdc 00001a16 R_ARM_JUMP_SLOT 000019c3 unw_get_proc_info
00003fe0 00002316 R_ARM_JUMP_SLOT 000019e5 unw_resume
00003fe4 00000d16 R_ARM_JUMP_SLOT 00000000 __aeabi_memcpy
00003fe8 00000c16 R_ARM_JUMP_SLOT 00000000 __aeabi_memclr8
00003fec 00001016 R_ARM_JUMP_SLOT 00000ffd decode_eht_entry
00003ff0 00001816 R_ARM_JUMP_SLOT 0000104d _Unwind_VRS_Interpret
00003ff4 00000316 R_ARM_JUMP_SLOT 00000000 dladdr@LIBC
00003ff8 00000416 R_ARM_JUMP_SLOT 00000000 snprintf@LIBC
00003ffc 00000e16 R_ARM_JUMP_SLOT 00000000 __gnu_Unwind_Find_exid

其他結構

上面是 ELF 文件的標準段,它們具有標準的結構定義,下面是一些其他常見段的作用和內容。

代碼段,.text 段用於存放編譯出來的機器代碼指令。

下面是 libfoo.so 中 Java_io_l0neman_nativetproject_NativeHandler_getHello 函數在 .text 段中的內容:

地址 16 進位值 彙編指令

.text:00000FDC D0 B5 PUSH {R4,R6,R7,LR}
.text:00000FDE 02 AF ADD R7, SP, #8
.text:00000FE0 04 46 MOV R4, R0
.text:00000FE2 FF F7 30 EF BLX j__Z4testv ; test(void)
.text:00000FE6 20 68 LDR R0, [R4]
.text:00000FE8 03 49 LDR R1, =(aHello - 0xFF2)
.text:00000FEA D0 F8 9C 22 LDR.W R2, [R0,#0x29C]
.text:00000FEE 79 44 ADD R1, PC ; "hello"
.text:00000FF0 20 46 MOV R0, R4
.text:00000FF2 BD E8 D0 40 POP.W {R4,R6,R7,LR}
.text:00000FF6 10 47 BX R2

GOT 是 ELF 文件中的全局偏移表(Global Offset Table,GOT),用於存放全局符號地址。

在動態連結的情況下,對於橫跨不同模塊的全局符號地址要等到模塊裝載時才能確認,為了實現地址無關代碼(PIC,Position-independent Code),需要將符號地址放入數據段,建立一個存放這些全局符號的數組結構,就是 GOT 結構,代碼中訪問這些全局符號的地址將是在 GOT 結構中的偏移,等到裝載完畢,這些符號地址被確認後會被填入 GOT 中,此時代碼執行時通過 GOT 表中對應符號的偏移即可獲取對應符號的地址,此時代碼段可被多個進程共享,從而實現地址無關代碼。

下面是截取 libfoo.so 文件 .got 段中的內容:

地址 16 進位值 符號 彙編指令

.got:00003FB8 3C 40 00 00 fprintf_ptr DCD __imp_fprintf
.got:00003FBC 38 40 00 00 fflush_ptr DCD __imp_fflush
.got:00003FC0 34 40 00 00 abort_ptr DCD __imp_abort
.got:00003FC4 81 19 00 00 unw_set_fpreg_ptr DCD unw_set_fpreg+1

.plt 是用於存放 PLT 的代碼項表,PLT(Procedure Linkage Table)是一種用於實現延遲綁定(Lazy Binding)的方法。

為了提高動態連結重定位的效率,避免將所有函數地址一次性重定位,ELF 採用了延遲綁定的做法,就是當函數第一次被用到時才進行綁定(符號查找和重定位),.plt 段就是為了存放每個全局函數處理延遲綁定的一段代碼。

ELF 將 GOT 拆分成了兩個表,叫做 .got 和 .got.plt,其中 .got 用來保存全局變量引用的地址,.got.plt 用來保存全局函數的引用。

.got.plt 的前三項具有特殊含義:

第一項保存的是 .dynamic 段的地址,這個段描述了本模塊動態連結相關的信息;

第二項保存的是本模塊的 ID;

第三項保存的是 _dl_runtime_resolve() 的地址。

解析代碼

到這裡完成了 ELF 文件結構的分析,下面使用 C++ 代碼對上述主要結構進行手動解析。

定義 ElfParser 類,支持 32 位和 64 位的 ELF 文件解析:

class ElfParser
{
public:
ElfParser();
explicit ElfParser(char const* elf_file);
~ElfParser();
void parse(); // 開始解析
private:
FILE* elf_file_; // ELF 文件指針
uint8_t elf_bit_; // 32 位或 64 位

Elf32_Ehdr elf_header32_{}; // ELF 頭結構
Elf64_Ehdr elf_header64_{};

Elf32_Phdr* program_header32_list_; // ELF Program Header Table
Elf64_Phdr* program_header64_list_; // (程序頭表)

Elf32_Shdr* section_header32_list_; // ELF Section Header Table
Elf64_Shdr* section_header64_list_; // (段描述表)

char* string_table_; // .shstrtab 字符串表
char* symbol_string_table_; // .dynstr 符號字符串表

Elf32_Sym* symbol32_list_; // ELF Symbol Table
Elf64_Sym* symbol64_list_; // (符號表)

Elf32_Rel* relocation32_list_; // ELF Relocation Table
Elf64_Rel* relocation64_list_; // (重定位表)

bool check_elf();
void parse_elf_header();
void parse_section_header_list();
void parse_string_table();
void print_section_header_list() const;
void parse_symbol_string_table();
void parse_program_header_list();
void parse_section_list();
void parse_symbol_table(long offset, size_t size);
void parse_relocation_table(long offset, size_t size);

const char* get_string_from_string_table(size_t offset) const;
const char* get_string_from_symbol_string_table(size_t offset) const;
};

#endif // ELF_PARSER_H

打開 ELF 文件

首先打開 ELF 文件,將文件指針保存起來用於解析其他結構。

ElfParser::ElfParser(char const* elf_file)
{
// ...

const auto f = fopen_s(&this->elf_file_, elf_file, "rb");
printf("open elf file: %s\n\n", elf_file);
if (f != 0 || this->elf_file_ == nullptr)
printf("open elf file error: %s\n", elf_file);
}

檢查 ELF 文件

通過對比 ELF 魔數,檢查是否為 ELF 格式,同時了解 ELF 位數以及大小段:

static constexpr char ElfMagic[] = { 0x7f, 'E', 'L', 'F', '\0' };

bool ElfParser::check_elf()
{
unsigned char elf_ident[16] = { 0 };
if (0 == fread(elf_ident, sizeof(char), 16, this->elf_file_))
{
printf("check elf error: read error");
return false;
}

if (memcmp(elf_ident, ElfMagic, strlen(ElfMagic)) != 0)
return false;

char elf_type[10] = "ERROR";
// 確定 ELF 文件位數
switch (elf_ident[4])
{
case ELFCLASSNONE:
strcpy_s(elf_type, "invalid");
break;
case ELFCLASS32:
strcpy_s(elf_type, "ELF32");
this->elf_bit_ = 32;
break;
case ELFCLASS64:
strcpy_s(elf_type, "ELF64");
this->elf_bit_ = 64;
break;
default:
break;
}

printf("Class: \t\t%s\n", elf_type);

char elf_order[15] = "ERROR";
// ELF 文件大小端
switch (elf_ident[5])
{
case ELFDATANONE:
strcpy_s(elf_order, "invalid");
break;
case ELFDATA2LSB:
strcpy_s(elf_order, "little endian");
break;
case ELFDATA2MSB:
strcpy_s(elf_order, "big endian");
break;
default:
break;
}

printf("Order: \t\t%s\n", elf_order);

return true;
}

解析 ELF 頭部結構

使用模板對 32 位和 64 位頭 ELF 結構進行列印。

void ElfParser::parse_elf_header()
{
if (0 != fseek(this->elf_file_, 0, 0))
{
printf("#parse_elf_header - seek file error.\n");
return;
}

void* elf_header = nullptr;
size_t elf_header_size = 0;
if (this->elf_bit_ == 32)
{
elf_header = &this->elf_header32_;
elf_header_size = sizeof(Elf32_Ehdr);
}
else // this->elf_bit_ == 64
{
elf_header = &this->elf_header64_;
elf_header_size = sizeof(Elf64_Ehdr);
}

if (0 == fread(elf_header, elf_header_size, 1, this->elf_file_))
{
printf("parse elf header%d error.\n", this->elf_bit_);
return;
}

if (this->elf_bit_ == 32)
print_elf_header(&this->elf_header32_, 32);
else // this->elf_bit_ == 64
print_elf_header(&this->elf_header64_, 64);
}

template <typename T = Elf32_Ehdr>
void print_elf_header(T* header, const uint8_t bit)
{
printf("ident: \t\t");
Printer::print_char_array(header->e_ident, 16);
printf("type: \t\t%u\n", header->e_type);
printf("machine: \t%u\n", header->e_machine);
printf("version: \t%u\n", header->e_version);
if (bit == 32)
{
printf("entry: \t\t%u\n", header->e_entry);
printf("phoff: \t\t%u\n", header->e_phoff);
printf("shoff: \t\t%u\n", header->e_shoff);
}
else // bit == 64
{
printf("entry: \t\t%llu\n", header->e_entry);
printf("phoff: \t\t%llu\n", header->e_phoff);
printf("shoff: \t\t%llu\n", header->e_shoff);
}

printf("flags: \t\t0x%x\n", header->e_flags);
printf("ehsize: \t%u\n", header->e_ehsize);
printf("phentsize: \t%u\n", header->e_phentsize);
printf("phnum: \t\t%u\n", header->e_phnum);
printf("shentsize: \t%u\n", header->e_shentsize);
printf("shnum: \t\t%u\n", header->e_shnum);
printf("shstrndx: \t%u\n", header->e_shstrndx);
}

解析段描述表結構

void ElfParser::parse_section_header_list()
{
printf("\n>>>>>>>>>>>> parse section header list <<<<<<<<<<<<\n\n");

long section_header_offset = 0;
size_t section_header_count = 0;
size_t section_header_size = 0;
void* section_header_list = nullptr;

if (this->elf_bit_ == 32)
{
section_header_offset = this->elf_header32_.e_shoff;
section_header_count = this->elf_header32_.e_shnum;
section_header_size = sizeof(Elf32_Shdr);

this->section_header32_list_ = new Elf32_Shdr[section_header_count];
section_header_list = this->section_header32_list_;

printf("section header offset: \t%u\n", this->elf_header32_.e_shoff);
printf("section header size: \t%u\n", this->elf_header32_.e_shnum);
}
else // this->elf_bit_ == 64
{
section_header_offset = this->elf_header64_.e_shoff;
section_header_count = this->elf_header64_.e_shnum;
section_header_size = sizeof(Elf64_Shdr);

this->section_header64_list_ = new Elf64_Shdr[section_header_count];
section_header_list = this->section_header64_list_;

printf("section header offset: \t%llu\n", this->elf_header64_.e_shoff);
printf("section header size: \t%u\n", this->elf_header64_.e_shnum);
}

if (0 != fseek(this->elf_file_, section_header_offset, 0))
{
printf("#parse_section_header - seek file error.\n");
return;
}

if (0 == fread(section_header_list, section_header_size, section_header_count, this->elf_file_))
{
printf("parse section header%d error.\n", this->elf_bit_);
return;
}
}

解析字符串表

字符串表就是一段字節,直接存放起來,使用偏移進行訪問即可。

void ElfParser::parse_string_table()
{
printf("\n>>>>>>>>>>>> parse string table <<<<<<<<<<<<\n\n");
// for .shstrtab;

size_t offset;
size_t size;
if (this->elf_bit_ == 32)
{
// 字符串表下標
const auto str_table_index = this->elf_header32_.e_shstrndx;
const auto& section_header = this->section_header32_list_[str_table_index];
offset = section_header.sh_offset;
size = section_header.sh_size;
}
else // this->elf_bit_ == 64
{
const auto str_table_index = this->elf_header64_.e_shstrndx;
const auto& section_header = this->section_header64_list_[str_table_index];
offset = section_header.sh_offset;
size = section_header.sh_size;
}

if (0 != fseek(this->elf_file_, offset, 0))
{
printf("#parse_string_table - seek file error.\n");
return;
}

this->string_table_ = new char[size];
if (0 == fread(this->string_table_, size, 1, this->elf_file_))
{
printf("parse string table%d error.\n", this->elf_bit_);
return;
}

size_t string_count = 0;
for (size_t i = 0; i < size; i++)
{
if (this->string_table_[i] == 0 && i != (size - 1))
{
const auto off = i + 1;
const auto* str = get_string_from_string_table(off);
const auto len = strlen(str);

printf("str[%llu] \tlen: %llu, s: %s\n", off, len, str);

string_count++;
}
}

printf("string count: %llu\n", string_count);
}

字符串表解析後,其他結構中有字符串欄位就可以使用偏移直接訪問對應的字符串了。

const char* ElfParser::get_string_from_string_table(const size_t offset) const
{
return &this->string_table_[offset];
}

列印段描述表結構

段描述表這裡放在解析完字符串表後進行列印,因為此時可以列印出段名,更直觀。

void ElfParser::print_section_header_list() const
{
#ifdef _PRINT_SECTION_HEADER_LIST_
size_t section_header_count = 0;

if (this->elf_bit_ == 32)
section_header_count = this->elf_header32_.e_shnum;
else // this->elf_bit_ == 64
section_header_count = this->elf_header64_.e_shnum;

for (size_t i = 0; i < section_header_count; i++)
{
printf("\n>>>>>>>>>>>> parse section header <<<<<<<<<<<<\n\n");

printf("index: \t\t%llu\n", i);

if (this->elf_bit_ == 32)
{
printf("name: \t\t%s\n\n", get_string_from_string_table(this->section_header32_list_[i].sh_name));
print_section_header(&this->section_header32_list_[i], this->elf_bit_);
}
else // this->elf_bit_ == 64
{
printf("name: \t\t%s\n\n", get_string_from_string_table(this->section_header64_list_[i].sh_name));
print_section_header(&this->section_header64_list_[i], this->elf_bit_);
}
}
#endif // _PRINT_SECTION_HEADER_LIST_
}

template <typename T = Elf32_Shdr>
static void print_section_header(T* header, const uint8_t bit)
{
#ifdef _PRINT_SECTION_HEADER_LIST_
printf("sh_name: \t%u\n", header->sh_name);
printf("sh_type: \t0x%x\n", header->sh_type);
printf("sh_link: \t%u\n", header->sh_link);
printf("sh_info: \t%u\n", header->sh_info);

if (bit == 32)
{
printf("sh_flags: \t%u\n", header->sh_flags);
printf("sh_offset: \t%u\n", header->sh_offset);
printf("sh_size: \t%u\n", header->sh_size);
printf("sh_addr: \t%u\n", header->sh_addr);
printf("sh_addralign: \t%u\n", header->sh_addralign);
printf("sh_entsize: \t%u\n", header->sh_entsize);
}
else // bit == 64
{
printf("sh_flags: \t%llu\n", header->sh_flags);
printf("sh_offset: \t%llu\n", header->sh_offset);
printf("sh_size: \t%llu\n", header->sh_size);
printf("sh_addr: \t%llu\n", header->sh_addr);
printf("sh_addralign: \t%llu\n", header->sh_addralign);
printf("sh_entsize: %llu\n", header->sh_entsize);
}
#endif // _PRINT_PROGRAM_HEADER_LIST_
}

解析符號字符串表

符號字符串表需要首先從段描述表中查詢到 .dynstr 名字得到段偏移和段大小後進行解析。

void ElfParser::parse_symbol_string_table()
{
printf("\n>>>>>>>>>>>> parse symbol string table <<<<<<<<<<<<\n\n");
// for .dynstr

size_t offset = 0;
size_t size = 0;
// 查詢 `.dynstr` 段信息
if(this->elf_bit_ == 32)
{
for (size_t i = 0; i < this->elf_header32_.e_shnum; i++)
{
auto& section_header = this->section_header32_list_[i];
const auto* section_name = get_string_from_string_table(section_header.sh_name);
if(section_header.sh_type == SHT_STRTAB && strcmp(section_name, ".dynstr") == 0)
{
offset = section_header.sh_offset;
size = section_header.sh_size;
break;
}
}
}
else // this->elf_bit_ == 32
{
for (size_t i = 0; i < this->elf_header64_.e_shnum; i++)
{
auto& section_header = this->section_header64_list_[i];
const auto* section_name = get_string_from_string_table(section_header.sh_name);
if (section_header.sh_type == SHT_STRTAB && strcmp(section_name, ".dynstr") == 0)
{
offset = section_header.sh_offset;
size = section_header.sh_size;
break;
}
}
}

if(offset == 0 || size == 0)
{
printf("error: not found section .dynstr\n");
return;
}

if (0 != fseek(this->elf_file_, offset, 0))
{
printf("#parse_symbol_string_table - seek file error.\n");
return;
}

this->symbol_string_table_ = new char[size];
if (0 == fread(this->symbol_string_table_, size, 1, this->elf_file_))
{
printf("parse symbol string table%d error.\n", this->elf_bit_);
return;
}

size_t string_count = 0;
for (size_t i = 0; i < size; i++)
{
if (this->symbol_string_table_[i] == 0 && i != (size - 1))
{
const auto off = i + 1;
const auto* str = get_string_from_symbol_string_table(off);
const auto len = strlen(str);

printf("str[%llu] \tlen: %llu, s: %s\n", off, len, str);

string_count++;
}
}

printf("string count: %llu\n", string_count);
}

和上面字符串表一樣,提供一個通過偏移訪問的方法,那麼符號表可通過此函數查詢符號名:

const char* ElfParser::get_string_from_string_table(const size_t offset) const
{
return &this->string_table_[offset];
}

解析程序頭表

void ElfParser::parse_program_header_list()
{
printf("\n>>>>>>>>>>>> parse program list <<<<<<<<<<<<\n\n");

long program_header_list_offset = 0;
size_t program_header_count = 0;
size_t program_header_size = 0;
void* program_header_list = nullptr;

if (this->elf_bit_ == 32)
{
program_header_list_offset = this->elf_header32_.e_phoff;
program_header_count = this->elf_header32_.e_phnum;
program_header_size = sizeof(Elf32_Phdr);

this->program_header32_list_ = new Elf32_Phdr[program_header_count];
program_header_list = this->program_header32_list_;

printf("program header offset: \t%u\n", this->elf_header32_.e_phoff);
printf("program header size: \t%u\n", this->elf_header32_.e_phnum);
}
else // this->elf_bit_ == 64
{
program_header_list_offset = this->elf_header64_.e_phoff;
program_header_count = this->elf_header64_.e_phnum;
program_header_size = sizeof(Elf64_Phdr);

this->program_header64_list_ = new Elf64_Phdr[program_header_count];
program_header_list = this->program_header64_list_;

printf("program header offset: \t%llu\n", this->elf_header64_.e_phoff);
printf("program header size: \t%u\n", this->elf_header64_.e_phnum);
}

if (0 != fseek(this->elf_file_, program_header_list_offset, 0))
{
printf("#parse_program_header_list - seek file error.\n");
return;
}

if (0 == fread(program_header_list, program_header_size, program_header_count, this->elf_file_))
{
printf("parse program header%d error.\n", this->elf_bit_);
return;
}

#ifdef _PRINT_PROGRAM_HEADER_LIST_
for (size_t i = 0; i < program_header_count; i++)
{
printf("\n>>>>>>>>>>>> parse program header <<<<<<<<<<<<\n\n");
printf("index: \t\t%llu\n\n", i);

if (this->elf_bit_ == 32)
print_program_header(&this->program_header32_list_[i], this->elf_bit_);
else // this->elf_bit_ == 64
print_program_header(&this->program_header64_list_[i], this->elf_bit_);
}
#endif // _PRINT_PROGRAM_HEADER_LIST_
}

template <typename T = Elf32_Phdr>
static void print_program_header(T* header, const uint8_t bit)
{
#ifdef _PRINT_PROGRAM_HEADER_LIST_
printf("p_type: \t0x%x\n", header->p_type);
printf("p_flags: \t%u\n", header->p_flags);
if (bit == 32)
{
printf("p_offset: \t%u\n", header->p_offset);
printf("p_vaddr: \t%u\n", header->p_vaddr);
printf("p_paddr: \t%u\n", header->p_paddr);
printf("p_filesz: \t%u\n", header->p_filesz);
printf("p_memsz: \t%u\n", header->p_memsz);
printf("p_align: \t%u\n", header->p_align);
}
else // bit == 64
{
printf("p_offset: \t0x%x\n", header->p_offset);
printf("p_vaddr: \t%llu\n", header->p_vaddr);
printf("p_paddr: \t%llu\n", header->p_paddr);
printf("p_filesz: \t%llu\n", header->p_filesz);
printf("p_memsz: \t%llu\n", header->p_memsz);
printf("p_align: \t%llu\n", header->p_align);
}
#endif // _PRINT_PROGRAM_HEADER_LIST_
}

解析段

遍歷段描述表的過程中可以獲取段名、段偏移和段大小信息從而解析 ELF 符號表和 ELF 重定位表。

void ElfParser::parse_section_list()
{
printf("\n>>>>>>>>>>>> parse section list <<<<<<<<<<<<\n\n");

size_t list_len = 0;
if (this->elf_bit_ == 32)
list_len = this->elf_header32_.e_shnum;
else // this->elf_bit_ == 64
list_len = this->elf_header64_.e_shnum;

if (this->elf_bit_ == 32)
{
for (size_t i = 0; i < list_len; i++)
{
auto& section_header = this->section_header32_list_[i];
printf("parse section: %s\n", get_string_from_string_table(section_header.sh_name));

switch (section_header.sh_type)
{
case SHT_SYMTAB:
break;
case SHT_DYNSYM:
// 解析符號表
parse_symbol_table(section_header.sh_offset, section_header.sh_size);
break;
case SHT_REL:
// 解析重定位表
parse_relocation_table(section_header.sh_offset, section_header.sh_size);
break;
default:
printf("ignored.\n");
break;
}
}
}
else // this->elf_bit_ == 64
{
for (size_t i = 0; i < list_len; i++)
{
auto& section_header = this->section_header64_list_[i];
printf("parse section: %s\n", get_string_from_string_table(section_header.sh_name));

switch (section_header.sh_type)
{
case SHT_SYMTAB:
break;
case SHT_DYNSYM:
parse_symbol_table(section_header.sh_offset, section_header.sh_size);
break;
default:
printf("ignored.\n");
break;
}
}
}
}

解析符號表

void ElfParser::parse_symbol_table(const long offset, const size_t size)
{
printf("\n>>>>>>>>>>>> parse symbol table <<<<<<<<<<<<\n\n");

if (0 != fseek(this->elf_file_, offset, 0))
{
printf("#parse_symbol_table - seek file error.\n");
return;
}

size_t sym_size = 0;
size_t sym_count = 0;
void* symbol_buffer = nullptr;
if (this->elf_bit_ == 32)
{
sym_size = sizeof(Elf32_Sym);
sym_count = size / sym_size;

this->symbol32_list_ = new Elf32_Sym[sym_count];
symbol_buffer = this->symbol32_list_;
}
else // this->elf_bit_ == 64
{
sym_size = sizeof(Elf64_Sym);
sym_count = size / sym_size;

this->symbol64_list_ = new Elf64_Sym[sym_count];
symbol_buffer = this->symbol64_list_;
}

printf("symbol count: %llu\n", sym_count);

if (0 == fread(symbol_buffer, sym_size, sym_count, this->elf_file_))
{
printf("parse symbol table%d error.\n", this->elf_bit_);
return;
}

#ifdef _PRINT_SYMBOL_TABLE_
for (size_t i = 0; i < sym_count; i++)
{
printf("\n>>>>>>>>>>>> parse symbol <<<<<<<<<<<<\n\n");

printf("index: %llu\n", i);

if (this->elf_bit_ == 32)
{
auto& symbol = this->symbol32_list_[i];
printf("symbol name: %s\n\n", get_string_from_symbol_string_table(symbol.st_name));
print_symbol(&symbol, this->elf_bit_);
}
else // this-elf_bit_ == 64
{
auto& symbol = this->symbol64_list_[i];
printf("symbol name: %s\n\n", get_string_from_symbol_string_table(symbol.st_name));
print_symbol(&symbol, this->elf_bit_);
}
}
#endif // _PRINT_SYMBOL_TABLE_
}

解析重定位表

void ElfParser::parse_relocation_table(const long offset, const size_t size)
{
printf("\n>>>>>>>>>>>> parse relocation table <<<<<<<<<<<<\n\n");

if (0 != fseek(this->elf_file_, offset, 0))
{
printf("#parse_relocation_table - seek file error.\n");
return;
}

size_t rel_size = 0;
size_t rel_count = 0;
void* rel_buffer = nullptr;
if (this->elf_bit_ == 32)
{
rel_size = sizeof(Elf32_Rel);
rel_count = size / rel_size;

this->relocation32_list_ = new Elf32_Rel[rel_count];
rel_buffer = this->relocation32_list_;
}
else // this->elf_bit_ == 64
{
rel_size = sizeof(Elf64_Rel);
rel_count = size / rel_size;

this->relocation64_list_ = new Elf64_Rel[rel_count];
rel_buffer = this->relocation64_list_;
}

printf("relocation entries count: %llu\n", rel_count);

if (0 == fread(rel_buffer, rel_size, rel_count, this->elf_file_))
{
printf("parse relocation table%d error.\n", this->elf_bit_);
return;
}

#ifdef _PRINT_RELOCATION_TABLE
for (size_t i = 0; i < rel_count; i++)
{
printf("\n>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<\n\n");

printf("index: %llu\n\n", i);

if (this->elf_bit_ == 32)
{
auto& relocation = this->relocation32_list_[i];
printf("r_offset: \t%u\n", relocation.r_offset);
printf("r_info: \t%u\n", relocation.r_info);
}
else // this-elf_bit_ == 64
{
auto& relocation = this->relocation64_list_[i];
printf("r_offset: \t%llu\n", relocation.r_offset);
printf("r_info: \t%llu\n", relocation.r_info);
}
}
#endif // _PRINT_RELOCATION_TABLE
}

測試

對 armeabi-v7a 架構的 libfoo.so 文件進行解析:

int main()
{
cout << "Hello World!" << endl;
ElfParser elf_parser(R"(..\..\..\file\armeabi-v7a\libfoo.so)");
elf_parser.parse();
return 0;
}

結果如下(省略部分過長內容):

Hello World!
open elf file: ..\..\..\file\armeabi-v7a\libfoo.so

>>>>>>>>>>>> parse elf header <<<<<<<<<<<<

Class: ELF32
Order: little endian
ident: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
type: 3
machine: 40
version: 1
entry: 0
phoff: 52
shoff: 12920
flags: 0x5000200
ehsize: 52
phentsize: 32
phnum: 8
shentsize: 40
shnum: 27
shstrndx: 26

>>>>>>>>>>>> parse section header list <<<<<<<<<<<<

section header offset: 12920
section header size: 27

>>>>>>>>>>>> parse string table <<<<<<<<<<<<

str[1] len: 9, s: .shstrtab
str[11] len: 19, s: .note.android.ident
str[31] len: 18, s: .note.gnu.build-id
str[50] len: 7, s: .dynsym
str[58] len: 7, s: .dynstr
str[66] len: 9, s: .gnu.hash
str[76] len: 12, s: .gnu.version
str[89] len: 14, s: .gnu.version_d
str[104] len: 14, s: .gnu.version_r
str[119] len: 8, s: .rel.dyn
str[128] len: 8, s: .rel.plt
str[137] len: 5, s: .text
str[143] len: 10, s: .ARM.exidx
str[154] len: 10, s: .ARM.extab
str[165] len: 7, s: .rodata
str[173] len: 11, s: .fini_array
str[185] len: 12, s: .data.rel.ro
str[198] len: 8, s: .dynamic
str[207] len: 4, s: .got
str[212] len: 5, s: .data
str[218] len: 4, s: .bss
str[223] len: 8, s: .comment
str[232] len: 22, s: .note.gnu.gold-version
str[255] len: 15, s: .ARM.attributes
string count: 24

>>>>>>>>>>>> parse section header <<<<<<<<<<<<

index: 0
name:

sh_name: 0
sh_type: 0x0
sh_link: 0
sh_info: 0
sh_flags: 0
sh_offset: 0
sh_size: 0
sh_addr: 0
sh_addralign: 0
sh_entsize: 0

>>>>>>>>>>>> parse section header <<<<<<<<<<<<

省略(1~24)……

>>>>>>>>>>>> parse section header <<<<<<<<<<<<

index: 25
name: .ARM.attributes

sh_name: 255
sh_type: 0x70000003
sh_link: 0
sh_info: 0
sh_flags: 0
sh_offset: 12596
sh_size: 52
sh_addr: 0
sh_addralign: 1
sh_entsize: 0

>>>>>>>>>>>> parse section header <<<<<<<<<<<<

index: 26
name: .shstrtab

sh_name: 1
sh_type: 0x3
sh_link: 0
sh_info: 0
sh_flags: 0
sh_offset: 12648
sh_size: 271
sh_addr: 0
sh_addralign: 1
sh_entsize: 0

>>>>>>>>>>>> parse symbol string table <<<<<<<<<<<<

str[1] len: 12, s: __cxa_atexit
str[14] len: 4, s: LIBC
str[19] len: 7, s: libc.so
str[27] len: 9, s: libfoo.so
str[37] len: 14, s: __cxa_finalize
str[52] len: 53, s: Java_io_l0neman_nativetproject_NativeHandler_getHello
str[106] len: 6, s: dladdr
str[113] len: 8, s: libdl.so
str[122] len: 8, s: _Z4testv
str[131] len: 9, s: _Z5func1i
str[141] len: 22, s: __aeabi_unwind_cpp_pr0
str[164] len: 8, s: snprintf
str[173] len: 22, s: __aeabi_unwind_cpp_pr1
str[196] len: 15, s: global_init_var
str[212] len: 17, s: global_uninit_var
str[230] len: 6, s: printf
str[237] len: 16, s: _Unwind_Complete
str[254] len: 23, s: _Unwind_DeleteException
str[278] len: 31, s: _Unwind_GetLanguageSpecificData
str[310] len: 22, s: _Unwind_GetRegionStart
str[333] len: 22, s: _Unwind_RaiseException
str[356] len: 14, s: _Unwind_Resume
str[371] len: 15, s: _Unwind_VRS_Get
str[387] len: 21, s: _Unwind_VRS_Interpret
str[409] len: 15, s: _Unwind_VRS_Pop
str[425] len: 15, s: _Unwind_VRS_Set
str[441] len: 22, s: __aeabi_unwind_cpp_pr2
str[464] len: 18, s: __gnu_unwind_frame
str[483] len: 4, s: __sF
str[488] len: 16, s: __stack_chk_fail
str[505] len: 17, s: __stack_chk_guard
str[523] len: 5, s: abort
str[529] len: 16, s: decode_eht_entry
str[546] len: 6, s: fflush
str[553] len: 7, s: fprintf
str[561] len: 13, s: unw_get_fpreg
str[575] len: 17, s: unw_get_proc_info
str[593] len: 11, s: unw_get_reg
str[605] len: 14, s: unw_getcontext
str[620] len: 14, s: unw_init_local
str[635] len: 10, s: unw_resume
str[646] len: 17, s: unw_save_vfp_as_X
str[664] len: 13, s: unw_set_fpreg
str[678] len: 11, s: unw_set_reg
str[690] len: 8, s: unw_step
str[699] len: 15, s: __aeabi_memclr8
str[715] len: 14, s: __aeabi_memcpy
str[730] len: 23, s: __gnu_Unwind_Find_exidx
str[754] len: 17, s: unw_get_proc_name
str[772] len: 12, s: unw_is_fpreg
str[785] len: 19, s: unw_is_signal_frame
str[805] len: 20, s: unw_local_addr_space
str[826] len: 11, s: unw_regname
str[838] len: 6, s: _edata
str[845] len: 11, s: __bss_start
str[857] len: 4, s: _end
str[862] len: 12, s: libstdc++.so
str[875] len: 7, s: libm.so
string count: 58

>>>>>>>>>>>> parse program list <<<<<<<<<<<<

program header offset: 52
program header size: 8

>>>>>>>>>>>> parse program header <<<<<<<<<<<<

index: 0

p_type: 0x6
p_flags: 4
p_offset: 52
p_vaddr: 52
p_paddr: 52
p_filesz: 256
p_memsz: 256
p_align: 4

>>>>>>>>>>>> parse program header <<<<<<<<<<<<

省略(1~5)……

>>>>>>>>>>>> parse program header <<<<<<<<<<<<

index: 6

p_type: 0x70000001
p_flags: 4
p_offset: 9776
p_vaddr: 9776
p_paddr: 9776
p_filesz: 416
p_memsz: 416
p_align: 4

>>>>>>>>>>>> parse program header <<<<<<<<<<<<

index: 7

p_type: 0x6474e552
p_flags: 6
p_offset: 11784
p_vaddr: 15880
p_paddr: 15880
p_filesz: 504
p_memsz: 504
p_align: 4

>>>>>>>>>>>> parse section list <<<<<<<<<<<<

parse section:
ignoreparse section: .note.android.ident
ignoreparse section: .note.gnu.build-id
ignoreparse section: .dynsym

>>>>>>>>>>>> parse symbol table <<<<<<<<<<<<

symbol count: 53

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

index: 0
symbol name:

st_name: 0
st_value: 0
st_size: 0
st_info: 0
st_other: 0
st_shndx: 0

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

省略(1~17)……

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

index: 18
symbol name: Java_io_l0neman_nativetproject_NativeHandler_getHello

st_name: 52
st_value: 4061
st_size: 32
st_info: 18
st_other: 0
st_shndx: 13

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

省略(19~49)……

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

index: 50
symbol name: _Z5func1i

st_name: 131
st_value: 4033
st_size: 16
st_info: 18
st_other: 0
st_shndx: 13

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

index: 51
symbol name: _Unwind_GetRegionStart

st_name: 310
st_value: 6145
st_size: 52
st_info: 18
st_other: 0
st_shndx: 13

>>>>>>>>>>>> parse symbol <<<<<<<<<<<<

index: 52
symbol name: unw_get_proc_name

st_name: 754
st_value: 6649
st_size: 32
st_info: 18
st_other: 0
st_shndx: 13
parse section: .dynstr
ignoreparse section: .gnu.hash
ignoreparse section: .hash
ignoreparse section: .gnu.version
ignoreparse section: .gnu.version_d
ignoreparse section: .gnu.version_r
ignoreparse section: .rel.dyn

>>>>>>>>>>>> parse relocation table <<<<<<<<<<<<

relocation entries count: 25

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 0

r_offset: 15880
r_info: 23

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

省略(1~22)……

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 23

r_offset: 16240
r_info: 4885

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 24

r_offset: 16248
r_info: 5397

parse section: .rel.plt

>>>>>>>>>>>> parse relocation table <<<<<<<<<<<<

relocation entries count: 30

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 0

r_offset: 16264
r_info: 534

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

省略(1~26)……

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 27

r_offset: 16372
r_info: 790

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 28

r_offset: 16376
r_info: 1046

>>>>>>>>>>>> parse relocation entry <<<<<<<<<<<<

index: 29

r_offset: 16380
r_info: 3606
parse section: .plt
ignoreparse section: .text
ignoreparse section: .ARM.exidx
ignoreparse section: .ARM.extab
ignoreparse section: .rodata
ignoreparse section: .fini_array
ignoreparse section: .data.rel.ro
ignoreparse section: .dynamic
ignoreparse section: .got
ignoreparse section: .data
ignoreparse section: .bss
ignoreparse section: .comment
ignoreparse section: .note.gnu.gold-version
ignoreparse section: .ARM.attributes
ignoreparse section: .shstrtab
ignore
>>>>>>>>>>>> release <<<<<<<<<<<<

close elf file.
delete section header 32 array.
delete program header 32 array.
delete string table.
delete symbol string table.
delete symbol 32 list.
delete relocation 32 list.

F:\L0neLabs\AndroidLabs\TProject\Executable\analysis\ElfFileParser\ElfFileParser\out\build\x64-Debug (default)\ElfFileParser.exe (process 16804) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .

解析源碼參考

相關焦點

  • 理解elf文件的got和plt
    本文為看雪論壇優秀文章看雪論壇作者ID:Heavenml在Linux平臺下,elf文件中的got節和plt節在動態連結過程中起著非常重要的作用
  • Android Backup 文件解析
    第一題首先是android backup 的問題,這個可以手動提取backup的文件,也可以使用abe.jar提取(需要改一下文件格式),使用前者的推薦看一下* [這篇博文](http://bobao.360.cn/learning/detail/169.html)。
  • Android-HIDL實例解析
    test ├── Android.mk └── client.cpp3 directories, 9 filesweiqifa@bsp-ubuntu1804:~/mt8167s-9.0-sdk$default 裡面編譯後會生成服務,服務的名字看Android.bp文件
  • 幾個命令了解ELF文件的「秘密」
    在交叉編譯的時候,這個文件頭的信息也非常有用。例如你在x86的機器上交叉編譯出powerpc的可執行文件,在powerpc上卻不被識別,不能運行,不如用readelf看看它的Machine欄位,是不是沒有編譯好。
  • ELF文件格式解析
    ELF文件格式的相關知識是Linux下進行pwn以及reverse的基礎,是二進位可執行文件的一種形式,下面我們通過一個ELF文件的生成,並結合其ELF文件結構分析一下一個二進位文件在系統中執行時與權限相關的一些ELF結構知識點。文章內容較為淺顯,大佬可略過,文章有不足之處,也懇請批評指正。
  • Android Webp 完全解析 快來縮小apk的大小吧
    4.2.1起步的話,目前來看,我是不能接受的,所以只有引入so來做低版本兼容了。(2)兼容so的獲取好在官方已經提供了相關webp支持的源碼了,點擊下載:如果你的ndk的知識足夠的話,可以自己利用源碼,去生成so文件使用。
  • Android系統啟動流程(三)解析SyetemServer進程啟動過程
    frameworks/base/core/java/com/android/internal/os/RuntimeInit.java啟動Binder線程池接著我們來查看nativeZygoteInit函數對用的JNI文件,如下所示。frameworks/base/core/jni/AndroidRuntime.cpp
  • android平臺arm指令學習和調試
    include$(BUILD_EXECUTABLE)2)編譯一個so,android.mk文件內容如下:LOCAL_PATH:=$(callmy-dir)include$(CLEAR_VARS)LOCAL_MODULE:=testJniSoLOCAL_SRC_FILES:=testJniSo.c
  • android 動態創建布局文件 - CSDN
    在應用程式開發時有時不同的狀態需要對應不同的布局文件。其實比較簡單,以橫屏與豎屏變換為例切換不同的布局文件。首先,創建一個項目,在Manifest文件中為MainActivity添加configChanges屬性如下:<span style="white-space:pre"> </span><activity android:name=".MyActivity" android:label="@string/app_name"
  • 【Android 原創】從OWASP CrackMe學Android逆向(二)
    這個題目涉及.so動態調試的知識。UnCrackable-Level2同樣的, 先將UnCrackable-Level2.apk放到JEB中分析, 一進到MainActivity, 就發現了一個神奇的東西, 這個應用加載了libfoo.so文件。
  • Android模擬器和安裝APK文件
    如果搜尋引擎能夠做到讓每個相同的文件只有一份就好了。本文引用地址:http://www.eepw.com.cn/article/201610/305388.htm回過頭來你會發現,做為一個開發者,有的時候,官方文檔是最好的參考資料。而不是Google百度。好了進入正題,今天要講的是關於android模擬器和apk鏡像文件的一些事情。
  • AndServer+Service 打造 Android 伺服器實現 so 文件調用
    掃碼或搜索:進擊的Coder 發送 即可立即永久解鎖本站全部文章 so 文件調用 隨著 Android 移動安全的高速發展,不管是為了執行效率還是程序的安全性等
  • Hook Android C代碼(Cydia Substrate)
    參考學習的英文博文https://koz.io/android-substrate-c-hooking/創建一個目標apk編寫目標項目,用於本次實操過程的hook對象1.創建項目android create project--target android-23 --path targetapp --package
  • Android搭建屬於自己的技術堆棧和App架構
    最終我們的日誌記錄模塊將由timber+Logger+LogUtils組成,當然輪子找到了,輪子的兼容合併就得靠我們自己實現了,同時我們還得增加列印到文件的日誌樹和列印到網絡的日誌樹實現。六、JSON解析能力移動網際網路產品與伺服器端通信的數據格式,如果沒有特殊需求的話,一般都使用JSON格式。
  • 迅為開發板安卓JNI開發實戰教程使用編譯好的 so 庫
    轉自迅為開發板安卓JNI開發指南硬體平臺:迅為iTOP-412開發板B站搜索:北京迅為,免費觀看同步視頻教程:上一小節我們已經編譯了 so 庫,那麼我們或者其他人拿到這個 so 後要怎麼使用呢存放 so 庫的文件夾一定要為 jniLibs,名字不要改,如下圖所示:Jnilibs 文件夾放的就是我們生成的 so 庫,如下圖所示:然後我們點擊一下 AS 軟體的同步,如下所示:同步完成以後就可以在工程目錄下看到我們這個 so 的文件夾了
  • 一次Android權限刪除經歷
    2.初步定位首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest文件並不是app下的該文件,從android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的manifest文件是由多個menifest文件合併而來的,其合併順序如下
  • 如何自學Android, 教大家玩爆Android
    文件存儲通過Java的IO流將數據以文件的形式存儲在磁碟上。SharedPreferences以固定格式的xml文件的形式將數據存儲在磁碟上。資料庫存儲一般是指將數據存儲到Android自帶的sqlite資料庫中。網絡存儲是將數據傳到雲端進行存儲。
  • 將兩個 Crosswalk* Android* APK 文件提交到 Google Play Store*...
    請務必將兩個 APK 文件提交到 Android* 商店,確保儘可能多的設備能夠安裝並運行您的 Crosswalk 應用。 以下是操作說明。為簡化文件下載流程,構建系統將這兩個文件組合成一個 zip 文件。 下載完 zip 文件後,先解壓文件內容,然後再執行後續步驟。這兩個 APK 文件的名稱採用以下格式: AppName.android.crosswalk.x86.
  • android so逆向專題及常見問題 - CSDN
    2.從smali中找到相應的so,IDA打開。用baksmali將apk反編譯成.smali文件然後打開IntelliJ IDEA,創建一個java工程.將上一步反編譯出的smali文件導入到src文件夾中。如圖:然後打開eclipse,切換到DDMS視圖,選中手機,查看其埠,如圖:圖上可以看到,目標應用的埠為8631.
  • THE ELF OF THE ROSE故事
    THE ELF OF THE ROSE故事      IN the midst of a garden grew a rose-tree, in full      blossom, and in the prettiest of all the roses lived an elf.