编译器用法

参考:

todo

  • gcc

    gcc: link

    g++: link

  • LLVM

    todo

  • LLVM2.0 - Clang

    todo

编译工程工具

makefile: link

cmake: link

交叉编译脚本

#!/bin/bash

install_arm32() {
  sudo apt install gcc make gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
}

install_arm64() {
  sudo apt install gcc make gcc-aarch64-linux-gun binutils-aarch64-linux-gnu
}
gen_hello() {
  cat > helloworld.c << EOF
#include<stdio.h>
int main()
{
  printf("Hello World!\n");
  return 0;
}
EOF
}

compile_64() {
  gcc helloworld.c -o helloworld-x86_64
}

compile_arm32() {
  arm-linux-gnueabi-gcc helloworld.c -o helloworld-arm -static
}

compile_arm64() {
  aarchi64-linux-gnu-gcc helloworld.c -o helloworld-aarch64 -static
}

# Main.
# install_arm32
# install_arm64
gen_hello
compile_64 || exit 1
compile_arm32
compile_arm64

动态链接

参考:

Linux支持动态连接库,不仅节省了磁盘、内存空间,而且可以提高程序运行效率。不过引入动态连接库也可能会带来很多问题,例如动态连接库的调试、升级更新和潜在的安全威胁。

为了让动态链接器能够进行符号的重定位,必须把动态链接库的相关信息写入到可执行文件当中:

# 通过 readelf -d 可以打印出该文件直接依赖的库
$ readelf -d test | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
# 通过 ldd 命令则可以打印出所有依赖或者间接依赖的库
$ ldd test
      linux-gate.so.1 =>  (0xffffe000) # 在文件系统中并没有对应的库文件,它是一个虚拟的动态链接库,对应进程内存映像的内核部分。参考: http://www.linux010.cn/program/Linux-gateso1-DeHanYi-pcee6103.htm
      libc.so.6 => /lib/libc.so.6 (0xb7da2000)
      /lib/ld-linux.so.2 (0xb7efc000) # 动态链接器 | 绝对路径 | readelf -x .interp test | interpeter
      # 程序的加载过程 - 动态链接器
      # 当 Shell 解释器或者其他父进程通过exec启动我们的程序时,系统会先为ld-linux创建内存映像,然后把控制权交给ld-linux,
      # 之后ld-linux负责为可执行程序提供运行环境,负责解释程序的运行,因此ld-linux也叫做dynamic loader(或intepreter)
      # 参考: http://www.ibm.com/developerworks/cn/linux/l-elf/part1/index.html

程序有两种方式使用库

  • 编译时通过 -l,-L 参数隐式使用: gcc -o test test.c -lmyprintf -L./ -I./
  • 运行时通过 LD_LIBRARY_PATH 环境变量显示使用: LD_LIBRARY_PATH=$PWD

提示

指定动态库位置

通过 LD_LIBRARY_PATH 参数,它类似 Shell 解释器中用于查找可执行文件的 PATH 环境变量,也是通过冒号分开指定了各个存放库函数的路径。

该变量实际上也可以通过 /etc/ld.so.conf 文件来指定,一行对应一个路径名。 (一般需要管理员权限)
为了提高查找和加载动态链接库的效率,系统启动后会通过 ldconfig 工具创建一个库的缓存 /etc/ld.so.cache。 如果用户通过 /etc/ld.so.conf 加入了新的库搜索路径或者是把新库加到某个原有的库目录下,最好是执行一下 ldconfig 以便刷新缓存。

更多参考: man ld-linux, /lib/ld-linux.so.2

动态链接器(dynamic linker/loader)

Linux 下 elf 文件的动态链接器是 ld-linux.so,即 /lib/ld-linux.so.2。 通过 man ld-linux 可以获取与动态链接器相关的资料,包括各种相关的环境变量和文件都有详细的说明。

如:

  • LD_LIBRARY_PATH

  • LD_BIND_NOW

  • LD_PRELOAD 指定预装载一些库,以便替换其他库中的函数,从而做一些安全方面的处理

  • LD_DEBUG 用来进行动态链接的相关调试

  • ld.so.conf

  • ld.so.cache

  • /etc/ld.so.preload 指定需要预装载的库

运行时库的连接 LD_LIBRARY_PATH

库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。

一般Linux系统把 /lib/usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库是不需要进行设置搜索路径即可直接使用。 对于处于默认库搜索路径之外的库,需要将库的位置添加到 库的搜索路径之中。 设置库文件的搜索路径有下列两种方式,可任选其一使用:

  1. 会话生效 —— 在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径。

export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH

  1. 永久生效 —— 在 /etc/ld.so.conf 文件中添加库的搜索路径(绝对路径)。 ⚠️一般需管理员权限

e.g.

/usr/X11R6/lib
/usr/local/lib
/opt/lib

提示

另外,为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,会有 /etc/ld.so.cache 库列表文件可以直接读取、搜索。 /etc/ld.so.cache 是一个非文本的数据文件,不能直接编辑,它是根据 /etc/ld.so.conf 中设置的搜索路径由 /sbin/ldconfig 命令将这些搜索路径下的共享库文件集中在一起而生成的( ldconfig 命令要以 root 权限执行)。

如当安装完一些库文件(例如刚安装好glib),或者修改 ld.so.conf 增加新的库路径后,需要运行一下 /sbin/ldconfig 使所有的库文件都被缓存到 ld.so.cache 中。 否则 ld.so.conf 的更改不生效。

glibc

glibc官网地址: https://www.gnu.org/software/libc/在新窗口打开
glibc源代码包: https://ftp.gnu.org/gnu/glibc/在新窗口打开

glibc是标准C库的GNU实现。 我们采用C/C++所写的程序,运行时基本都依赖与它。 如果我们想看当前机器glibc的源代码,首先需要知道当前机器glibc的版本号,然后到glibc的官网下载对应版本的源代码。

查看版本号

# 方法一:
# 使用命令ldd,查看可执行程序依赖libc的路径
$ ldd python
        linux-vdso.so.1 (0x00007fff40beb000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9b44d70000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9b44b47000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f9b4552e000)
$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.6) stable release version 2.35. # 💡
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

# 方法二:
# ldd是glibc提供的命令,由此可知glibc的版本号
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.4) 2.35 # 💡
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

# 方法三:
$ getconf GNU_LIBC_VERSION
glibc 2.35

# 方法四:
$ strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.18
GLIBC_2.22
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_2.27
GLIBC_2.28
GLIBC_2.29
GLIBC_2.30
GLIBC_2.31
GLIBC_2.32
GLIBC_2.33
GLIBC_2.34
GLIBC_2.35 # 💡
GLIBC_PRIVATE
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.6) stable release version 2.35.

# 方法五: 调用C函数
#include <stdio.h>
#include <gnu/libc-version.h>
int main(void) 
{ 
    printf("glibc version:%s\n", gnu_get_libc_version());
 
    return 0; 
}

Musl libc

参考:

  • Musl libc:为什么我们会需要另一个 libc? | https://linuxstory.org/musl-libc-yet-another-libc/

todo 整理理解

静态编译

-static

提示

静态编译的二进制通过 ldd 可以看到 "not a dynamic executable" 的字样。 而 -nostdlib 通过 ldd 看到的是 "statically linked",这表示 “它恰好没有链接到任何库,但在其他方面与普通 PIE 相同,指定了 ELF 解释器。” —— me: 这运行情况应该跟静态编译出来的差不多???

todo What's the difference between "statically linked" and "not a dynamic executable" from Linux ldd? | https://stackoverflow.com/questions/61553723/whats-the-difference-between-statically-linked-and-not-a-dynamic-executable

问题: 安全编译识别失败

todo

nm 可以查看 elf 文件的符号信息

$ gcc -c test.c
$ nm test.o
00000000 B global
00000000 T main
          U printf

程序执行流程

todo http://www.ibm.com/developerworks/cn/linux/l-elf/part1/index.html

# 程序入口
$ readelf -h test | grep Entry
  Entry point address:               0x80482b0

# 程序有 printf 符号的地址没有确定 
# 💡 已知: printf 函数在动态链接库 libc.so 中定义,需要进行动态链接; 这里假设采用 lazy mode 方式,即执行到 printf 所在位置时才去解析该符号的地址。

# 反编译发现: 地址指向了 plt (即过程链接表)
$ objdump -d -s -j .text test | grep printf
 804837c:       e8 1f ff ff ff          call   80482a0 <printf@plt>
$ objdump -D test | grep "80482a0" | grep -v call
080482a0 <printf@plt>:
 80482a0:       ff 25 8c 95 04 08       jmp    *0x804958c
# 过程链接表(plt)
$ readelf -d test

Dynamic section at offset 0x4ac contains 20 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x8048258
 0x0000000d (FINI)                       0x8048454
 0x00000004 (HASH)                       0x8048148
 0x00000005 (STRTAB)                     0x80481c0
 0x00000006 (SYMTAB)                     0x8048170
 0x0000000a (STRSZ)                      76 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x8049578 # 这就是GOT表(全局偏移表)。该地址与0x804958c相近,说明在读GOT表! 
 0x00000002 (PLTRELSZ)                   24 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x8048240
 0x00000011 (REL)                        0x8048238
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x8048218
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x804820c
 0x00000000 (NULL)                       0x0

# 全局偏移表(got)
$ readelf -x .got.plt test

Hex dump of section '.got.plt':
  0x08049578 ac940408 00000000 00000000 86820408 ................
  0x08049588 96820408 a6820408                   ........

todo ...

二进制文件结构

先准备一个二进制可执行文件:

代码

calc.c

#include <stdio.h>

int add(int a, int b);
int c2i(char* s);

int main(int argc, char* argv[])
{
  int a = c2i(argv[1]);
  int b = c2i(argv[2]);
  printf("%d + %d = %d\n", a, b, add(a, b));
}

add.c

int add(int a, int b)
{
    return a + b;
}

tfunc.c

int c2i(char* s)
{
    return atoi(s);
}

编译/运行(静态库)


# 【静态链接】
# .o 编译过程各自独立。虽然其中calc.c使用了另外两个的符号,但是此时并不知道那些符号是在哪个文件中定义的。
# -st 当链接器把所有的.o文件链接成可执行文件的过程中,可执行文件才能确定那些符号是在哪里。
gcc -c add.c tfunc.c calc.c
# gcc add.o tfunc.o calc.o -o calc-st
ar -r liboperation.a add.o tfunc.o
gcc calc.c liboperation.a -o calc-st

# 【动态链接】
gcc -c -fpic tfunc.c add.c
gcc -shared tfunc.o add.o -o liboperation.so
gcc calc.c -o calc-dy -loperation -L$(pwd) -Wl,-rpath=$(pwd) 

# 运行
./calc 1 1

ELF 文件

ELF(Executable and Linkable Format,可执行和可链接格式)

参考:

通过 file calc 可看到 calc 程序的文件类型为 ELF

$ file calc
calc: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0e4f3ed1b2b35f1c63e26a0b7e6b59bfb2ebe1a2, for GNU/Linux 3.2.0, not stripped

ELF文件格式如下:

名称说明
ELF header (ELF 头部)
Program header table (程序表头)
Sections (节)
Section header table (节表头)

通过 readelf -l calc 可看到 calc 程序的二进制区域划分

-l # 列出二进制全部区域划分信息
-h # 头文件信息 | 这个内容与结构体Elf32_Ehdr中的成员变量是一一对应的!
-S # 片段布局

$ readelf -l calc

Elf file type is DYN (Position-Independent Executable file) # 💡位置独立的可执行文件
Entry point 0x1080                                          # 💡入口地址
There are 13 program headers, starting at offset 64         # 💡13个程序头信息,从第64个偏移地址开始

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000660 0x0000000000000660  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000225 0x0000000000000225  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000144 0x0000000000000144  R      0x1000
  LOAD           0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000260 0x0000000000000268  RW     0x1000
  DYNAMIC        0x0000000000002dc0 0x0000000000003dc0 0x0000000000003dc0
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000030 0x0000000000000030  R      0x8
  NOTE           0x0000000000000368 0x0000000000000368 0x0000000000000368
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000030 0x0000000000000030  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000250 0x0000000000000250  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
   03     .init .plt .plt.got .plt.sec .text .fini
   04     .rodata .eh_frame_hdr .eh_frame
   05     .init_array .fini_array .dynamic .got .data .bss
   06     .dynamic
   07     .note.gnu.property
   08     .note.gnu.build-id .note.ABI-tag
   09     .note.gnu.property
   10     .eh_frame_hdr
   11
   12     .init_array .fini_array .dynamic .got

静态库 vs 动态库

  • 静态链接库(Static Link Library) —— 静态库是在链接可执行文件时,代码段和数据段直接拷贝到可执行文件中

    $ objdump -r liboperation.a # 有两个.o文件需要重定位(RELOCATION)
    In archive liboperation.a:
    
    add.o:     file format elf64-x86-64
    
    RELOCATION RECORDS FOR [.eh_frame]:
    OFFSET           TYPE              VALUE
    0000000000000020 R_X86_64_PC32     .text
    
    
    
    tfunc.o:     file format elf64-x86-64
    
    RELOCATION RECORDS FOR [.text]:
    OFFSET           TYPE              VALUE
    000000000000001d R_X86_64_PLT32    atoi-0x0000000000000004
    
    
    RELOCATION RECORDS FOR [.eh_frame]:
    OFFSET           TYPE              VALUE
    0000000000000020 R_X86_64_PC32     .text
    
  • 动态链接库(Dynamic Link Library,简称DLL) —— 不像静态库是在链接可执行文件时,代码段和数据段直接拷贝到可执行文件中。动态库来是在运行时加载动态库代码(由 ld-linux.so 来负责读取),因此无法在编译和链接阶段获取代码段的符号地址(代码段的符号包括引用的全局数据,调用的函数等)。

    $ objdump -r liboperation.so # 不需要重定位(RELOCATION)
    
    liboperation.so:     file format elf64-x86-64
    

静态链接重定位过程

$ readelf -h add.o
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          464 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         12
  Section header string table index: 11
$ readelf -s add.o
Symbol table '.symtab' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 0000000000000000    24 FUNC    GLOBAL DEFAULT    1 add

# -Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
# -t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
# -N 52:只需要读取 52 个字节;
$ od -Ax -t x1 -N 52 calc.o # 读取字节码
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 00 30 03 00 00 00 00 00 00
000030 00 00 00 00
000034

todo 重定位节

重定位节(Relocation Section)是DLL文件的一个特殊节,用于存储在加载和链接过程中需要修改的地址偏移量信息。它记录了DLL中涉及到的符号(如函数、变量)的地址,以便在运行时进行重定位,使得DLL可以正确加载和运行。

todo 重定位表

重定位表(Relocation Table)是重定位节中的一部分,它包含了需要重定位的地址和偏移量信息。重定位表的作用是指导系统在加载DLL时,根据其中的重定位信息,动态修正代码和数据的地址,以适应当前的加载地址。

动态链接 PIC 原理

-fpic

位置无关码 -fpic

回顾 gcc 编译过程: 首先把源代码编译成目标文件, 然后把目标文件链接起来。

-fPIC

如果是动态链接库的话,目标文件需要创建成 PIC(position-independent code,位置无关码) ,概念上就是在可执行程序装载它们的时候, 它们可以放在可执行程序的内存里的任何地方。要生成这种形式的目标文件,就需要添加参数 -fPIC

  • -fpic —— 在动态库中生成位置无关的代码。通过全局偏移表(GOT)访问所有常量地址。程序启动时动态加载程序解析GOT条目。对GOT的大小有限制。
  • -fPIC —— 作用同-fpic。但是对GOT表大小无限制。 即: 如果链接的可执行文件的GOT大小超过计算机特定的最大大小,则会从链接器收到错误消息,指示-fpic不起作用。在这种情况下,需要使用-fPIC重新编译。 💡为了兼容各个系统,在生成位置无关的代码的时候,应该使用-fPIC参数。

-DPIC

根据 Re: What does -DPIC linker flag mean在新窗口打开 提到: todo

GOT/PLT

载入时重定位的描述:

在调用动态库中的函数时,动态加载器动态分配一段进程地址空间,将动态库加载到该地址空间后,再修改代码段的符号地址。 至于需要修改的哪些地址,链接器在动态库的文件头中预先写好,供加载器读取修改。

载入时重定位的缺点:

  1. 动态库的代码段不能在进程间共享: 多个进程加载同一个动态库到各自不同的地址空间,导致代码段需要不同的重定位,所以最终每个引用该动态库的进程拥有一份该动态库代码段的不同拷贝。
  2. 代码段必须是可写的,增加了被攻击风险。

为了解决载入时重定位的问题,引入了PIC的概念,即位置无关代码。

PIC实现原理

  1. GOT: 在动态库的数据段增加 GOT(Global Offset Table),该表的每一项是符号到地址的绝对映射。 由于代码段到数据段的偏移是固定的,因此可以在编译时确定代码段中的某个符号到GOT特定项之间的偏移。 这样,代码段中的符号偏移就可以在编译时确定了,在加载时也无需修改代码段的内容,只需要填写位于数据段的GOT的所有项的符号的绝对地址就完成了。 因为数据段本来就是进程间不共享,每个进程独立的一份,因此GOT的设计完全解决了以上两个问题,从而达到两个目的: 1,代码段可以在多进程间共享; 2,代码段是只读的。
  2. PLT: PLT(Program Linkage Table,程序链接表) 的出现是为了延时定位的目的。 一个动态库中的函数往往要远多于全局变量,并且被调用的函数往往少于定义的函数。 由于 GOT 中包含了该动态库中的所有的全局变量的映射,并且在连接器加载时解析所有的全局变量的地址。 如果用同样的方式去处理函数调用符号,则开销会非常大。 因此在代码段设计了一个 PLT (表中每一项其实是个代码段) 用于执行如下逻辑: 首次访问时,解析参数和向GOT填写函数地址,后续访问直接访问 GOT 中的函数地址。 如此达到了延时定位的目的。

因此,在一个 PIC 的动态库中, 对全局变量使用 GOT 来映射, 对函数调用使用 PLT + GOT 来映射。 从而达到共享库代码段复用,代码段安全访问的目的。 而这些就是 PIC 的意义。

分析程序字节码

todo od -Ad -t x1 -j 1136 -N 434 main

todo 反汇编 objdump -d main.o

编译选项

安全编译/链接选项

todo linux程序保护机制&gcc编译选项 - https://www.jianshu.com/p/91fae054f922在新窗口打开

若不加上,可能运行时会泄露某些信息,方便逆向、CTF。

 编译选项链接选项
必选PIC/PIE/Protect-Stackrpath/Bind-Now/Strip
可选Fortify-Source/ftrapv

具体含义

编译/链接选项必选含义使用方法
PIC
(Position-Independent-Code)
必选
high
位置无关代码
实现动态库随机加载
-fPIC/-fpic(旧) (编译选项)
PIE(ASLR)
PIE
(Position-Independent-Executable)
必选
high
位置无关代码/随机化
可执行文件在加载执行时可像共享库一样随机加载
降低固定地址类攻击、缓冲溢出攻击的成功率
-fPIE/-fpie(旧) (编译选项)
-pie (链接选项)
⚠️需要上述两个选项同时使用
解释:
  • -no-pie: 关闭
  • -pie: 开启
Canary
Protect-Stack
SP
必选
high
栈保护。可以判断是否发生溢出攻击
参考: https://www.cnblogs.com/arnoldlu/p/11630979.html在新窗口打开
-fstack-protector-strong/-fstack-protector-all(旧) (编译选项)
解释:
  • -fno-stack-protector: 关闭
  • -fstack-protector: 开启
  • -fstack-protector-all: 全开启
Fortify
Fortify-Source
FS
可选
medium
GCC编译器和glibc库配合提供在编译时和运行时对固定大小的缓冲区的访问
(无论时动态分配的还是静态声明的内存空间)
参考: https://forum.butian.net/share/1190在新窗口打开
Fority 其实非常轻微的检查,用于检查是否存在缓冲区溢出的错误。
Fortify 是GCC在编译源码时判断程序的哪些buffer会存在可能的溢出。
在buffer大小已知的情况下,GCC会把 strcpy、memcpy、memset等函数自动替换成相应的__strcpy_chk(dst, src, dstlen)等函数,达到防止缓冲区溢出的作用。
-O2 (编译选项)
-D_FORTIFY_SOURCE=2 (编译选项,默认开启,但需要-O2启动时才会激活)
ftrapv可选
medium
整数溢出检查
使用了它之后,在执行有符号整数间的加减乘运算时,不是通过CPU的指令,而是用包含了GCC附属库的libgcc.c里面的函数来实现执行带符号的整数间的加/减/乘/除运算。
对性能影响比较大。
-ftrapv (编译选项)
NX(DEP)可选
high
堆栈不可执行-z noexecstack
解释:
  • -z execstack: 关闭
  • -z noexecstack: 开启
rpath必选
high
禁用: “动态库搜索路径”
禁用: -rpath
二进制特征会显示rpath/runpath路径。攻击者更加容易构造rpath类的攻击
指定运行时避免使用链接器 -Wl,-rpath=. 寻找动态库的路径
set(CMAKE_SKIP_RPATH TRUE) (Cmake)
Bind-Now必选
high
立即绑定-Wl,-z,now (链接选项)
或:LD_BIND_NOW=1
RELRO必选
high
GOT表保护
全部重定向只读保护,防止内存越界,一旦越界就会segmentation faul。
对ret2plt攻击进行防护
-Wl,-z,relro (链接选项)
解释:
  • -z norelro: 关闭
  • -z lazy: 部分开启
  • -z now: 完全开启
Strip必选
medium
去除符号表:链接过程完成后,符号表对可执行文件运行已无任何作用,反而会成为攻击者构造攻击的工具。
同时,删除符号表还可以对文件“减肥”,降低文件大小
-Wl,-s (链接选项)

安全编译选项检测工具

binscope todo

参考:

configure

参考:

  • 简述configure、pkg-config、pkg_config_path三者的关系 | https://www.cnblogs.com/wliangde/p/3807532.html

configure 是一个脚本文件,定义了执行时可以传入的必要参数,告知配置项目。 configure 程序它会根据传入的配置项目检查程序编译时所依赖的环境以及对程序编译安装进行配置,最终生成编译所需的 Makefile 文件供程序 make 读入使用进而调用相关编译程式来编译最终的二进制程序。

pkg-config 链接库配置生成器

一般来说,如果库的头文件不在 /usr/include 目录中,那么在编译的时候需要用 -I 参数指定其路径。

由于编译的环境和编译后程序运行的环境大概率不同,通过 -I 指定的文件路径大概率也不一样,这是需要编译后的 lib/pkgconfig 目录中添加 .pc 文件来指定库的各种必要信息,包括版本信息、编译和连接需要的参数等。 这样,不管库文件安装在哪,通过库对应的 .pc 文件就可以准确定位,可以使用相同的编译和连接命令,使得编译和连接界面统一。

prefix=/opt/gtk/
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
 
glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums
 
Name: GLib
Description: C Utility Library
Version: 2.12.13
Libs: -L${libdir} -lglib-2.0
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include
# 列出所有可使用的包
# 位置在:
# + /usr/lib/pkgconfig —— 此目录下都是各种.pc文件。
# + /usr/local/lib/pkgconfig —— 新软件一般都会安装.pc文件
# + 没有可以自己创建,并且设置环境变量 PKG_CONFIG_PATH 寻找 .pc 文件路径。
$ pkg-config –list-all

# 给出在编译时所需要的选项
$ gcc -c `pkg-config --cflags glib-2.0` sample.c
# 给出连接时的选项
$ gcc sample.o -o sample `pkg-config --libs glib-2.0`
export PKG_CONFIG_PATH=/opt/gtk/lib/pkgconfig:$PKG_CONFIG_PATH
export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH