03 - Play with ELF symbols -------------------------- In this tutorial we will see how to modify dynamic symbols in both a binary and a library. Scripts and materials are available here: `materials `_ By Romain Thomas - `@rh0main `_ ----- When a binary in linked against a library, the library needed is stored in a ``DT_NEEDED`` entry from the dynamic table and the needed functions needed are registered in the dynamic symbols table with the following attributes: * :attr:`~lief.ELF.Symbol.value` set to ``0`` * :attr:`~lief.ELF.Symbol.type` set to :attr:`~lief.ELF.SYMBOL_TYPES.FUNC` Similarly, when a library exports functions it has a ``DT_SONAME`` entry in the dynamic table and the functions exported are registered in the dynamic symbols table with the following attributes: * :attr:`~lief.ELF.Symbol.value` set to address of the function in the library * :attr:`~lief.ELF.Symbol.type` set to :attr:`~lief.ELF.SYMBOL_TYPES.FUNC` Imported and exported functions are abstracted by LIEF thus you can iterate over these elements with :attr:`~lief.Binary.exported_functions` and :attr:`~lief.Binary.imported_functions` .. code-block:: python import lief binary = lief.parse("/usr/bin/ls") library = lief.parse("/usr/lib/libc.so.6") print(binary.imported_functions) print(library.exported_functions) When analyzing a binary, imported function names are very helpful for the reverse engineering. One solution is to link statically the binary and the library. Another solution is to blow mind the reverser's mind by swapping these symbols. Take a look at the following code: .. code-block:: C #include #include #include double hashme(double input) { return pow(input, 4) + log(input + 3); } int main(int argc, char** argv) { if (argc != 2) { printf("Usage: %s N\n", argv[0]); return EXIT_FAILURE; } double N = (double)atoi(argv[1]); double hash = hashme(N); printf("%f\n", hash); return EXIT_SUCCESS; } Basically, this program takes an integer as argument and performs some computation on this value. .. code-block:: console $ hasme 123 228886645.836282 .. image:: ../_static/tutorial/03/hashme.png :scale: 60 % :align: center The ``pow`` and ``log`` functions are located in the ``libm.so.6`` library. One interesting trick to do with LIEF is to swap this function **name** with other functions **name**. In this tutorial we will swap them with ``cos`` and ``sin`` functions. First we have to load both the library and the binary: .. code-block:: python #!/usr/bin/env python3 import lief hasme = lief.parse("hasme") libm = lief.parse("/usr/lib/libm.so.6") Then when change the name of the two imported functions in the **binary**: .. code-block:: python hashme_pow_sym = next(filter(lambda e : e.name == "pow", my_binary.imported_symbols)) hashme_log_sym = next(filter(lambda e : e.name == "log", my_binary.imported_symbols)) hashme_pow_sym.name = "cos" hashme_log_sym.name = "sin" finally we swap ``log`` with ``sin`` and ``pow`` with ``cos`` in the **library** and we rebuild the two objects: .. code-block:: python #!/usr/bin/env python3 import lief hasme = lief.parse("hasme") libm = lief.parse("/usr/lib/libm.so.6") def swap(obj, a, b): symbol_a = next(filter(lambda e : e.name == a, obj.dynamic_symbols)) symbol_b = next(filter(lambda e : e.name == b, obj.dynamic_symbols)) b_name = symbol_b.name symbol_b.name = symbol_a.name symbol_a.name = b_name hashme_pow_sym = next(filter(lambda e : e.name == "pow", my_binary.imported_symbols)) hashme_log_sym = next(filter(lambda e : e.name == "log", my_binary.imported_symbols)) hashme_pow_sym.name = "cos" hashme_log_sym.name = "sin" swap(libm, "log", "sin") swap(libm, "pow", "cos") hashme.write("hashme.obf") libm.write("libm.so.6") .. image:: ../_static/tutorial/03/hashme_obf.png :scale: 60 % :align: center With this script, we built a modified ``libm`` in our current directory and we have to force the Linux loader to use this one when executing ``binary.obf``. To do so we export ``LD_LIBRARY_PATH`` to the current directory: .. code-block:: console $ LD_LIBRARY_PATH=. hashme.obf 123 228886645.836282 If we omit it, it will use the default ``libm`` and hash computation will be done with ``sin`` and ``cos``: .. code-block:: console $ hashme.obf 123 -0.557978 One real use case could be to swap symbols in cryptographic libraries like OpenSSL. For example ``EVP_DecryptInit`` and ``EVP_EncryptInit`` have the same prototype so we could swap them.