2017-04-03 17:54:51 +02:00
05 - Infecting the plt/got
--------------------------
2017-03-30 16:56:49 +02:00
The objective of this tutorial is to hook an imported function in an ELF binary.
Scripts and materials are available here: `materials <https://github.com/lief-project/tutorials/tree/master/05_ELF_infect_plt-got> `_
2018-03-26 11:05:32 +02:00
By Romain Thomas - `@rh0main <https://twitter.com/rh0main> `_
2017-03-30 16:56:49 +02:00
-----
Hooking imported functions by infecting the `` .got `` section is a well-known technique [#f1]_ [#f2]_ and this tutorial will be focused
on its implementation using LIEF.
These figures illustrate the `` plt/got `` mechanism:
.. figure :: ../_static/tutorial/05/pltgot.png
:scale: 40 %
:align: center
With lazy binding, the first time that the function is called the `` got `` entry redirects to the plt instruction.
.. figure :: ../_static/tutorial/05/pltgot3.png
:scale: 40 %
:align: center
The Second time, `` got `` entry holds the address in the shared library.
Basically the infection is done in two steps:
* Firstly, we inject our hook
* Secondly, we redirect the targeted function to our hook by patching the `` got ``
It can be summed up by the following figure:
.. figure :: ../_static/tutorial/05/pltgot2.png
:scale: 50 %
:align: center
As example, we will use a basic *crackme* which performs a :manpage: `memcmp(3)` on the flag and user's input.
.. code-block :: cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Damn_YoU_Got_The_Flag
char password[] = "\x18\x3d\x31\x32\x03\x05\x33\x09\x03\x1b\x33\x28\x03\x08\x34\x39\x03\x1a\x30\x3d\x3b";
inline int check(char* input);
int check(char* input) {
for (int i = 0; i < sizeof(password) - 1; ++i) {
password[i] ^= 0x5c;
}
return memcmp(password, input, sizeof(password) - 1);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <password>\n", argv[0]);
return EXIT_FAILURE;
}
if (strlen(argv[1]) == (sizeof(password) - 1) && check(argv[1]) == 0) {
puts("You got it !!");
return EXIT_SUCCESS;
}
puts("Wrong");
return EXIT_FAILURE;
}
2017-04-04 16:25:04 +02:00
The flag is *xored* with `` 0x5C `` . To validate the *crackme* , the user has to enter `` Damn_YoU_Got_The_Flag `` :
2017-03-30 16:56:49 +02:00
.. code-block :: console
$ crackme.bin foo
Wrong
$ crackme.bin Damn_YoU_Got_The_Flag
You got it !!
2017-04-04 16:25:04 +02:00
The hook will consist in printing arguments of `` memcmp `` and returning `` 0 `` :
2017-03-30 16:56:49 +02:00
.. code-block :: cpp
#include "arch/x86_64/syscall.c"
#define stdout 1
int my_memcmp(const void* lhs, const void* rhs, int n) {
const char msg[] = "Hook memcmp\n";
_write(stdout, msg, sizeof(msg));
_write(stdout, (const char*)lhs, n);
_write(stdout, "\n", 2);
_write(stdout, (const char*)rhs, n);
_write(stdout, "\n", 2);
return 0;
}
As the hook is going to be injected into the crackme, it must have the following requirements:
2020-12-23 07:10:17 +01:00
* Assembly code must be *position independant* (compiled with `` -fPIC `` or `` -pie/-fPIE `` flags)
* Don't use external libraries such as `` libc.so `` (`` -nostdlib -nodefaultlibs `` flags)
2017-03-30 16:56:49 +02:00
Due to the requirements, the hook is compiled with: :code: `gcc -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook` .
Injecting the hook
~~~~~~~~~~~~~~~~~~
The first step is to inject the hook into the binary. To do so we will add a :class: `~lief.ELF.Segment` :
.. code-block :: python
import lief
crackme = lief.parse("crackme.bin")
hook = lief.parse("hook")
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
segment_added = crackme.add(hook.segments[0])
2017-03-30 16:56:49 +02:00
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
All assembly code of the hook stands in the first :attr: `~lief.ELF.SEGMENT_TYPES.LOAD` segment of `` hook `` .
2017-03-30 16:56:49 +02:00
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
Once the hook added, its virtual address is :attr: `~lief.ELF.Segment.virtual_address` of `` segment_added `` and we can processed to the `` got `` patching.
2017-03-30 16:56:49 +02:00
Patching the `` got ``
~~~~~~~~~~~~~~~~~~~~
LIEF provides a function to easily patch the `` got `` entry associated with a :class: `~lief.ELF.Symbol` :
.. automethod :: lief.ELF.Binary.patch_pltgot
:noindex:
The offset of the `` memcmp `` function is stored in the :attr: `~lief.ELF.Symbol.value` attribute of the associated dynamic symbol. Thus its virtual address will be:
2020-12-23 07:10:17 +01:00
* `` my_memcpy `` : :attr: `~lief.ELF.Symbol.value` + `` segment_added.virtual_address ``
2017-03-30 16:56:49 +02:00
.. code-block :: python
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
my_memcmp = hook.get_symbol("my_memcmp")
my_memcmp_addr = segment_added.virtual_address + my_memcmp.value
2017-03-30 16:56:49 +02:00
Finally we can patch the `` memcmp `` from the crakme with this value:
.. code-block :: python
crackme.patch_pltgot('memcmp', my_memcmp_addr)
And rebuild it:
.. code-block :: python
crackme.write("crackme.hooked")
Run
~~~
2017-04-04 16:25:04 +02:00
As a check on the input size is performed before checking the flag value, we have to provide an input with the correct length (no matter its content):
2017-03-30 16:56:49 +02:00
.. code-block :: console
$ crackme.hooked XXXXXXXXXXXXXXXXXXXXX
Hook add
Damn_YoU_Got_The_Flag
XXXXXXXXXXXXXXXXXXXXX
You got it !!
2017-04-03 17:54:51 +02:00
.. rubric :: References
2017-03-30 16:56:49 +02:00
2020-12-20 14:19:04 +01:00
.. role :: strike
:class: strike
.. [#f1] :strike: `hxxp://vxheaven.org/lib/vrn00.html`
2017-04-03 17:54:51 +02:00
.. [#f2] http://phrack.org/issues/56/7.html
2017-03-30 16:56:49 +02:00