目标文件函数隐藏初探

目标文件函数隐藏初探

场景如下,需要以.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
"); }
void inner_fun2() { printf("inner 2
"); }
void api_1() { printf("api 1
"); }
void api_2() { printf("api 2
"); };

编译生产目标文件

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
"); }
static void inner_fun2() { printf("inner 2
"); }
void api_1() { printf("api 1
"); }
void api_2() { printf("api 2
"); };

重新生成库,再查看符号表

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删除了符号表,也还是可以从二进制文件中分析到内外部函数名称的。所以如果想隐藏内部函数名称,以避免暴露内部逻辑,那就还需要使用一些其他的手段。

原文地址:https://www.cnblogs.com/zqb-all/p/9822108.html