这个主要是我学习二进制分析实战的笔记,今天来到第一章节,简单的介绍一下二进制分析。本章节主要涉及编译相关的简要知识。如下是一个C语言的程序源码。
#include <stdio.h>
#define FORMAT_STRING "%s"
#define MESSAGE "Hello, world!\n"
int
main(int argc, char * argv[]) {
printf(FORMAT_STRING, MESSAGE);
return 0;
}
以下的内容是围绕这段代码的进行展开,首先说一下这段代码也就是一个简单的输出。
C编译过程
上图是介绍了源代码文件通过预处理、编译、汇编、链接等过程,生成了二进制可执行文件。
1、预处理阶段
stdio.h头文件全部包含在内,其所有的类型定义、全局变量及函 数原型都被“复制”到源文件中。因为每个#include指令都会发生这种情况,所以预处理器输出可能非常冗长。预处理器还完整地扩展 了#define定义的任何宏的所有用法。在示例中,这意味着对 printf(FORMAT_STRING和MESSAGE)的两个参数进行计算,并用它们所代表的常量字符串进行替换。
使用命令:gcc -E -P test1.c 。生成的结果:
typedef long unsigned int size_t; typedef unsigned long int __fsblkcnt_t; struct _IO_FILE; struct _IO_marker; struct _IO_codecvt; struct _IO_wide_data; typedef void _IO_lock_t; struct _IO_FILE { int _flags; char *_IO_read_ptr; char *_IO_read_end; char *_IO_read_base; char *_IO_write_base; char *_IO_write_ptr; char *_IO_write_end; char *_IO_buf_base; char *_IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; __off64_t _offset; struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; extern int printf (const char *__restrict __format, ...); extern int __overflow (FILE *, int); .......... int main(int argc, char * argv[]) { printf("%s", "Hello, world!\n"); return 0; }
编译阶段
使用命令:gcc -S -masm=intel test1.c ,生成了汇编文件 test1.s , 查看汇编的内容:
.file "test1.c" .intel_syntax noprefix .text .section .rodata .LC0: .string "Hello, world!" .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 push rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 mov rbp, rsp .cfi_def_cfa_register 6 sub rsp, 16 mov DWORD PTR -4[rbp], edi mov QWORD PTR -16[rbp], rsi lea rdi, .LC0[rip] call puts@PLT mov eax, 0 leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8
汇编阶段
使用命令:gcc -c test1.c
# 生成“.o"文件,查看“test1.o"文件,可以看到已经是ELF文件了。
# file test1.o
test1.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
链接阶段
此阶段将所有对象 文件链接到一个二进制可执行文件中。在现代系统中,链接阶段有时 会包含额外的优化过程,被称为链接时优化(Link-Time Optimization,LTO)。
C++root@z:~/bin_learn/chater01# gcc test1.c root@z:~/bin_learn/chater01# file a.out a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ea93fdba43c44214e687d2943d2d68c1f4f56403, for GNU/Linux 3.2.0, not stripped
最后a.out文件可以直接执行。解释器/lib64/ld-linux-x86- 64.so.2的文件输出会告诉你,当可执行文件加载到内存中执行时, 哪个动态链接器将会被用来解析动态库的最终依赖关系。
符号和剥离的二进制文件
查看符号信息
” readelf --syms a.out“ 输出的结果做了一些删减,各位朋友可以自己做实验的时候查看具体的输出。
Symbol table '.dynsym' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 65 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND ………… 26: 0000000000004010 0 SECTION LOCAL DEFAULT 26 27: 0000000000000000 0 SECTION LOCAL DEFAULT 27 28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones 58: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end 59: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start 60: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 61: 0000000000001149 38 FUNC GLOBAL DEFAULT 16 main 62: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__ 63: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 64: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
我们可以关注索引号为61的符号:
61: 0000000000001149 38 FUNC GLOBAL DEFAULT 16 main
main函数有一个符号。你可以看到它指定了当二进制文件加载到内存时main将驻留的地址(0x00001149)。输出 还显示main的代码大小(38字节),并指出你正在处理一个函数符号 (类型为FUNC)。
剥离二进制文件
1)查看对象文件
# .rodata节代 表的是“只读数据” root@z:~/bin_learn/chater01# objdump -sj .rodata test1.o test1.o: file format elf64-x86-64 Contents of section .rodata: 0000 48656c6c 6f2c2077 6f726c64 2100 Hello, world!. # 以intel的语法反汇编 root@z:~/bin_learn/chater01# objdump -M intel -d test1.o test1.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: f3 0f 1e fa endbr64 4: 55 push rbp 5: 48 89 e5 mov rbp,rsp 8: 48 83 ec 10 sub rsp,0x10 c: 89 7d fc mov DWORD PTR [rbp-0x4],edi f: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi 13: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 1a <main+0x1a> 1a: e8 00 00 00 00 call 1f <main+0x1f> 1f: b8 00 00 00 00 mov eax,0x0 24: c9 leave 25: c3 ret root@z:~/bin_learn/chater01#
显示对象文件中存在的所有重定位符号
# readelf --relocs test1.o Relocation section '.rela.text' at offset 0x260 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000016 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4、 # 000000000016重定位符号告诉链接器应该解析对字符串的引用,使其 指向在.rodata节中结束的任意位置。 00000000001b 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4 # 00000000001b 重定位符号 告诉链接器应该解析对puts的引用。 Relocation section '.rela.eh_frame' at offset 0x290 contains 1 entry: Offset Info Type Sym. Value Sym. Name + Addend 000000000020 000200000002 R