How to hide the exported symbols name within a shared library

萝らか妹 提交于 2019-11-30 02:07:30
technosaurus

The previous answers regarding attribute ((visibility ("hidden"))) is good when you want to maintain the code long term, but if you only have a few symbols that you want visible and want a quick fix... On the symbols that you want to export use, add

__attribute__ ((visibility ("default"))) 

Then you can pass -fvisibility=hidden to the compiler

There is a thorough explanation here:

http://gcc.gnu.org/wiki/Visibility

Edit: An alternative would be to build a static library/archive (make .a archive with ar -cru mylib.a *.o) or combine the objects into a single object file according to this combine two GCC compiled .o object files into a third .o file

If you are asking "Why combine object files instead of just making a static library?" ... because the linker will treat .o files differently than .a files (I don't know why, just that it does), specifically it will allow you to link a .o file into a shared library or a binary even if all of the symbols are hidden (even the ones you are using) This has the added benefit of reducing startup times (one less DSO and a lot less symbols to look up) and binary size (the symbols typically make up ~20% of the size and stripping only takes care of about half of that - just the externally visible parts)

for binaries strip --strip-all -R .note -R .comment mybinary

for libraries strip --strip-unneeded -R .note -R .comment mylib.so

More on the benefits of static linking here: http://sta.li/faq but they don't discuss licensing issues which are the main reason not to use a static library and since you are wanting to hide your API, that may be an issue

Now that we know have an object that is "symbol clean", it is possible to use our combined object to build a libpublic.so by linking private.o and public.c (which aliases/exports only what you want public) into a shared library.

This method lends itself well to finding the "extra code" that is unneeded in your public API as well. If you add -fdata-sections -ffunction-sections to your object builds, when you link with -Wl,--gc-sections,--print-gc-sections , it will eliminate unused sections and print an output of what was removed.

Edit 2 - or you could hide the whole API and alias only the functions you want to export

alias ("target")

The alias attribute causes the declaration to be emitted as an alias for another symbol, which must be specified. For instance,

          void __f () { /* Do something. */; }
          void f () __attribute__ ((weak, alias ("__f")));

defines f' to be a weak alias for__f'. In C++, the mangled name for the target must be used. It is an error if `__f' is not defined in the same translation unit.

Not all target machines support this attribute.

You could consider using GCC function attribute for visibility and make it hidden, i.e. adding __attribute__((visibility ("hidden"))) at many appropriate places in your header file.

You'll then hide thus your useless symbols, and keep the good ones.

This is a GCC extension (perhaps supported by other compilers like Clang or Icc).

addenda

In the Linux world, a shared library should export functions (or perhaps global data) by their names, as published in header files. Otherwise, don't call these functions "exported" -they are not!

If you absolutely want to have a function in a shared library which is reachable but not exported, you could register it in some way (for instance, putting the function pointer in some slot of a global data, e.g. an array), this means that you have (or provide) some function registration machinery. But this is not an exported function anymore.

To be more concrete, you could have in your main program a global array of function pointers

 // 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];

then in your main.c file of your program

 signature_t *funtab[MAX_NBFUN];

Then in your shared object (.e.g. in myshared.c file compiled into libmyshared.so) a constructor function:

 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;
 }

Later on your main program (or some other shared object) might call

 funtab[3](124, "foo");

but I would never call such things "exported" functions, only reachable functions.

An example of program doing such similar things is my MELT (which don't use arrays, but more complex heap-allocated values). Another example of program doing array function pointers tricks is J.Pitrat CAIA/Malice program. BTW, his book on Artificial Beings (the conscience of a conscious machine) is very interesting (and mentions that trick -which I had suggested him- in an appendix).

To hide the meaning of the exported functions on UNIX, you can just obfuscate their names with simple renaming, by using #defines. Like this:

#define YourGoodFunction_CreateSomething              MeaninglessFunction1
#define YourGoodFunction_AddSomethingElseToSomething  lxstat__
#define YourGoodFunction_SaveSomething                GoAway_Cracker
#define YourGoodFunction_ReleaseSomething             Abracadabra

and so on.

In a case of a few functions it can be done by hands. If you need thousands, you should use code generation.

  1. get the list of your real function names, use grep, awk, cut, etc.
  2. prepare a dictionary of the meaningless names
  3. write a script (or binary) generator which will output a C header file with #defines as shown above.

The only question is how you can get the dictionary. Well, I see a few options here:

  • you could ask your co-workers to randomly type on their keyboards ;-)
  • generate a random strings like: read(/dev/urandom, 10-20 bytes) | base64
  • use some real dictionary (general English, specific domain)
  • collect real system API names and change them a bit: __lxstat -> lxstat__

this is limited only by your imagination.

You can write a version-script and pass it to the linker to do this.

A simple script looks like this:

testfile.exp:

{
global:
  myExportedFunction1;
  myExportedFunction2;

local: *;
}

Then link your executable with the following options:

  -Wl,--version-script=testfile.exp

When applied to a shared library this will still list the symbols in the .so file for debugging purposes, but it is not possible to access them from the outside of the library.

I was looking for a solution for the same problem. So, far I couldn't find a robust solution. However, as a prove of concept I used objcopy to achieve desired results. Basically, after compiling an object file I redefine some of its symbols. Then the translated object file is used to build the final shared object or executable. As a result the class/method names that could be used as a hint to reverse engineer my algorithm are completely renamed by some meaningless names m1,m2,m3.

Here is the test I used to ensure that the idea works:

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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!