动机:因为一个高中生CTF有近40%的pwn题是关于动态链接和ELF文件格式的修改的,虽然我一道题都没有写出来(借口是从没做过这类题😬😬),但是在解题的过程中我却收获了很多原来不知道的知识,因此产生了RTFSC一探究竟的想法
写的时候看到了一篇不错的博客:动态链接的步骤与实现 – yooooooo – 博客园 (cnblogs.com 我发现这个博客真的是深入浅出,正想着怎么有这么好的一篇博客,看到了一个“本书”,这个好像就是我上学期学习动态链接看的一本书,《程序员的自我修养》,哎,果然不同的时候看有完全不一样的感觉
申明,这一部分很多我都是拷问GPT和自己猜的,不保证完全是这样啊
请读者(和以后复习的我)先随我进入glibc的深处,从一条syscall开始出发,先从操作系统的视角来看动态链接器(内核态),之后我会将视角拉到动态链接器上面(我称之为伪内核态嘿嘿),再之后我会将视角拉到普通程序上面(用户态),最后视角将是用户程序trap into动态链接器(syscall部分的原理是我的猜测(合理推测))
这是这条syscall的原型:
int execve(const char *path, char *const argv[], char *const envp[]);
os接收到这样一条syscall时,会调用内部的loader(加载器),这个加载器会将这个ELF文件指示的部分加载到内存中,加载器看的是ELF文件中的phdr的部分,他将ELF文件中phdr指示的类型为PT_LOAD的条目按照指示的权限,大小,被加载进去的虚拟内存地址加载到指定的位置(这里插一句,如果是人来看readelf -a的话不要去看phdr,去看shdr,section比segment分的更细,并且非常清晰,顺序基本上也就是加载进内存的顺序,也有权限描述啥的,你可以在shdr中看到一些有趣的现象,比如symtab和strtab没有加载进内存中,但是dynsym和dynstr都被加载进去了),loader除了加载ELF loader所指示的部分,他还会看phdr的INTERP segment,这个segment指示了动态链接器的路径,于是os的loader就会根据操作系统的策略来决定来将ld加载到哪里(其实ld.so也不过就是一个ELF文件,并且这个文件也是一个需要动态链接的程序,我也是写这些文字的时候突然想到,这特别像世界第一个款编译器是被怎么编译的这个问题,所以说这个动态链接器肯定需要和一般的ELF文件不同,他通过“自举”的方式来对自己进行动态链接,这个之后应该会在源码中有所体现),之后os根据动态链接器中的ehdr的e_entry来设置pc,由动态链接器接管
到此就是动态链接器在进程中的“诞生”过程了(pc = 动态链接器的_start),现在将视角拉到动态链接器上面
ok,开始看源码吧
先在glibc中找到_start函数的符号,发现他一开始调用了_dl_start函数……
give up了,放弃了,我原本以为不难的,还是眼高手低了哎,可是这其实是libc的启动代码,比我之前看的malloc什么的源码还难的多,我觉得那本书中有段话说到我心里去了:
“源代码在经过多年的发展和锤炼后,变得非常注重性能和效率,而很少考虑可读性,这使得通过挖掘源代码理解机制变得更为困难”
最主要的是malloc,printf这些函数的行为我太清楚了,原理清楚那自然看代码不难了,但是这个启动的原理我就有点搞不明白,但是我发现了手册上似乎清楚的定义了libc启动的详细过程
startup (gnu.org)(这个手册似乎描述的不是linux内核的)
DynamicLoader – glibc wiki (sourceware.org)(ftrace)
还是手册管用,直接把我想要了解的全部写的清清楚楚,不禁反思,为什么我之前想要了解一个程序的行为第一反应是直接RTFSC而不是RTFM,这下碰壁了吧,不过这样我也记忆深刻,每当想RTFSC的时候想一想我有没有先好好读手册。
似乎主要的函数是dl_main,但是我还没看到那就放弃了……(超,好烦,我觉得我还是有必要看一下dl_main这个函数的,但是他有1000行啊,之后有时间再看吧)
丢几个链接
我在上学期刚学pwn的时候就看了《程序员的自我修养》那本书,和我现在看的感觉完全不同,我打算好好琢磨一下这本书💪💪