Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Linux 二進制工具完整指南

目錄

核心分析工具

1. nm - 符號表查看器

# 常用選項
nm [options] file

# 選項說明
-A  # 顯示文件名
-C  # demangle C++ 符號
-D  # 顯示動態符號
-g  # 只顯示外部符號
-n  # 按地址排序
-u  # 只顯示未定義符號
-r  # 反向排序
--size-sort  # 按大小排序

# 符號類型
# T/t - Text (代碼段)
# D/d - Data (初始化數據)
# B/b - BSS (未初始化數據)
# U   - Undefined
# W/w - Weak symbol

# 實例
nm -C program | grep "std::"  # 查看 C++ STL 符號
nm -D --size-sort /lib/x86_64-linux-gnu/libc.so.6 | tail -20

2. objdump - 目標文件反彙編器

# 核心功能
objdump -d file       # 反彙編
objdump -D file       # 反彙編所有節區
objdump -S file       # 源碼混合反彙編(需調試信息)
objdump -t file       # 符號表
objdump -T file       # 動態符號表
objdump -r file       # 重定位信息
objdump -R file       # 動態重定位
objdump -x file       # 所有 headers
objdump -h file       # 節區 headers
objdump -j .text -d file  # 只反彙編 .text 節

# Intel 語法(更易讀)
objdump -M intel -d file

# 查看特定函數
objdump -d program | sed -n '/<main>:/,/^$/p'

3. readelf - ELF 格式分析器

# ELF 結構分析
readelf -h file       # ELF header
readelf -l file       # Program headers (segments)
readelf -S file       # Section headers
readelf -s file       # 符號表
readelf -r file       # 重定位
readelf -d file       # 動態段
readelf -V file       # 版本信息
readelf -n file       # Notes
readelf -a file       # 所有信息

# 實用查詢
readelf -p .comment file    # 查看編譯器版本
readelf -p .rodata file     # 查看只讀字串
readelf -x .got.plt file    # 十六進制顯示 GOT 表

4. strings - 字串提取器

# 高級用法
strings -a file      # 掃描整個文件
strings -f file*     # 顯示文件名
strings -n 20 file   # 最小長度 20
strings -t x file    # 十六進制偏移
strings -e S file    # 7-bit byte strings (ASCII)
strings -e l file    # 16-bit littleendian
strings -e b file    # 16-bit bigendian

# 組合使用
strings file | grep -i password
strings -t x file | grep "error"

5. file - 文件類型識別

file program
file -b program      # 簡潔輸出
file -i program      # MIME 類型
file -L symlink      # 跟隨符號鏈接
file -s /dev/sda     # 特殊文件

6. size - 節區大小統計

size program
size -A program      # System V 格式
size -B program      # Berkeley 格式
size --format=SysV *.o  # 多文件比較

動態鏈接工具

1. ldd - 共享庫依賴查看

# 安全模式(避免執行不信任的二進制)
ldd -r file          # 檢查未解析的符號
ldd -u file          # 未使用的直接依賴
ldd -v file          # 詳細信息(包括版本)

# 替代方案(更安全)
objdump -p file | grep NEEDED
readelf -d file | grep NEEDED

# 遞歸查看所有依賴
function ldd_recursive() {
    for lib in $(ldd $1 | awk '{print $3}' | grep "^/"); do
        echo "=== $lib ==="
        ldd $lib
    done
}

2. ldconfig - 動態鏈接器配置

# 管理共享庫緩存
sudo ldconfig              # 更新緩存
sudo ldconfig -v           # 詳細輸出
ldconfig -p                # 打印緩存內容
ldconfig -p | wc -l       # 統計庫數量

# 配置文件
/etc/ld.so.conf            # 主配置
/etc/ld.so.conf.d/*.conf   # 模塊化配置
/etc/ld.so.cache           # 緩存文件

# 添加自定義路徑
echo "/opt/myapp/lib" | sudo tee /etc/ld.so.conf.d/myapp.conf
sudo ldconfig

3. LD 環境變數詳解

LD_LIBRARY_PATH

# 搜索優先級(從高到低):
# 1. DT_RPATH (除非有 DT_RUNPATH)
# 2. LD_LIBRARY_PATH
# 3. DT_RUNPATH
# 4. /etc/ld.so.cache
# 5. /lib, /usr/lib

export LD_LIBRARY_PATH=/custom/lib:$LD_LIBRARY_PATH

# 查看加載過程
LD_DEBUG=libs LD_LIBRARY_PATH=/test/lib ./program

LD_PRELOAD

# Hook 機制範例
# malloc_hook.c
cat > malloc_hook.c << 'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>

void* malloc(size_t size) {
    static void* (*real_malloc)(size_t) = NULL;
    if (!real_malloc)
        real_malloc = dlsym(RTLD_NEXT, "malloc");
    
    void* ptr = real_malloc(size);
    fprintf(stderr, "malloc(%zu) = %p\n", size, ptr);
    return ptr;
}
EOF

gcc -shared -fPIC -o malloc_hook.so malloc_hook.c -ldl
LD_PRELOAD=./malloc_hook.so ls

LD_DEBUG 完整選項

# 所有選項
LD_DEBUG=help ./program

# 常用選項組合
LD_DEBUG=libs          # 庫搜索路徑
LD_DEBUG=files         # 文件載入
LD_DEBUG=symbols       # 符號解析
LD_DEBUG=bindings      # 符號綁定
LD_DEBUG=versions      # 版本依賴
LD_DEBUG=reloc         # 重定位處理
LD_DEBUG=statistics    # 統計信息
LD_DEBUG=all           # 所有信息

# 組合使用
LD_DEBUG=libs,symbols ./program 2>&1 | tee debug.log

其他 LD 變數

LD_BIND_NOW=1          # 立即綁定所有符號
LD_TRACE_LOADED_OBJECTS=1  # 類似 ldd
LD_SHOW_AUXV=1         # 顯示輔助向量
LD_AUDIT=./audit.so    # 審計庫
LD_PROFILE=libc.so.6   # 性能分析
LD_PROFILE_OUTPUT=/tmp # 分析輸出目錄

調試追蹤工具

1. strace - 系統調用追蹤

# 高級用法
strace -e trace=file ./program     # 只追蹤文件操作
strace -e trace=network ./program  # 網絡操作
strace -e trace=memory ./program   # 內存操作
strace -e trace=process ./program  # 進程操作
strace -e trace=signal ./program   # 信號操作

# 過濾器
strace -e open,openat,close ./program
strace -e 'open*' ./program        # 通配符
strace -e '!futex' ./program       # 排除

# 輸出控制
strace -t ./program                # 時間戳
strace -tt ./program               # 微秒時間戳
strace -T ./program                # 系統調用時長
strace -c ./program                # 統計摘要
strace -C ./program                # 統計+正常輸出

# 進程控制
strace -p PID                      # 附加到進程
strace -f ./program                # 追蹤子進程
strace -ff -o trace ./program      # 分離輸出文件

# 高級過濾
strace -e inject=open:error=ENOENT ./program  # 注入錯誤
strace -e fault=malloc:error=ENOMEM:when=3 ./program

2. ltrace - 庫調用追蹤

# 進階用法
ltrace -c ./program               # 統計
ltrace -S ./program               # 同時顯示系統調用
ltrace -f ./program               # 追蹤子進程
ltrace -l /lib/libssl.so ./program  # 特定庫
ltrace -x 'malloc+free' ./program   # 特定函數

# 配置文件
~/.ltrace.conf                    # 用戶配置
/etc/ltrace.conf                  # 系統配置

# 自定義函數簽名
echo "int myfunction(string,int);" >> ~/.ltrace.conf

3. ptrace - 進程追蹤 API

// ptrace 範例
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    } else {
        wait(NULL);
        ptrace(PTRACE_CONT, child, NULL, NULL);
        wait(NULL);
    }
    return 0;
}

性能分析工具

1. perf - Linux 性能分析

# 基本使用
perf stat ./program               # 性能統計
perf record ./program              # 記錄性能數據
perf report                        # 查看報告
perf top                           # 實時分析

# 詳細分析
perf record -g ./program          # 調用圖
perf record -e cycles,instructions ./program
perf annotate                      # 註釋彙編

# 事件類型
perf list                          # 列出所有事件
perf stat -e cache-misses ./program

2. valgrind - 內存分析

# 內存洩漏檢測
valgrind --leak-check=full ./program
valgrind --leak-check=full --show-leak-kinds=all ./program

# 內存錯誤檢測
valgrind --track-origins=yes ./program

# 緩存分析
valgrind --tool=cachegrind ./program
cg_annotate cachegrind.out.*

# 調用圖生成
valgrind --tool=callgrind ./program
kcachegrind callgrind.out.*       # GUI 查看

# Heap 分析
valgrind --tool=massif ./program
ms_print massif.out.*

3. gprof - GNU 性能分析器

# 編譯時啟用
gcc -pg -o program program.c

# 運行程序生成 gmon.out
./program

# 生成報告
gprof program gmon.out > analysis.txt
gprof -b program gmon.out         # 簡潔輸出
gprof -p program gmon.out         # 平面檔案
gprof -q program gmon.out         # 調用圖

7. addr2line - 地址到源碼映射

# 基本用法
addr2line -e executable address

# 常用選項
-e file      # 指定可執行文件
-f           # 顯示函數名
-s           # 顯示簡短文件名(去掉路徑)
-C           # Demangle C++ 符號
-p           # 優雅輸出格式
-i           # 顯示內聯函數
-a           # 顯示地址
-j section   # 指定節區

# 實例用法
# 1. 單個地址查詢
addr2line -e program 0x400534

# 2. 多個地址查詢
addr2line -e program 0x400534 0x400550 0x400570

# 3. 從管道接收地址
echo "0x400534" | addr2line -e program

# 4. 顯示函數名和源碼位置
addr2line -fe program 0x400534

# 5. C++ 符號 demangle
addr2line -Cfe program 0x400534

# 6. 優雅格式輸出
addr2line -pfe program 0x400534

# 實戰範例:解析段錯誤
# 從 dmesg 或 coredump 獲取地址
dmesg | grep segfault
# program[1234]: segfault at 0 ip 00000000004005b4 sp 00007ffd12345678

# 解析崩潰地址
addr2line -Cfpe program 0x4005b4

# 從 backtrace 解析多個地址
cat backtrace.txt | while read addr; do
    addr2line -Cfpe program "$addr"
done

# 結合 objdump 使用
objdump -d program | grep "call" | awk '{print $1}' | sed 's/://' | \
    xargs addr2line -fe program

# 從 core dump 提取地址
gdb -q -batch -ex "bt" -ex "quit" program core | \
    grep -oE '0x[0-9a-f]+' | \
    xargs addr2line -Cfpe program

安全分析工具

1. checksec - 安全機制檢查

# 安裝
git clone https://github.com/slimm609/checksec.sh
cd checksec.sh

# 使用
./checksec --file=/bin/ls
./checksec --dir=/usr/bin
./checksec --proc-all

# 檢查項目
# - RELRO (Relocation Read-Only)
# - Stack Canary
# - NX (No-eXecute)
# - PIE (Position Independent Executable)
# - RPATH/RUNPATH
# - FORTIFY_SOURCE

2. patchelf - ELF 修改工具

# 修改動態鏈接器
patchelf --set-interpreter /lib/ld-linux.so.2 program

# 修改 RPATH/RUNPATH
patchelf --set-rpath /custom/lib program
patchelf --remove-rpath program
patchelf --print-rpath program

# 添加/刪除依賴
patchelf --add-needed libfoo.so program
patchelf --remove-needed libbar.so program
patchelf --replace-needed libold.so libnew.so program

# 修改 SONAME
patchelf --set-soname libnew.so.1 library.so

3. radare2 - 逆向工程框架

# 基本使用
r2 program
[0x00000000]> aa              # 分析所有
[0x00000000]> afl             # 列出函數
[0x00000000]> pdf @main       # 反彙編 main
[0x00000000]> iz              # 列出字串
[0x00000000]> iI              # 二進制信息
[0x00000000]> ie              # 入口點
[0x00000000]> iS              # 節區

# 視覺模式
[0x00000000]> V               # 十六進制視圖
[0x00000000]> VV              # 圖形視圖

# 調試模式
r2 -d program
[0x00000000]> db main         # 設置斷點
[0x00000000]> dc              # 繼續執行
[0x00000000]> dr              # 顯示寄存器

4. binwalk - 固件分析

# 掃描二進制
binwalk firmware.bin

# 提取文件
binwalk -e firmware.bin

# 熵分析
binwalk -E firmware.bin

# 簽名掃描
binwalk -B firmware.bin

動態和靜態庫完整指南

靜態庫 (.a) 創建和使用

1. 創建靜態庫

// math_utils.c
#include "math_utils.h"
#include <math.h>

double calculate_area(double radius) {
    return M_PI * radius * radius;
}

double calculate_volume(double radius) {
    return (4.0/3.0) * M_PI * radius * radius * radius;
}

// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

double calculate_area(double radius);
double calculate_volume(double radius);

#endif

// string_utils.c
#include "string_utils.h"
#include <string.h>
#include <ctype.h>

void to_uppercase(char *str) {
    while (*str) {
        *str = toupper(*str);
        str++;
    }
}

int count_words(const char *str) {
    int count = 0;
    int in_word = 0;
    
    while (*str) {
        if (isspace(*str)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            count++;
        }
        str++;
    }
    return count;
}

編譯和創建靜態庫:

# 編譯目標文件
gcc -c math_utils.c -o math_utils.o
gcc -c string_utils.c -o string_utils.o

# 創建靜態庫
ar rcs libutils.a math_utils.o string_utils.o

# 查看靜態庫內容
ar -t libutils.a
ar -tv libutils.a  # 詳細信息

# 提取特定目標文件
ar -x libutils.a math_utils.o

# 添加新目標文件到現有庫
gcc -c new_utils.c -o new_utils.o
ar -r libutils.a new_utils.o

# 創建索引(提高鏈接速度)
ranlib libutils.a

# 查看庫中的符號
nm libutils.a

2. 使用靜態庫

// main_static.c
#include <stdio.h>
#include "math_utils.h"
#include "string_utils.h"

int main() {
    // 使用數學工具
    double radius = 5.0;
    printf("Circle area: %.2f\n", calculate_area(radius));
    printf("Sphere volume: %.2f\n", calculate_volume(radius));
    
    // 使用字串工具
    char text[] = "hello world";
    to_uppercase(text);
    printf("Uppercase: %s\n", text);
    
    const char *sentence = "This is a test sentence";
    printf("Word count: %d\n", count_words(sentence));
    
    return 0;
}

編譯和鏈接:

# 方法1:直接指定庫文件
gcc main_static.c libutils.a -lm -o program_static

# 方法2:使用 -L 和 -l 選項
gcc main_static.c -L. -lutils -lm -o program_static

# 方法3:分步編譯
gcc -c main_static.c -o main_static.o
gcc main_static.o -L. -lutils -lm -o program_static

# 查看鏈接的庫(靜態庫會被嵌入)
ldd program_static  # 不會顯示 libutils.a
size program_static  # 查看大小

# 確認符號已經嵌入
nm program_static | grep calculate_area

動態庫 (.so) 創建和使用

1. 創建動態庫

// dynamic_lib.c
#include <stdio.h>
#include <time.h>

// 使用 visibility 屬性控制導出
__attribute__((visibility("default"))) 
void public_function() {
    printf("This is a public function\n");
}

__attribute__((visibility("hidden")))
void private_function() {
    printf("This is a private function (hidden)\n");
}

// 構造和析構函數
__attribute__((constructor))
void lib_init() {
    printf("Library initialized at %ld\n", time(NULL));
}

__attribute__((destructor))
void lib_cleanup() {
    printf("Library cleanup\n");
}

// 版本化符號
__asm__(".symver old_function_v1,old_function@VERSION_1.0");
void old_function_v1() {
    printf("Old implementation (v1.0)\n");
}

__asm__(".symver old_function_v2,old_function@@VERSION_2.0");
void old_function_v2() {
    printf("New implementation (v2.0)\n");
}

創建版本腳本:

# version.map
VERSION_1.0 {
    global:
        old_function;
    local:
        *;
};

VERSION_2.0 {
    global:
        old_function;
        public_function;
} VERSION_1.0;

編譯動態庫:

# 基本編譯
gcc -fPIC -c dynamic_lib.c -o dynamic_lib.o
gcc -shared -o libdynamic.so dynamic_lib.o

# 帶版本控制
gcc -fPIC -shared -Wl,--version-script=version.map \
    -o libdynamic.so.2.0 dynamic_lib.c

# 創建符號鏈接
ln -s libdynamic.so.2.0 libdynamic.so.2
ln -s libdynamic.so.2 libdynamic.so

# 設置 SONAME
gcc -fPIC -shared -Wl,-soname,libdynamic.so.2 \
    -o libdynamic.so.2.0 dynamic_lib.c

# 控制符號可見性
gcc -fPIC -fvisibility=hidden -shared -o libdynamic.so dynamic_lib.c

# 查看導出符號
nm -D libdynamic.so
readelf -W -s libdynamic.so | grep -E "FUNC.*GLOBAL.*DEFAULT"

# 查看版本信息
readelf -V libdynamic.so

2. 使用動態庫 - 編譯時鏈接

// main_dynamic.c
#include <stdio.h>

// 聲明外部函數
extern void public_function();
extern void old_function();

int main() {
    printf("=== Using Dynamic Library ===\n");
    
    public_function();
    old_function();  // 會使用默認版本 (VERSION_2.0)
    
    return 0;
}

編譯和運行:

# 編譯鏈接
gcc main_dynamic.c -L. -ldynamic -o program_dynamic

# 設置運行時庫路徑
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./program_dynamic

# 或使用 rpath
gcc main_dynamic.c -L. -ldynamic -Wl,-rpath,. -o program_dynamic
./program_dynamic

# 查看依賴
ldd program_dynamic
readelf -d program_dynamic | grep NEEDED

dlopen 動態加載範例

1. 基本 dlopen 使用

// dlopen_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main() {
    void *handle;
    void (*func)();
    char *error;
    
    // 打開動態庫
    handle = dlopen("./libdynamic.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "dlopen error: %s\n", dlerror());
        return 1;
    }
    
    // 清除錯誤
    dlerror();
    
    // 獲取函數指針
    func = (void (*)()) dlsym(handle, "public_function");
    error = dlerror();
    if (error) {
        fprintf(stderr, "dlsym error: %s\n", error);
        dlclose(handle);
        return 1;
    }
    
    // 調用函數
    func();
    
    // 獲取變量地址
    int *var = (int *)dlsym(handle, "global_variable");
    if (var) {
        printf("Global variable value: %d\n", *var);
    }
    
    // 關閉庫
    dlclose(handle);
    
    return 0;
}

2. 進階 dlopen - 插件系統

// plugin_interface.h
#ifndef PLUGIN_INTERFACE_H
#define PLUGIN_INTERFACE_H

typedef struct {
    const char *name;
    const char *version;
    int (*initialize)(void);
    int (*execute)(const char *args);
    void (*cleanup)(void);
} plugin_info_t;

#define PLUGIN_EXPORT __attribute__((visibility("default")))

#endif

// plugin1.c
#include "plugin_interface.h"
#include <stdio.h>

static int plugin1_init() {
    printf("Plugin 1 initialized\n");
    return 0;
}

static int plugin1_execute(const char *args) {
    printf("Plugin 1 executing with args: %s\n", args ? args : "(none)");
    return 0;
}

static void plugin1_cleanup() {
    printf("Plugin 1 cleanup\n");
}

PLUGIN_EXPORT plugin_info_t plugin_info = {
    .name = "Sample Plugin 1",
    .version = "1.0.0",
    .initialize = plugin1_init,
    .execute = plugin1_execute,
    .cleanup = plugin1_cleanup
};

// plugin_loader.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include "plugin_interface.h"

typedef struct plugin_node {
    void *handle;
    plugin_info_t *info;
    struct plugin_node *next;
} plugin_node_t;

plugin_node_t *plugins = NULL;

void load_plugin(const char *path) {
    void *handle = dlopen(path, RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "Cannot load plugin %s: %s\n", path, dlerror());
        return;
    }
    
    plugin_info_t *info = dlsym(handle, "plugin_info");
    if (!info) {
        fprintf(stderr, "Plugin %s has no plugin_info: %s\n", path, dlerror());
        dlclose(handle);
        return;
    }
    
    // 初始化插件
    if (info->initialize && info->initialize() != 0) {
        fprintf(stderr, "Plugin %s initialization failed\n", path);
        dlclose(handle);
        return;
    }
    
    // 添加到插件列表
    plugin_node_t *node = malloc(sizeof(plugin_node_t));
    node->handle = handle;
    node->info = info;
    node->next = plugins;
    plugins = node;
    
    printf("Loaded plugin: %s (version %s)\n", info->name, info->version);
}

void load_all_plugins(const char *dir) {
    DIR *d = opendir(dir);
    if (!d) return;
    
    struct dirent *entry;
    while ((entry = readdir(d)) != NULL) {
        if (strstr(entry->d_name, ".so")) {
            char path[PATH_MAX];
            snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);
            load_plugin(path);
        }
    }
    closedir(d);
}

void execute_all_plugins(const char *args) {
    plugin_node_t *node = plugins;
    while (node) {
        if (node->info->execute) {
            node->info->execute(args);
        }
        node = node->next;
    }
}

void unload_all_plugins() {
    plugin_node_t *node = plugins;
    while (node) {
        plugin_node_t *next = node->next;
        if (node->info->cleanup) {
            node->info->cleanup();
        }
        dlclose(node->handle);
        free(node);
        node = next;
    }
    plugins = NULL;
}

int main() {
    printf("=== Plugin System Demo ===\n");
    
    // 載入所有插件
    load_all_plugins("./plugins");
    
    // 執行插件
    execute_all_plugins("test arguments");
    
    // 卸載插件
    unload_all_plugins();
    
    return 0;
}

編譯插件系統:

# 編譯插件
gcc -fPIC -shared -fvisibility=hidden -o plugins/plugin1.so plugin1.c
gcc -fPIC -shared -fvisibility=hidden -o plugins/plugin2.so plugin2.c

# 編譯載入器
gcc -o plugin_loader plugin_loader.c -ldl

# 運行
./plugin_loader

3. dlopen 高級特性

// dlopen_advanced.c
#include <stdio.h>
#include <dlfcn.h>
#include <gnu/lib-names.h>

void test_dlopen_flags() {
    void *handle;
    
    // RTLD_LAZY vs RTLD_NOW
    handle = dlopen("libm.so.6", RTLD_LAZY);  // 延遲綁定
    handle = dlopen("libm.so.6", RTLD_NOW);   // 立即綁定所有符號
    
    // RTLD_GLOBAL vs RTLD_LOCAL
    handle = dlopen("libm.so.6", RTLD_GLOBAL); // 符號全局可見
    handle = dlopen("libm.so.6", RTLD_LOCAL);  // 符號局部可見(默認)
    
    // RTLD_NODELETE - 防止 dlclose 卸載
    handle = dlopen("libm.so.6", RTLD_NODELETE);
    
    // RTLD_NOLOAD - 不加載,只檢查是否已加載
    handle = dlopen("libm.so.6", RTLD_NOLOAD);
    if (handle) {
        printf("libm.so.6 is already loaded\n");
    }
    
    // RTLD_DEEPBIND - 優先使用庫自己的符號
    handle = dlopen("./mylib.so", RTLD_DEEPBIND);
}

void test_dladdr() {
    Dl_info info;
    void *addr = (void *)printf;  // 使用 printf 函數地址
    
    if (dladdr(addr, &info)) {
        printf("Function: %s\n", info.dli_sname);
        printf("Library: %s\n", info.dli_fname);
        printf("Base address: %p\n", info.dli_fbase);
        printf("Symbol address: %p\n", info.dli_saddr);
    }
}

void test_dlinfo() {
    void *handle = dlopen("libm.so.6", RTLD_LAZY);
    if (!handle) return;
    
    // 獲取鏈接映射
    struct link_map *lm;
    if (dlinfo(handle, RTLD_DI_LINKMAP, &lm) == 0) {
        printf("Library path: %s\n", lm->l_name);
        printf("Base address: %p\n", (void *)lm->l_addr);
    }
    
    // 獲取 TLS 模塊 ID
    size_t tls_modid;
    if (dlinfo(handle, RTLD_DI_TLS_MODID, &tls_modid) == 0) {
        printf("TLS module ID: %zu\n", tls_modid);
    }
    
    dlclose(handle);
}

// 使用 dlvsym 獲取特定版本的符號
void test_dlvsym() {
    void *handle = dlopen(LIBC_SO, RTLD_LAZY);
    if (!handle) return;
    
    // 獲取特定版本的 memcpy
    void *(*memcpy_2_2_5)(void *, const void *, size_t);
    memcpy_2_2_5 = dlvsym(handle, "memcpy", "GLIBC_2.2.5");
    
    if (memcpy_2_2_5) {
        printf("Found memcpy@GLIBC_2.2.5 at %p\n", memcpy_2_2_5);
    }
    
    dlclose(handle);
}

int main() {
    printf("=== Testing dlopen advanced features ===\n\n");
    
    printf("Testing dlopen flags:\n");
    test_dlopen_flags();
    
    printf("\nTesting dladdr:\n");
    test_dladdr();
    
    printf("\nTesting dlinfo:\n");
    test_dlinfo();
    
    printf("\nTesting dlvsym:\n");
    test_dlvsym();
    
    return 0;
}

混合使用靜態和動態庫

// hybrid_example.c
#include <stdio.h>
#include <dlfcn.h>

// 靜態鏈接的函數(來自 libutils.a)
extern double calculate_area(double radius);

// 動態鏈接的函數(來自 libdynamic.so)
extern void public_function();

// 運行時加載的函數(使用 dlopen)
typedef int (*plugin_func_t)(int);

int main() {
    printf("=== Hybrid Linking Example ===\n");
    
    // 1. 使用靜態鏈接的函數
    double area = calculate_area(5.0);
    printf("Static lib - Circle area: %.2f\n", area);
    
    // 2. 使用動態鏈接的函數
    printf("Dynamic lib - ");
    public_function();
    
    // 3. 使用 dlopen 加載的函數
    void *plugin = dlopen("./plugin.so", RTLD_LAZY);
    if (plugin) {
        plugin_func_t func = dlsym(plugin, "process");
        if (func) {
            int result = func(42);
            printf("Plugin result: %d\n", result);
        }
        dlclose(plugin);
    }
    
    return 0;
}

編譯:

# 編譯混合程序
gcc -c hybrid_example.c -o hybrid_example.o

# 鏈接靜態庫和動態庫
gcc hybrid_example.o \
    -L. -Wl,-Bstatic -lutils \     # 強制靜態鏈接 libutils
    -Wl,-Bdynamic -ldynamic \       # 動態鏈接 libdynamic
    -ldl -lm \                      # 系統庫
    -o hybrid_program

# 或者使用混合方式
gcc hybrid_example.c \
    ./libutils.a \                  # 直接指定靜態庫
    -L. -ldynamic \                  # 動態庫
    -ldl -lm \
    -Wl,-rpath,. \
    -o hybrid_program

# 驗證鏈接
ldd hybrid_program               # 只顯示動態庫
nm hybrid_program | grep calculate_area  # 靜態符號已嵌入

庫的調試和分析

#!/bin/bash
# analyze_library.sh

analyze_lib() {
    local lib=$1
    echo "=== Analyzing $lib ==="
    
    # 判斷庫類型
    if [[ $lib == *.a ]]; then
        echo "Static library detected"
        ar -t $lib
        echo -e "\nSymbols:"
        nm -C $lib | head -20
    elif [[ $lib == *.so* ]]; then
        echo "Shared library detected"
        
        # 基本信息
        file $lib
        
        # 依賴
        echo -e "\nDependencies:"
        ldd $lib 2>/dev/null || echo "Not executable"
        
        # SONAME
        echo -e "\nSONAME:"
        readelf -d $lib | grep SONAME
        
        # 導出符號
        echo -e "\nExported symbols (first 10):"
        nm -D -C $lib | grep " T " | head -10
        
        # 版本信息
        echo -e "\nVersion info:"
        readelf -V $lib | grep -A5 "Version symbols"
        
        # 構造/析構函數
        echo -e "\nConstructors/Destructors:"
        readelf -d $lib | grep -E "(INIT|FINI)"
        
        # 安全特性
        echo -e "\nSecurity features:"
        readelf -d $lib | grep -E "(BIND_NOW|RELRO)"
    fi
}

# 使用範例
analyze_lib "$1"

庫路徑調試技巧

# 調試庫搜索路徑
LD_DEBUG=libs ./program 2>&1 | grep "searching"

# 查看當前系統的庫搜索路徑
ldconfig -v 2>/dev/null | grep -v "^$" | grep "^/"

# 查看程序的 rpath/runpath
readelf -d program | grep -E "(RPATH|RUNPATH)"
chrpath -l program  # 需要安裝 chrpath

# 修改 rpath
chrpath -r /new/path program
patchelf --set-rpath /new/path program

# 查看當前進程的庫映射
cat /proc/$/maps | grep ".so"

# 預載入庫的順序測試
LD_PRELOAD="lib1.so lib2.so" LD_DEBUG=files ./program 2>&1 | grep "calling init"

完整 C++ 分析範例

// demo.cpp
#include <iostream>
#include <vector>
#include <dlfcn.h>
#include <cmath>

class Calculator {
private:
    std::vector<double> history;
    
public:
    double add(double a, double b) {
        double result = a + b;
        history.push_back(result);
        return result;
    }
    
    void printHistory() {
        for (auto val : history) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
};

extern "C" void exported_function() {
    std::cout << "This is an exported C function\n";
}

int main() {
    Calculator calc;
    
    // 使用類
    double result = calc.add(3.14, 2.86);
    std::cout << "Result: " << result << std::endl;
    calc.printHistory();
    
    // 動態載入
    void* handle = dlopen("libm.so.6", RTLD_LAZY);
    if (handle) {
        typedef double (*sqrt_func)(double);
        sqrt_func mysqrt = (sqrt_func)dlsym(handle, "sqrt");
        if (mysqrt) {
            std::cout << "sqrt(16) = " << mysqrt(16) << std::endl;
        }
        dlclose(handle);
    }
    
    exported_function();
    
    return 0;
}

編譯和分析腳本

#!/bin/bash
# analyze.sh

# 編譯
echo "=== 編譯程序 ==="
g++ -o demo demo.cpp -ldl -g -O2
g++ -shared -fPIC -o libdemo.so demo.cpp -ldl

# 基本信息
echo -e "\n=== 文件類型 ==="
file demo

echo -e "\n=== 節區大小 ==="
size demo

# ELF 分析
echo -e "\n=== ELF Headers ==="
readelf -h demo | head -20

echo -e "\n=== Program Headers ==="
readelf -l demo | grep -A5 "LOAD"

echo -e "\n=== 動態段 ==="
readelf -d demo

# 符號分析
echo -e "\n=== 導出的 C++ 符號 (demangled) ==="
nm -C demo | grep " T " | head -10

echo -e "\n=== 導出的 C 符號 ==="
nm demo | grep "exported_function"

echo -e "\n=== 未定義符號 ==="
nm -u demo | head -10

# 依賴分析
echo -e "\n=== 共享庫依賴 ==="
ldd demo
ldd -u demo 2>/dev/null

# 字串分析
echo -e "\n=== 程序中的字串 ==="
strings demo | grep -E "(Result|sqrt|History)" | head -5

# 安全特性
echo -e "\n=== 安全特性檢查 ==="
checksec --file=demo 2>/dev/null || {
    echo "RELRO: $(readelf -l demo | grep GNU_RELRO)"
    echo "Stack: $(readelf -s demo | grep -q __stack_chk && echo "Canary found" || echo "No canary")"
    echo "NX: $(readelf -l demo | grep GNU_STACK | grep -q "RW" && echo "NX enabled" || echo "NX disabled")"
    echo "PIE: $(readelf -h demo | grep -q "DYN" && echo "PIE enabled" || echo "No PIE")"
}

# 反彙編主要函數
echo -e "\n=== main 函數反彙編 (前20行) ==="
objdump -d demo | sed -n '/<main>:/,/^$/p' | head -20

# 動態分析準備
echo -e "\n=== 準備動態分析 ==="
echo "strace -c ./demo                  # 系統調用統計"
echo "ltrace -c ./demo                  # 庫調用統計"
echo "LD_DEBUG=bindings ./demo 2>&1     # 符號綁定"
echo "valgrind --leak-check=full ./demo # 內存檢查"

Rust 二進制分析範例

// main.rs
use std::ffi::{CString, c_void};
use std::ptr;
use std::collections::HashMap;

#[link(name = "m")]
extern "C" {
    fn sqrt(x: f64) -> f64;
    fn cos(x: f64) -> f64;
}

#[no_mangle]
pub extern "C" fn rust_exported_function(x: i32) -> i32 {
    println!("Called from C with value: {}", x);
    x * 2
}

struct DataProcessor {
    cache: HashMap<String, f64>,
}

impl DataProcessor {
    fn new() -> Self {
        DataProcessor {
            cache: HashMap::new(),
        }
    }
    
    fn process(&mut self, key: &str, value: f64) -> f64 {
        let result = unsafe { sqrt(value) + cos(value) };
        self.cache.insert(key.to_string(), result);
        result
    }
}

fn main() {
    println!("Rust Binary Analysis Demo");
    
    let mut processor = DataProcessor::new();
    let result = processor.process("test", 16.0);
    println!("Processed result: {}", result);
    
    // 動態載入
    unsafe {
        let lib = CString::new("libdl.so.2").unwrap();
        let handle = libc::dlopen(lib.as_ptr(), libc::RTLD_LAZY);
        if !handle.is_null() {
            println!("Successfully loaded libdl");
            libc::dlclose(handle);
        }
    }
    
    // 調用導出函數
    let doubled = rust_exported_function(21);
    println!("Doubled: {}", doubled);
}

// 添加 libc 依賴來使用 dlopen
mod libc {
    use std::ffi::c_void;
    pub const RTLD_LAZY: i32 = 1;
    
    extern "C" {
        pub fn dlopen(filename: *const i8, flag: i32) -> *mut c_void;
        pub fn dlclose(handle: *mut c_void) -> i32;
    }
}

Rust 分析腳本

#!/bin/bash
# analyze_rust.sh

# 編譯
echo "=== 編譯 Rust 程序 ==="
rustc -O -C debuginfo=2 main.rs -o rust_demo
rustc --crate-type=cdylib main.rs -o librust_demo.so

# Rust 特定分析
echo -e "\n=== Rust 符號 (未 mangle) ==="
nm rust_demo | grep "rust_exported_function"

echo -e "\n=== Rust 符號 (demangled) ==="
nm rust_demo | rustfilt | grep -E "(DataProcessor|process)" | head -5

echo -e "\n=== Rust 字串 ==="
strings rust_demo | grep "Rust Binary"

# 檢查 panic 處理
echo -e "\n=== Panic 處理 ==="
nm rust_demo | grep -E "panic|unwind" | head -5

# 查看 Rust 特定節區
echo -e "\n=== Rust 元數據 ==="
objdump -s -j .rodata rust_demo | head -20

動態鏈接除錯範例

// ld_debug_test.c
#include <stdio.h>
#include <dlfcn.h>
#include <gnu/lib-names.h>

void test_dlopen() {
    printf("Testing dlopen...\n");
    
    // 嘗試載入多個庫
    const char* libs[] = {
        LIBM_SO,        // "libm.so.6"
        "libpthread.so.0",
        "libdl.so.2",
        "nonexistent.so"
    };
    
    for (int i = 0; i < 4; i++) {
        void* handle = dlopen(libs[i], RTLD_LAZY);
        if (handle) {
            printf("Successfully loaded: %s\n", libs[i]);
            
            // 查詢符號
            if (i == 0) {  // libm
                double (*sqrt_fn)(double) = dlsym(handle, "sqrt");
                if (sqrt_fn) {
                    printf("  sqrt(144) = %f\n", sqrt_fn(144));
                }
            }
            
            dlclose(handle);
        } else {
            printf("Failed to load %s: %s\n", libs[i], dlerror());
        }
    }
}

int main() {
    printf("=== Dynamic Linking Debug Test ===\n");
    test_dlopen();
    return 0;
}

LD_DEBUG 測試腳本

#!/bin/bash
# test_ld_debug.sh

# 編譯
gcc -o ld_test ld_debug_test.c -ldl

echo "=== 1. 庫搜索路徑 ==="
LD_DEBUG=libs ./ld_test 2>&1 | grep "searching"

echo -e "\n=== 2. 文件載入 ==="
LD_DEBUG=files ./ld_test 2>&1 | grep "calling init"

echo -e "\n=== 3. 符號綁定 ==="
LD_DEBUG=bindings ./ld_test 2>&1 | grep "binding.*sqrt"

echo -e "\n=== 4. 版本信息 ==="
LD_DEBUG=versions ./ld_test 2>&1 | head -20

echo -e "\n=== 5. 統計信息 ==="
LD_DEBUG=statistics ./ld_test 2>&1

echo -e "\n=== 6. 自定義路徑測試 ==="
mkdir -p /tmp/testlib
cp /lib/x86_64-linux-gnu/libm.so.6 /tmp/testlib/
LD_LIBRARY_PATH=/tmp/testlib LD_DEBUG=libs ./ld_test 2>&1 | grep testlib

綜合分析工具鏈

#!/bin/bash
# comprehensive_analysis.sh

analyze_binary() {
    local binary=$1
    local output_dir="analysis_$(basename $binary)"
    
    mkdir -p $output_dir
    
    echo "Analyzing $binary..."
    
    # 靜態分析
    file $binary > $output_dir/file_type.txt
    readelf -a $binary > $output_dir/readelf_all.txt
    objdump -d $binary > $output_dir/disassembly.txt
    nm -C $binary > $output_dir/symbols.txt
    strings -n 10 $binary > $output_dir/strings.txt
    ldd $binary > $output_dir/dependencies.txt 2>&1
    
    # 安全檢查
    checksec --file=$binary > $output_dir/security.txt 2>&1
    
    # 動態分析(需要運行)
    if [[ -x $binary ]]; then
        timeout 5 strace -c $binary > $output_dir/strace_stats.txt 2>&1
        timeout 5 ltrace -c $binary > $output_dir/ltrace_stats.txt 2>&1
        LD_DEBUG=statistics timeout 5 $binary > $output_dir/ld_stats.txt 2>&1
    fi
    
    # 生成報告
    cat > $output_dir/report.md << EOF
# Binary Analysis Report: $(basename $binary)

## Basic Information
\`\`\`
$(file $binary)
$(size $binary)
\`\`\`

## Dependencies
\`\`\`
$(ldd $binary 2>&1)
\`\`\`

## Security Features
\`\`\`
$(checksec --file=$binary 2>&1 | grep -E "RELRO|STACK|NX|PIE|RPATH|FORTIFY" || echo "checksec not available")
\`\`\`

## Exported Symbols (Top 10)
\`\`\`
$(nm -C $binary | grep " T " | head -10)
\`\`\`

## Interesting Strings
\`\`\`
$(strings $binary | grep -E "(error|warning|password|token|key|secret)" | head -10)
\`\`\`

## Analysis Files Generated
- readelf_all.txt: Complete ELF analysis
- disassembly.txt: Full disassembly
- symbols.txt: All symbols (demangled)
- strings.txt: All strings (min length 10)
- dependencies.txt: Library dependencies
- security.txt: Security features check

EOF
    
    echo "Analysis complete. Results in $output_dir/"
}

# 使用範例
if [[ $# -eq 0 ]]; then
    echo "Usage: $0 <binary_file>"
    echo "Example: $0 /usr/bin/ls"
    exit 1
fi

analyze_binary "$1"

進階技巧和提示

1. 符號版本控制

# 查看符號版本
readelf -V /lib/x86_64-linux-gnu/libc.so.6

# 創建版本腳本
cat > version.script << EOF
VERSION_1.0 {
    global:
        exported_function_v1;
    local:
        *;
};

VERSION_2.0 {
    global:
        exported_function_v2;
} VERSION_1.0;
EOF

gcc -shared -Wl,--version-script=version.script -o lib.so lib.c

2. GOT/PLT 分析

# 查看 GOT (Global Offset Table)
objdump -R program | grep JUMP_SLOT
readelf -r program | grep PLT

# 查看 PLT (Procedure Linkage Table)
objdump -d -j .plt program
objdump -d -j .plt.got program

# GOT 內容
gdb program
(gdb) info got
(gdb) x/10gx &_GLOBAL_OFFSET_TABLE_

3. RPATH vs RUNPATH

# 設置 RPATH (舊方式,優先級高於 LD_LIBRARY_PATH)
gcc -Wl,-rpath,/custom/lib program.c

# 設置 RUNPATH (新方式,優先級低於 LD_LIBRARY_PATH)
gcc -Wl,-rpath,/custom/lib -Wl,--enable-new-dtags program.c

# 查看
readelf -d program | grep -E 'RPATH|RUNPATH'

4. 弱符號處理

// weak_symbol.c
#include <stdio.h>

// 弱符號定義
__attribute__((weak)) void optional_function() {
    printf("Default implementation\n");
}

// 弱引用
extern void another_function() __attribute__((weak));

int main() {
    optional_function();
    
    if (another_function) {
        another_function();
    } else {
        printf("another_function not available\n");
    }
    return 0;
}

5. 構造函數/析構函數

// constructor.c
#include <stdio.h>

__attribute__((constructor(101))) void init1() {
    printf("Constructor 1 (priority 101)\n");
}

__attribute__((constructor(100))) void init2() {
    printf("Constructor 2 (priority 100)\n");
}

__attribute__((destructor)) void cleanup() {
    printf("Destructor\n");
}

int main() {
    printf("Main function\n");
    return 0;
}

常見問題診斷

1. "undefined symbol" 錯誤

# 診斷步驟
ldd -r program                    # 查看未解析符號
nm -u program                      # 列出未定義符號
LD_DEBUG=symbols ./program 2>&1   # 追蹤符號解析

# 查找符號所在庫
for lib in /lib/x86_64-linux-gnu/*.so*; do
    nm -D "$lib" 2>/dev/null | grep -q "symbol_name" && echo "$lib"
done

2. "version `GLIBC_X.XX' not found" 錯誤

# 檢查 glibc 版本
ldd --version
strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC_

# 查看程序需要的版本
readelf -V program | grep GLIBC

# 解決方案:使用 patchelf 降低版本要求(危險)
patchelf --replace-needed libc.so.6 libc.so.6.old program

3. 性能問題診斷

# CPU 分析
perf record -g ./program
perf report

# 內存分析
valgrind --tool=massif ./program
ms_print massif.out.*

# 系統調用開銷
strace -c ./program

# 庫調用開銷
ltrace -c ./program

最佳實踐

  1. 安全編譯選項
gcc -Wall -Wextra -Werror \
    -D_FORTIFY_SOURCE=2 \
    -fstack-protector-strong \
    -fPIE -pie \
    -Wl,-z,relro -Wl,-z,now \
    -o program program.c
  1. 調試信息分離
# 編譯帶調試信息
gcc -g -o program program.c

# 分離調試信息
objcopy --only-keep-debug program program.debug
strip --strip-debug program
objcopy --add-gnu-debuglink=program.debug program
  1. 靜態分析工作流
# 自動化分析腳本
for binary in "$@"; do
    echo "=== $binary ==="
    file "$binary"
    ldd "$binary" 2>&1
    checksec --file="$binary" 2>&1
    nm -C "$binary" | grep " T " | wc -l
    echo
done

參考資源


Note: 本指南涵蓋了 Linux 二進制分析的主要工具和技術。建議根據具體需求選擇合適的工具組合使用。