From 4800a807693ba70f23f6f380d4d02dc080a74a33 Mon Sep 17 00:00:00 2001 From: Romain Thomas Date: Sat, 27 Jul 2024 05:01:30 +0200 Subject: [PATCH] Add support for Mach-O `LC_SUBCLIENT` command --- api/python/lief/MachO.pyi | 15 +++- api/python/src/MachO/init.cpp | 2 + api/python/src/MachO/objects/CMakeLists.txt | 1 + api/python/src/MachO/objects/pyBinary.cpp | 13 ++- api/python/src/MachO/objects/pySubClient.cpp | 50 ++++++++++++ api/rust/autocxx_ffi.rs | 4 + api/rust/cargo/lief/src/macho/binary.rs | 6 ++ api/rust/cargo/lief/src/macho/commands.rs | 14 ++++ .../lief/src/macho/commands/sub_client.rs | 62 ++++++++++++++ api/rust/cargo/lief/tests/macho_tests.rs | 4 + api/rust/include/LIEF/rust/MachO.hpp | 1 + api/rust/include/LIEF/rust/MachO/Binary.hpp | 15 ++++ .../include/LIEF/rust/MachO/SubClient.hpp | 32 ++++++++ doc/sphinx/api/cpp/macho.rst | 9 +++ doc/sphinx/api/python/macho.rst | 12 +++ doc/sphinx/changelog.rst | 2 + include/LIEF/MachO.hpp | 1 + include/LIEF/MachO/Binary.hpp | 13 +++ include/LIEF/MachO/Builder.hpp | 4 + include/LIEF/MachO/SubClient.hpp | 81 +++++++++++++++++++ src/MachO/Binary.cpp | 19 +++++ src/MachO/BinaryParser.tcc | 22 +++++ src/MachO/Builder.cpp | 5 ++ src/MachO/Builder.tcc | 36 +++++++++ src/MachO/CMakeLists.txt | 1 + src/MachO/SubClient.cpp | 40 +++++++++ tests/macho/test_generic.py | 7 ++ 27 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 api/python/src/MachO/objects/pySubClient.cpp create mode 100644 api/rust/cargo/lief/src/macho/commands/sub_client.rs create mode 100644 api/rust/include/LIEF/rust/MachO/SubClient.hpp create mode 100644 include/LIEF/MachO/SubClient.hpp create mode 100644 src/MachO/SubClient.cpp diff --git a/api/python/lief/MachO.pyi b/api/python/lief/MachO.pyi index 68bb9191..8acf3ab1 100644 --- a/api/python/lief/MachO.pyi +++ b/api/python/lief/MachO.pyi @@ -125,6 +125,13 @@ class Binary(lief.Binary): def __len__(self) -> int: ... def __next__(self) -> lief.MachO.SegmentCommand: ... + class it_sub_clients: + def __init__(self, *args, **kwargs) -> None: ... + def __getitem__(self, arg: int, /) -> lief.MachO.SubClient: ... + def __iter__(self) -> lief.MachO.Binary.it_sub_clients: ... + def __len__(self) -> int: ... + def __next__(self) -> lief.MachO.SubClient: ... + class it_symbols: def __init__(self, *args, **kwargs) -> None: ... def __getitem__(self, arg: int, /) -> lief.MachO.Symbol: ... @@ -274,7 +281,7 @@ class Binary(lief.Binary): @property def has_source_version(self) -> bool: ... @property - def has_sub_framework(self) -> bool: ... + def has_subclients(self) -> bool: ... @property def has_symbol_command(self) -> bool: ... @property @@ -322,6 +329,8 @@ class Binary(lief.Binary): @property def sub_framework(self) -> lief.MachO.SubFramework: ... @property + def subclients(self) -> lief.MachO.Binary.it_sub_clients: ... + @property def support_arm64_ptr_auth(self) -> bool: ... @property def symbol_command(self) -> lief.MachO.SymbolCommand: ... @@ -1521,6 +1530,10 @@ class SourceVersion(LoadCommand): version: list[int] def __init__(self, *args, **kwargs) -> None: ... +class SubClient(LoadCommand): + client: str + def __init__(self, *args, **kwargs) -> None: ... + class SubFramework(LoadCommand): umbrella: str def __init__(self, *args, **kwargs) -> None: ... diff --git a/api/python/src/MachO/init.cpp b/api/python/src/MachO/init.cpp index aa093479..b741c404 100644 --- a/api/python/src/MachO/init.cpp +++ b/api/python/src/MachO/init.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ void init_objects(nb::module_& m) { CREATE(VersionMin, m); CREATE(SegmentSplitInfo, m); CREATE(SubFramework, m); + CREATE(SubClient, m); CREATE(DyldEnvironment, m); CREATE(EncryptionInfo, m); CREATE(BuildVersion, m); diff --git a/api/python/src/MachO/objects/CMakeLists.txt b/api/python/src/MachO/objects/CMakeLists.txt index 942bd868..1d8c02f3 100644 --- a/api/python/src/MachO/objects/CMakeLists.txt +++ b/api/python/src/MachO/objects/CMakeLists.txt @@ -37,6 +37,7 @@ target_sources(pyLIEF PRIVATE pySegmentSplitInfo.cpp pySourceVersion.cpp pySubFramework.cpp + pySubClient.cpp pySymbol.cpp pySymbolCommand.cpp pyThreadCommand.cpp diff --git a/api/python/src/MachO/objects/pyBinary.cpp b/api/python/src/MachO/objects/pyBinary.cpp index a1527460..9ec0ffa5 100644 --- a/api/python/src/MachO/objects/pyBinary.cpp +++ b/api/python/src/MachO/objects/pyBinary.cpp @@ -49,6 +49,7 @@ #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" #include "LIEF/MachO/SubFramework.hpp" +#include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/Symbol.hpp" #include "LIEF/MachO/SymbolCommand.hpp" #include "LIEF/MachO/ThreadCommand.hpp" @@ -82,6 +83,7 @@ void create(nb::module_& m) { init_ref_iterator(bin, "it_libraries"); init_ref_iterator(bin, "it_relocations"); init_ref_iterator(bin, "it_rpaths"); + init_ref_iterator(bin, "it_sub_clients"); nb::class_(bin, "range_t") .def_rw("start", &Binary::range_t::start) @@ -345,9 +347,14 @@ void create(nb::module_& m) { "Return the binary's " RST_CLASS_REF(lief.MachO.SegmentSplitInfo) " if any, or None"_doc, nb::rv_policy::reference_internal) - .def_prop_ro("has_sub_framework", - &Binary::has_sub_framework, - "``True`` if the binary has a " RST_CLASS_REF(lief.MachO.SubFramework) " command"_doc) + .def_prop_ro("subclients", + nb::overload_cast<>(&Binary::subclients), + "Return an iterator over the binary's " RST_CLASS_REF(lief.MachO.SubClient) ""_doc, + nb::keep_alive<0, 1>()) + + .def_prop_ro("has_subclients", + &Binary::has_subclients, + "``True`` if the binary has a " RST_CLASS_REF(lief.MachO.SubClient) " command"_doc) .def_prop_ro("sub_framework", nb::overload_cast<>(&Binary::sub_framework), diff --git a/api/python/src/MachO/objects/pySubClient.cpp b/api/python/src/MachO/objects/pySubClient.cpp new file mode 100644 index 00000000..79cf8790 --- /dev/null +++ b/api/python/src/MachO/objects/pySubClient.cpp @@ -0,0 +1,50 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include "LIEF/MachO/SubClient.hpp" + +#include "MachO/pyMachO.hpp" + +namespace LIEF::MachO::py { + +template<> +void create(nb::module_& m) { + + nb::class_(m, "SubClient", + R"delim( + Class that represents the SubClient command. + Accodring to the Mach-O ``loader.h`` documentation: + + > For dynamically linked shared libraries that are subframework of an umbrella + > framework they can allow clients other than the umbrella framework or other + > subframeworks in the same umbrella framework. To do this the subframework + > is built with "-allowable_client client_name" and an LC_SUB_CLIENT load + > command is created for each -allowable_client flag. The client_name is + > usually a framework name. It can also be a name used for bundles clients + > where the bundle is built with "-client_name client_name". + )delim"_doc) + + .def_prop_rw("client", + nb::overload_cast<>(&SubClient::client, nb::const_), + nb::overload_cast(&SubClient::client), + "Name of the sub client"_doc) + + LIEF_DEFAULT_STR(SubClient); +} +} diff --git a/api/rust/autocxx_ffi.rs b/api/rust/autocxx_ffi.rs index b69d4e86..082536d5 100644 --- a/api/rust/autocxx_ffi.rs +++ b/api/rust/autocxx_ffi.rs @@ -322,6 +322,8 @@ include_cpp! { block_constructors!("MachO_Binary_it_sections") generate!("MachO_Binary_it_libraries") block_constructors!("MachO_Binary_it_libraries") + generate!("MachO_Binary_it_sub_clients") + block_constructors!("MachO_Binary_it_sub_clients") generate!("MachO_BindingInfo") block_constructors!("MachO_BindingInfo") generate!("MachO_BuildVersion") @@ -410,6 +412,8 @@ include_cpp! { block_constructors!("MachO_SourceVersion") generate!("MachO_SubFramework") block_constructors!("MachO_SubFramework") + generate!("MachO_SubClient") + block_constructors!("MachO_SubClient") generate!("MachO_Symbol") block_constructors!("MachO_Symbol") generate!("MachO_SymbolCommand") diff --git a/api/rust/cargo/lief/src/macho/binary.rs b/api/rust/cargo/lief/src/macho/binary.rs index 46202851..8e1b0c86 100644 --- a/api/rust/cargo/lief/src/macho/binary.rs +++ b/api/rust/cargo/lief/src/macho/binary.rs @@ -18,6 +18,7 @@ use super::commands::segment::Segments; use super::commands::segment_split_info::SegmentSplitInfo; use super::commands::source_version::SourceVersion; use super::commands::sub_framework::SubFramework; +use super::commands::sub_client::{SubClient, SubClients}; use super::commands::symbol_command::SymbolCommand; use super::commands::thread_command::ThreadCommand; use super::commands::two_level_hints::TwoLevelHints; @@ -172,6 +173,11 @@ impl Binary { into_optional(self.ptr.sub_framework()) } + /// Return the `LC_SUBCLIENT` command if present + pub fn subclients(&self) -> SubClients { + SubClients::new(self.ptr.subclients()) + } + /// Return the `LC_DYLD_ENVIRONMENT` command if present pub fn dyld_environment(&self) -> Option { into_optional(self.ptr.dyld_environment()) diff --git a/api/rust/cargo/lief/src/macho/commands.rs b/api/rust/cargo/lief/src/macho/commands.rs index 8cbdf0a8..835615a0 100644 --- a/api/rust/cargo/lief/src/macho/commands.rs +++ b/api/rust/cargo/lief/src/macho/commands.rs @@ -21,6 +21,7 @@ pub mod segment; pub mod segment_split_info; pub mod source_version; pub mod sub_framework; +pub mod sub_client; pub mod symbol_command; pub mod thread_command; pub mod two_level_hints; @@ -69,6 +70,8 @@ pub use source_version::SourceVersion; #[doc(inline)] pub use sub_framework::SubFramework; #[doc(inline)] +pub use sub_client::SubClient; +#[doc(inline)] pub use symbol_command::SymbolCommand; #[doc(inline)] pub use thread_command::ThreadCommand; @@ -291,6 +294,7 @@ pub enum Commands<'a> { SegmentSplitInfo(SegmentSplitInfo<'a>), SourceVersion(SourceVersion<'a>), SubFramework(SubFramework<'a>), + SubClient(SubClient<'a>), SymbolCommand(SymbolCommand<'a>), ThreadCommand(ThreadCommand<'a>), TwoLevelHints(TwoLevelHints<'a>), @@ -430,6 +434,13 @@ impl<'a> Commands<'a> { std::mem::transmute::(ffi_entry) }; Commands::SubFramework(SubFramework::from_ffi(raw)) + } else if ffi::MachO_SubClient::classof(cmd_ref) { + let raw = { + type From = cxx::UniquePtr; + type To = cxx::UniquePtr; + std::mem::transmute::(ffi_entry) + }; + Commands::SubClient(SubClient::from_ffi(raw)) } else if ffi::MachO_DyldEnvironment::classof(cmd_ref) { let raw = { type From = cxx::UniquePtr; @@ -605,6 +616,9 @@ impl Command for Commands<'_> { Commands::SubFramework(cmd) => { cmd.get_base() } + Commands::SubClient(cmd) => { + cmd.get_base() + } Commands::SymbolCommand(cmd) => { cmd.get_base() } diff --git a/api/rust/cargo/lief/src/macho/commands/sub_client.rs b/api/rust/cargo/lief/src/macho/commands/sub_client.rs new file mode 100644 index 00000000..57c76e90 --- /dev/null +++ b/api/rust/cargo/lief/src/macho/commands/sub_client.rs @@ -0,0 +1,62 @@ +use super::Command; +use lief_ffi as ffi; +use crate::declare_iterator; +use crate::common::FromFFI; + +use std::marker::PhantomData; + +/// Class that represents the SubClient command. +/// Accodring to the Mach-O `loader.h` documentation: +/// +/// > For dynamically linked shared libraries that are subframework of an umbrella +/// > framework they can allow clients other than the umbrella framework or other +/// > subframeworks in the same umbrella framework. To do this the subframework +/// > is built with "-allowable_client client_name" and an LC_SUB_CLIENT load +/// > command is created for each -allowable_client flag. The client_name is +/// > usually a framework name. It can also be a name used for bundles clients +/// > where the bundle is built with "-client_name client_name". +pub struct SubClient<'a> { + ptr: cxx::UniquePtr, + _owner: PhantomData<&'a ffi::MachO_Binary> +} + + +impl SubClient<'_> { + /// Name of the subclient + pub fn client(&self) -> String { + self.ptr.client().to_string() + } +} + +impl std::fmt::Debug for SubClient<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let base = self as &dyn Command; + f.debug_struct("SubClient") + .field("base", &base) + .field("client", &self.client()) + .finish() + } +} + +impl FromFFI for SubClient<'_> { + fn from_ffi(cmd: cxx::UniquePtr) -> Self { + Self { + ptr: cmd, + _owner: PhantomData + } + } +} + +impl Command for SubClient<'_> { + fn get_base(&self) -> &ffi::MachO_Command { + self.ptr.as_ref().unwrap().as_ref() + } +} + +declare_iterator!( + SubClients, + SubClient<'a>, + ffi::MachO_SubClient, + ffi::MachO_Binary, + ffi::MachO_Binary_it_sub_clients +); diff --git a/api/rust/cargo/lief/tests/macho_tests.rs b/api/rust/cargo/lief/tests/macho_tests.rs index f4e579c5..fa13f0bd 100644 --- a/api/rust/cargo/lief/tests/macho_tests.rs +++ b/api/rust/cargo/lief/tests/macho_tests.rs @@ -209,6 +209,10 @@ fn explore_macho(_: &str, macho: &lief::macho::Binary) { format!("{sub_framework:?}"); } + for sc in macho.subclients() { + format!("{sc:?}"); + } + if let Some(dyld_environment) = macho.dyld_environment() { format!("{dyld_environment:?}"); } diff --git a/api/rust/include/LIEF/rust/MachO.hpp b/api/rust/include/LIEF/rust/MachO.hpp index 61ddd3eb..853300bf 100644 --- a/api/rust/include/LIEF/rust/MachO.hpp +++ b/api/rust/include/LIEF/rust/MachO.hpp @@ -41,6 +41,7 @@ #include "LIEF/rust/MachO/SegmentSplitInfo.hpp" #include "LIEF/rust/MachO/SourceVersion.hpp" #include "LIEF/rust/MachO/SubFramework.hpp" +#include "LIEF/rust/MachO/SubClient.hpp" #include "LIEF/rust/MachO/Symbol.hpp" #include "LIEF/rust/MachO/SymbolCommand.hpp" #include "LIEF/rust/MachO/ThreadCommand.hpp" diff --git a/api/rust/include/LIEF/rust/MachO/Binary.hpp b/api/rust/include/LIEF/rust/MachO/Binary.hpp index b58c09a1..4593e299 100644 --- a/api/rust/include/LIEF/rust/MachO/Binary.hpp +++ b/api/rust/include/LIEF/rust/MachO/Binary.hpp @@ -39,6 +39,7 @@ #include "LIEF/rust/MachO/SegmentSplitInfo.hpp" #include "LIEF/rust/MachO/EncryptionInfo.hpp" #include "LIEF/rust/MachO/SubFramework.hpp" +#include "LIEF/rust/MachO/SubClient.hpp" #include "LIEF/rust/MachO/DyldEnvironment.hpp" #include "LIEF/rust/MachO/BuildVersion.hpp" #include "LIEF/rust/MachO/DyldChainedFixups.hpp" @@ -115,6 +116,16 @@ class MachO_Binary : public AbstractBinary { auto size() const { return Iterator::size(); } }; + class it_sub_clients : + public Iterator + { + public: + it_sub_clients(const MachO_Binary::lief_t& src) + : Iterator(std::move(src.subclients())) { } + auto next() { return Iterator::next(); } + auto size() const { return Iterator::size(); } + }; + MachO_Binary(const lief_t& bin) : AbstractBinary(bin) {} auto header() const { @@ -192,6 +203,10 @@ class MachO_Binary : public AbstractBinary { return details::try_unique(impl().sub_framework()); } + auto subclients() const { + return std::make_unique(impl()); + } + auto dyld_environment() const { return details::try_unique(impl().dyld_environment()); } diff --git a/api/rust/include/LIEF/rust/MachO/SubClient.hpp b/api/rust/include/LIEF/rust/MachO/SubClient.hpp new file mode 100644 index 00000000..b8e1f7c1 --- /dev/null +++ b/api/rust/include/LIEF/rust/MachO/SubClient.hpp @@ -0,0 +1,32 @@ +/* Copyright 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "LIEF/MachO/SubClient.hpp" +#include "LIEF/rust/MachO/LoadCommand.hpp" + +class MachO_SubClient : public MachO_Command { + public: + using lief_t = LIEF::MachO::SubClient; + MachO_SubClient(const lief_t& base) : MachO_Command(base) {} + std::string client() const { return impl().client(); } + + static bool classof(const MachO_Command& cmd) { + return lief_t::classof(&cmd.get()); + } + + private: + const lief_t& impl() const { return as(this); } +}; diff --git a/doc/sphinx/api/cpp/macho.rst b/doc/sphinx/api/cpp/macho.rst index 438a8421..6a34006a 100644 --- a/doc/sphinx/api/cpp/macho.rst +++ b/doc/sphinx/api/cpp/macho.rst @@ -290,6 +290,15 @@ Sub-Framework ---------- + +Sub-Client +********** + +.. doxygenclass:: LIEF::MachO::SubClient + :project: lief + +---------- + Dyld Environment **************** diff --git a/doc/sphinx/api/python/macho.rst b/doc/sphinx/api/python/macho.rst index 444fed46..93e0a764 100644 --- a/doc/sphinx/api/python/macho.rst +++ b/doc/sphinx/api/python/macho.rst @@ -377,6 +377,18 @@ Sub Framework ---------- + +Sub Client +********** + +.. lief-inheritance:: lief._lief.MachO.SubClient + :top-classes: lief._lief.MachO.LoadCommand + :parts: 2 + +.. autoclass:: lief.MachO.SubClient + +---------- + Dyld Environment **************** diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index 6ec702a5..f56a3b29 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -3,7 +3,9 @@ Changelog 0.16.0 - Not Released Yet ------------------------- +:MachO: + * Add support for the ``LC_SUBCLIENT`` command: :class:`lief.MachO.SubClient` 0.15.1 - July 23th, 2024 ------------------------ diff --git a/include/LIEF/MachO.hpp b/include/LIEF/MachO.hpp index ad14fd99..d2262ff4 100644 --- a/include/LIEF/MachO.hpp +++ b/include/LIEF/MachO.hpp @@ -62,6 +62,7 @@ #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" #include "LIEF/MachO/SubFramework.hpp" +#include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/Symbol.hpp" #include "LIEF/MachO/SymbolCommand.hpp" #include "LIEF/MachO/ThreadCommand.hpp" diff --git a/include/LIEF/MachO/Binary.hpp b/include/LIEF/MachO/Binary.hpp index 937739ce..14b992d1 100644 --- a/include/LIEF/MachO/Binary.hpp +++ b/include/LIEF/MachO/Binary.hpp @@ -66,6 +66,7 @@ class SegmentCommand; class SegmentSplitInfo; class SourceVersion; class SubFramework; +class SubClient; class Symbol; class SymbolCommand; class ThreadCommand; @@ -174,6 +175,12 @@ class LIEF_API Binary : public LIEF::Binary { //! Iterator which outputs const RPathCommand& using it_const_rpaths = const_filter_iterator; + //! Iterator which outputs SubClient& + using it_sub_clients = filter_iterator; + + //! Iterator which outputs const SubClient& + using it_const_sub_clients = const_filter_iterator; + public: Binary(const Binary&) = delete; Binary& operator=(const Binary&) = delete; @@ -690,6 +697,12 @@ class LIEF_API Binary : public LIEF::Binary { SubFramework* sub_framework(); const SubFramework* sub_framework() const; + //! Iterator over **all** the MachO::SubClient commands. + it_sub_clients subclients(); + it_const_sub_clients subclients() const; + + bool has_subclients() const; + //! ``true`` if the binary has Dyld envrionment variables. bool has_dyld_environment() const { return dyld_environment() != nullptr; diff --git a/include/LIEF/MachO/Builder.hpp b/include/LIEF/MachO/Builder.hpp index dbb6f05b..7efe81c6 100644 --- a/include/LIEF/MachO/Builder.hpp +++ b/include/LIEF/MachO/Builder.hpp @@ -45,6 +45,7 @@ class MainCommand; class SegmentSplitInfo; class SourceVersion; class SubFramework; +class SubClient; class SymbolCommand; class ThreadCommand; class TwoLevelHints; @@ -146,6 +147,9 @@ class LIEF_API Builder { template ok_error_t build(SubFramework& sf); + template + ok_error_t build(SubClient& sf); + template ok_error_t build(DyldEnvironment& de); diff --git a/include/LIEF/MachO/SubClient.hpp b/include/LIEF/MachO/SubClient.hpp new file mode 100644 index 00000000..9b5fa9f5 --- /dev/null +++ b/include/LIEF/MachO/SubClient.hpp @@ -0,0 +1,81 @@ +/* Copyright 2017 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_MACHO_SUB_CLIENT_H +#define LIEF_MACHO_SUB_CLIENT_H +#include +#include + +#include "LIEF/visibility.h" + +#include "LIEF/MachO/LoadCommand.hpp" + +namespace LIEF { +namespace MachO { + +class BinaryParser; + +namespace details { +struct sub_client_command; +} + +/// Class that represents the SubClient command. +/// Accodring to the Mach-O `loader.h` documentation: +/// +/// > For dynamically linked shared libraries that are subframework of an umbrella +/// > framework they can allow clients other than the umbrella framework or other +/// > subframeworks in the same umbrella framework. To do this the subframework +/// > is built with "-allowable_client client_name" and an LC_SUB_CLIENT load +/// > command is created for each -allowable_client flag. The client_name is +/// > usually a framework name. It can also be a name used for bundles clients +/// > where the bundle is built with "-client_name client_name". +class LIEF_API SubClient : public LoadCommand { + friend class BinaryParser; + public: + SubClient() = default; + SubClient(const details::sub_client_command& cmd); + + SubClient& operator=(const SubClient& copy) = default; + SubClient(const SubClient& copy) = default; + + std::unique_ptr clone() const override { + return std::unique_ptr(new SubClient(*this)); + } + + //! Name of the client + const std::string& client() const { + return client_; + } + + void client(std::string u) { + client_ = std::move(u); + } + + ~SubClient() override = default; + + void accept(Visitor& visitor) const override; + + std::ostream& print(std::ostream& os) const override; + + static bool classof(const LoadCommand* cmd) { + return cmd->command() == LoadCommand::TYPE::SUB_CLIENT; + } + + private: + std::string client_; +}; + +} +} +#endif diff --git a/src/MachO/Binary.cpp b/src/MachO/Binary.cpp index e70a6249..918436f8 100644 --- a/src/MachO/Binary.cpp +++ b/src/MachO/Binary.cpp @@ -53,6 +53,7 @@ #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" #include "LIEF/MachO/SubFramework.hpp" +#include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/Symbol.hpp" #include "LIEF/MachO/SymbolCommand.hpp" #include "LIEF/MachO/ThreadCommand.hpp" @@ -2181,6 +2182,24 @@ const SegmentSplitInfo* Binary::segment_split_info() const { } +// SubClient command +// ++++++++++++++++++++ +Binary::it_sub_clients Binary::subclients() { + return {commands_, [] (const std::unique_ptr& cmd) { + return SubClient::classof(cmd.get()); + }}; +} + +Binary::it_const_sub_clients Binary::subclients() const { + return {commands_, [] (const std::unique_ptr& cmd) { + return SubClient::classof(cmd.get()); + }}; +} + +bool Binary::has_subclients() const { + return has_command(); +} + // SubFramework command // ++++++++++++++++++++ SubFramework* Binary::sub_framework() { diff --git a/src/MachO/BinaryParser.tcc b/src/MachO/BinaryParser.tcc index 5fb34f37..538b292b 100644 --- a/src/MachO/BinaryParser.tcc +++ b/src/MachO/BinaryParser.tcc @@ -53,6 +53,7 @@ #include "LIEF/MachO/SegmentCommand.hpp" #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" +#include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/SubFramework.hpp" #include "LIEF/MachO/Symbol.hpp" #include "LIEF/MachO/SymbolCommand.hpp" @@ -841,6 +842,27 @@ ok_error_t BinaryParser::parse_load_commands() { break; } + case LoadCommand::TYPE::SUB_CLIENT: + { + /* + * DO NOT FORGET TO UPDATE SubClient::classof + */ + const auto cmd = stream_->peek(loadcommands_offset); + if (!cmd) { + LIEF_ERR("Can't parse sub_client_command"); + break; + } + auto u = stream_->peek_string_at(loadcommands_offset + cmd->client); + if (!u) { + LIEF_ERR("Can't read client name string"); + break; + } + auto sf = std::make_unique(*cmd); + sf->client(*u); + load_command = std::move(sf); + break; + } + case LoadCommand::TYPE::DYLD_ENVIRONMENT: { diff --git a/src/MachO/Builder.cpp b/src/MachO/Builder.cpp index fcb89f3d..d16bc10d 100644 --- a/src/MachO/Builder.cpp +++ b/src/MachO/Builder.cpp @@ -97,6 +97,11 @@ ok_error_t Builder::build() { continue; } + if (SubClient::classof(cmd.get())) { + build(*cmd->as()); + continue; + } + if (SubFramework::classof(cmd.get())) { build(*cmd->as()); continue; diff --git a/src/MachO/Builder.tcc b/src/MachO/Builder.tcc index 450ed535..a9db4ec1 100644 --- a/src/MachO/Builder.tcc +++ b/src/MachO/Builder.tcc @@ -42,6 +42,7 @@ #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" #include "LIEF/MachO/SubFramework.hpp" +#include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/Symbol.hpp" #include "LIEF/MachO/SymbolCommand.hpp" #include "LIEF/MachO/ThreadCommand.hpp" @@ -934,6 +935,41 @@ ok_error_t Builder::build(SubFramework& sf) { return ok(); } +template +ok_error_t Builder::build(SubClient& sc) { + details::sub_client_command raw_cmd; + std::memset(&raw_cmd, 0, sizeof(details::sub_client_command)); + + const uint32_t raw_size = sizeof(details::sub_client_command) + sc.client().size() + 1; + const uint32_t size_needed = align(raw_size, sizeof(typename T::uint)); + const uint32_t padding = size_needed - raw_size; + + if (sc.original_data_.size() != size_needed || sc.size() != size_needed) { + LIEF_WARN("Not enough spaces to rebuild {}. Size required: 0x{:x} vs 0x{:x}", + sc.client(), sc.original_data_.size(), size_needed); + } + + raw_cmd.cmd = static_cast(sc.command()); + raw_cmd.cmdsize = static_cast(size_needed); + raw_cmd.client = static_cast(sizeof(details::sub_client_command)); + + sc.size_ = size_needed; + sc.original_data_.clear(); + + // Write Header + std::move(reinterpret_cast(&raw_cmd), + reinterpret_cast(&raw_cmd) + sizeof(raw_cmd), + std::back_inserter(sc.original_data_)); + + // Write String + const std::string& um = sc.client(); + std::move(std::begin(um), std::end(um), + std::back_inserter(sc.original_data_)); + sc.original_data_.push_back(0); + sc.original_data_.insert(std::end(sc.original_data_), padding, 0); + return ok(); +} + template ok_error_t Builder::build(DyldEnvironment& de) { details::dylinker_command raw_cmd; diff --git a/src/MachO/CMakeLists.txt b/src/MachO/CMakeLists.txt index 9073238d..05682812 100644 --- a/src/MachO/CMakeLists.txt +++ b/src/MachO/CMakeLists.txt @@ -47,6 +47,7 @@ target_sources(LIB_LIEF PRIVATE SegmentCommand.cpp SegmentSplitInfo.cpp SourceVersion.cpp + SubClient.cpp SubFramework.cpp Symbol.cpp SymbolCommand.cpp diff --git a/src/MachO/SubClient.cpp b/src/MachO/SubClient.cpp new file mode 100644 index 00000000..9e827731 --- /dev/null +++ b/src/MachO/SubClient.cpp @@ -0,0 +1,40 @@ +/* Copyright 2017 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LIEF/Visitor.hpp" + +#include "LIEF/MachO/SubClient.hpp" +#include "MachO/Structures.hpp" + +namespace LIEF { +namespace MachO { + +SubClient::SubClient(const details::sub_client_command& cmd) : + LoadCommand::LoadCommand{LoadCommand::TYPE(cmd.cmd), cmd.cmdsize} +{} + +void SubClient::accept(Visitor& visitor) const { + visitor.visit(*this); +} + +std::ostream& SubClient::print(std::ostream& os) const { + LoadCommand::print(os); + os << "client: " << client(); + return os; +} + + +} +} diff --git a/tests/macho/test_generic.py b/tests/macho/test_generic.py index 66f63ea9..f48510a3 100644 --- a/tests/macho/test_generic.py +++ b/tests/macho/test_generic.py @@ -296,6 +296,13 @@ def test_unknown_command(): print(hash(unknown_cmd)) print(unknown_cmd) +def test_subclients(): + macho = lief.MachO.parse(get_sample("MachO/StocksAnalytics")).at(0) + assert len(macho.subclients) == 19 + + assert macho.subclients[0].client == "NewsArticles" + assert macho.subclients[-1].client == "StocksAppKitBundle" + @pytest.mark.skipif(not has_private_samples(), reason="needs private samples") def test_arm64e(): sample = lief.MachO.parse(get_sample("private/MachO/libCoreKE_arm64e.dylib")).at(0)