1. 程式人生 > >目標檔案函式隱藏初探

目標檔案函式隱藏初探

目標檔案函式隱藏初探

場景如下,需要以.o形式(靜態庫形式),釋出一個庫,給其他程式碼整合。生成庫mylib.o之後,使用nm檢視,可以檢視到很多函式符號。但其實這個庫跟外界,應該是隻通過一組指定的函式介面進行互動,其他的函式不應該暴露給外界,更不應該供外界直接呼叫。

為此,可以進行一些處理。

將函式標記為static

一種可行的方式是,將內部使用的函式,原始碼中標記為static。

但這麼修改之後,庫本身的其他原始檔,也無法使用該函數了,因為c語言中的static是將函式的作用域限定在了函式所在的原始檔。

objcopy修改符號表

生成庫之後,可使用工具鏈中的 objcopy 工具,修改符號表,將內部函式都修改為本地函式,這樣外部程式碼無法直接連結到這些函式,只能使用指定的函式。

檢視幫助可知,objcopy 支援將除 -G 引數指定的符號外,其他符號全部修改成本地符號。

objcopy --help
-G --keep-global-symbol <name>   Localize all symbols except <name>

於是使用如下命令,

mv mylib.o mylib_origin.o
objcopy -G api_1 -G api_2  mylib_origin.o  mylib.o

strip刪減符號表

生成庫之後,可使用工具鏈中的 strip 工具,裁剪符號表,將不打算給外界使用的函式,直接從符號表中刪除。

檢視幫助可知,strip可用 -s 引數刪除所有符號,使用 -K 引數指定要保留的符號,使用 -N 指定要strip掉的符號。

strip --help
-s --strip-all                   Remove all symbol and relocation information
-N --strip-symbol=<name>         Do not copy symbol <name>
-K --keep-symbol=<name>          Do not strip symbol <name>
     --keep-file-symbols           Do not strip file symbol(s)

於是使用如下命令,可刪除所有符號,只保留api_1和api_2

cp mylib.o mylib_origin.o
strip -s -K api_1 -K api_2 mylib.o 

使用如下命令,則是隻刪除inner_fun1和inner_fun2

cp mylib.o mylib_origin.o
strip -N inner_fun1 -N inner_fun2 mylib.o

例子

假設庫mylib.c 中有四個函式,inner_fun1,inner_fun2是內部使用的函式,api_1,api_2時給外部使用的介面。

#include <stdio.h>

void inner_fun1() { printf("inner 1\n"); }
void inner_fun2() { printf("inner 2\n"); }
void api_1() { printf("api 1\n"); }
void api_2() { printf("api 2\n"); };

編譯生產目標檔案

gcc -o mylib.o -c mylib.c

檢視符號表

nm mylib.o

0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T inner_fun1
0000000000000013 T inner_fun2
                 U puts

四個函式都可以看到,且都是 T ,即可被外部連結。

注:對於每一個符號來說,其型別如果是小寫的,則表明該符號是 local 的;大寫則表明該符號是 global(external) 的。

寫個main.c連結下試試

int main()
{
    api_1();
    api_2();
    inner_fun1();
    inner_fun2();
    return 0;
}

編譯連結

gcc main.c mylib.o -o main

執行main,可以看到成功呼叫了api,也成功呼叫了inner的函式。

./main 

api 1
api 2
inner 1
inner 2

使用static的效果

那麼先試試 static 定義,將mylib.c中的inner函式加上static

#include <stdio.h>

static void inner_fun1() { printf("inner 1\n"); }
static void inner_fun2() { printf("inner 2\n"); }
void api_1() { printf("api 1\n"); }
void api_2() { printf("api 2\n"); };

重新生成庫,再檢視符號表

nm mylib.o

0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 t inner_fun1
0000000000000013 t inner_fun2
                 U puts

可以看到,inner_fun1和inner_fun2的標記,已經不是 T,而是 t 了。

此時,外部函式嘗試連結使用,會報錯

gcc main.c mylib.o -o main

/tmp/cccUN3aL.o:在函式‘main’中:
main.c:(.text+0x1e):對‘inner_fun1’未定義的引用
main.c:(.text+0x28):對‘inner_fun2’未定義的引用
collect2: error: ld returned 1 exit status

使用objcopy的效果

不修改原始檔,直接使用objcopy修改mylib.o

mv mylib.o mylib_origin.o
objcopy -G api_1 -G api_2  mylib_origin.o  mylib.o

修改前後

nm mylib_origin.o mylib.o

mylib_origin.o:
0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T inner_fun1
0000000000000013 T inner_fun2
                 U puts
                 
mylib.o:
0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 t inner_fun1
0000000000000013 t inner_fun2
                 U puts

此時,main.c 就只能使用api_1, api_2,無法使用inner_fun1, inner_fun2了。因為inner_fun1, inner_fun2是內部符號了。

使用strip的效果

不修改原始檔,直接使用strip修改mylib.o

cp mylib.o mylib_origin.o
strip -N inner_fun1 -N inner_fun2 mylib.o

修改前後

nm mylib_origin.o mylib.o 

mylib_origin.o:
0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T inner_fun1
0000000000000013 T inner_fun2
                 U puts

mylib.o:
0000000000000026 T api_1
0000000000000039 T api_2
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

此時,main.c 就只能使用api_1, api_2,無法使用inner_fun1, inner_fun2了。因為inner_fun1, inner_fun2不存在符號表中了。

結語

本文主要介紹了,static標記函式,objcopy和strip三種方式,避免庫內部函式被外部程式使用。但即使strip刪除了符號表,也還是可以從二進位制檔案中分析到內外部函式名稱的。所以如果想隱藏內部函式名稱,以避免暴露內部邏輯,那就還需要使用一些其他的手段。