Skip to content

Commit e6dc40a

Browse files
committed
Move EFI Resolver to API
Support all existing features in EFI Resolver, 1. Doesn't support running on existing BNDBs (though we tried to support this in python plugins, it doesn't work well) 2. Perform analysis on MLIL rather than HLIL, previous pattern matching on HLIL constains many false negatives
1 parent d14c6c4 commit e6dc40a

16 files changed

+1917
-0
lines changed
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
cmake_minimum_required(VERSION 3.22 FATAL_ERROR)
2+
3+
project(efi_resolver)
4+
5+
option(DEBUG "DEBUG Mode" ON)
6+
7+
if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD))
8+
set(BN_API_PATH $ENV{BN_API_PATH})
9+
if(NOT BN_API_PATH)
10+
message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH")
11+
endif()
12+
endif()
13+
if(NOT BN_INTERNAL_BUILD)
14+
set(HEADLESS ON CACHE BOOL "")
15+
add_subdirectory(${BN_API_PATH} ${PROJECT_BINARY_DIR}/api)
16+
endif()
17+
18+
# Binary Ninja plugin ----------------------------------------------------------
19+
20+
file(
21+
GLOB_RECURSE SOURCE_FILES
22+
CONFIGURE_DEPENDS # Automatically reconfigure if source files are added/removed.
23+
${PROJECT_SOURCE_DIR}/src/*.cpp ${PROJECT_SOURCE_DIR}/include/*.h
24+
)
25+
26+
add_library(efi_resolver SHARED ${SOURCE_FILES})
27+
target_link_libraries(efi_resolver binaryninjaapi)
28+
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
29+
target_compile_features(efi_resolver PRIVATE cxx_std_17 c_std_99)
30+
31+
# Library targets linking against the Binary Ninja API need to be compiled with
32+
# position-independent code on Linux.
33+
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
34+
target_compile_options(efi_resolver PRIVATE "-fPIC")
35+
endif()
36+
37+
# Configure plugin output directory for internal builds, otherwise configure
38+
# plugin installation for public builds.
39+
40+
if(BN_INTERNAL_BUILD)
41+
set_target_properties(efi_resolver PROPERTIES
42+
LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}
43+
RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR})
44+
else()
45+
bn_install_plugin(${PROJECT_NAME})
46+
endif()
47+

platform/efi/efi_resolver/LICENSE

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2023-2024 Vector 35 Inc.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

platform/efi/efi_resolver/README.md

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# EFI Resolver
2+
Author: **Vector 35 Inc**
3+
4+
_A Binary Ninja built-in plugin that automatically resolves type information for EFI protocol usage._
5+
6+
This repository contains C++ version of EFI Resolver, which is bundled with Binary Ninja. For the original Python
7+
version, please refer to https://github.com/vector35/efi-resolver/tree/main
8+
9+
## Description:
10+
11+
EFI Resolver is a Binary Ninja plugin that automates the task of resolving EFI protocol type information. It supports both DXE files and PEI files. It propagates parameter pointers from entry points to system table, MM system table, boot services, and runtime services to any global variables where they are stored. For PEI files, it also supports identifying [processor-specific mechanisms](https://uefi.org/specs/PI/1.8/V1_PEI_Foundation.html#pei-services-table-retrieval) for retrieving PEI services pointers. The plugin also identifies references to the boot services, MM protocol functions and PEI services, and applies type information according to the GUID passed to these functions. The plugin supports the core UEFI specification, and allows users to provide custom vendor protocols.
12+
13+
## Build Instructions
14+
15+
```bash
16+
git clone https://github.com/Vector35/binaryninja-api.git
17+
git clone https://github.com/Vector35/efi-resolver.git && cd efi-resolver
18+
export BN_API_PATH=../binaryninja-api # Or specifying the path to api repo
19+
cmake -S . -B build -GNinja
20+
cmake --build build -t install
21+
```
22+
23+
## License
24+
25+
This plugin is released under an Apache-2.0 license.
26+
27+
## Supplying Custom UEFI Protocol GUIDs and Types
28+
29+
By default, EFI Resolver propagates types and GUIDs using Binary Ninja's native platform types for EFI. Many UEFI
30+
firmware binaries include types (and GUIDs) for proprietary protocols. This section describes how users can supply
31+
custom UEFI types and GUIDs for use with EFI Resolver type propagation.
32+
33+
### User-supplied EFI GUIDs
34+
35+
EFI Resolver uses a JSON file to associate user-supplied EFI GUIDs with types for propagation. GUIDs for proprietary
36+
protocol types can be used with EFI Resolver by creating a file at `<user folder>/types/efi-guids.json` containing JSON
37+
entries in the following format:
38+
39+
```json
40+
{
41+
"EFI_EXAMPLE_CUSTOM_PROTOCOL_GUID": [
42+
19088743,
43+
35243,
44+
52719,
45+
1,
46+
35,
47+
69,
48+
103,
49+
137,
50+
171,
51+
205,
52+
239
53+
]
54+
}
55+
```
56+
57+
In this example, the protocol type of `EFI_EXAMPLE_CUSTOM_PROTOCOL` is mapped to the
58+
`{0x01234567,0x89ab,0xcdef,{0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef}}` GUID (named `EFI_EXAMPLE_CUSTOM_PROTOCOL_GUID`).
59+
To test that the file is a valid JSON file, run `python -m json.tool < efi-guids.json`.
60+
61+
__Note: user-supplied proprietary GUIDs from `efi-guids.json` are used to name variables regardless of whether or not an associated platform type has been loaded. If EFI Resolver fails to query the type for an EFI protocol interface, it will set the variable type for the protocol interface pointer to `VOID*`.__
62+
63+
### User-supplied EFI Platform Types
64+
65+
Types and structures for proprietary protocols are to be imported using Binary Ninja's standard mechanism for loading
66+
user-supplied platform types. Instructions on adding custom platform types can be found [here](https://docs.binary.ninja/guide/types/platformtypes.html). Available EFI platform names include:
67+
- `efi-x86`
68+
- `efi-x86_64`
69+
- `efi-thumb2`
70+
- `efi-armv7`
71+
- `efi-aarch64`
72+
- `efi-windows-aarch64`
73+
- `efi-windows-x86`
74+
- `efi-windows-x86_64`
75+
76+
To avoid having to add duplicate types in each platform-specific `*.c` file, it is recommended to add common types
77+
to a top-level `efi.c` file and `#include` the file in the platform-specific `*.c` files. For example:
78+
79+
```C
80+
// <user folder>/types/platform/efi-x86_64.c including <user folder>/types/efi.c
81+
#include "../efi.c"
82+
```
83+
84+
To test that C source files containing custom EFI platform types are in the correct format, use the `bv.platform.parse_types_from_source_file` API.
85+
86+
Alternatively, user types can be supplied manually from type libraries, header files, or any other mechanism supported
87+
by Binary Ninja. Just ensure that the name for types associated with GUIDs match what is in `efi-guids.json`. Protocol
88+
GUID names in `efi-guids.json` should end with `_PROTOCOL_GUID` and the prefix must be identical to the associated
89+
protocol type name. For example, if the GUID is named `EFI_EXAMPLE_PROTOCOL_GUID`, EFI Resolver will attempt to
90+
look up a type named `EFI_EXAMPLE_PROTOCOL`.
91+
92+
### Full Example
93+
94+
In summary, including a custom platform type of `EFI_EXAMPLE_CUSTOM_PROTOCOL` for the `efi-x86` platform and associating
95+
it with a GUID named `EFI_EXAMPLE_CUSTOM_PROTOCOL_GUID` requires two steps:
96+
97+
1. Create the `<user folder>/types/platform/efi-x86.c` header file:
98+
99+
```C
100+
struct EFI_EXAMPLE_CUSTOM_PROTOCOL
101+
{
102+
uint32_t length;
103+
}
104+
```
105+
106+
2. Create the `<user folder>/types/efi-guids.json` file:
107+
108+
```json
109+
{
110+
"EFI_EXAMPLE_CUSTOM_PROTOCOL_GUID": [
111+
19088743,
112+
35243,
113+
52719,
114+
1,
115+
35,
116+
69,
117+
103,
118+
137,
119+
171,
120+
205,
121+
239
122+
]
123+
}
124+
```
125+
126+
After a Binary Ninja restart, when a binary is loaded with the `efi-x86` platform, the `EFI_EXAMPLE_CUSTOM_PROTOCOL`
127+
type will be imported. When EFI Resolver runs, it will detect uses of `EFI_EXAMPLE_CUSTOM_PROTOCOL_GUID` and propagate
128+
the `EFI_EXAMPLE_CUSTOM_PROTOCOL` type.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include "Resolver.h"
4+
5+
class DxeResolver : Resolver {
6+
bool resolveBootServices();
7+
bool resolveRuntimeServices();
8+
9+
bool resolveSmmTables(string serviceName, string tableName);
10+
bool resolveSmmServices();
11+
bool resolveSmiHandlers();
12+
13+
public:
14+
/*!
15+
resolve BootServices and RuntimeServices, define protocol types that loaded by BootServices
16+
*/
17+
bool resolveDxe();
18+
19+
/*!
20+
Define MMST/SMMST and resolve SMM related protocols
21+
*/
22+
bool resolveSmm();
23+
24+
DxeResolver(Ref<BinaryView> view, Ref<BackgroundTask> task);
25+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include "binaryninjaapi.h"
4+
#include <iomanip>
5+
6+
using namespace BinaryNinja;
7+
using namespace std;
8+
9+
class EfiGuidRenderer : public BinaryNinja::DataRenderer {
10+
EfiGuidRenderer() = default;
11+
12+
public:
13+
bool IsValidForData(BinaryView*, uint64_t address, Type*,
14+
vector<pair<Type*, size_t>>&) override;
15+
16+
vector<DisassemblyTextLine> GetLinesForData(
17+
BinaryView*, uint64_t address, Type*, const vector<InstructionTextToken>& prefix,
18+
size_t width, vector<pair<Type*, size_t>>&) override;
19+
20+
static void Register();
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "binaryninjaapi.h"
4+
5+
using namespace BinaryNinja;
6+
7+
enum EFIModuleType {
8+
UNKNOWN,
9+
PEI,
10+
DXE,
11+
};
12+
13+
static inline EFIModuleType identifyModuleType(BinaryView* bv)
14+
{
15+
std::string viewType = bv->GetCurrentView();
16+
if (viewType == "Linear:PE")
17+
return DXE;
18+
else if (viewType == "Linear:TE")
19+
return PEI;
20+
else
21+
return UNKNOWN;
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include "Resolver.h"
4+
5+
class PeiResolver : Resolver {
6+
bool resolvePeiIdt();
7+
bool resolvePeiMrc();
8+
bool resolvePeiMrs();
9+
bool resolvePlatformPointers();
10+
bool resolvePeiDescriptors();
11+
bool resolvePeiServices();
12+
13+
public:
14+
/*!
15+
resolve Pei related types and PPIs, this function will also resolve processor-specific pointers
16+
and tried to define the EFI_PEI_DESCRIPTORS
17+
*/
18+
bool resolvePei();
19+
PeiResolver(Ref<BinaryView> view, Ref<BackgroundTask> task);
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma once
2+
3+
#include <fstream>
4+
#include <thread>
5+
6+
#include "GuidRenderer.h"
7+
#include "ModuleType.h"
8+
#include "TypePropagation.h"
9+
#include "binaryninjaapi.h"
10+
#include "highlevelilinstruction.h"
11+
#include "lowlevelilinstruction.h"
12+
#include "mediumlevelilinstruction.h"
13+
14+
using namespace BinaryNinja;
15+
using namespace std;
16+
17+
typedef array<uint8_t, 16> EFI_GUID;
18+
19+
class Resolver {
20+
protected:
21+
Ref<BinaryView> m_view;
22+
Ref<BackgroundTask> m_task;
23+
size_t m_width;
24+
map<EFI_GUID, pair<string, string>> m_protocol;
25+
map<EFI_GUID, string> m_user_guids;
26+
27+
vector<pair<uint64_t, string>> m_service_usages;
28+
vector<pair<uint64_t, string>> m_protocol_usages;
29+
vector<pair<uint64_t, EFI_GUID>> m_guid_usages;
30+
vector<pair<uint64_t, string>> m_variable_usages;
31+
32+
bool parseUserGuidIfExists(const string& filePath);
33+
bool parseProtocolMapping(const string& filePath);
34+
35+
/*!
36+
For backward compatibility, if a user saved a bndb with older version Binary Ninja
37+
this function will try to retrieve types from Platform Types if it doesn't find one
38+
in BinaryView
39+
*/
40+
Ref<Type> GetTypeFromViewAndPlatform(string type_name);
41+
void initProtocolMapping();
42+
43+
public:
44+
bool setModuleEntry(EFIModuleType fileType);
45+
bool resolveGuidInterface(Ref<Function> func, uint64_t addr, int guid_pos, int interface_pos);
46+
Resolver(Ref<BinaryView> view, Ref<BackgroundTask> task);
47+
48+
pair<string, string> lookupGuid(EFI_GUID guidBytes);
49+
pair<string, string> defineAndLookupGuid(uint64_t addr);
50+
51+
string nonConflictingName(const string& basename);
52+
static string nonConflictingLocalName(Ref<Function> func, const string& basename);
53+
54+
/*!
55+
Define the structure used at the callsite with type `typeName`, propagate it to the data section. If it's a structure type, define it fields
56+
according to the `followFields` parameter. The input `addr` should be a call instruction
57+
\param func the function that contains the callsite (it's parent function)
58+
\param addr address of the callsite
59+
\param typeName the type that need to define
60+
\param paramIdx the parameter index that want to define
61+
\param followFields whether to define the structure's fields if they are pointers
62+
\return False if failed
63+
64+
\b Example:
65+
\code{.cpp}
66+
refs = bv->GetCodeReferencesForType(QualifiedName("EFI_GET_VARIABLE"));
67+
for (auto ref : refs)
68+
{
69+
// ... some checking, need to make sure is a call instruction
70+
bool ok = defineTypeAtCallsite(ref.func, ref.addr, "EFI_GUID", 2, false);
71+
}
72+
\endcode
73+
*/
74+
bool defineTypeAtCallsite(Ref<Function> func, uint64_t addr, string typeName, int paramIdx, bool followFields = false);
75+
vector<HighLevelILInstruction> HighLevelILExprsAt(Ref<Function> func, Ref<Architecture> arch, uint64_t addr);
76+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include "Utils.h"
4+
#include "binaryninjaapi.h"
5+
6+
using namespace BinaryNinja;
7+
8+
class TypePropagation {
9+
Ref<BinaryView> m_view;
10+
std::deque<uint64_t> m_queue;
11+
Ref<Platform> m_platform;
12+
13+
public:
14+
TypePropagation(BinaryView* view);
15+
bool propagateFuncParamTypes(Function* func);
16+
bool propagateFuncParamTypes(Function* func, SSAVariable ssa_var);
17+
};

0 commit comments

Comments
 (0)