Fix oat_hook for recent Android versions (#387)
We used to hook functions `OatHeader::GetKeyValueStore` and `OatHeader::GetKeyValueStoreSize` to clean the `dex2oat` trace introduced by LSPosed. However, in recent versions of Android, these two functions are no longer exported. Moreover, the `OatHeader` structure has changed, which now sets certain entries to have fixed length by padding zeros. To address these two changes, we hook `OatHeader::ComputeChecksum` as an entrypoint (fallback), and then employ the header file of `OatHeader` copied from AOSP to precisely locate its fields. Using this strategy, we modify `key_value_store` in memory, depending on whether the entry `dex2oat-cmdline` is padded.
This commit is contained in:
parent
e75b6007b4
commit
6703b45350
|
|
@ -1,12 +1,14 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(dex2oat)
|
||||
|
||||
add_executable(dex2oat dex2oat.c)
|
||||
add_library(oat_hook SHARED oat_hook.cpp)
|
||||
add_executable(dex2oat dex2oat.cpp)
|
||||
add_library(oat_hook SHARED oat_hook.cpp oat.cpp)
|
||||
|
||||
OPTION(LSPLT_BUILD_SHARED OFF)
|
||||
add_subdirectory(${EXTERNAL_ROOT}/lsplt/lsplt/src/main/jni external)
|
||||
|
||||
target_include_directories(oat_hook PUBLIC include)
|
||||
target_include_directories(dex2oat PUBLIC include)
|
||||
target_link_libraries(dex2oat log)
|
||||
target_link_libraries(oat_hook log lsplt_static)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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 <stddef.h> // for size_t
|
||||
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
||||
|
||||
#include <utility>
|
||||
|
||||
// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't.
|
||||
#ifndef TEMP_FAILURE_RETRY
|
||||
#define TEMP_FAILURE_RETRY(exp) \
|
||||
({ \
|
||||
decltype(exp) _rc; \
|
||||
do { \
|
||||
_rc = (exp); \
|
||||
} while (_rc == -1 && errno == EINTR); \
|
||||
_rc; \
|
||||
})
|
||||
#endif
|
||||
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This must be placed in the private: declarations for a class.
|
||||
//
|
||||
// For disallowing only assign or copy, delete the relevant operator or
|
||||
// constructor, for example:
|
||||
// void operator=(const TypeName&) = delete;
|
||||
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
|
||||
// semantically, one should either use disallow both or neither. Try to
|
||||
// avoid these in new code.
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName &) = delete; \
|
||||
void operator=(const TypeName &) = delete
|
||||
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName() = delete; \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
//
|
||||
// One caveat is that arraysize() doesn't accept any array of an
|
||||
// anonymous type or a type defined inside a function. In these rare
|
||||
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
|
||||
// due to a limitation in C++'s template system. The limitation might
|
||||
// eventually be removed, but it hasn't happened yet.
|
||||
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(T (&array)[N]))[N]; // NOLINT(readability/casting)
|
||||
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
|
||||
#define SIZEOF_MEMBER(t, f) sizeof(std::declval<t>().f)
|
||||
|
||||
// Changing this definition will cause you a lot of pain. A majority of
|
||||
// vendor code defines LIKELY and UNLIKELY this way, and includes
|
||||
// this header through an indirect path.
|
||||
#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
|
||||
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
|
||||
|
||||
#define WARN_UNUSED __attribute__((warn_unused_result))
|
||||
|
||||
// A deprecated function to call to create a false use of the parameter, for
|
||||
// example:
|
||||
// int foo(int x) { UNUSED(x); return 10; }
|
||||
// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED.
|
||||
template <typename... T>
|
||||
void UNUSED(const T &...) {}
|
||||
|
||||
// An attribute to place on a parameter to a function, for example:
|
||||
// int foo(int x ATTRIBUTE_UNUSED) { return 10; }
|
||||
// to avoid compiler warnings.
|
||||
#define ATTRIBUTE_UNUSED __attribute__((__unused__))
|
||||
|
||||
// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through
|
||||
// between switch labels:
|
||||
// switch (x) {
|
||||
// case 40:
|
||||
// case 41:
|
||||
// if (truth_is_out_there) {
|
||||
// ++x;
|
||||
// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in
|
||||
// // comments.
|
||||
// } else {
|
||||
// return x;
|
||||
// }
|
||||
// case 42:
|
||||
// ...
|
||||
//
|
||||
// As shown in the example above, the FALLTHROUGH_INTENDED macro should be
|
||||
// followed by a semicolon. It is designed to mimic control-flow statements
|
||||
// like 'break;', so it can be placed in most places where 'break;' can, but
|
||||
// only if there are no statements on the execution path between it and the
|
||||
// next switch label.
|
||||
//
|
||||
// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to
|
||||
// [[clang::fallthrough]] attribute, which is analysed when performing switch
|
||||
// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang
|
||||
// documentation on language extensions for details:
|
||||
// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough
|
||||
//
|
||||
// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no
|
||||
// effect on diagnostics.
|
||||
//
|
||||
// In either case this macro has no effect on runtime behavior and performance
|
||||
// of code.
|
||||
#ifndef FALLTHROUGH_INTENDED
|
||||
#define FALLTHROUGH_INTENDED [[fallthrough]] // NOLINT
|
||||
#endif
|
||||
|
||||
// Current ABI string
|
||||
#if defined(__arm__)
|
||||
#define ABI_STRING "arm"
|
||||
#elif defined(__aarch64__)
|
||||
#define ABI_STRING "arm64"
|
||||
#elif defined(__i386__)
|
||||
#define ABI_STRING "x86"
|
||||
#elif defined(__riscv)
|
||||
#define ABI_STRING "riscv64"
|
||||
#elif defined(__x86_64__)
|
||||
#define ABI_STRING "x86_64"
|
||||
#endif
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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 ART_LIBARTBASE_BASE_MACROS_H_
|
||||
#define ART_LIBARTBASE_BASE_MACROS_H_
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
||||
|
||||
// Declare a friend relationship in a class with a test. Used rather that FRIEND_TEST to avoid
|
||||
// globally importing gtest/gtest.h into the main ART header files.
|
||||
#define ART_FRIEND_TEST(test_set_name, individual_test) \
|
||||
friend class test_set_name##_##individual_test##_Test
|
||||
|
||||
// Declare a friend relationship in a class with a typed test.
|
||||
#define ART_FRIEND_TYPED_TEST(test_set_name, individual_test) \
|
||||
template <typename T> \
|
||||
ART_FRIEND_TEST(test_set_name, individual_test)
|
||||
|
||||
// Shorthand for formatting with compile time checking of the format string
|
||||
#define ART_FORMAT(str, ...) ::fmt::format(FMT_STRING(str), __VA_ARGS__)
|
||||
|
||||
// A macro to disallow new and delete operators for a class. It goes in the private: declarations.
|
||||
// NOTE: Providing placement new (and matching delete) for constructing container elements.
|
||||
#define DISALLOW_ALLOCATION() \
|
||||
public: \
|
||||
NO_RETURN ALWAYS_INLINE void operator delete(void*, size_t) { UNREACHABLE(); } \
|
||||
ALWAYS_INLINE void* operator new(size_t, void* ptr) noexcept { return ptr; } \
|
||||
ALWAYS_INLINE void operator delete(void*, void*) noexcept {} \
|
||||
\
|
||||
private: \
|
||||
void* operator new(size_t) = delete // NOLINT
|
||||
|
||||
// offsetof is not defined by the spec on types with non-standard layout,
|
||||
// however it is implemented by compilers in practice.
|
||||
// (note that reinterpret_cast is not valid constexpr)
|
||||
//
|
||||
// Alternative approach would be something like:
|
||||
// #define OFFSETOF_HELPER(t, f) \
|
||||
// (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u))
|
||||
// #define OFFSETOF_MEMBER(t, f) \
|
||||
// (__builtin_constant_p(OFFSETOF_HELPER(t,f)) ? OFFSETOF_HELPER(t,f) : OFFSETOF_HELPER(t,f))
|
||||
#define OFFSETOF_MEMBER(t, f) offsetof(t, f)
|
||||
|
||||
#define OFFSETOF_MEMBERPTR(t, f) \
|
||||
(reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - \
|
||||
static_cast<uintptr_t>(16)) // NOLINT
|
||||
|
||||
#define ALIGNED(x) __attribute__((__aligned__(x)))
|
||||
#define PACKED(x) __attribute__((__aligned__(x), __packed__))
|
||||
|
||||
// Stringify the argument.
|
||||
#define QUOTE(x) #x
|
||||
#define STRINGIFY(x) QUOTE(x)
|
||||
|
||||
// Append tokens after evaluating.
|
||||
#define APPEND_TOKENS_AFTER_EVAL_2(a, b) a##b
|
||||
#define APPEND_TOKENS_AFTER_EVAL(a, b) APPEND_TOKENS_AFTER_EVAL_2(a, b)
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define ALWAYS_INLINE
|
||||
#define FLATTEN
|
||||
#else
|
||||
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||
#define FLATTEN __attribute__((flatten))
|
||||
#endif
|
||||
|
||||
#define NO_STACK_PROTECTOR __attribute__((no_stack_protector))
|
||||
|
||||
// clang doesn't like attributes on lambda functions. It would be nice to say:
|
||||
// #define ALWAYS_INLINE_LAMBDA ALWAYS_INLINE
|
||||
#define ALWAYS_INLINE_LAMBDA
|
||||
|
||||
#define NO_INLINE __attribute__((noinline))
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#define HOT_ATTR
|
||||
#define COLD_ATTR
|
||||
#else
|
||||
#define HOT_ATTR __attribute__((hot))
|
||||
#define COLD_ATTR __attribute__((cold))
|
||||
#endif
|
||||
|
||||
#define PURE __attribute__((__pure__))
|
||||
|
||||
// Define that a position within code is unreachable, for example:
|
||||
// int foo () { LOG(FATAL) << "Don't call me"; UNREACHABLE(); }
|
||||
// without the UNREACHABLE a return statement would be necessary.
|
||||
#define UNREACHABLE __builtin_unreachable
|
||||
|
||||
// Add the C++11 noreturn attribute.
|
||||
#define NO_RETURN [[noreturn]] // NOLINT[whitespace/braces] [5]
|
||||
|
||||
// Annotalysis thread-safety analysis support. Things that are not in base.
|
||||
|
||||
#define LOCKABLE CAPABILITY("mutex")
|
||||
#define SHARED_LOCKABLE SHARED_CAPABILITY("mutex")
|
||||
|
||||
// Some of the libs (e.g. libarttest(d)) require more public symbols when built
|
||||
// in debug configuration.
|
||||
// Using symbol visibility only for release builds allows to reduce the list of
|
||||
// exported symbols and eliminates the need to check debug build configurations
|
||||
// when changing the exported symbols.
|
||||
#ifdef NDEBUG
|
||||
#define HIDDEN __attribute__((visibility("hidden")))
|
||||
#define PROTECTED __attribute__((visibility("protected")))
|
||||
#define EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define HIDDEN
|
||||
#define PROTECTED
|
||||
#define EXPORT
|
||||
#endif
|
||||
|
||||
// Protected symbols must be declared with "protected" visibility attribute when
|
||||
// building the library and "default" visibility when referred to from external
|
||||
// libraries/binaries. Otherwise, the external code will expect the symbol to be
|
||||
// defined locally and fail to link.
|
||||
#ifdef BUILDING_LIBART
|
||||
#define LIBART_PROTECTED PROTECTED
|
||||
#else
|
||||
#define LIBART_PROTECTED EXPORT
|
||||
#endif
|
||||
|
||||
// Some global variables shouldn't be visible outside libraries declaring them.
|
||||
// The attribute allows hiding them, so preventing direct access.
|
||||
#define ALWAYS_HIDDEN __attribute__((visibility("hidden")))
|
||||
|
||||
#endif // ART_LIBARTBASE_BASE_MACROS_H_
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 ART_RUNTIME_OAT_OAT_H_
|
||||
#define ART_RUNTIME_OAT_OAT_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "base_macros.h"
|
||||
#include "macros.h"
|
||||
|
||||
namespace art {
|
||||
|
||||
enum class InstructionSet;
|
||||
|
||||
class EXPORT PACKED(4) OatHeader {
|
||||
public:
|
||||
static constexpr std::array<uint8_t, 4> kOatMagic{{'o', 'a', 't', '\n'}};
|
||||
// Last oat version changed reason: Ensure oat checksum determinism across hosts and devices.
|
||||
static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '9', '\0'}};
|
||||
|
||||
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
|
||||
static constexpr const char* kDebuggableKey = "debuggable";
|
||||
static constexpr const char* kNativeDebuggableKey = "native-debuggable";
|
||||
static constexpr const char* kCompilerFilter = "compiler-filter";
|
||||
static constexpr const char* kClassPathKey = "classpath";
|
||||
static constexpr const char* kBootClassPathKey = "bootclasspath";
|
||||
static constexpr const char* kBootClassPathChecksumsKey = "bootclasspath-checksums";
|
||||
static constexpr const char* kApexVersionsKey = "apex-versions";
|
||||
static constexpr const char* kConcurrentCopying = "concurrent-copying";
|
||||
static constexpr const char* kCompilationReasonKey = "compilation-reason";
|
||||
static constexpr const char* kRequiresImage = "requires-image";
|
||||
|
||||
// Fields listed here are key value store fields that are deterministic across hosts and
|
||||
// devices, meaning they should have exactly the same value when the oat file is generated on
|
||||
// different hosts and devices for the same app / boot image and for the same device model with
|
||||
// the same compiler options. If you are adding a new field that doesn't hold this property, put
|
||||
// it in `kNonDeterministicFieldsAndLengths` and assign a length limit.
|
||||
//
|
||||
// When writing the oat header, the non-deterministic fields are padded to their length limits
|
||||
// and excluded from the oat checksum computation. This makes the oat checksum deterministic
|
||||
// across hosts and devices, which is important for Cloud Compilation, where we generate an oat
|
||||
// file on a host and use it on a device.
|
||||
static constexpr std::array<std::string_view, 9> kDeterministicFields{
|
||||
kDebuggableKey, kNativeDebuggableKey, kCompilerFilter,
|
||||
kClassPathKey, kBootClassPathKey, kBootClassPathChecksumsKey,
|
||||
kConcurrentCopying, kCompilationReasonKey, kRequiresImage,
|
||||
};
|
||||
|
||||
static constexpr std::array<std::pair<std::string_view, size_t>, 2>
|
||||
kNonDeterministicFieldsAndLengths{
|
||||
std::make_pair(kDex2OatCmdLineKey, 2048),
|
||||
std::make_pair(kApexVersionsKey, 1024),
|
||||
};
|
||||
|
||||
static constexpr const char kTrueValue[] = "true";
|
||||
static constexpr const char kFalseValue[] = "false";
|
||||
|
||||
static constexpr size_t Get_key_value_store_size_Offset() {
|
||||
return offsetof(OatHeader, key_value_store_size_);
|
||||
}
|
||||
static constexpr size_t Get_key_value_store_Offset() {
|
||||
return offsetof(OatHeader, key_value_store_);
|
||||
}
|
||||
|
||||
uint32_t GetKeyValueStoreSize() const;
|
||||
const uint8_t* GetKeyValueStore() const;
|
||||
|
||||
void SetKeyValueStoreSize(uint32_t new_size);
|
||||
|
||||
void ComputeChecksum(/*inout*/ uint32_t* checksum) const;
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 4> magic_;
|
||||
std::array<uint8_t, 4> version_;
|
||||
uint32_t oat_checksum_;
|
||||
|
||||
InstructionSet instruction_set_;
|
||||
uint32_t instruction_set_features_bitmap_;
|
||||
uint32_t dex_file_count_;
|
||||
uint32_t oat_dex_files_offset_;
|
||||
uint32_t bcp_bss_info_offset_;
|
||||
// Offset of the oat header (i.e. start of the oat data) in the ELF file.
|
||||
// It is used to additional validation of the oat header as it is not
|
||||
// page-aligned in the memory.
|
||||
uint32_t base_oat_offset_;
|
||||
uint32_t executable_offset_;
|
||||
uint32_t jni_dlsym_lookup_trampoline_offset_;
|
||||
uint32_t jni_dlsym_lookup_critical_trampoline_offset_;
|
||||
uint32_t quick_generic_jni_trampoline_offset_;
|
||||
uint32_t quick_imt_conflict_trampoline_offset_;
|
||||
uint32_t quick_resolution_trampoline_offset_;
|
||||
uint32_t quick_to_interpreter_bridge_offset_;
|
||||
uint32_t nterp_trampoline_offset_;
|
||||
|
||||
uint32_t key_value_store_size_;
|
||||
uint8_t key_value_store_[0]; // note variable width data at end
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(OatHeader);
|
||||
};
|
||||
} // namespace art
|
||||
#endif // ART_RUNTIME_OAT_OAT_H_
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#include "oat.h"
|
||||
|
||||
namespace art {
|
||||
|
||||
uint32_t OatHeader::GetKeyValueStoreSize() const {
|
||||
return *(uint32_t*)((uintptr_t)this + OatHeader::Get_key_value_store_size_Offset());
|
||||
}
|
||||
|
||||
const uint8_t* OatHeader::GetKeyValueStore() const {
|
||||
return (const uint8_t*)((uintptr_t)this + OatHeader::Get_key_value_store_Offset());
|
||||
}
|
||||
|
||||
void OatHeader::SetKeyValueStoreSize(uint32_t new_size) {
|
||||
*reinterpret_cast<uint32_t*>((uintptr_t)this + Get_key_value_store_size_Offset()) = new_size;
|
||||
}
|
||||
|
||||
} // namespace art
|
||||
|
|
@ -1,43 +1,88 @@
|
|||
#include <dlfcn.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <lsplt.hpp>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "logging.h"
|
||||
#include "oat.h"
|
||||
|
||||
const std::string_view parameter_to_remove = " --inline-max-code-units=0";
|
||||
const std::string_view param_to_remove = " --inline-max-code-units=0";
|
||||
|
||||
#define DCL_HOOK_FUNC(ret, func, ...) \
|
||||
ret (*old_##func)(__VA_ARGS__); \
|
||||
ret new_##func(__VA_ARGS__)
|
||||
|
||||
bool store_updated = false;
|
||||
bool store_resized = false;
|
||||
|
||||
void UpdateKeyValueStore(std::map<std::string, std::string>* key_value, uint8_t* store) {
|
||||
LOGD("updating KeyValueStore");
|
||||
char* data_ptr = reinterpret_cast<char*>(store);
|
||||
if (key_value != nullptr) {
|
||||
auto it = key_value->begin();
|
||||
auto end = key_value->end();
|
||||
for (; it != end; ++it) {
|
||||
strlcpy(data_ptr, it->first.c_str(), it->first.length() + 1);
|
||||
data_ptr += it->first.length() + 1;
|
||||
strlcpy(data_ptr, it->second.c_str(), it->second.length() + 1);
|
||||
data_ptr += it->second.length() + 1;
|
||||
bool ModifyStoreInPlace(uint8_t* store, uint32_t store_size) {
|
||||
if (store == nullptr || store_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Define the search space
|
||||
uint8_t* const store_begin = store;
|
||||
uint8_t* const store_end = store + store_size;
|
||||
|
||||
// 1. Search for the parameter in the memory buffer
|
||||
auto it = std::search(store_begin, store_end, param_to_remove.begin(), param_to_remove.end());
|
||||
|
||||
// Check if the parameter was found
|
||||
if (it == store_end) {
|
||||
LOGD("Parameter '%.*s' not found.", (int)param_to_remove.size(), param_to_remove.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* location_of_param = it;
|
||||
LOGD("Parameter found at offset %td.", location_of_param - store_begin);
|
||||
|
||||
// 2. Check if there is padding immediately after the string
|
||||
uint8_t* const byte_after_param = location_of_param + param_to_remove.size();
|
||||
bool has_padding = false;
|
||||
|
||||
// Boundary check: ensure the byte after the parameter is within the buffer
|
||||
if (byte_after_param + 1 < store_end) {
|
||||
if (*(byte_after_param + 1) == '\0') {
|
||||
has_padding = true;
|
||||
}
|
||||
}
|
||||
LOGD("KeyValueStore updated");
|
||||
store_updated = true;
|
||||
|
||||
// 3. Perform the conditional action
|
||||
if (has_padding) {
|
||||
// CASE A: Padding exists. Overwrite the parameter with zeros.
|
||||
LOGD("Padding found. Overwriting parameter with zeros.");
|
||||
memset(location_of_param, 0, param_to_remove.size());
|
||||
return false; // Size did not change
|
||||
} else {
|
||||
// CASE B: No padding exists (or parameter is at the very end).
|
||||
// Remove the parameter by shifting the rest of the memory forward.
|
||||
LOGD("No padding found. Removing parameter and shifting memory.");
|
||||
|
||||
// Calculate what to move
|
||||
uint8_t* source = byte_after_param;
|
||||
uint8_t* destination = location_of_param;
|
||||
size_t bytes_to_move = store_end - source;
|
||||
|
||||
// memmove is required because the source and destination buffers overlap
|
||||
if (bytes_to_move > 0) {
|
||||
memmove(destination, source, bytes_to_move);
|
||||
}
|
||||
|
||||
// 4. Update the total size of the store
|
||||
store_size -= param_to_remove.size();
|
||||
LOGD("Store size changed. New size: %u", store_size);
|
||||
|
||||
return true; // Size changed
|
||||
}
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(uint32_t, _ZNK3art9OatHeader20GetKeyValueStoreSizeEv, void* header) {
|
||||
uint32_t size = old__ZNK3art9OatHeader20GetKeyValueStoreSizeEv(header);
|
||||
if (store_updated) {
|
||||
if (store_resized) {
|
||||
LOGD("OatHeader::GetKeyValueStoreSize() called on object at %p\n", header);
|
||||
size = size - parameter_to_remove.size();
|
||||
size = size - param_to_remove.size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
|
@ -46,47 +91,32 @@ DCL_HOOK_FUNC(uint8_t*, _ZNK3art9OatHeader16GetKeyValueStoreEv, void* header) {
|
|||
LOGD("OatHeader::GetKeyValueStore() called on object at %p\n", header);
|
||||
uint8_t* key_value_store_ = old__ZNK3art9OatHeader16GetKeyValueStoreEv(header);
|
||||
uint32_t key_value_store_size_ = old__ZNK3art9OatHeader20GetKeyValueStoreSizeEv(header);
|
||||
const char* ptr = reinterpret_cast<const char*>(key_value_store_);
|
||||
const char* end = ptr + key_value_store_size_;
|
||||
std::map<std::string, std::string> new_store = {};
|
||||
|
||||
LOGD("scanning [%p-%p] for oat headers", ptr, end);
|
||||
while (ptr < end) {
|
||||
// Scan for a closing zero.
|
||||
const char* str_end = reinterpret_cast<const char*>(memchr(ptr, 0, end - ptr));
|
||||
if (str_end == nullptr) [[unlikely]] {
|
||||
LOGE("failed to find str_end");
|
||||
return key_value_store_;
|
||||
}
|
||||
std::string_view key = std::string_view(ptr, str_end - ptr);
|
||||
const char* value_start = str_end + 1;
|
||||
const char* value_end =
|
||||
reinterpret_cast<const char*>(memchr(value_start, 0, end - value_start));
|
||||
if (value_end == nullptr) [[unlikely]] {
|
||||
LOGE("failed to find value_end");
|
||||
return key_value_store_;
|
||||
}
|
||||
std::string_view value = std::string_view(value_start, value_end - value_start);
|
||||
LOGV("header %s:%s", key.data(), value.data());
|
||||
if (key == "dex2oat-cmdline") {
|
||||
value = value.substr(0, value.size() - parameter_to_remove.size());
|
||||
}
|
||||
new_store.insert(std::make_pair(std::string(key), std::string(value)));
|
||||
// Different from key. Advance over the value.
|
||||
ptr = value_end + 1;
|
||||
}
|
||||
UpdateKeyValueStore(&new_store, key_value_store_);
|
||||
LOGD("KeyValueStore via hook: [addr: %p, size: %u]", key_value_store_, key_value_store_size_);
|
||||
store_resized = ModifyStoreInPlace(key_value_store_, key_value_store_size_);
|
||||
|
||||
return key_value_store_;
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(void, _ZNK3art9OatHeader15ComputeChecksumEPj, void* header, uint32_t* checksum) {
|
||||
art::OatHeader* oat_header = reinterpret_cast<art::OatHeader*>(header);
|
||||
const uint8_t* key_value_store_ = oat_header->GetKeyValueStore();
|
||||
uint32_t key_value_store_size_ = oat_header->GetKeyValueStoreSize();
|
||||
LOGD("KeyValueStore via offset: [addr: %p, size: %u]", key_value_store_, key_value_store_size_);
|
||||
store_resized =
|
||||
ModifyStoreInPlace(const_cast<uint8_t*>(key_value_store_), key_value_store_size_);
|
||||
if (store_resized) {
|
||||
oat_header->SetKeyValueStoreSize(key_value_store_size_ - param_to_remove.size());
|
||||
}
|
||||
old__ZNK3art9OatHeader15ComputeChecksumEPj(header, checksum);
|
||||
LOGD("ComputeChecksum called: %" PRIu32, *checksum);
|
||||
}
|
||||
|
||||
#undef DCL_HOOK_FUNC
|
||||
|
||||
void register_hook(dev_t dev, ino_t inode, const char* symbol, void* new_func, void** old_func) {
|
||||
LOGD("RegisterHook: %s, %p, %p", symbol, new_func, old_func);
|
||||
if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
|
||||
LOGE("Failed to register plt_hook \"%s\"\n", symbol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +139,8 @@ __attribute__((constructor)) static void initialize() {
|
|||
|
||||
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader20GetKeyValueStoreSizeEv);
|
||||
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader16GetKeyValueStoreEv);
|
||||
if (lsplt::CommitHook()) {
|
||||
LOGD("lsplt hooks done");
|
||||
};
|
||||
if (!lsplt::CommitHook()) {
|
||||
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader15ComputeChecksumEPj);
|
||||
lsplt::CommitHook();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2efebed64ecce060530c6959b8654019aa796f52
|
||||
Subproject commit e3db0003526b1e5be3fb9323ff5ff128a59f9ffb
|
||||
Loading…
Reference in New Issue