diff --git a/api/python/lief/MachO.pyi b/api/python/lief/MachO.pyi index ca410de3..3d2c9e2e 100644 --- a/api/python/lief/MachO.pyi +++ b/api/python/lief/MachO.pyi @@ -1,6 +1,7 @@ from typing import Any, ClassVar, Iterator, Optional, Union from typing import overload +import collections.abc import io import lief # type: ignore import lief.MachO # type: ignore @@ -20,6 +21,7 @@ import lief.MachO.LoadCommand # type: ignore import lief.MachO.Relocation # type: ignore import lief.MachO.Section # type: ignore import lief.MachO.SegmentCommand # type: ignore +import lief.MachO.Stub # type: ignore import lief.MachO.Symbol # type: ignore import lief.MachO.TwoLevelHints # type: ignore import lief.objc # type: ignore @@ -344,6 +346,8 @@ class Binary(lief.Binary): @property def symbol_command(self) -> lief.MachO.SymbolCommand: ... @property + def symbol_stubs(self) -> collections.abc.Sequence[lief.MachO.Stub]: ... + @property def symbols(self) -> lief.MachO.Binary.it_symbols: ... # type: ignore @property def thread_command(self) -> lief.MachO.ThreadCommand: ... @@ -1562,6 +1566,22 @@ class SourceVersion(LoadCommand): version: list[int] def __init__(self, *args, **kwargs) -> None: ... +class Stub: + class target_info_t: + arch: lief.MachO.Header.CPU_TYPE + subtype: int + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: lief.MachO.Header.CPU_TYPE, arg1: int, /) -> None: ... + def __init__(self, target_info: lief.MachO.Stub.target_info_t, address: int, raw_stub: list[int]) -> None: ... + @property + def address(self) -> int: ... + @property + def raw(self) -> memoryview: ... + @property + def target(self) -> Union[int,lief.lief_errors]: ... + class SubClient(LoadCommand): client: str def __init__(self, *args, **kwargs) -> None: ... diff --git a/api/python/src/MachO/init.cpp b/api/python/src/MachO/init.cpp index e3abfe33..4815fcd4 100644 --- a/api/python/src/MachO/init.cpp +++ b/api/python/src/MachO/init.cpp @@ -17,54 +17,55 @@ #include "MachO/enums.hpp" #include "MachO/pyMachO.hpp" -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include #include -#include +#include +#include #include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define CREATE(X,Y) create(Y) @@ -119,6 +120,7 @@ void init_objects(nb::module_& m) { CREATE(LinkerOptHint, m); CREATE(IndirectBindingInfo, m); CREATE(UnknownCommand, m); + CREATE(Stub, m); CREATE(Builder, m); } diff --git a/api/python/src/MachO/objects/CMakeLists.txt b/api/python/src/MachO/objects/CMakeLists.txt index 8de57b23..ccceb0b4 100644 --- a/api/python/src/MachO/objects/CMakeLists.txt +++ b/api/python/src/MachO/objects/CMakeLists.txt @@ -38,6 +38,7 @@ target_sources(pyLIEF PRIVATE pySegmentCommand.cpp pySegmentSplitInfo.cpp pySourceVersion.cpp + pyStub.cpp pySubClient.cpp pySubFramework.cpp pySymbol.cpp diff --git a/api/python/src/MachO/objects/pyBinary.cpp b/api/python/src/MachO/objects/pyBinary.cpp index eabf27d5..82ae584d 100644 --- a/api/python/src/MachO/objects/pyBinary.cpp +++ b/api/python/src/MachO/objects/pyBinary.cpp @@ -19,6 +19,7 @@ #include #include #include "nanobind/extra/memoryview.hpp" +#include "nanobind/extra/random_access_iterator.hpp" #include "LIEF/MachO/Binary.hpp" #include "LIEF/MachO/BuildVersion.hpp" @@ -65,7 +66,6 @@ #include "pyIterator.hpp" namespace LIEF::MachO::py { - template<> void create(nb::module_& m) { using namespace LIEF::py; @@ -680,6 +680,21 @@ void create(nb::module_& m) { )doc"_doc ) + .def_prop_ro("symbol_stubs", + [] (const Binary& self) { + auto stubs = self.symbol_stubs(); + return nb::make_random_access_iterator(nb::type(), "stub_iterator", stubs); + }, nb::keep_alive<0, 1>(), + R"doc( + Return an iterator over the symbol stubs. + + These stubs are involved when calling an **imported** function and are + similar to the ELF's plt/got mechanism. + + There are located in sections like: ``__stubs,__auth_stubs,__symbol_stub,__picsymbolstub4`` + )doc"_doc + ) + .def_prop_ro("has_nx_heap", &Binary::has_nx_heap, R"doc( Return True if the **heap** is flagged as non-executable. False diff --git a/api/python/src/MachO/objects/pyStub.cpp b/api/python/src/MachO/objects/pyStub.cpp new file mode 100644 index 00000000..d6f7221d --- /dev/null +++ b/api/python/src/MachO/objects/pyStub.cpp @@ -0,0 +1,94 @@ +/* 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 "LIEF/MachO/Stub.hpp" + +#include "nanobind/utils.hpp" +#include +#include + +#include "MachO/pyMachO.hpp" + +#include "pyLIEF.hpp" +#include "pyErr.hpp" + +namespace LIEF::MachO::py { + +template<> +void create(nb::module_& m) { + nb::class_ object(m, "Stub", + R"doc( + This class represents a stub entry in sections like ``__stubs,__auth_stubs``. + + It wraps assembly instructions which are used to access the *got* where the + address of the symbol is resolved. + + Example: + + .. code-block:: text + + 0000000236a3c1bc: ___memcpy_chk + adrp x17, #0x241513aa8 + add x17, x17, #0x241513aa8 + ldr x16, [x17] + braa x16, x17 + )doc"_doc + ); + + nb::class_(object, "target_info_t") + .def(nb::init<>()) + .def(nb::init()) + .def_rw("arch", &Stub::target_info_t::arch) + .def_rw("subtype", &Stub::target_info_t::subtype); + + object + .def(nb::init>(), + "target_info"_a, "address"_a, "raw_stub"_a + ) + .def_prop_ro("address", &Stub::address, + "The virtual address where the stub is located"_doc + ) + .def_prop_ro("raw", + [] (const Stub& stub) { + return nb::to_memoryview(stub.raw()); + }, + "The (raw) instructions of this entry as a memory view of bytes"_doc) + + .def_prop_ro("target", + [] (Stub& self) { + return LIEF::py::error_or(&Stub::target, self); + }, + R"doc( + The address resolved by this stub. + + For instance, given this stub: + + .. code-block:: + + 0x3eec: adrp x16, #4096 + 0x3ef0: ldr x16, [x16, #24] + 0x3ef4: br x16 + + The function returns: ``0x4018``. + + .. warning:: + + This function is only available with LIEF's extended version + )doc"_doc) + + LIEF_DEFAULT_STR(Stub); +} +} diff --git a/api/python/src/nanobind/extra/random_access_iterator.hpp b/api/python/src/nanobind/extra/random_access_iterator.hpp new file mode 100644 index 00000000..64877699 --- /dev/null +++ b/api/python/src/nanobind/extra/random_access_iterator.hpp @@ -0,0 +1,103 @@ +#ifndef PY_LIEF_RANDOM_ACCESS_IT_H +#define PY_LIEF_RANDOM_ACCESS_IT_H + +#include +#include + +NAMESPACE_BEGIN(NB_NAMESPACE) +namespace detail { +template +class TypedRandomIterator : public nanobind::iterator { + public: + static constexpr auto Name = const_name("collections.abc.Sequence[") + make_caster::Name + const_name("]"); + TypedRandomIterator(nanobind::iterator&& it) : + nanobind::iterator::iterator(std::move(it)) + {} +}; + +template +struct random_iterator_state { + Iterator it; + Iterator begin; + Sentinel end; + bool first_or_done; +}; + +template +iterator make_rnd_iterator_impl(handle scope, const char *name, + Iterator &&first, Sentinel &&last, + Extra &&...extra) { + using State = random_iterator_state; + + if (!type().is_valid()) { + class_(scope, name) + .def("__iter__", [](handle h) { return h; }) + .def("__len__", [](State &s) { return std::distance(s.begin, s.end); }) + .def("__getitem__", + [] (State& s, Py_ssize_t i) -> ValueType { + const size_t size = std::distance(s.begin, s.end); + if (i < 0) { + i += static_cast(size); + } + if (i < 0 || static_cast(i) >= size) { + throw nanobind::index_error(); + } + Iterator it = s.begin + i; + return Access()(it); + }, std::forward(extra)..., Policy) + + .def("__next__", + [](State &s) -> ValueType { + if (!s.first_or_done) + ++s.it; + else + s.first_or_done = false; + + if (s.it == s.end) { + s.first_or_done = true; + throw stop_iteration(); + } + + return Access()(s.it); + }, + std::forward(extra)..., + Policy); + } + auto begin = first; + return borrow(cast(State{ std::forward(first), + std::move(begin), + std::forward(last), true })); +} +} + + +template ::result_type, + typename... Extra> +detail::TypedRandomIterator make_random_access_iterator(handle scope, const char *name, Iterator &&first, Sentinel &&last, Extra &&...extra) { + return detail::make_rnd_iterator_impl, Policy, + Iterator, Sentinel, ValueType, Extra...>( + scope, name, std::forward(first), + std::forward(last), std::forward(extra)...); +} + +template ::result_type, + typename... Extra> +detail::TypedRandomIterator make_random_access_iterator( + handle scope, const char *name, Type &value, Extra &&...extra) +{ + return make_random_access_iterator( + scope, name, std::begin(value), std::end(value), + std::forward(extra)... + ); +} + +NAMESPACE_END(NB_NAMESPACE) + +#endif diff --git a/api/rust/autocxx_ffi.rs b/api/rust/autocxx_ffi.rs index 1d15e0f4..171ac0f1 100644 --- a/api/rust/autocxx_ffi.rs +++ b/api/rust/autocxx_ffi.rs @@ -309,6 +309,8 @@ include_cpp! { // ------------------------------------------------------------------------- generate!("MachO_Binary") block_constructors!("MachO_Binary") + generate!("MachO_Binary_it_stubs") + block_constructors!("MachO_Binary_it_stubs") generate!("MachO_Binary_it_symbols") block_constructors!("MachO_Binary_it_symbols") generate!("MachO_Binary_it_relocations") @@ -345,6 +347,8 @@ include_cpp! { block_constructors!("MachO_DataCodeEntry") generate!("MachO_DataInCode") block_constructors!("MachO_DataInCode") + generate!("MachO_Stub") + block_constructors!("MachO_Stub") generate!("MachO_DataInCode_it_entries") block_constructors!("MachO_DataInCode_it_entries") diff --git a/api/rust/cargo/lief/src/macho.rs b/api/rust/cargo/lief/src/macho.rs index 38d0dac0..acd322e4 100644 --- a/api/rust/cargo/lief/src/macho.rs +++ b/api/rust/cargo/lief/src/macho.rs @@ -7,6 +7,7 @@ pub mod relocation; pub mod section; pub mod symbol; pub mod header; +pub mod stub; #[doc(inline)] pub use binary::Binary; @@ -26,4 +27,6 @@ pub use symbol::Symbol; pub use commands::Commands; #[doc(inline)] pub use header::Header; +#[doc(inline)] +pub use stub::Stub; diff --git a/api/rust/cargo/lief/src/macho/binary.rs b/api/rust/cargo/lief/src/macho/binary.rs index 5d018c5d..869945a4 100644 --- a/api/rust/cargo/lief/src/macho/binary.rs +++ b/api/rust/cargo/lief/src/macho/binary.rs @@ -31,10 +31,11 @@ use super::relocation::Relocations; use super::section::Sections; use super::symbol::Symbols; use super::binding_info::BindingInfo; +use super::stub::Stub; use lief_ffi as ffi; use crate::common::{into_optional, FromFFI}; -use crate::{generic, declare_fwd_iterator}; +use crate::{generic, declare_fwd_iterator, declare_iterator}; use crate::objc::Metadata; /// This is the main interface to read and write Mach-O binary attributes. @@ -230,6 +231,16 @@ impl Binary { BindingsInfo::new(self.ptr.bindings()) } + /// Return an iterator over the symbol stubs. + /// + /// These stubs are involved when calling an **imported** function and are + /// similar to the ELF's plt/got mechanism. + /// + /// There are located in sections like: `__stubs,__auth_stubs,__symbol_stub,__picsymbolstub4` + pub fn symbol_stubs(&self) -> Stubs { + Stubs::new(self.ptr.symbol_stubs()) + } + /// Return Objective-C metadata if present pub fn objc_metadata(&self) -> Option { into_optional(self.ptr.objc_metadata()) @@ -250,3 +261,11 @@ declare_fwd_iterator!( ffi::MachO_Binary, ffi::MachO_Binary_it_bindings_info ); + +declare_iterator!( + Stubs, + Stub<'a>, + ffi::MachO_Stub, + ffi::MachO_Binary, + ffi::MachO_Binary_it_stubs +); diff --git a/api/rust/cargo/lief/src/macho/stub.rs b/api/rust/cargo/lief/src/macho/stub.rs new file mode 100644 index 00000000..98b7ec5d --- /dev/null +++ b/api/rust/cargo/lief/src/macho/stub.rs @@ -0,0 +1,72 @@ +use lief_ffi as ffi; + +use crate::{to_slice, to_result, Error}; +use crate::common::FromFFI; + +use std::fmt; +use std::marker::PhantomData; + +/// This class represents a stub entry in sections like `__stubs,__auth_stubs`. +/// +/// It wraps assembly instructions which are used to access the *got* where the +/// address of the symbol is resolved. +/// +/// Example: +/// +/// ```text +/// 0000000236a3c1bc: ___memcpy_chk +/// adrp x17, #0x241513aa8 +/// add x17, x17, #0x241513aa8 +/// ldr x16, [x17] +/// braa x16, x17 +/// ``` +pub struct Stub<'a> { + ptr: cxx::UniquePtr, + _owner: PhantomData<&'a ffi::MachO_Binary> +} + +impl Stub<'_> { + /// The virtual address where the stub is located + pub fn address(&self) -> u64 { + self.ptr.address() + } + + /// The (raw) instructions of this entry as a slice of bytes + pub fn raw(&self) -> &[u8] { + to_slice!(self.ptr.raw()); + } + + /// + /// For instance, given this stub: + /// + /// ```text + /// 0x3eec: adrp x16, #4096 + /// 0x3ef0: ldr x16, [x16, #24] + /// 0x3ef4: br x16 + /// ``` + /// + /// The function returns: `0x4018`. + /// + ///
This function is only available with LIEF's extended version
+ pub fn target(&self) -> Result { + to_result!(ffi::MachO_Stub::target, self); + } +} + +impl fmt::Debug for Stub<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Stub") + .field("address", &self.address()) + .finish() + } +} + + +impl FromFFI for Stub<'_> { + fn from_ffi(stub: cxx::UniquePtr) -> Self { + Self { + ptr: stub, + _owner: PhantomData + } + } +} diff --git a/api/rust/cargo/lief/tests/macho_tests.rs b/api/rust/cargo/lief/tests/macho_tests.rs index d4b1fee8..b1c72856 100644 --- a/api/rust/cargo/lief/tests/macho_tests.rs +++ b/api/rust/cargo/lief/tests/macho_tests.rs @@ -50,6 +50,11 @@ fn explore_macho(_: &str, macho: &lief::macho::Binary) { format!("{:?}", binding); } + for stub in macho.symbol_stubs() { + format!("{stub:?}"); + format!("{}", stub.raw().len()); + } + for command in macho.commands() { format!("{command:?}"); match command { diff --git a/api/rust/include/LIEF/rust/Iterator.hpp b/api/rust/include/LIEF/rust/Iterator.hpp index 9c627c13..e4c197a5 100644 --- a/api/rust/include/LIEF/rust/Iterator.hpp +++ b/api/rust/include/LIEF/rust/Iterator.hpp @@ -57,6 +57,37 @@ class ForwardIterator { V end_; }; +template +class RandomRangeIterator { + public: + using lief_t = V; + std::unique_ptr next() { + if (it_ == end_) return nullptr; + return std::make_unique(*it_++); + } + + uint64_t size() const { + return std::distance(begin_, end_); + } + + protected: + RandomRangeIterator(LIEF::iterator_range range) : + begin_(std::move(range.begin())), + end_(std::move(range.end())), + it_(begin_) + {} + + RandomRangeIterator(V begin, V end) : + begin_(std::move(begin)), + end_(std::move(end)), + it_(begin_) + {} + + V begin_; + V end_; + V it_; +}; + template class ContainerIterator { public: diff --git a/api/rust/include/LIEF/rust/MachO.hpp b/api/rust/include/LIEF/rust/MachO.hpp index 3e08c6f9..7f893289 100644 --- a/api/rust/include/LIEF/rust/MachO.hpp +++ b/api/rust/include/LIEF/rust/MachO.hpp @@ -42,6 +42,7 @@ #include "LIEF/rust/MachO/SegmentCommand.hpp" #include "LIEF/rust/MachO/SegmentSplitInfo.hpp" #include "LIEF/rust/MachO/SourceVersion.hpp" +#include "LIEF/rust/MachO/Stub.hpp" #include "LIEF/rust/MachO/SubClient.hpp" #include "LIEF/rust/MachO/SubFramework.hpp" #include "LIEF/rust/MachO/Symbol.hpp" diff --git a/api/rust/include/LIEF/rust/MachO/Binary.hpp b/api/rust/include/LIEF/rust/MachO/Binary.hpp index 119b8df2..7e00c488 100644 --- a/api/rust/include/LIEF/rust/MachO/Binary.hpp +++ b/api/rust/include/LIEF/rust/MachO/Binary.hpp @@ -17,37 +17,38 @@ #include #include -#include "LIEF/rust/MachO/LoadCommand.hpp" -#include "LIEF/rust/MachO/Header.hpp" -#include "LIEF/rust/MachO/Symbol.hpp" -#include "LIEF/rust/MachO/Dylib.hpp" -#include "LIEF/rust/MachO/SegmentCommand.hpp" -#include "LIEF/rust/MachO/Relocation.hpp" -#include "LIEF/rust/MachO/DyldInfo.hpp" -#include "LIEF/rust/MachO/UUIDCommand.hpp" -#include "LIEF/rust/MachO/Main.hpp" -#include "LIEF/rust/MachO/Dylinker.hpp" -#include "LIEF/rust/MachO/SourceVersion.hpp" -#include "LIEF/rust/MachO/ThreadCommand.hpp" -#include "LIEF/rust/MachO/FunctionStarts.hpp" -#include "LIEF/rust/MachO/RPathCommand.hpp" -#include "LIEF/rust/MachO/Routine.hpp" -#include "LIEF/rust/MachO/SymbolCommand.hpp" -#include "LIEF/rust/MachO/DynamicSymbolCommand.hpp" +#include "LIEF/rust/MachO/BuildVersion.hpp" #include "LIEF/rust/MachO/CodeSignature.hpp" #include "LIEF/rust/MachO/CodeSignatureDir.hpp" #include "LIEF/rust/MachO/DataInCode.hpp" -#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" +#include "LIEF/rust/MachO/DyldEnvironment.hpp" #include "LIEF/rust/MachO/DyldExportsTrie.hpp" -#include "LIEF/rust/MachO/VersionMin.hpp" -#include "LIEF/rust/MachO/TwoLevelHints.hpp" +#include "LIEF/rust/MachO/DyldInfo.hpp" +#include "LIEF/rust/MachO/Dylib.hpp" +#include "LIEF/rust/MachO/Dylinker.hpp" +#include "LIEF/rust/MachO/DynamicSymbolCommand.hpp" +#include "LIEF/rust/MachO/EncryptionInfo.hpp" +#include "LIEF/rust/MachO/FunctionStarts.hpp" +#include "LIEF/rust/MachO/Header.hpp" #include "LIEF/rust/MachO/LinkerOptHint.hpp" +#include "LIEF/rust/MachO/LoadCommand.hpp" +#include "LIEF/rust/MachO/Main.hpp" +#include "LIEF/rust/MachO/RPathCommand.hpp" +#include "LIEF/rust/MachO/Relocation.hpp" +#include "LIEF/rust/MachO/Routine.hpp" +#include "LIEF/rust/MachO/SegmentCommand.hpp" +#include "LIEF/rust/MachO/SegmentSplitInfo.hpp" +#include "LIEF/rust/MachO/SourceVersion.hpp" +#include "LIEF/rust/MachO/Stub.hpp" +#include "LIEF/rust/MachO/SubClient.hpp" +#include "LIEF/rust/MachO/SubFramework.hpp" +#include "LIEF/rust/MachO/Symbol.hpp" +#include "LIEF/rust/MachO/SymbolCommand.hpp" +#include "LIEF/rust/MachO/ThreadCommand.hpp" +#include "LIEF/rust/MachO/TwoLevelHints.hpp" +#include "LIEF/rust/MachO/UUIDCommand.hpp" +#include "LIEF/rust/MachO/VersionMin.hpp" #include "LIEF/rust/Abstract/Binary.hpp" @@ -136,6 +137,16 @@ class MachO_Binary : public AbstractBinary { auto next() { return ForwardIterator::next(); } }; + class it_stubs : + public RandomRangeIterator + { + public: + it_stubs(const MachO_Binary::lief_t& src) + : RandomRangeIterator(src.symbol_stubs()) { } + auto next() { return RandomRangeIterator::next(); } + auto size() const { return RandomRangeIterator::size(); } + }; + MachO_Binary(const lief_t& bin) : AbstractBinary(bin) {} auto header() const { @@ -149,6 +160,7 @@ class MachO_Binary : public AbstractBinary { auto libraries() const { return std::make_unique(impl()); } auto relocations() const { return std::make_unique(impl()); } auto bindings() const { return std::make_unique(impl()); } + auto symbol_stubs() const { return std::make_unique(impl()); } auto dyld_info() const { return details::try_unique(impl().dyld_info()); diff --git a/api/rust/include/LIEF/rust/MachO/BindingInfo.hpp b/api/rust/include/LIEF/rust/MachO/BindingInfo.hpp index d28a5b54..a5453818 100644 --- a/api/rust/include/LIEF/rust/MachO/BindingInfo.hpp +++ b/api/rust/include/LIEF/rust/MachO/BindingInfo.hpp @@ -20,7 +20,7 @@ #include "LIEF/rust/MachO/Dylib.hpp" #include "LIEF/rust/MachO/Symbol.hpp" -class MachO_BindingInfo : public Mirror{ +class MachO_BindingInfo : public Mirror { public: using lief_t = LIEF::MachO::BindingInfo; using Mirror::Mirror; diff --git a/api/rust/include/LIEF/rust/MachO/Stub.hpp b/api/rust/include/LIEF/rust/MachO/Stub.hpp new file mode 100644 index 00000000..f4f5bb6c --- /dev/null +++ b/api/rust/include/LIEF/rust/MachO/Stub.hpp @@ -0,0 +1,35 @@ +/* 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/Stub.hpp" + +#include "LIEF/rust/Span.hpp" +#include "LIEF/rust/Mirror.hpp" +#include "LIEF/rust/error.hpp" + +class MachO_Stub : public Mirror { + public: + using lief_t = LIEF::MachO::Stub; + using Mirror::Mirror; + + auto address() const { return get().address(); }; + Span raw() const { return make_span(get().raw()); } + + uint64_t target(uint32_t& err) const { + return details::make_error(get().target(), err); + } + +}; diff --git a/doc/sphinx/api/cpp/macho.rst b/doc/sphinx/api/cpp/macho.rst index 4b98d8b1..4d5d0c45 100644 --- a/doc/sphinx/api/cpp/macho.rst +++ b/doc/sphinx/api/cpp/macho.rst @@ -399,6 +399,13 @@ Unknown Command ---------- +Stub +**** + +.. doxygenclass:: LIEF::MachO::Stub + :project: lief + +---------- Utilities ********* diff --git a/doc/sphinx/api/python/macho.rst b/doc/sphinx/api/python/macho.rst index 2e842b02..9c3cb328 100644 --- a/doc/sphinx/api/python/macho.rst +++ b/doc/sphinx/api/python/macho.rst @@ -521,6 +521,14 @@ UnknownCommand ---------- +Stub +**** + + +.. autoclass:: lief.MachO.Stub + +---------- + Builder ******* diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index ad806bdc..776e6b62 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -6,6 +6,11 @@ Changelog :MachO: + * Expose an iterator over the stub entries located in ``__stubs,__auth_stubs,__symbol_stub,__picsymbolstub4`` + + - :attr:`lief.MachO.Binary.symbol_stubs`, :class:`lief.MachO.Stub` + - :cpp:func:`LIEF::MachO::Binary::symbol_stubs`, :cpp:class:`LIEF::MachO::Stub` + * Add support for the ``LC_SUBCLIENT`` command: :class:`lief.MachO.SubClient` * Add support for the ``LC_ROUTINE/LC_ROUTINE64`` command: :class:`lief.MachO.Routine` * Expose an iterator for the indirect symbols in :class:`lief.MachO.DynamicSymbolCommand`: :attr:`~lief.MachO.DynamicSymbolCommand.indirect_symbols` diff --git a/include/LIEF/MachO.hpp b/include/LIEF/MachO.hpp index 905a7f06..9eb454a2 100644 --- a/include/LIEF/MachO.hpp +++ b/include/LIEF/MachO.hpp @@ -64,6 +64,7 @@ #include "LIEF/MachO/SegmentCommand.hpp" #include "LIEF/MachO/SegmentSplitInfo.hpp" #include "LIEF/MachO/SourceVersion.hpp" +#include "LIEF/MachO/Stub.hpp" #include "LIEF/MachO/SubClient.hpp" #include "LIEF/MachO/SubFramework.hpp" #include "LIEF/MachO/Symbol.hpp" diff --git a/include/LIEF/MachO/Binary.hpp b/include/LIEF/MachO/Binary.hpp index 0a8b2782..d0161c48 100644 --- a/include/LIEF/MachO/Binary.hpp +++ b/include/LIEF/MachO/Binary.hpp @@ -24,6 +24,7 @@ #include "LIEF/MachO/LoadCommand.hpp" #include "LIEF/MachO/Header.hpp" #include "LIEF/MachO/BindingInfoIterator.hpp" +#include "LIEF/MachO/Stub.hpp" #include "LIEF/visibility.h" @@ -188,6 +189,9 @@ class LIEF_API Binary : public LIEF::Binary { using it_bindings = iterator_range; + //! Iterator type for Symbol's stub + using stub_iterator = iterator_range; + public: Binary(const Binary&) = delete; Binary& operator=(const Binary&) = delete; @@ -782,6 +786,14 @@ class LIEF_API Binary : public LIEF::Binary { //! Return Objective-C metadata if present std::unique_ptr objc_metadata() const; + //! Return an iterator over the symbol stubs. + //! + //! These stubs are involved when calling an **imported** function and are + //! similar to the ELF's plt/got mechanism. + //! + //! There are located in sections like: `__stubs,__auth_stubs,__symbol_stub,__picsymbolstub4` + stub_iterator symbol_stubs() const; + template LIEF_LOCAL bool has_command() const; diff --git a/include/LIEF/MachO/Stub.hpp b/include/LIEF/MachO/Stub.hpp new file mode 100644 index 00000000..19e54a31 --- /dev/null +++ b/include/LIEF/MachO/Stub.hpp @@ -0,0 +1,168 @@ +/* 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. + */ +#ifndef LIEF_MACHO_STUB_H +#define LIEF_MACHO_STUB_H +#include "LIEF/visibility.h" +#include "LIEF/span.hpp" +#include "LIEF/iterators.hpp" +#include "LIEF/errors.hpp" + +#include "LIEF/MachO/Header.hpp" + +#include +#include +#include + + +namespace LIEF { +namespace MachO { + +class Binary; +class Section; + +/// This class represents a stub entry in sections like `__stubs,__auth_stubs`. +/// +/// It wraps assembly instructions which are used to access the *got* where the +/// address of the symbol is resolved. +/// +/// Example: +/// +/// ```text +/// 0000000236a3c1bc: ___memcpy_chk +/// adrp x17, #0x241513aa8 +/// add x17, x17, #0x241513aa8 +/// ldr x16, [x17] +/// braa x16, x17 +/// ``` +class LIEF_API Stub { + public: + struct target_info_t { + Header::CPU_TYPE arch; + uint32_t subtype = 0; + friend bool operator==(const Stub::target_info_t& lhs, + const Stub::target_info_t& rhs) + { + return lhs.arch == rhs.arch && lhs.subtype == rhs.subtype; + } + }; + class LIEF_API Iterator : + public iterator_facade_base + { + public: + Iterator() = default; + + Iterator(target_info_t target_info, std::vector sections, + size_t pos) : + target_info_(target_info), + stubs_(std::move(sections)), + pos_(pos) + { + } + + Iterator(const Iterator&) = default; + Iterator& operator=(const Iterator&) = default; + + Iterator(Iterator&&) noexcept = default; + Iterator& operator=(Iterator&&) noexcept = default; + + ~Iterator() = default; + + bool operator<(const Iterator& rhs) const { + return pos_ < rhs.pos_; + } + + std::ptrdiff_t operator-(const Iterator& R) const { + return pos_ - R.pos_; + } + + Iterator& operator+=(std::ptrdiff_t n) { + pos_ += n; + return *this; + } + + Iterator& operator-=(std::ptrdiff_t n) { + pos_ -= n; + return *this; + } + + friend LIEF_API bool operator==(const Iterator& LHS, const Iterator& RHS) { + return LHS.pos_ == RHS.pos_; + } + + friend LIEF_API bool operator!=(const Iterator& LHS, const Iterator& RHS) { + return !(LHS == RHS); + } + + Stub operator*() const; + + private: + const Section* find_section_offset(size_t pos, uint64_t& offset) const; + target_info_t target_info_; + std::vector stubs_; + size_t pos_ = 0; + }; + public: + Stub() = delete; + Stub(const Stub&) = default; + Stub& operator=(const Stub&) = default; + + Stub(Stub&&) noexcept = default; + Stub& operator=(Stub&&) noexcept = default; + ~Stub() = default; + + Stub(target_info_t target_info, uint64_t addr, std::vector raw) : + target_info_(target_info), + address_(addr), + raw_(std::move(raw)) + {} + + /// The (raw) instructions of this entry as a slice of bytes + span raw() const { + return raw_; + } + + /// The virtual address where the stub is located + uint64_t address() const { + return address_; + } + + /// The address resolved by this stub. + /// + /// For instance, given this stub: + /// + /// ```text + /// 0x3eec: adrp x16, #4096 + /// 0x3ef0: ldr x16, [x16, #24] + /// 0x3ef4: br x16 + /// ``` + /// + /// The function returns: `0x4018`. + /// + /// @warning This function is only available with LIEF's extended version + result target() const; + + friend LIEF_API std::ostream& operator<<(std::ostream& os, const Stub& stub); + + private: + target_info_t target_info_; + uint64_t address_ = 0; + mutable uint64_t target_addr_ = 0; + std::vector raw_; +}; + +} +} +#endif diff --git a/src/MachO/Binary.cpp b/src/MachO/Binary.cpp index 00196670..f4b936ed 100644 --- a/src/MachO/Binary.cpp +++ b/src/MachO/Binary.cpp @@ -2426,6 +2426,48 @@ void Binary::refresh_seg_offset() { } } +Binary::stub_iterator Binary::symbol_stubs() const { + static stub_iterator empty_iterator( + Stub::Iterator{}, + Stub::Iterator{} + ); + + std::vector stub_sections; + stub_sections.reserve(3); + + uint32_t total = 0; + + for (const Section& section : sections()) { + if (section.type() != Section::TYPE::SYMBOL_STUBS) { + continue; + } + + const uint32_t stride = section.reserved2(); + if (stride == 0) { + continue; + } + + const uint32_t count = section.content().size() / stride; + if (count == 0) { + continue; + } + + total += count; + stub_sections.push_back(§ion); + } + + if (stub_sections.empty() || total == 0) { + return empty_iterator; + } + Stub::Iterator begin( + {header_.cpu_type(), header_.cpu_subtype()}, + std::move(stub_sections), 0 + ); + Stub::Iterator end({}, {}, total); + + return make_range(std::move(begin), std::move(end)); +} + Binary::~Binary() = default; std::ostream& Binary::print(std::ostream& os) const { diff --git a/src/MachO/CMakeLists.txt b/src/MachO/CMakeLists.txt index d3b12040..93eb1691 100644 --- a/src/MachO/CMakeLists.txt +++ b/src/MachO/CMakeLists.txt @@ -50,6 +50,7 @@ target_sources(LIB_LIEF PRIVATE SegmentCommand.cpp SegmentSplitInfo.cpp SourceVersion.cpp + Stub.cpp SubClient.cpp SubFramework.cpp Symbol.cpp diff --git a/src/MachO/Stub.cpp b/src/MachO/Stub.cpp new file mode 100644 index 00000000..5f89e0d5 --- /dev/null +++ b/src/MachO/Stub.cpp @@ -0,0 +1,109 @@ +/* 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 "LIEF/MachO/Stub.hpp" +#include "LIEF/MachO/Section.hpp" +#include "LIEF/utils.hpp" + +#include "internal_utils.hpp" +#include "logging.hpp" + +namespace LIEF::MachO { + +inline uint32_t nb_stubs(const Section& section) { + return section.size() / section.reserved2(); +} + +inline uint64_t get_offset(size_t pos, const Section& section) { + return section.reserved2() * pos; +} + +const Section* + Stub::Iterator::find_section_offset(size_t pos, uint64_t& offset) const +{ + if (stubs_.size() == 1) { + offset = get_offset(pos, *stubs_.back()); + return stubs_.back(); + } + + if (stubs_.size() == 2) { + const Section* first = stubs_[0]; + const Section* second = stubs_[1]; + + const uint32_t nb_stubs_1 = nb_stubs(*first); + [[maybe_unused]] const uint32_t nb_stubs_2 = nb_stubs(*second); + if (pos < nb_stubs_1) { + offset = get_offset(pos, *first); + return first; + } + + assert(nb_stubs_1 <= pos && pos < nb_stubs_1 + nb_stubs_2); + + offset = get_offset(pos - nb_stubs_1, *second); + return second; + } + + uint64_t limit = nb_stubs(*stubs_[0]); + for (size_t idx = 0; idx < stubs_.size(); ++idx) { + if (pos < limit) { + offset = idx > 0 ? + get_offset(pos - (limit - nb_stubs(*stubs_[idx - 1])), *stubs_[idx]) : + get_offset(pos, *stubs_[idx]); + return stubs_[idx]; + } + if (idx < stubs_.size() - 1) { + limit += nb_stubs(*stubs_[idx + 1]); + } + } + return nullptr; +} + +Stub Stub::Iterator::operator*() const { + uint64_t offset = 0; + const Section* section = find_section_offset(pos_, offset); + if (section == nullptr) { + logging::fatal_error("Can't find section for stub position: {}", pos_); + } + LIEF::span content = section->content(); + const uint32_t stride = section->reserved2(); + if (offset >= content.size() || offset + stride > content.size()) { + logging::fatal_error("Stub out of range at pos: {}", pos_); + } + LIEF::span stub_raw = section->content().subspan(offset, stride); + const uint64_t addr = section->virtual_address() + offset; + return Stub( + target_info_, addr, as_vector(stub_raw) + ); +} + +#if !defined(LIEF_EXTENDED) +result Stub::target() const { + logging::needs_lief_extended(); + return make_error_code(lief_errors::require_extended_version); +} +#endif + +std::ostream& operator<<(std::ostream& os, const Stub& stub) { + if (is_extended()) { + os << fmt::format("0x{:010x}: 0x{:010x} ({} bytes)", + stub.address(), stub.target().value_or(0), + stub.raw().size()); + } else { + os << fmt::format("0x{:010x} ({} bytes)", stub.address(), stub.raw().size()); + } + return os; +} + +} diff --git a/tests/macho/test_stubs.py b/tests/macho/test_stubs.py new file mode 100644 index 00000000..c4a23100 --- /dev/null +++ b/tests/macho/test_stubs.py @@ -0,0 +1,141 @@ +import lief +import pytest +from utils import get_sample, has_private_samples +#lief.logging.set_level(lief.logging.LEVEL.DEBUG) + +def test_simple(): + macho = lief.MachO.parse(get_sample("MachO/liblog_srp.dylib")).at(0) + stubs = macho.symbol_stubs + + assert len([s for s in macho.sections if s.type == lief.MachO.Section.TYPE.SYMBOL_STUBS]) == 1 + assert len(stubs) == 25 + + assert stubs[0].address == 0x236a3c1bc + assert stubs[1].address == 0x236a3c1cc + assert stubs[24].address == 0x236A3C33C + assert str(stubs[2]) + +def test_IOKit(): + macho = lief.MachO.parse(get_sample("MachO/IOKit")).at(0) + assert macho.header.cpu_subtype == 6 # CPU_SUBTYPE_ARM_V6 + assert macho.header.cpu_type == lief.MachO.Header.CPU_TYPE.ARM + stubs = macho.symbol_stubs + + sections = [s.name for s in macho.sections if s.type == lief.MachO.Section.TYPE.SYMBOL_STUBS] + assert len(sections) == 2 + + assert sections[0] == "__picsymbolstub1" + assert sections[1] == "__picsymbolstub4" + + assert len(stubs) == 334 + + assert stubs[0].address == 0x30a204dc + assert stubs[333].address == 0x30a219ac + +def test_arm32(): + macho = lief.MachO.parse(get_sample("MachO/ios1-expr.bin")).at(0) + assert macho.header.cpu_type == lief.MachO.Header.CPU_TYPE.ARM + assert macho.header.cpu_subtype == 6 # CPU_SUBTYPE_ARM_V6 + stubs = macho.symbol_stubs + + sections = [s.name for s in macho.sections if s.type == lief.MachO.Section.TYPE.SYMBOL_STUBS] + assert len(sections) == 4 + + assert sections[0] == "__picsymbolstub1" + assert sections[1] == "__symbol_stub1" + assert sections[2] == "__picsymbolstub4" + assert sections[3] == "__symbol_stub4" + + assert len(stubs) == 23 + + assert stubs[0].address == 0x2ee0 + assert stubs[1].address == 0x2ef0 + assert stubs[2].address == 0x2f00 + assert stubs[3].address == 0x2f10 + assert stubs[4].address == 0x2f1c + assert stubs[5].address == 0x2f28 + assert stubs[6].address == 0x2f34 + assert stubs[7].address == 0x2f40 + assert stubs[8].address == 0x2f4c + assert stubs[9].address == 0x2f58 + assert stubs[10].address == 0x2f64 + assert stubs[11].address == 0x2f70 + assert stubs[12].address == 0x2f7c + assert stubs[13].address == 0x2f88 + assert stubs[14].address == 0x2f94 + assert stubs[15].address == 0x2fa0 + assert stubs[16].address == 0x2fac + assert stubs[17].address == 0x2fb8 + assert stubs[18].address == 0x2fc4 + assert stubs[19].address == 0x2fd0 + assert stubs[20].address == 0x2fdc + assert stubs[21].address == 0x2fe8 + assert stubs[22].address == 0x2ff4 + +@pytest.mark.skipif(not has_private_samples(), reason="needs private samples") +def test_empty_section(): + macho = lief.MachO.parse(get_sample("private/DWARF/libLIEF.dylib")).at(0) + + stubs = macho.symbol_stubs + + sections = [s for s in macho.sections if s.type == lief.MachO.Section.TYPE.SYMBOL_STUBS] + assert len(sections) == 1 + + assert sections[0].name == "__stubs" + assert sections[0].size > 0 + assert len(sections[0].content) == 0 + + assert len(stubs) == 0 + assert len(list(stubs)) == 0 + +def test_stub_resolution(): + raw_stub = [ + # Address: 0x3c47b08 + 0x10, 0x79, 0x00, 0xF0, # ADRP X16, #0x4B6A000 + 0x10, 0x9E, 0x43, 0xF9, # LDR X16, [X16,#0x738] + 0x00, 0x02, 0x1F, 0xD6, # BR X16 + ] + target = lief.MachO.Stub.target_info_t(lief.MachO.Header.CPU_TYPE.ARM64, 0) + stub = lief.MachO.Stub(target, 0x3c47b08, raw_stub) + assert stub.target == 0x4b6a738 if lief.__extended__ else lief.lief_errors.require_extended_version + + raw_stub = [ + # Address: 0x1804e4284 + 0x50, 0x2B, 0x23, 0x90, # ADRP X16, #0x1C6A4C000 + 0x10, 0x22, 0x13, 0x91, # ADD X16, X16, #0x4C8 + 0x00, 0x02, 0x1F, 0xD6, # BR X16 + ] + target = lief.MachO.Stub.target_info_t(lief.MachO.Header.CPU_TYPE.ARM64, 0) + stub = lief.MachO.Stub(target, 0x1804e4284, raw_stub) + assert stub.target == 0x1c6a4c4c8 if lief.__extended__ else lief.lief_errors.require_extended_version + + raw_stub = [ + # Address: 0x2018310 + 0x91, 0x08, 0x00, 0x90, # ADRP x17, #1114112 + 0x31, 0x02, 0x00, 0x91, # ADD X17, X17, #0 + 0x30, 0x02, 0x40, 0xf9, # LDR X16, [X17] + 0x11, 0x0a, 0x1f, 0xd7, # BRAA x16, x17 + ] + target = lief.MachO.Stub.target_info_t(lief.MachO.Header.CPU_TYPE.ARM64, 2) + stub = lief.MachO.Stub(target, 0x2018310, raw_stub) + assert stub.target == 0x2128000 if lief.__extended__ else lief.lief_errors.require_extended_version + + raw_stub = [ + # Address: 0x100175f2c + 0x1f, 0x20, 0x03, 0xd5, # NOP + 0xd0, 0x13, 0x3b, 0x58, # LDR X16, #483960 + 0x00, 0x02, 0x1F, 0xD6, # BR X16 + ] + target = lief.MachO.Stub.target_info_t(lief.MachO.Header.CPU_TYPE.ARM64, 2) + stub = lief.MachO.Stub(target, 0x100175f2c, raw_stub) + assert stub.target == 0x1001ec1a8 if lief.__extended__ else lief.lief_errors.require_extended_version + + raw_stub = [ + # Address: 0x100003b14 + 0xff, 0x25, 0xe6, 0x44, 0x00, 0x00, # jmp qword ptr [rip + 17638] + ] + target = lief.MachO.Stub.target_info_t(lief.MachO.Header.CPU_TYPE.X86_64, 0) + stub = lief.MachO.Stub(target, 0x100003b14, raw_stub) + assert stub.target == 0x100008000 if lief.__extended__ else lief.lief_errors.require_extended_version + +