魔棒生成三个文件的命令还copy 到这里!
$ echo 1234 |xxd -r -ps > 1.bin
$ objcopy -I binary -O elf64-x86-64 -B i386 1.bin 1.o
$ gcc -shared -o 1.so 1.o
shared object elf 格式研究.
共享对象是可以加载到内存中文件(so文件), 与可执行文件没什么差别.,其特点是更加突出了导入,导出的函数及变量.
编译的时候需要加入-fPIC 选项.
这个8bytes二进制文件1.bin一下暴涨到7828个字节.
共享对象又往这个结构中添加了什么内容?
让电脑执行程序为什么会添加这么多东西?
我们有用的8个byte 的信息被淹没在这8K 的数据中, 好在我们的数据特殊,找找还能看得到.
123456789abcdef0
把8K 的数据往上贴太占版面了, 阅读也不方便,所以此处忽略, 但研究方法还是类似.
$ objdump -s 1.so
有一个直观概念.
最好导出到文件, 用vim 加上标记来看.
$ readelf -S 1.so
There are 26 section headers, starting at offset 0x1140:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000190 00000190
0000000000000024 0000000000000000 A 0 0 4
.... 26 个section.
第一个section, 文件偏移190,
我们知道, 文件头64byte, 那0x40-0x190内容是什么呢? 哦! 明白了,是程序头.
用readelf -l 1.so 可以看到. 6个segment,每个segment header entity size 56 bytes
先来段小插曲, 6个segment 实际可简化为2个加载段. 一个可读可执行段0-6bc,
一个可读写,e00,238size, 中间缝隙6bc-e00 用0填充, 为对齐的需要.
第一个加载段分析
第一个加载段包含了很多节区,如下.
00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .eh_frame
它们的共性是只读, 占据section 的前半部.
读取note
$ readelf -n 1.so, 可导出.note.gnu.build-id
读取动态符号
$ readelf --dyn-sym 1.so
Symbol table '.dynsym' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000570 0 SECTION LOCAL DEFAULT 9
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
7: 0000000000000008 0 NOTYPE GLOBAL DEFAULT ABS _binary_1_bin_size
8: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 20 _edata
9: 0000000000201040 0 NOTYPE GLOBAL DEFAULT 21 _end
10: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 20 _binary_1_bin_end
11: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 21 __bss_start
12: 0000000000000570 0 FUNC GLOBAL DEFAULT 9 _init
13: 00000000000006a8 0 FUNC GLOBAL DEFAULT 12 _fini
14: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 20 _binary_1_bin_start
包含了15个动态符号, 看来要与外部模块连接了.
除了提供bin 文件数据3个符号外, 还提供_init, _fini 全局函数功能,
_init(570) 在.init 节, _fini(6a8) 在.fini 节.
代码入口(5c0), 是.text起始位置,对应于一个函数deregister_tm_clones,
但这是个局部符号, strip 后就没有了. 代码部分见后分析.
并增加了_edata,_end __bss_start全局符号.
读取 .gnu.version, .gnu.version_r 信息,
这些是与ABI 相关的信息.
$ readelf -V 1.so
Version symbols section '.gnu.version' contains 15 entries:
Addr: 0000000000000440 Offset: 0x000440 Link: 3 (.dynsym)
000: 0 (*local*) 0 (*local*) 0 (*local*) 0 (*local*)
004: 0 (*local*) 0 (*local*) 2 (GLIBC_2.2.5) 1 (*global*)
008: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*)
00c: 1 (*global*) 1 (*global*) 1 (*global*)
Version needs section '.gnu.version_r' contains 1 entries:
Addr: 0x0000000000000460 Offset: 0x000460 Link: 4 (.dynstr)
000000: Version: 1 File: libc.so.6 Cnt: 1
0x0010: Name: GLIBC_2.2.5 Flags: none Version: 2
读取1.so 的重定位信息.
共享对象的重定位信息是用来在模块间建立连接的, 是模块间的螺丝与螺母!
重定位的结构定义,带addend型.
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
offset 说明修改那里,
info 说明用那个符号,怎样修改,
addend是调整信息.
读取重定位内容
$ readelf -r 1.so
Relocation section '.rela.dyn' at offset 0x480 contains 8 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000200e00 000000000008 R_X86_64_RELATIVE 670
000000200e08 000000000008 R_X86_64_RELATIVE 630
000000201028 000000000008 R_X86_64_RELATIVE 201028
000000200fd8 000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000200fe0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200fe8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200ff0 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200ff8 000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0
这个so依赖于libc, 引入外部数据5个.外部数据地址在.got区中定义, 初始值是0, 由loader 填充正确值.
还有3个R_X86_64_RELATIVE, 绝对值630,670,201028, 这是无名符号,对应于.init_array, .fini_array .data 地址, 需要rebase 重定位
填充的内容:
670 是 <frame_dummy>入口
630 是<__do_global_dtors_aux>的入口
这2个符号都是LOCAL, 会被strip 掉, 因而会成为无名入口.
201028 是 .data 的地址,代码会跳转或者引用到该地址. 具体要看反汇编!
Relocation section '.rela.plt' at offset 0x540 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000201018 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000201020 000600000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0
引入外部函数2条R_X86_64_JUMP_SLOT,
外部函数地址在.got.plt 中,初始值一般是延迟加载对应的函数地址.
对照
$ objdump -R 1.so
1.so: file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000200e00 R_X86_64_RELATIVE *ABS*+0x0000000000000670
0000000000200e08 R_X86_64_RELATIVE *ABS*+0x0000000000000630
0000000000201028 R_X86_64_RELATIVE *ABS*+0x0000000000201028
0000000000200fd8 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable
0000000000200fe0 R_X86_64_GLOB_DAT __gmon_start__
0000000000200fe8 R_X86_64_GLOB_DAT _Jv_RegisterClasses
0000000000200ff0 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable
0000000000200ff8 R_X86_64_GLOB_DAT __cxa_finalize
0000000000201018 R_X86_64_JUMP_SLOT __gmon_start__
0000000000201020 R_X86_64_JUMP_SLOT __cxa_finalize
反编译代码节
$objdump -d 1.so 反汇编观察,
反编译了.init, .plt, .text, .fini 节,
.init, .fini节是为全局初始化,反初始化而构造的,
.fini 目前为空,
.init 判别是否调用gmon_start, 它的判断依据是.got 中的一项 200fe0 待分析!!
.plt (过程连接表)是调用外部函数的轻巧代码,向.got.plt表地址进行跳转.
201008 储存有本模块module_ID, 201010是dynamic_resolved_name, 依次解析它的外部调用地址.
那么是谁会调用这两个外部函数? 又是谁会访问5个外部数据? 还有谁访问了这3个需要rebase 的本地指针.
这个就是反汇编了.
.text 反汇编代码阅读很过瘾, 让人云里雾里!!! 框架,
拿外部是数据做判断,调用外部的函数.非我所控.
代码就不贴了,可以结合IDA 观察!
.eh_frame 是异常处理框架节,目前是空,就不用关心了.
第二个加载段分析
第二个加载段可读写属性, 重定位目的地址就在这了. 包含如下节区.
01 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
.init_array, .fini_array, 这里有见定义,它们的地址加载时有rebase重定位, 系统框架会使用该地址.
;org 200E00h
.init_array:0000000000200E00 __frame_dummy_init_array_entry dq offset frame_dummy
;org 200E08h
.fini_array:0000000000200E08 __do_global_dtors_aux_fini_array_entry dq offset __do_global_dtors_aux
但未见使用.
.jcr 为空(与java 有关?),
.dynamic 是动态连接的表头
$ readelf -d 1.so
Dynamic section at offset 0xe18 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 依赖libc 库
0x000000000000000c (INIT) 0x570 INIT 代码入口570
0x000000000000000d (FINI) 0x6a8 FINI 代码入口6a8
0x0000000000000019 (INIT_ARRAY) 0x200e00 array 是什么意思.
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x200e08
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x1b8 符号HASH 表地址
0x0000000000000005 (STRTAB) 0x368 字符串表地址
0x0000000000000006 (SYMTAB) 0x200 符号表地址
0x000000000000000a (STRSZ) 215 (bytes) 字符串表大小
0x000000000000000b (SYMENT) 24 (bytes) 符号项大学
0x0000000000000003 (PLTGOT) 0x201000 .got.plt 地址
0x0000000000000002 (PLTRELSZ) 48 (bytes) .rela.plt 的大小
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x540A .rela.plt 的地址
0x0000000000000007 (RELA) 0x480 .rela.dyn 的地址
0x0000000000000008 (RELASZ) 192 (bytes) .rela.dyn 的大小
0x0000000000000009 (RELAENT) 24 (bytes) .rel 项大小
0x000000006ffffffe (VERNEED) 0x460 VERSION need 地址
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x440 VERSION Symbol 地址
0x000000006ffffff9 (RELACOUNT) 3 不知道什么意思, 3个重定位? 哪三个? ABS 吗? 不懂!
0x0000000000000000 (NULL) 0x0
.got 是全局偏移表.
.rel.dyn 将会把修改的全局变量地址放到这个表中.
.got.plt 是外部函数重定位表
.rel.plt 将会把修改的全局函数地址放到这个表中.
.data 是数据节,
但前面多了几个数据. 是一个rebase 地址.
.bss
框架会使用, 多看反编译代码.
.comment 节:
$ readelf -p .comment 1.so // 按字符串输出节内容