如何在共享库中隐藏导出的符号名称
对于VC,我可以编写一个DEF文件并使用’NONAME’指令在dll的导出表中只保留序号。
我怎么能用gcc和ELF格式共享库做同样的事情?
或者,在ELF共享库中是否存在类似PE格式DLL中的序号的东西? 如果没有,我如何在共享库中隐藏导出的符号名称?
======================================
更新:一些额外的描述:
在Windows中,您可以通过仅放置具有空名称的整数ID(序号)来导出函数。
为了显示它,dll导出表的正常布局如下所示: http : //home.hiwaay.net/~georgech/WhitePapers/Exporting/HowTo22.gif 。
“NONAME”看起来像这样: http ://home.hiwaay.net/~georgech/WhitePapers/Exporting/HowTo23.gif。
请注意,第二张图片中的函数名称为“N / A”。 以下是对它的完整解释:hxxp://home.hiwaay.net/~georgech/WhitePapers/Exporting/Exp.htm。
======================================
更新:非常感谢所有给我建议的人。 最后,我决定在linux / posix平台上使用静态库。 但是将小的“特殊部分”(使用一些不适合静态库的特性,例如:TLS Slot等)提取到普通的共享库中 。 因为小型普通共享库只做很少的事情,而且这些工作完全不敏感,所以不需要隐藏/隐藏它的API。
我认为这是解决我问题的最简单方法:-D
以前关于属性 ((visibility(“hidden”)))的答案很好,如果你想长期维护代码,但是如果你只想要一些你想要的符号并且需要快速修复……就符号而言要导出使用,添加
__attribute__ ((visibility ("default")))
然后你可以将-fvisibility=hidden
传递给编译器
这里有一个详尽的解释:
http://gcc.gnu.org/wiki/Visibility
编辑:另一种方法是构建一个静态库/存档(使用ar -cru mylib.a *.o
制作.a存档)或将这些对象组合成单个目标文件,根据这两个GCC编译的.o对象文件组合成第三个.o文件
如果你问“为什么要组合目标文件而不是只创建一个静态库?” …因为链接器将.o文件与.a文件区别对待(我不知道原因,只是它确实如此),特别是它允许您将.o文件链接到共享库或二进制文件中,即使所有符号都是隐藏的(即使是你正在使用的符号)这还有一个额外的好处,即减少启动时间(少一个DSO,少一些符号来查找)和二进制大小(这些符号通常占20%左右)尺寸和剥离只需要大约一半 – 只是外部可见的部分)
for binaries strip --strip-all -R .note -R .comment mybinary
对于库strip --strip-unneeded -R .note -R .comment mylib.so
更多关于静态链接的好处: http : //sta.li/faq但他们不讨论许可问题,这是不使用静态库的主要原因,因为你想要隐藏你的API,可能是一个问题
现在我们知道有一个“符号清洁”的对象,可以使用我们的组合对象来构建一个libpublic.so,方法是将private.o和public.c(只将你想要公开的别名/导出)链接到一个共享库。
这种方法很适合在公共API中找到不需要的“额外代码”。 如果将-fdata-sections -ffunction-sections
添加到对象构建中,当您使用-Wl,--gc-sections,--print-gc-sections
,它将删除未使用的节并打印已删除的内容的输出。
编辑2 – 或者您可以隐藏整个API并仅为您要导出的函数设置别名
别名(“目标”)
alias属性使声明作为另一个符号的别名发出,必须指定该符号。 例如,
void __f () { /* Do something. */; } void f () __attribute__ ((weak, alias ("__f")));
将f' to be a weak alias for
定义f' to be a weak alias for
__f’ f' to be a weak alias for
。 在C ++中,必须使用目标的受损名称。 如果在同一翻译单元中未定义“__f”,则会出错。
并非所有目标计算机都支持此属性。
您可以考虑使用GCC函数属性进行可见性并使其隐藏,即在头文件中的许多适当位置添加__attribute__((visibility ("hidden")))
。
然后,你将隐藏无用的符号,并保留好的符号。
这是GCC扩展(可能由Clang或Icc等其他编译器支持)。
附加物
在Linux世界中,共享库应该按名称导出函数(或者可能是全局数据),如头文件中所发布的那样。 否则,不要将这些function称为“导出” – 它们不是!
如果您绝对希望在 可访问但未导出的共享库中具有函数 ,则可以以某种方式注册它(例如,将函数指针放在全局数据的某个槽中,例如数组),这意味着你有(或提供)一些function注册机制。 但这不再是导出function了。
更具体地说,您可以在主程序中使用全局函数指针数组
// in a global header.h // signature of some functions typedef void signature_t(int, char*); #define MAX_NBFUN 100 // global array of function pointers extern signature_t *funtab[MAX_NBFUN];
然后在你的程序的main.c
文件中
signature_t *funtab[MAX_NBFUN];
然后在你的共享对象( myshared.c
中的myshared.c
文件中编译成libmyshared.so
)构造函数:
static my_constructor(void) __attribute__((constructor)); static myfun(int, char*); // defined elsewhere is the same file static void my_constructor(void) { // called at shared object initialization funtab[3] = myfun; }
稍后您的主程序(或其他一些共享对象)可能会调用
funtab[3](124, "foo");
但我永远不会把这些东西称为“导出”function,只能称为可达function。
执行类似操作的程序的一个例子是我的MELT (它不使用数组,但是更复杂的堆分配值)。 执行数组函数指针技巧的程序的另一个例子是J.Pitrat CAIA / Malice程序。 顺便说一句,他的关于人造生物(有意识机器的良心)的书非常有趣(并提到了这个技巧 – 我在附录中提出了他的建议)。
要隐藏UNIX上导出函数的含义,可以使用#defines通过简单重命名来模糊其名称。 像这样:
#define YourGoodFunction_CreateSomething MeaninglessFunction1 #define YourGoodFunction_AddSomethingElseToSomething lxstat__ #define YourGoodFunction_SaveSomething GoAway_Cracker #define YourGoodFunction_ReleaseSomething Abracadabra
等等。
在少数function的情况下,可以用手完成。 如果您需要数千个,则应使用代码生成。
- 获取真实函数名称列表,使用grep,awk,cut等。
- 准备无意义名字的字典
- 编写一个脚本(或二进制)生成器,它将输出带有#defines的C头文件,如上所示。
唯一的问题是如何获得字典。 好吧,我在这里看到几个选项:
- 你可以让你的同事随机键入他们的键盘;-)
- 生成一个随机字符串,如:read(/ dev / urandom,10-20 bytes)| BASE64
- 使用一些真正的字典(通用英语,特定域名)
- 收集真实的系统API名称并稍微改变它们:
__lxstat -> lxstat__
这仅限于你的想象力。
您可以编写版本脚本并将其传递给链接器以执行此操作。
一个简单的脚本如下所示:
testfile.exp:
{ global: myExportedFunction1; myExportedFunction2; local: *; }
然后使用以下选项链接您的可执行文件:
-Wl,--version-script=testfile.exp
当应用于共享库时,仍将列出.so文件中的符号以进行调试,但无法从库外部访问它们。
我正在为同样的问题寻找解决方案。 所以,我找不到一个强大的解决方案。 然而,作为概念的certificate,我使用objcopy来达到预期的效果。 基本上,在编译目标文件后,我重新定义了它的一些符号。 然后,翻译的目标文件用于构建最终的共享对象或可执行文件。 因此,可以用作反向工程我的算法的提示的类/方法名称被一些无意义的名称m1,m2,m3完全重命名。
这是我用来确保这个想法有效的测试:
Makefile文件:
all: libshared_object.so executable.exe clean: rm *.o *.so *.exe libshared_object.so : shared_object.o g++ -fPIC --shared -O2 $< -o $@ strip $@ shared_object.o : shared_object.cpp interface.h g++ -fPIC -O2 $< -c -o $@ objcopy --redefine-sym _ZN17MyVerySecretClass14secret_method1Ev=m1 \ --redefine-sym _ZN17MyVerySecretClass14secret_method2Ev=m2 \ --redefine-sym _ZN17MyVerySecretClass14secret_method3Ev=m3 $@ executable.exe : executable.o libshared_object.so g++ -O2 -lshared_object -L. $< -o $@ strip $@ executable.o : executable.cpp interface.h g++ -O2 -lshared_object -L. $< -c -o $@ objcopy --redefine-sym _ZN17MyVerySecretClass14secret_method1Ev=m1 \ --redefine-sym _ZN17MyVerySecretClass14secret_method2Ev=m2 \ --redefine-sym _ZN17MyVerySecretClass14secret_method3Ev=m3 $@ run: all LD_LIBRARY_PATH=. ./executable.exe
interface.h
class MyVerySecretClass { private: int secret_var; public: MyVerySecretClass(); ~MyVerySecretClass(); void secret_method1(); void secret_method2(); void secret_method3(); };
shared_object.cpp
#include #include #include #include "interface.h" MyVerySecretClass::MyVerySecretClass() : secret_var(0) {} MyVerySecretClass::~MyVerySecretClass() { secret_var = -1; } void MyVerySecretClass::secret_method1() { ++secret_var; } void MyVerySecretClass::secret_method2() { printf("The value of secret variable is %d\n", secret_var); } void MyVerySecretClass::secret_method3() { char cmdln[128]; sprintf( cmdln, "pstack %d", getpid() ); system( cmdln ); }
executable.cpp
#include "interface.h" int main ( void ) { MyVerySecretClass o; o.secret_method1(); o.secret_method2(); o.secret_method1(); o.secret_method2(); o.secret_method1(); o.secret_method2(); o.secret_method3(); return 0; }