Skip to content

Commit 24f6b72

Browse files
committed
Enable PE hooking
* 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
1 parent 9508d22 commit 24f6b72

22 files changed

+558
-13
lines changed

Diff for: .appveyor.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ install:
6767
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_INCLUDE = $env:PYTHON32_INCLUDE }
6868
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_BINARY = $env:PYTHON32_BINARY }
6969
- ps: if ($env:PLATFORM -eq "x86") { $env:PYTHON_LIBRARY = $env:PYTHON32_LIBRARY }
70-
7170
- set PATH=%PYTHON_PATH%;%PATH%
7271

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

7877
test_script:
79-
- cmake --build . --config Release --target RUN_TESTS -- /v:m /logger:%MSBuildLogger%
78+
- cmake --build . --config Release --target RUN_TESTS -- /v:m /logger:%MSBuildLogger%
8079

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

8787
artifacts:
8888
- path: '*.zip'

Diff for: api/python/PE/objects/pyBinary.cpp

+27-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ void init_PE_Binary_class(py::module& m) {
148148
.def("add_section",
149149
&Binary::add_section,
150150
"Add a " RST_CLASS_REF(lief.PE.Section) " to the binary.",
151-
"section"_a, py::arg("type"),
151+
"section"_a, py::arg("type") = SECTION_TYPES::UNKNOWN,
152152
py::return_value_policy::reference)
153153

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

187+
.def("has_import",
188+
&Binary::has_import,
189+
"``True`` if the binary import the given library name",
190+
"import_name"_a)
191+
192+
.def("get_import",
193+
static_cast<no_const_func<Import&, const std::string&>>(&Binary::get_import),
194+
"Returns the " RST_CLASS_REF(lief.PE.Import) " from the given name",
195+
"import_name"_a,
196+
py::return_value_policy::reference)
197+
187198
.def_property_readonly("resources_manager",
188199
static_cast<no_const_getter<ResourcesManager>>(&Binary::get_resources_manager),
189200
"Return the " RST_CLASS_REF(lief.PE.ResourcesManager) " to manage resources")
@@ -210,6 +221,21 @@ void init_PE_Binary_class(py::module& m) {
210221
"Remove the " RST_CLASS_REF(lief.PE.Import) " from the given name",
211222
"import_name"_a)
212223

224+
.def("hook_function",
225+
static_cast<void (Binary::*)(const std::string&, uint64_t)>(&Binary::hook_function),
226+
"Hook the given function name\n\n"
227+
".. note:: \n\n"
228+
"\tWhen using this function, the :class:`~lief.PE.Builder` should be configured as follow:\n\n"
229+
"\t.. code-block:: python\n\n"
230+
"\t\t\n\n"
231+
"\t\tbuilder.build_imports(True).patch_imports(True)\n\n",
232+
"function_name"_a, "hook_address"_a)
233+
234+
.def("hook_function",
235+
static_cast<void (Binary::*)(const std::string&, const std::string&, uint64_t)>(&Binary::hook_function),
236+
"Hook the function name from the given library name",
237+
"library_name"_a, "function_name"_a, "hook_address"_a)
238+
213239
.def("remove_all_libraries",
214240
&Binary::remove_all_libraries,
215241
"Remove all libraries imported")

Diff for: api/python/PE/pyPE.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ void init_PE_module(py::module& m) {
2828
"Convert an OID to a human-readable string");
2929

3030

31+
// Enums
32+
init_PE_Structures_enum(LIEF_PE_module);
33+
3134
// Objects
3235
init_PE_Parser_class(LIEF_PE_module);
3336
init_PE_Binary_class(LIEF_PE_module);
@@ -54,6 +57,4 @@ void init_PE_module(py::module& m) {
5457
init_PE_SignerInfo_class(LIEF_PE_module);
5558
init_PE_AuthenticatedAttributes_class(LIEF_PE_module);
5659

57-
// Enums
58-
init_PE_Structures_enum(LIEF_PE_module);
5960
}

Diff for: api/python/PE/pyPEStructures.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ void init_PE_Structures_enum(py::module& m) {
104104
.value(PY_ENUM(LIEF::PE::DATA_DIRECTORY::CLR_RUNTIME_HEADER))
105105
.export_values();
106106

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

121121

122-
py::enum_<LIEF::PE::SECTION_CHARACTERISTICS>(m, "SECTION_CHARACTERISTICS")
122+
py::enum_<LIEF::PE::SECTION_CHARACTERISTICS>(m, "SECTION_CHARACTERISTICS", py::arithmetic())
123123
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_TYPE_NO_PAD))
124124
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_CODE))
125125
.value(PY_ENUM(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_INITIALIZED_DATA))

Diff for: doc/sphinx/_static/tutorial/06/06_hooking_1.png

40.5 KB
Loading

Diff for: doc/sphinx/_static/tutorial/06/06_hooking_2.png

52.8 KB
Loading

Diff for: doc/sphinx/_static/tutorial/06/06_hooking_3.png

2.64 KB
Loading

Diff for: doc/sphinx/tutorials/02_pe_from_scratch.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _02-pe-from-scratch:
2+
13
02 - Create a PE from scratch
24
-----------------------------
35

Diff for: doc/sphinx/tutorials/04_elf_hooking.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
04 - Hooking
2-
------------
1+
04 - ELF Hooking
2+
----------------
33

44
The objective of this tutorial is to hook a library function
55

Diff for: doc/sphinx/tutorials/06_pe_hooking.rst

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
06 - PE Hooking
2+
---------------
3+
4+
The objective of this tutorial is show how we can hook imported functions
5+
6+
Scripts and materials are available here: `materials <https://github.com/lief-project/tutorials/tree/master/06_PE_hooking>`_
7+
8+
------
9+
10+
The targeted binary is a simple ``PE64`` *HelloWorld* which prints the first argument on in the console:
11+
12+
.. code-block:: cpp
13+
14+
#include "stdafx.h"
15+
#include <stdio.h>
16+
17+
int main(int argc, char** argv) {
18+
printf("Hello: %s\n", argv[1]);
19+
return 0;
20+
}
21+
22+
.. code-block:: console
23+
24+
$ PE64_x86-64_binary_HelloWorld.exe World
25+
$ Hello: World
26+
27+
Using LIEF, we will replace the function that prints the message in the console with a ``MessageBox``
28+
29+
By disassembling the binary we can see that the *print* occurs in the function ``sub_140001030`` and it uses two
30+
external functions: ``__acrt_iob_func`` and ``__stdio_common_vfprintf``.
31+
32+
33+
.. figure:: ../_static/tutorial/06/06_hooking_1.png
34+
:scale: 80 %
35+
:align: center
36+
37+
38+
.. figure:: ../_static/tutorial/06/06_hooking_2.png
39+
:scale: 80 %
40+
:align: center
41+
42+
Due to the Microsoft x64 calling convention, the format is located in the ``rcx`` and the input message in the ``rdx`` register.
43+
44+
Basically the :ref:`hooking-code` replaces the ``__acrt_iob_func`` function and shows a ``MessageBox`` with the ``rdx`` message.
45+
46+
.. code-block:: nasm
47+
:caption: hooking code
48+
:name: hooking-code
49+
50+
add rsp, 0x48 ; Stack unwind
51+
xor rcx, rcx ; hWnd
52+
mov rdx, rdx ; Message
53+
mov r8, 0x0140009000 ; Title
54+
xor r9, r9 ; MB_OK
55+
mov rax, 0x014000A3E4 ; MessageBoxA address
56+
call [rax] ; MessageBoxA(hWnd, Message, Title, MB_OK)
57+
xor rcx, rcx ; exit value
58+
mov rax, 0x014000A3d4 ; ExitProcess address
59+
call [rax] ; ExitProcess(0)
60+
ret ; Never reached
61+
62+
.. note::
63+
64+
As for tutorial :ref:`02-pe-from-scratch`, the address of ``MessageBoxA`` and ``ExitProcess`` can be found
65+
with the function:
66+
67+
.. automethod:: lief.PE.Binary.predict_function_rva
68+
:noindex:
69+
70+
71+
72+
73+
74+
First we create the ``.htext`` section which will hold the hooking code:
75+
76+
.. code-block:: python
77+
78+
section_text = lief.PE.Section(".htext")
79+
section_text.content = code
80+
section_text.virtual_address = 0x7000
81+
section_text.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_CODE | lief.PE.SECTION_CHARACTERISTICS.MEM_READ | lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE
82+
83+
section_text = pe.add_section(section_text)
84+
85+
Then the ``.hdata`` section for the ``MessageBox`` title:
86+
87+
.. code-block:: python
88+
89+
title = "LIEF is awesome\0"
90+
data = list(map(ord, title))
91+
92+
section_data = lief.PE.Section(".hdata")
93+
section_data.content = data
94+
section_data.virtual_address = 0x8000
95+
section_data.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA | lief.PE.SECTION_CHARACTERISTICS.MEM_READ
96+
97+
section_data = pe.add_section(section_data)
98+
99+
As the ASLR is enabled we will disable it to avoid to deal with relocations:
100+
101+
.. code-block:: python
102+
103+
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE
104+
105+
We will also disable the ``NX`` protection:
106+
107+
108+
.. code-block:: python
109+
110+
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.NX_COMPAT
111+
112+
As ``ExitProcess`` is not imported in ``KERNEL32.dll`` we need to add it:
113+
114+
.. code-block:: python
115+
116+
kernel32 = binary.get_import("KERNEL32.dll")
117+
kernel32.add_entry("ExitProcess")
118+
119+
The ``MessageBoxA`` function is located in the ``user32.dll`` thus we have to add it:
120+
121+
122+
.. code-block:: python
123+
124+
user32 = binary.add_library("user32.dll")
125+
user32.add_entry("MessageBoxA")
126+
127+
Then we proceed to the hook of the ``__acrt_iob_func`` function:
128+
129+
.. code-block:: python
130+
131+
pe.hook_function("__acrt_iob_func", binary.optional_header.imagebase + section_text.virtual_address)
132+
133+
And finally we configure the :class:`~lief.PE.Builder` to create a new import table and to patch the original one with trampolines.
134+
135+
.. code-block:: python
136+
137+
138+
builder = lief.PE.Builder(binary)
139+
140+
builder.build_imports(True).patch_imports(True)
141+
142+
builder.build()
143+
144+
builder.write("lief_pe_hooking.exe")
145+
146+
Now we can run the final executable:
147+
148+
.. code-block:: console
149+
150+
$ lief_pe_hooking.exe "Hooking World"
151+
152+
153+
.. figure:: ../_static/tutorial/06/06_hooking_3.png
154+
:scale: 80 %
155+
:align: center
156+
157+
158+
159+
160+
161+
162+
163+
164+
165+
166+
167+
168+
169+
170+
171+

Diff for: doc/sphinx/tutorials/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Tutorials
99
03_elf_change_symbols.rst
1010
04_elf_hooking.rst
1111
05_elf_infect_plt_got.rst
12+
06_pe_hooking.rst
1213

1314

1415

Diff for: examples/python/nm.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# >>> nm("C:\\Windows\\explorer.exe")
1515

1616
import sys
17-
from lief import *
17+
from lief import parse
1818

1919
def nm(filename):
2020
""" Return symbols from *filename* binary """

Diff for: include/LIEF/PE/Binary.hpp

+34
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#ifndef LIEF_PE_BINARY_H_
1717
#define LIEF_PE_BINARY_H_
1818

19+
#include <map>
20+
1921
#include "LIEF/PE/Structures.hpp"
2022
#include "LIEF/PE/Header.hpp"
2123
#include "LIEF/PE/OptionalHeader.hpp"
@@ -232,6 +234,17 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
232234
it_imports imports(void);
233235
it_const_imports imports(void) const;
234236

237+
//! @brief Returns the PE::Import from the given name
238+
//!
239+
//! @param[in] import_name Name of the import
240+
Import& get_import(const std::string& import_name);
241+
const Import& get_import(const std::string& import_name) const;
242+
243+
//! @brief ``True`` if the binary import the given library name
244+
//!
245+
//! @param[in] import_name Name of the import
246+
bool has_import(const std::string& import_name) const;
247+
235248
//! @brief Add the function @p function of the library @p library
236249
//!
237250
//! @param[in] library library name of the function
@@ -247,6 +260,26 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
247260
//! @brief Remove all libraries in the binary
248261
void remove_all_libraries(void);
249262

263+
//! @brief Hook an imported function
264+
//!
265+
//! When using this function, LIEF::PE::Builder::build_imports and LIEF::PE::Builder::patch_imports
266+
//! should be set to ``true``
267+
//!
268+
//! @param[in] function Function name to hook
269+
//! @param[in] address Address of the hook
270+
void hook_function(const std::string& function, uint64_t address);
271+
272+
273+
//! @brief Hook an imported function
274+
//!
275+
//! When using this function, LIEF::PE::Builder::build_imports(true) and LIEF::PE::Builder::patch_imports
276+
//! should be set to ``true``
277+
//!
278+
//! @param[in] library Library name in which the function is located
279+
//! @param[in] function Function name to hook
280+
//! @param[in] address Address of the hook
281+
void hook_function(const std::string& library, const std::string& function, uint64_t address);
282+
250283
//! @brief Reconstruct the binary object and write it in `filename`
251284
//!
252285
//! Rebuild a PE binary from the current Binary object.
@@ -330,6 +363,7 @@ class DLL_PUBLIC Binary : public LIEF::Binary {
330363
Debug debug_;
331364
std::vector<uint8_t> overlay_;
332365

366+
std::map<std::string, std::map<std::string, uint64_t>> hooks_;
333367
};
334368

335369
}

Diff for: include/LIEF/PE/Builder.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class DLL_PUBLIC Builder
4949
template<typename PE_T>
5050
static std::vector<uint8_t> build_jmp(uint64_t address);
5151

52+
template<typename PE_T>
53+
static std::vector<uint8_t> build_jmp_hook(uint64_t address);
54+
5255
Builder& build_imports(bool flag = true);
5356
Builder& patch_imports(bool flag = true);
5457
Builder& build_relocations(bool flag = true);

0 commit comments

Comments
 (0)