ELF entry point和装载地址
为了研究ELF文件装载到内存的哪里,以及从哪里开始运行程序,环境:ubuntu12.04 64位,gcc4.6.3。
使用的源代码是:
#include stdlib.h void hello(void) exit(42); int main(void) return 24; }
程序并不是从main函数开始执行的,gcc -o main main.c时,默认会连接libc.so(可以指定-nodefaultlib, -nostdlib取消连接),并且会添加一个启动代码_start函数(可以指定-nodefaultlib, -nostdlib不添加启动代码),用于初始化,并提供main函数的argc, argv等参数,_start函数中会调用main函数。
readelf -d main可以看到,程序链接了libc.so.6:
$ readelf -d main Dynamic section at offset 0xe50 contains 20 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x400390 0x000000000000000d (FINI) 0x4005c8objdump -d main可以看到,程序中有个_start函数,_start的地址是4003d0:
$ objdump -d main | grep _start 400394: e8 63 00 00 00 callq 4003fc call_gmon_start 00000000004003b0 __libc_start_main@plt-0x10 : 00000000004003c0 __libc_start_main@plt : 00000000004003d0 _start : 4003f4: e8 c7 ff ff ff callq 4003c0 __libc_start_main@pltreadelf -h main | grep Entry可以看到:
Entry point address: 0x4003d0程序的入口地址是0x4003d0,也就是_start函数的地址,程序装载到内存后,从0x4003d0(_start)开始执行。
那么,我不想执行_start函数呢,可以通过ld的参数-e指定入口函数,使用gcc -o main mian.c -Wl,-ehello编译,-Wl用于指定后面的参数是给ld的,-e指定入口函数是hello。
注意hello不能直接return,因为返回地址是错误的,会导致coredump;hello应当使用exit()函数退出程序。./main得到:
$ ./main $ echo $? 42objdump -d main | grep hello查看hello的地址:
$ objdump -d main | grep hello 00000000004004f4 hello :readelf -h main | grep Entry查看程序的入口地址:
$ readelf -h main | grep Entry Entry point address: 0x4004f4证明了-Wl,-ehello可以指定程序的入口函数为hello。
那么程序的入口地址0x4004f4是怎么来的呢?
0x4f4其实是hello函数在ELF文件中的偏移量,可以通过hexdump -C main | grep -A5 -B5 4f0查看到
$ hexdump -C main | grep -A5 -B5 4f0 000004a0 0b 20 00 ff 14 c5 38 0e 60 00 48 8b 05 77 0b 20 |. ....8.`.H..w. | 000004b0 00 48 39 d8 72 e2 c6 05 63 0b 20 00 01 48 83 c4 |.H9.r...c. ..H..| 000004c0 08 5b 5d c3 66 66 66 2e 0f 1f 84 00 00 00 00 00 |.[].fff.........| 000004d0 48 83 3d 70 09 20 00 00 55 48 89 e5 74 12 b8 00 |H.=p. ..UH..t...| 000004e0 00 00 00 48 85 c0 74 08 5d bf 48 0e 60 00 ff e0 |...H..t.].H.`...| 000004f0 5d c3 90 90 span 55 48 89 e5 bf 2a 00 00 00 e8 fe fe /span |]...UH...*......| 00000500 span ff ff /span 55 48 89 e5 b8 18 00 00 00 5d c3 90 90 90 |..UH.......]....| 00000510 48 89 6c 24 d8 4c 89 64 24 e0 48 8d 2d 03 09 20 |H.l$.L.d$.H.-.. | 00000520 00 4c 8d 25 fc 08 20 00 4c 89 6c 24 e8 4c 89 74 |.L.%.. .L.l$.L.t| 00000530 24 f0 4c 89 7c 24 f8 48 89 5c 24 d0 48 83 ec 38 |$.L.|$.H.\$.H..8| 00000540 4c 29 e5 41 89 fd 49 89 f6 48 c1 fd 03 49 89 d7 |L).A..I..H...I..|然后objdump -d main | grep -A5 -B5 hello查看hello对应的机器码:
$ objdump -d main | grep -A5 -B5 hello 4004f0: 5d pop %rbp 4004f1: c3 retq 4004f2: 90 nop 4004f3: 90 nop 00000000004004f4 hello : 4004f4: span 55 /span push %rbp 4004f5: span 48 89 e5 /span mov %rsp,%rbp 4004f8: span bf 2a 00 00 00 /span mov $0x2a,%edi 4004fd: span e8 fe fe ff ff /span callq 400400 exit@plt证明了这一点。
然后0x400000是什么呢?
这个是ELF装载到内存时的起始位置,可以通过ld的参数-Wl,-Ttext-segment,0x400000指定,比如我们gcc -o main main.c -Wl,-ehello -Wl,-Ttext-segment,0x4200000编译程序,给程序换一个装载地址,然后readelf -h main | grep Entry得到:
Entry point address: 0x42004f4hello在文件中的偏移依然为0x4f4,然后加上装载地址0x4200000就可以得到程序执行的入口地址:0x42004f4。
objdump -d main | grep hello -A 10可以看到装载地址变了,程序的PC指针位置也都改变了(因为我们编译的main是地址相关的ELF):
$ objdump -d main | grep hello -A 10 00000000042004f4 hello : span 42004f4 /span : 55 push %rbp span 42004f5 /span : 48 89 e5 mov %rsp,%rbp span 42004f8 /span : bf 2a 00 00 00 mov $0x2a,%edi span 42004fd /span : e8 fe fe ff ff callq 4200400 exit@plt 0000000004200502 main : 4200502: 55 push %rbp 4200503: 48 89 e5 mov %rsp,%rbp 4200506: b8 18 00 00 00 mov $0x18,%eax 420050b: 5d pop %rbp
注意
1、-Ttext-segment指定的必需是一个页对齐的地址。
2、动态库的装载位置不是固定的,一般可以认为动态库的-Ttext-segment=0,没有使用;如果想直接运行.so的话(glibc里面很多.so都可以直接运行),需要为.so指定ld-linux.so(.so默认没有指定):
gcc -shared -fPIC -o libx.so x.c -Wl,--dynamic-linker=some_ld_linux.so.x
或者代码里面写:const char my_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.3"; //需要保证这个ld-linux.so存在且能用
可以使用readelf -l查看.inter段,也就是指定的ld-linux.so
3、-Ttext-segment并不是指定.text段的加载位置,而是指定整个elf的加载位置。
如果想指定.text段的加载位置,可以:
-Tbss=org
-Tdata=org
-Ttext=org
Same as --section-start, with ".bss", ".data" or ".text" as the
sectionname.
4、gcc -Wl,-Ttext-segment=0x400000 -Wl,-Ttext=0x800000 -o x x.c这样是可以的,但是如果-Ttext指定的比较小,要么程序无法运行,要么可能和其他段冲突。加完-Ttext之后,.text段以及后面的段,在文件中的偏移会变大不少,中间填充的0是为了加载到内存后的页对齐,在内存中的加载位置都会从-Ttext指定的位置开始。
Unity打包符号表 使用ndk addr2line.exe+符号表 将崩溃内存地址解析成函数名 符号表的路径,符号表发布出来的时候是一个zip文件要把它解压出来,里面会有两个文件:arm64-v8a(64位)、armeabi-v7a(32位)不过unity默认打包出来的都是64位的程序,所以这个前面加上你的真实路径+arm64-v8a\libil2cpp.sym.so就可以了。
PE格式:VA地址与FOA地址 PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等。
Android逆向:二进制xml文件解析(Start Tag Chunk) 在Android中,xml文件经过编译后都是不可读的二进制文件。今天我们来解析一下这个二进制文件的内容,看看如何与我们的源码进行对应。
gdb打印结构体member offset linux的crash有个好处就是可以方便打印结构体成员变量的offset, 有时候对汇编的时候, 需要偏移, 可惜crash需要一个活体才行, 不能单纯的vmlinux, 因为它就是这么设计的 gdb天生没有这个功能, 不过python可以实现 cat offset.py import gdb class Offsets(gdb.Command): def __in
相关文章
- 通过/dev/mem只能访问高端内存以下的内核线性地址空间
- Ubuntu18.04修改主机名和网卡地址
- 国内常用NTP服务器地址及
- 阿里云服务器怎么正确使用OSS内网地址?
- Springboot+Vue实现将图片和表单一起提交到后端,同时将图片地址保存到数据库、再次将存储的图片展示到前端vue页面
- 第14.18节 爬虫实战4: request+BeautifulSoup+os实现利用公众服务Wi-Fi作为公网IP动态地址池
- inux网卡与MAC地址绑定方法总结
- 使用 URLDecoder 和 URLEncoder 对统一认证中的http地址转义字符进行处理
- 海思HI35XX通过uboot查看flash指定地址的数据(转)
- linux下查看nginx配置文件地址
- Linux Shell脚本中获取本机ip地址方法
- BAT等大厂已开源的70个实用工具盘点(附下载地址)
- ajax请求成功后打开新窗口地址
- bootm命令中地址参数,内核加载地址以及内核入口地址