Skip to content

Commit

Permalink
Enable PE hooking
Browse files Browse the repository at this point in the history
  * Add 'hook_function' to hook a PE imported function
  * Add tutorial on PE hooking (resolve #5)
  * Add 'PE::get_import' and 'PE::has_import' to retrieve import
  • Loading branch information
romainthomas committed May 1, 2017
1 parent 86b2963 commit b60b36a
Show file tree
Hide file tree
Showing 19 changed files with 558 additions and 13 deletions.
4 changes: 2 additions & 2 deletions .appveyor.yml
Expand Up @@ -67,7 +67,6 @@ install:
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_INCLUDE = $env:PYTHON32_INCLUDE }
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_BINARY = $env:PYTHON32_BINARY }
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_LIBRARY = $env:PYTHON32_LIBRARY }

- set PATH=%PYTHON_PATH%;%PATH%

build_script:
Expand All @@ -76,13 +75,14 @@ build_script:
- cmake --build . --config Release --target ALL_BUILD -- /v:m /logger:%MSBuildLogger%

test_script:
- cmake --build . --config Release --target RUN_TESTS -- /v:m /logger:%MSBuildLogger%
- cmake --build . --config Release --target RUN_TESTS -- /v:m /logger:%MSBuildLogger%

after_build:
- cmake --build . --config Release --target package -- /v:m /logger:%MSBuildLogger%
- cd api\python
- python.exe .\setup.py sdist --formats=zip && exit 0 # Ignore warnings...
- ps: gci dist\*.zip | % { rename-item –path $_.Fullname –Newname ( "windows_" + $env:PLATFORM + "_" + $_.basename + "_py" + $env:PYTHON_VERSION + $_.extension) }
- cd ..\..

artifacts:
- path: '*.zip'
Expand Down
28 changes: 27 additions & 1 deletion api/python/PE/objects/pyBinary.cpp
Expand Up @@ -148,7 +148,7 @@ void init_PE_Binary_class(py::module& m) {
.def("add_section",
&Binary::add_section,
"Add a " RST_CLASS_REF(lief.PE.Section) " to the binary.",
"section"_a, py::arg("type"),
"section"_a, py::arg("type") = SECTION_TYPES::UNKNOWN,
py::return_value_policy::reference)

//.def("delete_section", (void (Binary::*)(const std::string&)) &Binary::delete_section)
Expand Down Expand Up @@ -184,6 +184,17 @@ void init_PE_Binary_class(py::module& m) {
"Return an iterator to the " RST_CLASS_REF(lief.PE.Import) " libraries",
py::return_value_policy::reference)

.def("has_import",
&Binary::has_import,
"``True`` if the binary import the given library name",
"import_name"_a)

.def("get_import",
static_cast<no_const_func<Import&, const std::string&>>(&Binary::get_import),
"Returns the " RST_CLASS_REF(lief.PE.Import) " from the given name",
"import_name"_a,
py::return_value_policy::reference)

.def_property_readonly("resources_manager",
static_cast<no_const_getter<ResourcesManager>>(&Binary::get_resources_manager),
"Return the " RST_CLASS_REF(lief.PE.ResourcesManager) " to manage resources")
Expand All @@ -210,6 +221,21 @@ void init_PE_Binary_class(py::module& m) {
"Remove the " RST_CLASS_REF(lief.PE.Import) " from the given name",
"import_name"_a)

.def("hook_function",
static_cast<void (Binary::*)(const std::string&, uint64_t)>(&Binary::hook_function),
"Hook the given function name\n\n"
".. note:: \n\n"
"\tWhen using this function, the :class:`~lief.PE.Builder` should be configured as follow:\n\n"
"\t.. code-block:: python\n\n"
"\t\t\n\n"
"\t\tbuilder.build_imports(True).patch_imports(True)\n\n",
"function_name"_a, "hook_address"_a)

.def("hook_function",
static_cast<void (Binary::*)(const std::string&, const std::string&, uint64_t)>(&Binary::hook_function),
"Hook the function name from the given library name",
"library_name"_a, "function_name"_a, "hook_address"_a)

.def("remove_all_libraries",
&Binary::remove_all_libraries,
"Remove all libraries imported")
Expand Down
5 changes: 3 additions & 2 deletions api/python/PE/pyPE.cpp
Expand Up @@ -28,6 +28,9 @@ void init_PE_module(py::module& m) {
"Convert an OID to a human-readable string");


// Enums
init_PE_Structures_enum(LIEF_PE_module);

// Objects
init_PE_Parser_class(LIEF_PE_module);
init_PE_Binary_class(LIEF_PE_module);
Expand All @@ -54,6 +57,4 @@ void init_PE_module(py::module& m) {
init_PE_SignerInfo_class(LIEF_PE_module);
init_PE_AuthenticatedAttributes_class(LIEF_PE_module);

// Enums
init_PE_Structures_enum(LIEF_PE_module);
}
4 changes: 2 additions & 2 deletions api/python/PE/pyPEStructures.cpp
Expand Up @@ -104,7 +104,7 @@ void init_PE_Structures_enum(py::module& m) {
.value(PY_ENUM(LIEF::PE::DATA_DIRECTORY::CLR_RUNTIME_HEADER))
.export_values();

py::enum_<LIEF::PE::DLL_CHARACTERISTICS>(m, "DLL_CHARACTERISTICS")
py::enum_<LIEF::PE::DLL_CHARACTERISTICS>(m, "DLL_CHARACTERISTICS", py::arithmetic())
.value(PY_ENUM(LIEF::PE::DLL_CHARACTERISTICS::IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA))
.value(PY_ENUM(LIEF::PE::DLL_CHARACTERISTICS::IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE))
.value(PY_ENUM(LIEF::PE::DLL_CHARACTERISTICS::IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY))
Expand All @@ -119,7 +119,7 @@ void init_PE_Structures_enum(py::module& m) {
.export_values();


py::enum_<LIEF::PE::SECTION_CHARACTERISTICS>(m, "SECTION_CHARACTERISTICS")
py::enum_<LIEF::PE::SECTION_CHARACTERISTICS>(m, "SECTION_CHARACTERISTICS", py::arithmetic())
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_TYPE_NO_PAD))
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_CODE))
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_INITIALIZED_DATA))
Expand Down
Binary file added doc/sphinx/_static/tutorial/06/06_hooking_1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/sphinx/_static/tutorial/06/06_hooking_2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/sphinx/_static/tutorial/06/06_hooking_3.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions doc/sphinx/tutorials/02_pe_from_scratch.rst
@@ -1,3 +1,5 @@
.. _02-pe-from-scratch:

02 - Create a PE from scratch
-----------------------------

Expand Down
4 changes: 2 additions & 2 deletions doc/sphinx/tutorials/04_elf_hooking.rst
@@ -1,5 +1,5 @@
04 - Hooking
------------
04 - ELF Hooking
----------------

The objective of this tutorial is to hook a library function

Expand Down
171 changes: 171 additions & 0 deletions doc/sphinx/tutorials/06_pe_hooking.rst
@@ -0,0 +1,171 @@
06 - PE Hooking
---------------

The objective of this tutorial is show how we can hook imported functions

Scripts and materials are available here: `materials <https://github.com/lief-project/tutorials/tree/master/06_PE_hooking>`_

------

The targeted binary is a simple ``PE64`` *HelloWorld* which prints the first argument on in the console:

.. code-block:: cpp
#include "stdafx.h"
#include <stdio.h>
int main(int argc, char** argv) {
printf("Hello: %s\n", argv[1]);
return 0;
}
.. code-block:: console
$ PE64_x86-64_binary_HelloWorld.exe World
$ Hello: World
Using LIEF, we will replace the function that prints the message in the console with a ``MessageBox``

By disassembling the binary we can see that the *print* occurs in the function ``sub_140001030`` and it uses two
external functions: ``__acrt_iob_func`` and ``__stdio_common_vfprintf``.


.. figure:: ../_static/tutorial/06/06_hooking_1.png
:scale: 80 %
:align: center


.. figure:: ../_static/tutorial/06/06_hooking_2.png
:scale: 80 %
:align: center

Due to the Microsoft x64 calling convention, the format is located in the ``rcx`` and the input message in the ``rdx`` register.

Basically the :ref:`hooking-code` replaces the ``__acrt_iob_func`` function and shows a ``MessageBox`` with the ``rdx`` message.

.. code-block:: nasm
:caption: hooking code
:name: hooking-code
add rsp, 0x48 ; Stack unwind
xor rcx, rcx ; hWnd
mov rdx, rdx ; Message
mov r8, 0x0140009000 ; Title
xor r9, r9 ; MB_OK
mov rax, 0x014000A3E4 ; MessageBoxA address
call [rax] ; MessageBoxA(hWnd, Message, Title, MB_OK)
xor rcx, rcx ; exit value
mov rax, 0x014000A3d4 ; ExitProcess address
call [rax] ; ExitProcess(0)
ret ; Never reached
.. note::

As for tutorial :ref:`02-pe-from-scratch`, the address of ``MessageBoxA`` and ``ExitProcess`` can be found
with the function:

.. automethod:: lief.PE.Binary.predict_function_rva
:noindex:





First we create the ``.htext`` section which will hold the hooking code:

.. code-block:: python
section_text = lief.PE.Section(".htext")
section_text.content = code
section_text.virtual_address = 0x7000
section_text.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_CODE | lief.PE.SECTION_CHARACTERISTICS.MEM_READ | lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE
section_text = pe.add_section(section_text)
Then the ``.hdata`` section for the ``MessageBox`` title:

.. code-block:: python
title = "LIEF is awesome\0"
data = list(map(ord, title))
section_data = lief.PE.Section(".hdata")
section_data.content = data
section_data.virtual_address = 0x8000
section_data.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA | lief.PE.SECTION_CHARACTERISTICS.MEM_READ
section_data = pe.add_section(section_data)
As the ASLR is enabled we will disable it to avoid to deal with relocations:

.. code-block:: python
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE
We will also disable the ``NX`` protection:


.. code-block:: python
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.NX_COMPAT
As ``ExitProcess`` is not imported in ``KERNEL32.dll`` we need to add it:

.. code-block:: python
kernel32 = binary.get_import("KERNEL32.dll")
kernel32.add_entry("ExitProcess")
The ``MessageBoxA`` function is located in the ``user32.dll`` thus we have to add it:


.. code-block:: python
user32 = binary.add_library("user32.dll")
user32.add_entry("MessageBoxA")
Then we proceed to the hook of the ``__acrt_iob_func`` function:

.. code-block:: python
pe.hook_function("__acrt_iob_func", binary.optional_header.imagebase + section_text.virtual_address)
And finally we configure the :class:`~lief.PE.Builder` to create a new import table and to patch the original one with trampolines.

.. code-block:: python
builder = lief.PE.Builder(binary)
builder.build_imports(True).patch_imports(True)
builder.build()
builder.write("lief_pe_hooking.exe")
Now we can run the final executable:

.. code-block:: console
$ lief_pe_hooking.exe "Hooking World"
.. figure:: ../_static/tutorial/06/06_hooking_3.png
:scale: 80 %
:align: center
















1 change: 1 addition & 0 deletions doc/sphinx/tutorials/index.rst
Expand Up @@ -9,6 +9,7 @@ Tutorials
03_elf_change_symbols.rst
04_elf_hooking.rst
05_elf_infect_plt_got.rst
06_pe_hooking.rst



2 changes: 1 addition & 1 deletion examples/python/nm.py
Expand Up @@ -14,7 +14,7 @@
# >>> nm("C:\\Windows\\explorer.exe")

import sys
from lief import *
from lief import parse

def nm(filename):
""" Return symbols from *filename* binary """
Expand Down
34 changes: 34 additions & 0 deletions include/LIEF/PE/Binary.hpp
Expand Up @@ -16,6 +16,8 @@
#ifndef LIEF_PE_BINARY_H_
#define LIEF_PE_BINARY_H_

#include <map>

#include "LIEF/PE/Structures.hpp"
#include "LIEF/PE/Header.hpp"
#include "LIEF/PE/OptionalHeader.hpp"
Expand Down Expand Up @@ -232,6 +234,17 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
it_imports imports(void);
it_const_imports imports(void) const;

//! @brief Returns the PE::Import from the given name
//!
//! @param[in] import_name Name of the import
Import& get_import(const std::string& import_name);
const Import& get_import(const std::string& import_name) const;

//! @brief ``True`` if the binary import the given library name
//!
//! @param[in] import_name Name of the import
bool has_import(const std::string& import_name) const;

//! @brief Add the function @p function of the library @p library
//!
//! @param[in] library library name of the function
Expand All @@ -247,6 +260,26 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
//! @brief Remove all libraries in the binary
void remove_all_libraries(void);

//! @brief Hook an imported function
//!
//! When using this function, LIEF::PE::Builder::build_imports and LIEF::PE::Builder::patch_imports
//! should be set to ``true``
//!
//! @param[in] function Function name to hook
//! @param[in] address Address of the hook
void hook_function(const std::string& function, uint64_t address);


//! @brief Hook an imported function
//!
//! When using this function, LIEF::PE::Builder::build_imports(true) and LIEF::PE::Builder::patch_imports
//! should be set to ``true``
//!
//! @param[in] library Library name in which the function is located
//! @param[in] function Function name to hook
//! @param[in] address Address of the hook
void hook_function(const std::string& library, const std::string& function, uint64_t address);

//! @brief Reconstruct the binary object and write it in `filename`
//!
//! Rebuild a PE binary from the current Binary object.
Expand Down Expand Up @@ -330,6 +363,7 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
Debug debug_;
std::vector<uint8_t> overlay_;

std::map<std::string, std::map<std::string, uint64_t>> hooks_;
};

}
Expand Down
3 changes: 3 additions & 0 deletions include/LIEF/PE/Builder.hpp
Expand Up @@ -49,6 +49,9 @@ class DLL_PUBLIC Builder
template<typename PE_T>
static std::vector<uint8_t> build_jmp(uint64_t address);

template<typename PE_T>
static std::vector<uint8_t> build_jmp_hook(uint64_t address);

Builder& build_imports(bool flag = true);
Builder& patch_imports(bool flag = true);
Builder& build_relocations(bool flag = true);
Expand Down

0 comments on commit b60b36a

Please sign in to comment.