├── helper ├── hook.h ├── trampoline.S └── main.c ├── include ├── Hook.h ├── Config.h ├── Decoder.h ├── Logger.h ├── SharedObject.h ├── ELF.h └── Process.h ├── Android.mk ├── Logger.cpp ├── LICENSE ├── main.cpp ├── Hook.cpp ├── Decoder.cpp ├── README.md ├── Config.cpp ├── ELF.cpp ├── SharedObject.cpp └── Process.cpp /helper/hook.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_LIB_HOOK_H 2 | #define ARMHOOK_LIB_HOOK_H 3 | 4 | #include 5 | 6 | #define SIZE_SAVED 4*10 7 | 8 | struct hook_data { 9 | uint32_t cpsr; 10 | uint32_t r0, r1, r2, r3; 11 | uint32_t *sp; 12 | uint32_t skip_lr; 13 | }; 14 | 15 | typedef int8_t (*hook_handler)(struct hook_data*); 16 | 17 | struct __attribute__((packed)) saved_prolog { 18 | uint8_t prolog[SIZE_SAVED]; /* saved prolog bytes */ 19 | uint32_t cont; /* address to continue hooked function */ 20 | }; 21 | 22 | struct __attribute__((packed)) hook_mapping { 23 | uint32_t lr; 24 | hook_handler handler; 25 | struct saved_prolog *prolog; 26 | }; 27 | 28 | #endif /* ARMHOOK_LIB_HOOK_H */ 29 | -------------------------------------------------------------------------------- /include/Hook.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_HOOK_H_ 2 | #define ARMHOOK_HOOK_H_ 3 | 4 | #include 5 | 6 | namespace armhook { 7 | 8 | class Process; 9 | 10 | class Hook 11 | { 12 | public: 13 | Hook(uint32_t abs, const char *handler, const char *lib); 14 | Hook(uint32_t relative, const char *base, const char *handler, 15 | const char *lib); 16 | 17 | bool GetDetour(uint8_t *out, uint8_t *size); 18 | bool GetNops(uint8_t *out, int8_t size); 19 | 20 | const char *handler() const { return handler_; } 21 | const char *library() const { return library_; } 22 | const char *base() const { return base_; } 23 | 24 | uint32_t location() const { return location_; } 25 | 26 | bool relative() const { return relative_; } 27 | 28 | private: 29 | bool relative_; 30 | 31 | const char *handler_; 32 | const char *library_; 33 | const char *base_; 34 | 35 | uint32_t location_; 36 | }; 37 | 38 | } /* namespace armhook */ 39 | 40 | #endif /* ARMHOOK_HOOK_H_ */ 41 | -------------------------------------------------------------------------------- /include/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_CONFIG_H_ 2 | #define ARMHOOK_CONFIG_H_ 3 | 4 | #include 5 | 6 | #include "jansson.h" 7 | 8 | namespace armhook { 9 | 10 | class Hook; 11 | 12 | class Config 13 | { 14 | public: 15 | static Config* Instance(); 16 | 17 | bool Parse(const char *file); 18 | 19 | const char *helper() const { return helper_; } 20 | const char *libc() const { return libc_; } 21 | 22 | const std::vector& hooks() const { return hooks_; } 23 | 24 | private: 25 | static Config *kInstance_; 26 | 27 | Config(); 28 | 29 | const char *GetJSONString(json_t *obj, const char *name); 30 | 31 | json_t *GetJSONObject(json_t *obj, const char *name); 32 | json_t *GetJSONArray(json_t *obj, const char *name); 33 | 34 | json_int_t GetJSONInteger(json_t *obj, const char *name); 35 | 36 | json_t *root_; 37 | json_error_t last_error_; 38 | 39 | const char *helper_; 40 | const char *libc_; 41 | 42 | std::vector hooks_; 43 | }; 44 | 45 | } /* namespace armhook */ 46 | 47 | #endif /* ARMHOOK_CONFIG_H_ */ 48 | -------------------------------------------------------------------------------- /include/Decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_DECODER_H_ 2 | #define ARMHOOK_DECODER_H_ 3 | 4 | #include 5 | 6 | namespace armhook { 7 | 8 | class Decoder 9 | { 10 | public: 11 | typedef enum { 12 | MODE_ARM, 13 | MODE_THUMB 14 | } Mode; 15 | 16 | typedef uint32_t ArmCode; 17 | typedef uint16_t Thumb16Code; 18 | 19 | typedef struct __attribute__((packed)) { 20 | uint16_t code1; 21 | uint16_t code2; 22 | } Thumb32Code; 23 | 24 | 25 | typedef union { 26 | Thumb16Code thumb16; 27 | Thumb32Code thumb32; 28 | ArmCode arm; 29 | } Instruction; 30 | 31 | Decoder(); 32 | 33 | bool BytesToSave(void *op, bool arm, uint8_t required, uint8_t *total); 34 | 35 | private: 36 | bool ProcessInstruction(void **iter, uint8_t *size); 37 | 38 | bool CheckInstruction(const ArmCode &code); 39 | bool CheckInstruction(const Thumb16Code &code); 40 | bool CheckInstruction(const Thumb32Code &code); 41 | 42 | Mode mode_; /* instruction mode, 0 = arm, 1 = thumb */ 43 | }; 44 | 45 | } /* namespace armhook */ 46 | 47 | #endif /* ARMHOOK_DECODER_H_ */ 48 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_SRC_FILES := \ 5 | ELF.cpp \ 6 | Config.cpp \ 7 | SharedObject.cpp \ 8 | Process.cpp \ 9 | Logger.cpp \ 10 | Decoder.cpp \ 11 | Hook.cpp \ 12 | main.cpp 13 | 14 | LOCAL_C_INCLUDES += \ 15 | $(LOCAL_PATH)/include \ 16 | $(ARMHOOK_ROOT_PATH)/deps/libjansson/src \ 17 | $(ARMHOOK_ROOT_PATH)/deps/libjansson/android \ 18 | bionic \ 19 | bionic/libstdc++/include \ 20 | external/stlport/stlport 21 | 22 | LOCAL_MODULE_TAGS := optional 23 | LOCAL_SHARED_LIBRARIES := libstlport libjansson 24 | LOCAL_CFLAGS += -std=c++11 -Wall -Wextra 25 | 26 | LOCAL_MODULE:= armhook 27 | include $(BUILD_EXECUTABLE) 28 | 29 | include $(CLEAR_VARS) 30 | LOCAL_ARM_MODE := arm 31 | 32 | LOCAL_SRC_FILES := \ 33 | helper/main.c \ 34 | helper/trampoline.S 35 | 36 | LOCAL_C_INCLUDES += \ 37 | $(LOCAL_PATH)/helper 38 | 39 | LOCAL_MODULE_TAGS := optional 40 | LOCAL_SHARED_LIBRARIES := 41 | LOCAL_CFLAGS += -Wall -Wextra 42 | LOCAL_LDFLAGS := -Wl,--no-warn-shared-textrel 43 | 44 | LOCAL_MODULE := libarmhook 45 | include $(BUILD_SHARED_LIBRARY) 46 | -------------------------------------------------------------------------------- /include/Logger.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_LOGGER_H_ 2 | #define ARMHOOK_LOGGER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define LOG_MSG(lvl, ...) \ 8 | Logger::Instance()->log(lvl, __FILE__, __LINE__, __VA_ARGS__); 9 | 10 | #define LOG_ERROR(...) \ 11 | LOG_MSG(Logger::kMsgError,__VA_ARGS__) 12 | 13 | #define LOG_WARN(...) \ 14 | LOG_MSG(Logger::kMsgWarn, __VA_ARGS__) 15 | 16 | #define LOG_INFO(...) \ 17 | LOG_MSG(Logger::kMsgInfo, __VA_ARGS__) 18 | 19 | #define LOG_DEBUG(...) \ 20 | LOG_MSG(Logger::kMsgDebug, __VA_ARGS__) 21 | 22 | namespace armhook { 23 | 24 | class Logger 25 | { 26 | public: 27 | typedef enum { 28 | kMsgNone = 0, 29 | kMsgError, 30 | kMsgWarn, 31 | kMsgInfo, 32 | kMsgDebug 33 | } LogLevel; 34 | 35 | static Logger* Instance(LogLevel lvl = kMsgNone, int fd = -1); 36 | 37 | void log(LogLevel lvl, const char *file, uint32_t line, 38 | const char *fmt, ...); 39 | 40 | private: 41 | static Logger *instance_; 42 | 43 | Logger(LogLevel lvl, int fd); 44 | 45 | int log_fd_; 46 | LogLevel log_level_; 47 | }; 48 | 49 | } /* armhook */ 50 | 51 | #endif /* ARMHOOK_LOGGER_H_ */ 52 | -------------------------------------------------------------------------------- /Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | 3 | #include 4 | 5 | #ifdef __ANDROID__ 6 | #define dprintf fdprintf 7 | #define vdprintf vfdprintf 8 | #endif 9 | 10 | using namespace armhook; 11 | 12 | Logger *Logger::instance_ = NULL; 13 | 14 | Logger *Logger::Instance(LogLevel lvl /* = kMsgNone */, int fd /* = -1 */) 15 | { 16 | if (!instance_) 17 | instance_ = new Logger(lvl, fd); 18 | 19 | return instance_; 20 | } 21 | 22 | Logger::Logger(LogLevel lvl, int fd) 23 | : log_fd_(fd) 24 | , log_level_(lvl) 25 | { 26 | } 27 | 28 | void Logger::log(LogLevel lvl, const char *file, uint32_t line, 29 | const char *fmt, ...) 30 | { 31 | if (log_level_ < lvl || log_fd_ < 0) 32 | return; 33 | 34 | va_list args; 35 | va_start(args, fmt); 36 | 37 | char *msg = NULL; 38 | switch (lvl) { 39 | case kMsgNone: return; 40 | case kMsgError: msg = "ERROR"; break; 41 | case kMsgWarn: msg = "WARN"; break; 42 | case kMsgInfo: msg = "INFO"; break; 43 | case kMsgDebug: msg = "DEBUG"; break; 44 | } 45 | 46 | dprintf(log_fd_, "%s [%s:%d] ", msg, file, line); 47 | vdprintf(log_fd_, fmt, args); 48 | dprintf(log_fd_, "\n"); 49 | 50 | va_end(args); 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Julian Hector 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Process.h" 9 | #include "Logger.h" 10 | #include "Config.h" 11 | 12 | using namespace armhook; 13 | 14 | int main(int32_t argc, const char *argv[]) 15 | { 16 | Logger::Instance(Logger::kMsgDebug, 1); 17 | 18 | if (argc < 2) { 19 | printf("Arrrrgs...\n"); 20 | return 0; 21 | } 22 | 23 | pid_t pid = atoi(argv[2]); 24 | 25 | Config *conf = Config::Instance(); 26 | if (!conf->Parse(argv[1])) 27 | return 0; 28 | 29 | Process *proc = new Process(pid); 30 | if (!proc) 31 | return 0; 32 | 33 | if (!proc->Attach()) { 34 | LOG_ERROR("attach failed"); 35 | return 0; 36 | } 37 | 38 | LOG_INFO("successfully attached to pid: %d", pid); 39 | 40 | if (!proc->Init(conf->libc())) { 41 | LOG_ERROR("failed to initialize process, with libc: %s", 42 | conf->libc()); 43 | goto exit_detach; 44 | } 45 | 46 | if (!proc->PrepareHooking()) { 47 | LOG_ERROR("failed to prepare for hooking"); 48 | goto exit_detach; 49 | } 50 | 51 | proc->InsertHooks(conf->hooks()); 52 | 53 | exit_detach: 54 | proc->Detach(); 55 | delete proc; 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /Hook.cpp: -------------------------------------------------------------------------------- 1 | #include "Hook.h" 2 | 3 | #include 4 | 5 | #include "Process.h" 6 | #include "Logger.h" 7 | 8 | namespace armhook { 9 | 10 | /* detour opcodes for arm and thumb mode */ 11 | uint8_t kArmDetour[] = { 12 | 0x1f, 0x40, 0x2d, 0xe9, /* push {r0, r1, r2, r3, r4, lr} */ 13 | 0x02, 0x01, 0xa0, 0xe3, /* mov r0, 0x80000000 */ 14 | 0x30, 0xff, 0x2f, 0xe1, /* blx r0 */ 15 | }; 16 | 17 | uint8_t kThumbDetour[] = { 18 | 0x1f, 0xb5, /* push {r0, r1, r2, r3, r4, lr} */ 19 | 0x08, 0x20, /* mov r0, 0x8 */ 20 | 0x00, 0x07, /* lsl r0, r0, 28 */ 21 | 0x80, 0x47, /* blx r0 */ 22 | }; 23 | 24 | uint8_t kThumbNops[] = { 25 | 0x00, 0x1c, /* mov r0, r0 */ 26 | 0x00, 0x1c, /* mov r0, r0 */ 27 | 0x00, 0x1c, /* mov r0, r0 */ 28 | 0x00, 0x1c, /* mov r0, r0 */ 29 | }; 30 | 31 | 32 | Hook::Hook(uint32_t abs, const char *handler, const char *lib) 33 | : relative_(false) 34 | , handler_(handler) 35 | , library_(lib) 36 | , base_(NULL) 37 | , location_(abs) 38 | { 39 | } 40 | 41 | Hook::Hook(uint32_t relative, const char *base, const char *handler, 42 | const char *lib) 43 | : relative_(true) 44 | , handler_(handler) 45 | , library_(lib) 46 | , base_(base) 47 | , location_(relative) 48 | { 49 | } 50 | 51 | bool Hook::GetDetour(uint8_t *out, uint8_t *size) 52 | { 53 | if (*size < sizeof(kArmDetour) || *size < sizeof(kThumbDetour)) 54 | return false; 55 | 56 | switch (location_ & 1) { 57 | case 1: 58 | memcpy((void*)out, kThumbDetour, sizeof(kThumbDetour)); 59 | *size = sizeof(kThumbDetour); 60 | break; 61 | 62 | case 0: 63 | memcpy((void*)out, kArmDetour, sizeof(kArmDetour)); 64 | *size = sizeof(kArmDetour); 65 | break; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | bool Hook::GetNops(uint8_t *out, int8_t size) 72 | { 73 | switch (location_ & 1) { 74 | case 1: 75 | if (size > sizeof(kThumbNops)) 76 | size = sizeof(kThumbNops); 77 | 78 | memcpy((void*)out, kThumbNops, size); 79 | break; 80 | case 0: 81 | LOG_WARN("ARM nop instruction currently not present"); 82 | return false; 83 | break; 84 | } 85 | 86 | return true; 87 | } 88 | 89 | } /* namespace armhook */ 90 | -------------------------------------------------------------------------------- /include/SharedObject.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_SHAREDOBJECT_H_ 2 | #define ARMHOOK_SHAREDOBJECT_H_ 3 | 4 | #include 5 | 6 | #include "ELF.h" 7 | 8 | #define RESOLVE_SYM(obj, a, b, c) \ 9 | if (!obj->resolve(a, b, c)) { \ 10 | LOG_ERROR("couldn't resolve symbol %s in library: %s", \ 11 | b, obj->name().c_str()); \ 12 | return false; \ 13 | } 14 | 15 | namespace armhook { 16 | 17 | class Process; 18 | 19 | /* loadcmd struct like in elf/dl-load.h of glibc */ 20 | typedef struct { 21 | Elf(Addr) mapstart, mapend, dataend, allocend; 22 | Elf(Off) mapoff; 23 | int32_t prot; 24 | } LoadCommand; 25 | 26 | /* mapped segment in memory (/proc//maps) */ 27 | typedef struct { 28 | Elf(Addr) start; 29 | Elf(Addr) end; 30 | int32_t prot; 31 | } MemorySegment; 32 | 33 | using namespace std; 34 | 35 | class SharedObject : public ELF 36 | { 37 | public: 38 | SharedObject(std::string full_path); 39 | ~SharedObject(); 40 | 41 | bool Injectable(); 42 | bool Inject(Process *proc); 43 | 44 | bool set_base(Elf(Addr) base); 45 | bool add_segment(Elf(Addr) start, Elf(Addr) end, char *prot); 46 | MemorySegment* get_segment(Elf(Addr) addr); 47 | 48 | bool resolve(const char *symbol, Elf(Addr) &out, bool abs = true); 49 | 50 | Elf(Addr) load_start() const { return load_start_; } 51 | 52 | LoadCommand *loadcmds_; /* mmap information for each PT_LOAD */ 53 | uint16_t ncmds_; /* total PT_LOAD entries */ 54 | 55 | uint32_t load_size_; /* total memory space to reserve for object */ 56 | 57 | private: 58 | bool PrepareInjection(); 59 | 60 | bool ReserveMemory(Process *proc); 61 | 62 | bool MapSegments(Process *proc); 63 | 64 | bool LinkObject(Process *proc); 65 | 66 | bool FixRelocations(Process *proc, Elf(Rel) *rel, uint32_t count); 67 | 68 | bool ProtectRelocations(Process *proc); 69 | 70 | bool RemapSegments(Process *proc); 71 | bool RemapSegments(Process *proc, bool add_write); 72 | 73 | Elf(Addr) load_start_; /* base address in memory */ 74 | Elf(Addr) load_bias_; /* first segment bias to align to page */ 75 | 76 | std::vector segments_; 77 | }; 78 | 79 | } /* namespace armhook */ 80 | 81 | #endif /* ARMHOOK_SHAREDOBJECT_H_ */ 82 | -------------------------------------------------------------------------------- /Decoder.cpp: -------------------------------------------------------------------------------- 1 | #include "Decoder.h" 2 | 3 | #include "Logger.h" 4 | 5 | namespace armhook { 6 | 7 | Decoder::Decoder() 8 | : mode_(MODE_ARM) 9 | {} 10 | 11 | bool Decoder::BytesToSave(void *op, bool arm, uint8_t required, uint8_t *total) 12 | { 13 | void *iter = op; 14 | 15 | mode_ = arm ? MODE_ARM : MODE_THUMB; 16 | *total = 0; 17 | 18 | LOG_DEBUG("Detour size: %d, mode: %d", required, mode_); 19 | 20 | while (*total < required) { 21 | uint8_t size = 0; 22 | if (!ProcessInstruction(&iter, &size)) 23 | return false; 24 | 25 | *total += size; 26 | } 27 | 28 | return true; 29 | } 30 | 31 | bool Decoder::ProcessInstruction(void **iter, uint8_t *size) 32 | { 33 | Instruction inst; 34 | inst.arm= *((uint32_t*)(*iter)); 35 | 36 | switch (mode_) { 37 | case MODE_ARM: 38 | *((uint8_t**)iter) += sizeof(inst.arm); 39 | *size = sizeof(inst.arm); 40 | 41 | LOG_DEBUG("instruction [%d]: %08x", *size, inst.arm); 42 | 43 | CheckInstruction(inst.arm); 44 | break; 45 | 46 | case MODE_THUMB: 47 | if ((((inst.thumb16 & 0xe000) >> 13) == 0x7) && 48 | (((inst.thumb16 & 0x1800) >> 11) != 0x00)) { 49 | *((uint8_t**)iter) += sizeof(inst.thumb32); 50 | *size = sizeof(inst.thumb32); 51 | 52 | LOG_DEBUG("instruction [%d]: %04x %04x", *size, 53 | inst.thumb32.code1, inst.thumb32.code2); 54 | 55 | CheckInstruction(inst.thumb32); 56 | } else { 57 | *((uint8_t**)iter) += sizeof(inst.thumb16); 58 | *size = sizeof(inst.thumb16); 59 | 60 | LOG_DEBUG("instruction [%d]: %04x", *size, 61 | inst.thumb16); 62 | 63 | CheckInstruction(inst.thumb16); 64 | } 65 | break; 66 | } 67 | 68 | /* 69 | * TODO: a couple of instructions might make some trouble, especially 70 | * relative branches, or PC relative memory access, uncertain to what 71 | * extend those instructions can be 'fixed'. This might be a problem why 72 | * the target process might crash 73 | */ 74 | 75 | return true; 76 | } 77 | 78 | bool Decoder::CheckInstruction(const ArmCode &code) 79 | { 80 | return true; 81 | } 82 | 83 | bool Decoder::CheckInstruction(const Thumb16Code &code) 84 | { 85 | return true; 86 | } 87 | 88 | bool Decoder::CheckInstruction(const Thumb32Code &code) 89 | { 90 | return true; 91 | } 92 | 93 | } /* namespace armhook */ 94 | -------------------------------------------------------------------------------- /include/ELF.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_ELF_H_ 2 | #define ARMHOOK_ELF_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #if __x86_64__ 12 | #define Elf(x) Elf64_##x 13 | #else 14 | #define Elf(x) Elf32_##x 15 | #endif 16 | 17 | #define PAGE_OFFSET(P) ((uint32_t)(P) & (PAGE_SIZE-1)) 18 | #define PAGE_START(P) ((uint32_t)(P) & ~(PAGE_SIZE-1)) 19 | #define PAGE_END(P) (((uint32_t)(P) + PAGE_SIZE - 1) & ~(PAGE_SIZE-1)) 20 | 21 | namespace armhook { 22 | 23 | class ELF 24 | { 25 | public: 26 | ELF(std::string full_path); 27 | ~ELF(); 28 | 29 | bool init(); 30 | 31 | bool resolve(const char *symbol, Elf(Addr) &out); 32 | 33 | const std::string& full_path() const { return full_path_; } 34 | const std::string& path() const { return path_; } 35 | const std::string& name() const { return name_; } 36 | const std::vector& needed() const { return needed_; } 37 | 38 | bool textrel() const { return textrel_; } 39 | 40 | private: 41 | bool read_ehdr(std::ifstream &is); 42 | bool read_phdr(std::ifstream &is); 43 | 44 | bool iterate_phdr(std::ifstream &is); 45 | 46 | bool read_dyn(std::ifstream &is, Elf(Phdr) *ph); 47 | 48 | bool iterate_dyn(std::ifstream &is); 49 | 50 | bool read_reloc(std::ifstream &is, Elf(Rel) **rel, Elf(Addr) pos, 51 | Elf32_Word count); 52 | 53 | bool read_table(std::ifstream &is, void **out, Elf(Addr) pos, 54 | Elf32_Word size); 55 | 56 | void cleanup(); 57 | 58 | std::string full_path_; 59 | std::string path_; 60 | std::string name_; 61 | 62 | std::vector needed_; 63 | 64 | /* parts of the ELF file */ 65 | Elf(Ehdr) *ehdr_; /* ELF header */ 66 | Elf(Phdr) *phdr_; /* Program header array */ 67 | 68 | Elf(Sym) *symtab_; /* DT_SYMTAB[] */ 69 | Elf(Addr) symsz_; 70 | 71 | char *strtab_; /* DT_STRTAB[] */ 72 | Elf32_Word strsz_; 73 | 74 | Elf(Dyn) *dyn_; /* PT_DYNAMIC[] */ 75 | 76 | Elf(Rel) *rel_; /* DT_REL[] */ 77 | Elf32_Word num_rel_; /* num entries */ 78 | 79 | Elf(Rel) *plt_rel_; /* DT_JMPREL[] */ 80 | Elf32_Word num_plt_rel_; /* num entries */ 81 | 82 | bool textrel_; /* DT_TEXTREL present */ 83 | 84 | friend class SharedObject; 85 | }; 86 | 87 | } /* namespace armhook */ 88 | 89 | #endif /* ARMHOOK_ELF_H_ */ 90 | -------------------------------------------------------------------------------- /include/Process.h: -------------------------------------------------------------------------------- 1 | #ifndef ARMHOOK_PROCESS_H_ 2 | #define ARMHOOK_RPOCESS_H_ 3 | 4 | #include "ELF.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define OFFSETOF(type, field) ((unsigned long) &(((type *)0)->field)) 13 | #define SAFE_SP_OFFSET 0x100 14 | 15 | #define PFUNC_DEFAULT_DECL(n, ...) \ 16 | bool p_##n(__VA_ARGS__); 17 | 18 | #define PCALL(obj, func, out, ...) \ 19 | if (obj->p_##func(out, __VA_ARGS__) < 0) { \ 20 | return false; \ 21 | } 22 | 23 | namespace armhook { 24 | 25 | class SharedObject; 26 | class Hook; 27 | 28 | /* 29 | * Note: Entries 0-15 match r0..r15 30 | * Entry 16 is used to store the CPSR register 31 | * Entry 17 is used to store the "orig_r0" value. 32 | */ 33 | typedef struct { Elf(Addr) uregs[18]; } UserRegs; 34 | 35 | class Process 36 | { 37 | public: 38 | Process(pid_t pid); 39 | ~Process(); 40 | 41 | bool Init(std::string libc_name); 42 | bool InitLibraries(); 43 | 44 | bool Attach(); 45 | bool Detach(); 46 | 47 | bool Wait() { return Wait(NULL); } 48 | bool Wait(int *status); 49 | 50 | bool Run() { return Run(NULL); } 51 | bool Run(int *sig); 52 | 53 | bool WriteRegisters(UserRegs *regs); 54 | bool ReadRegisters(UserRegs *regs); 55 | 56 | bool WriteMemory(uint32_t addr, const void *in, int32_t length, 57 | bool preserve = false); 58 | bool ReadMemory(uint32_t addr, void *out, int32_t length); 59 | 60 | bool Resolve(std::string lib, std::string sym, Elf(Addr) &addr); 61 | 62 | int32_t Execute(uint32_t fn, uint32_t *args, uint32_t nargs, 63 | uint32_t *ret); 64 | 65 | bool Inject(std::string path); 66 | 67 | SharedObject* lib_find(const char *symbol, 68 | const std::vector &needed); 69 | 70 | bool PrepareHooking(); 71 | 72 | bool InsertHooks(const std::vector &hooks); 73 | bool InsertHook(Hook *hook); 74 | 75 | /* functions being called inside the process */ 76 | PFUNC_DEFAULT_DECL(malloc, uint32_t*, uint32_t); 77 | PFUNC_DEFAULT_DECL(free, uint32_t*, uint32_t); 78 | PFUNC_DEFAULT_DECL(mmap, uint32_t*, uint32_t, uint32_t, uint32_t, 79 | uint32_t, uint32_t, uint32_t); 80 | PFUNC_DEFAULT_DECL(mprotect, uint32_t*, uint32_t, uint32_t, uint32_t); 81 | PFUNC_DEFAULT_DECL(open, uint32_t*, uint32_t, uint32_t, uint32_t); 82 | PFUNC_DEFAULT_DECL(close, uint32_t*, uint32_t); 83 | PFUNC_DEFAULT_DECL(memset, uint32_t*, uint32_t, uint32_t, uint32_t); 84 | 85 | private: 86 | SharedObject* lib_get(std::string name); 87 | 88 | bool lib_check_deps(SharedObject *lib); 89 | 90 | std::map libs_; 91 | 92 | /* store common function addresses in the process */ 93 | std::map common_functions_; 94 | 95 | pid_t pid_; 96 | bool attached_; 97 | }; 98 | 99 | } /* namespace armhook */ 100 | 101 | #endif /* ARMHOOK_PROCESS_H_ */ 102 | -------------------------------------------------------------------------------- /helper/trampoline.S: -------------------------------------------------------------------------------- 1 | .extern hook_map 2 | 3 | .text 4 | .global trampoline 5 | 6 | /* 7 | struct saved_prolog { 8 | uint8_t saved_ops[4*10]; 9 | uint32_t cont; 10 | }; 11 | 12 | struct hook_data { 13 | uint32_t cpsr; 14 | uint32_t r0, r1, r2, r3; 15 | uint32_t *sp; 16 | uint32_t skip_lr; 17 | }; 18 | 19 | struct hook_mapping { 20 | uint32_t lr; 21 | hook_handler handler; 22 | struct saved_prolog *prolog; 23 | }; 24 | */ 25 | 26 | /* r0 = &trampoline, lr = &target_func+x */ 27 | /* sp = {orig_r0, orig_r1, orig_r2, orig_r3, orig_r4, orig_lr, ...} */ 28 | trampoline: 29 | /* 30 | push {lr} 31 | 32 | ldr r0, =mutex 33 | bl lock_mutex 34 | 35 | pop {lr} 36 | */ 37 | mov r1, lr 38 | 39 | /* save content of CPSR as early as possible */ 40 | mrs r2, APSR 41 | push {r2} 42 | 43 | /* get pointer to &hook_map[0] */ 44 | ldr r0, =hook_map 45 | ldr r0, [r0] 46 | 47 | loop: 48 | ldr r2, [r0] 49 | cmp r2, r1 50 | beq handle 51 | 52 | add r0, r0, $12 /* increase by sizeof(struct hook_mapping) */ 53 | b loop 54 | 55 | handle: /* r0 points to correct hook_map entry */ 56 | 57 | /* sp = {cpsr, orig_r0, orig_r1, orig_r2, orig_r3, orig_r4, orig_lr} */ 58 | add r1, sp, $28 59 | str r1, [sp, $20] 60 | 61 | /* sp = {cpsr, orig_r0, orig_r1, orig_r2, orig_r3, orig_sp, orig_lr} */ 62 | push {r0} 63 | ldr r1, [r0, $4] 64 | 65 | add r0, sp, $4 /* sp = struct hook_data *data */ 66 | blx r1 67 | 68 | pop {r1} /* get pointer to hook_map entry */ 69 | 70 | /* sp = {cpsr, orig_r0, orig_r1, orig_r2, orig_r3, orig_sp, orig_lr} */ 71 | cmp r0, $0 72 | beq cont_func 73 | 74 | skip_func: 75 | ldr lr, [sp, $24] /* let r0 be the return address to caller */ 76 | b fix_register 77 | 78 | cont_func: 79 | ldr lr, [r1, $8] /* get pointer to saved prolog */ 80 | 81 | fix_register: 82 | /* 83 | * lr = return address 84 | * r1 = &hook_map[x] 85 | */ 86 | 87 | /* restore CPSR */ 88 | pop {r0} 89 | msr APSR_nzcvqg, r0 90 | 91 | /* replace orig_sp with orig_lr and orig_lr with lr */ 92 | ldr r0, [sp, $20] 93 | str r0, [sp, $16] 94 | str lr, [sp, $20] 95 | 96 | /* 97 | * restore registers 98 | * sp = {orig_r0, orig_r1, orig_r2, orig_r3, orig_lr, lr} 99 | */ 100 | pop {r0, r1, r2, r3, lr} 101 | 102 | /* 103 | push {r0, r1} 104 | 105 | ldr r0, =mutex 106 | bl unlock_mutex 107 | 108 | pop {r0, r1} 109 | */ 110 | 111 | /* sp = {lr} */ 112 | pop {pc} /* sp should now be returned to original */ 113 | 114 | lock_mutex: 115 | mov r1, $1 116 | ldrex r2, [r0] 117 | cmp r2, r1 118 | 119 | wfeeq 120 | beq lock_mutex 121 | 122 | strexne r2, r1, [r0] 123 | cmpne r2, $1 124 | beq lock_mutex 125 | 126 | /* lock aquired */ 127 | dmb 128 | bx lr 129 | 130 | unlock_mutex: 131 | mov r1, $0 132 | dmb 133 | str r1, [r0] 134 | 135 | dsb 136 | sev 137 | 138 | bx lr 139 | 140 | .data 141 | mutex: 142 | .byte 0x00, 0x00, 0x00, 0x00 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # armhook-core 2 | Core of Linux hooking engine for ARM architecture 3 | 4 | ## Shared Library Injection 5 | The engine contains a linker which is used to inject a shared library into a given process. 6 | It provides minimal functionality and uses `ptrace` to load and link the library in the target process. 7 | 8 | The implemented linker can't inject a library whose dependancies are not already present in the target process memory. 9 | Kepp that in mind when developing your hook handlers. 10 | 11 | ## Hook Mechanism 12 | A **trampoline** is used as an intermediate stop for each hook. It finds the correct hook handler based on the return address given by the `lr` register when the **trampoline** is called. 13 | 14 | It is also responsible to construct the argument for the hook handler and handle the return to the original function or the caller based on the return value of the hook handler. The signature of a hook handler funciton and the argument structure is shown below: 15 | 16 | ```C 17 | struct hook_data { 18 | uint32_t cpsr; 19 | uint32_t r0, r1, r2, r3; 20 | uint32_t *sp; 21 | uint32_t skip_lr; 22 | }; 23 | 24 | typedef int8_t (*hook_handler)(struct hook_data*); 25 | ``` 26 | 27 | The orginal function prolog is saved and used when the handler returns `1` meaning that the original function should be continued. To modify the arguments to the original function, it is only required to modify the fields inside the argument struct. 28 | The **trampoline** will restore the register content based on the values given in the struct before executing the saved prolog. 29 | 30 | Due to the changed location of the prolog instructions, continuation of the original function may not work properly. 31 | This is the case when the prolog contains a PC-relative instruction or a relative branch. 32 | Currently the engine does not detect these kind of instructions, so it is up to the user to determine whether or not the original function can be continued without breaking the application. 33 | 34 | The size of the detour for ARM mode is 12 bytes and for Thumb mode 8 bytes. 35 | 36 | An instruction decoder is used to avoid breaking instructions by only saving part of them. 37 | 38 | ## Configuration 39 | A JSON file is used to configure the hooks that should be inserted. An example configuration is shown below: 40 | 41 | ```JSON 42 | { 43 | "hooks": [ 44 | { 45 | "relative": 1141, 46 | "handler": "handler_get_value", 47 | "base": "example_target", 48 | "library": "/data/libhandler.so" 49 | } 50 | ], 51 | "settings": { 52 | "libc": "libc.so", 53 | "helper": "/data/libarmhook.so" 54 | } 55 | } 56 | ``` 57 | 58 | An object in the `hooks` array represents one active hook. It is possible to specify either an `absolute` or `relative` value where in memory the hook should be inserted. If `relative` is specified the hook will be inserted at the given offset relative to the base specified in `base` which is the name of a segment in memory as it can be found in `/proc//maps`. For example, in the above configuration, and the following `/proc//maps` content: 59 | 60 | ``` 61 | b6fed000-b6fee000 r-xp 00000000 b3:0c 110 /system/bin/example_target 62 | b6fee000-b6fef000 r--p 00000000 b3:0c 110 /system/bin/example_target 63 | ``` 64 | 65 | the base address of `example_target` serves as the base for the relative hook. 66 | So the hook would be inserted at `0xb6fed000 + 1141 = 0xb6fed475`. It is important to note that the value given in the configuration is used to determine the instruction mode used for that function. If the LSB is set, then the engine assumes that the function is called in Thumb mode. 67 | 68 | `libc` in the `settings` section specifies the libc name as it appears in `/proc//maps`, it is used to resolve functions from libc which are called during the injection process. 69 | 70 | `heler` in the `settings` sections specifies the full path to the helper library that is build as part of the engine. 71 | This library will be injected into the target process as well and is used to set up the hook mappings and copy the trampoline at its location. 72 | -------------------------------------------------------------------------------- /helper/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "hook.h" 7 | 8 | #define PAGE_START(P) ((uint32_t)(P) & ~(PAGE_SIZE-1)) 9 | #define PAGE_END(P) (((uint32_t)(P) + PAGE_SIZE - 1) & ~(PAGE_SIZE-1)) 10 | 11 | extern void trampoline(void); 12 | 13 | void clearcache(char* begin, char *end); 14 | 15 | uint8_t arm_return[] = { 16 | 0x20, 0xf0, 0x9f, 0xe5, /* ldr pc, [pc, $0x20] */ 17 | 0x00, 0x00, 0x00, 0x00 /* padding */ 18 | }; 19 | 20 | uint8_t thumb_return[] = { 21 | 0x30, 0xb4, /* push {r4, r5} */ 22 | 0x00, 0x4c, /* ldr r4, [pc, $0] */ 23 | 0x01, 0x94, /* str r4, [sp, $4] */ 24 | 0x10, 0xbd /* pop {r4, pc} */ 25 | }; 26 | 27 | uint8_t thumb_nop[] = { 28 | 0x00, 0x1c /* mov r0, r0 */ 29 | }; 30 | 31 | struct hook_mapping *hook_map; 32 | struct saved_prolog *hook_prologs; 33 | 34 | uint32_t hooks_limit = 0; 35 | uint32_t hooks_counter = 0; 36 | 37 | /* TODO: This function is used to determine the 38 | * the size of the 'trampoline' function due to relative relocations, 39 | * dirty way to do it, might change it later 40 | */ 41 | uint32_t dummy() 42 | { 43 | return 0; 44 | } 45 | 46 | /* setup the trampoline at a given location */ 47 | void* setup_trampoline(void *loc) 48 | { 49 | uint32_t size = (uint32_t)&dummy - (uint32_t)&trampoline; 50 | int32_t flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; 51 | 52 | void *map = mmap(loc, 0x2000, 7, flags, -1, 0); 53 | if (map == MAP_FAILED) 54 | return NULL; 55 | 56 | memcpy(map, (void*)&trampoline, size); 57 | 58 | return (void*)((uint32_t)loc ^ (uint32_t)size); 59 | } 60 | 61 | /* allocate memory with correct permissions */ 62 | int32_t allocate_structs(uint32_t max_hooks) 63 | { 64 | if (max_hooks > 50) 65 | return -1; 66 | 67 | uint32_t size = max_hooks * sizeof(*hook_prologs); 68 | hook_prologs = mmap(0, size, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 69 | 70 | if (hook_prologs == MAP_FAILED) 71 | return -2; 72 | 73 | size = max_hooks * sizeof(*hook_map); 74 | hook_map = mmap(0, size, 3, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 75 | if (hook_map == MAP_FAILED) 76 | return -3; 77 | 78 | return 0; 79 | } 80 | 81 | void* setup(void *loc, uint32_t max_hooks) 82 | { 83 | uint32_t tramp = (uint32_t)setup_trampoline(loc); 84 | if ((tramp & (uint32_t)loc) != (uint32_t)loc) 85 | return NULL; 86 | 87 | if (allocate_structs(max_hooks) < 0) 88 | return NULL; 89 | 90 | hooks_limit = max_hooks; 91 | 92 | return (void*)tramp; 93 | } 94 | 95 | int32_t hook_add(hook_handler func, uint32_t loc, uint8_t bytes) 96 | { 97 | if (hooks_counter >= hooks_limit) 98 | return -100; 99 | 100 | if (!hook_prologs || !hook_map) 101 | return -101; 102 | 103 | uint32_t max = sizeof(hook_prologs->prolog); 104 | uint32_t padding = 0; 105 | 106 | /* in case of thumb we might require more bytes for alignment */ 107 | if (loc & 1) 108 | max -= (sizeof(thumb_return) + sizeof(thumb_nop)); 109 | else 110 | max -= sizeof(arm_return); 111 | 112 | if (bytes > max) 113 | return -max; 114 | 115 | struct saved_prolog *prolog = hook_prologs; 116 | struct hook_mapping *mapping = hook_map; 117 | 118 | while (prolog->cont != 0x0) 119 | prolog++; 120 | 121 | while (mapping->lr != 0x0) 122 | mapping++; 123 | 124 | memcpy(prolog->prolog, (void*)(loc & 0xfffffffe), bytes); 125 | 126 | switch (loc & 1) { 127 | case 1: 128 | if ((bytes % 4) == 0) { 129 | padding = sizeof(thumb_nop); 130 | memcpy(prolog->prolog + bytes, thumb_nop, 131 | sizeof(thumb_nop)); 132 | } 133 | thumb_return[2] = (uint8_t)((SIZE_SAVED - bytes - 134 | sizeof(thumb_return) + 2) / 4); 135 | memcpy(prolog->prolog + bytes + padding, thumb_return, 136 | sizeof(thumb_return)); 137 | break; 138 | case 0: 139 | arm_return[0] = (uint8_t)(SIZE_SAVED - 140 | sizeof(arm_return) - bytes); 141 | memcpy(prolog->prolog + bytes, arm_return, 142 | sizeof(arm_return)); 143 | break; 144 | } 145 | 146 | prolog->cont = loc + bytes; 147 | 148 | /* store the mapping */ 149 | mapping->lr = loc + bytes; 150 | mapping->handler = func; 151 | mapping->prolog = (loc & 1) ? ((uint8_t*)prolog + 1) : prolog; 152 | 153 | return hooks_counter++; 154 | } 155 | 156 | int32_t hook_del() 157 | { 158 | /* TODO: cleanup array */ 159 | return -1; 160 | } 161 | 162 | void clearcache(char* begin, char *end) 163 | { 164 | const int syscall = 0xf0002; 165 | asm volatile ( 166 | "mov r0, %0\n" 167 | "mov r1, %1\n" 168 | "mov r7, %2\n" 169 | "mov r2, #0x0\n" 170 | "svc 0x00000000\n" 171 | : 172 | : "r" (begin), "r" (end), "r" (syscall) 173 | : "r0", "r1", "r7" 174 | ); 175 | } 176 | -------------------------------------------------------------------------------- /Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Hook.h" 9 | #include "Logger.h" 10 | 11 | /* TODO: cleanup memory */ 12 | 13 | namespace armhook { 14 | 15 | Config* Config::kInstance_ = NULL; 16 | 17 | Config* Config::Instance() 18 | { 19 | if (!kInstance_) 20 | kInstance_ = new Config(); 21 | 22 | return kInstance_; 23 | } 24 | 25 | Config::Config() 26 | : root_(NULL) 27 | , helper_(NULL) 28 | {} 29 | 30 | bool Config::Parse(const char *file) 31 | { 32 | FILE *f = fopen(file, "rb"); 33 | if (!f) { 34 | LOG_ERROR("couldn't open file: %s", file); 35 | return false; 36 | } 37 | 38 | fseek(f, 0, SEEK_END); 39 | unsigned int fsize = ftell(f); 40 | fseek(f, 0, SEEK_SET); 41 | 42 | char *input = (char *)calloc(1, fsize + 1); 43 | fread(input, fsize, 1, f); 44 | fclose(f); 45 | 46 | input[fsize] = 0; 47 | 48 | root_ = json_loads(input, 0, &last_error_); 49 | if (!root_) { 50 | LOG_ERROR("json_loads() failed, error: " 51 | "text: %s, source: %s, line: %d, column: %d, pos: %d", 52 | last_error_.text, last_error_.source, last_error_.line, 53 | last_error_.column, last_error_.position); 54 | return false; 55 | } 56 | 57 | if (!json_is_object(root_)) { 58 | LOG_ERROR("root element of config must be an object"); 59 | return false; 60 | } 61 | 62 | json_t *settings = NULL; 63 | if ((settings = GetJSONObject(root_, "settings")) == NULL || 64 | (helper_ = GetJSONString(settings, "helper")) == NULL || 65 | (libc_ = GetJSONString(settings, "libc")) == NULL) { 66 | LOG_ERROR("invalid 'settings' section"); 67 | return false; 68 | } 69 | 70 | json_t *hooks = GetJSONArray(root_, "hooks"); 71 | if (!hooks) { 72 | LOG_ERROR("invalid 'hooks' section"); 73 | return false; 74 | } 75 | 76 | for (size_t i=0; i 4 | #include 5 | 6 | #include "Logger.h" 7 | 8 | namespace armhook { 9 | 10 | ELF::ELF(std::string full_path) 11 | : ehdr_(NULL) 12 | , phdr_(NULL) 13 | , symtab_(NULL) 14 | , symsz_(0) 15 | , strtab_(NULL) 16 | , strsz_(0) 17 | , dyn_(NULL) 18 | , rel_(NULL) 19 | , num_rel_(0) 20 | , plt_rel_(NULL) 21 | , num_plt_rel_(0) 22 | , textrel_(false) 23 | { 24 | full_path_ = full_path; 25 | 26 | unsigned pos = full_path.find_last_of("/"); 27 | path_ = full_path.substr(0, pos); 28 | name_ = full_path.substr(pos+1); 29 | } 30 | 31 | ELF::~ELF() 32 | { 33 | cleanup(); 34 | } 35 | 36 | bool ELF::init() 37 | { 38 | LOG_INFO("initializing library: %s", full_path_.c_str()); 39 | bool rval = false; 40 | 41 | std::ifstream is(full_path_.c_str(), std::ifstream::binary); 42 | if (!is) { 43 | LOG_ERROR("couldn't open %s", full_path_.c_str()); 44 | return false; 45 | } 46 | 47 | if (!read_ehdr(is) || !read_phdr(is) || !iterate_phdr(is) || 48 | !iterate_dyn(is)) { 49 | rval = false; 50 | } else { 51 | rval = true; 52 | } 53 | 54 | is.close(); 55 | return rval; 56 | } 57 | 58 | bool ELF::resolve(const char *symbol, Elf(Addr) &out) 59 | { 60 | Elf(Sym) *sym = NULL; 61 | 62 | if (!symtab_ || !strtab_) 63 | return false; 64 | 65 | out = 0; 66 | for (Elf(Addr) i=0; i<(symsz_/sizeof(Elf(Sym))); i++) { 67 | sym = &symtab_[i]; 68 | if (!strcmp(symbol, &strtab_[sym->st_name])) { 69 | out = sym->st_value; 70 | break; 71 | } 72 | } 73 | 74 | if (!out) 75 | return false; 76 | 77 | return true; 78 | } 79 | 80 | bool ELF::read_ehdr(std::ifstream &is) 81 | { 82 | if (ehdr_) 83 | return true; 84 | 85 | /* allocate space for ELF header and read it */ 86 | ehdr_ = (Elf(Ehdr)*)calloc(1, sizeof(*ehdr_)); 87 | if (!ehdr_) { 88 | LOG_ERROR("failed to allocate memory for Ehdr"); 89 | cleanup(); 90 | return false; 91 | } 92 | 93 | is.read((char*)ehdr_, sizeof(*ehdr_)); 94 | 95 | return true; 96 | } 97 | 98 | bool ELF::read_phdr(std::ifstream &is) 99 | { 100 | if (phdr_) 101 | return true; 102 | 103 | if (!ehdr_) 104 | return false; 105 | 106 | /* allocate space for Program header array and read it */ 107 | phdr_ = (Elf(Phdr)*)calloc(ehdr_->e_phnum, sizeof(*phdr_)); 108 | if (!phdr_) { 109 | LOG_ERROR("failed to allocate memory for Phdr"); 110 | cleanup(); 111 | return false; 112 | } 113 | 114 | is.seekg(ehdr_->e_phoff); 115 | is.read((char*)phdr_, ehdr_->e_phnum * sizeof(*phdr_)); 116 | 117 | return true; 118 | } 119 | 120 | bool ELF::iterate_phdr(std::ifstream &is) 121 | { 122 | if (!ehdr_ || !phdr_) 123 | return false; 124 | 125 | bool rval = true; 126 | 127 | for (int32_t i=0; ie_phnum; i++) { 128 | Elf(Phdr) *ph = &phdr_[i]; 129 | 130 | switch(ph->p_type) { 131 | case PT_DYNAMIC: 132 | rval = read_dyn(is, ph); 133 | break; 134 | } 135 | 136 | if (!rval) 137 | return false; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | bool ELF::read_dyn(std::ifstream &is, Elf(Phdr) *ph) 144 | { 145 | if (dyn_) 146 | return true; 147 | 148 | dyn_ = (Elf(Dyn)*)calloc(1, ph->p_filesz); 149 | if (!dyn_) { 150 | LOG_ERROR("failed to allocate memory for Dyn array"); 151 | cleanup(); 152 | return false; 153 | } 154 | 155 | is.seekg(ph->p_offset); 156 | is.read((char*)dyn_, ph->p_filesz); 157 | 158 | return true; 159 | } 160 | 161 | bool ELF::iterate_dyn(std::ifstream &is) 162 | { 163 | if (!dyn_) 164 | return false; 165 | 166 | Elf(Addr) rel = 0; 167 | Elf(Addr) plt_rel = 0; 168 | Elf(Addr) sym = 0; 169 | Elf(Addr) str = 0; 170 | 171 | Elf(Dyn) *dyn = &dyn_[0]; 172 | for(; dyn->d_tag != DT_NULL; ++dyn) { 173 | switch(dyn->d_tag) { 174 | case DT_JMPREL: 175 | plt_rel = dyn->d_un.d_ptr; 176 | break; 177 | case DT_PLTRELSZ: 178 | num_plt_rel_ = dyn->d_un.d_val / sizeof(*plt_rel_); 179 | break; 180 | case DT_REL: 181 | rel = dyn->d_un.d_ptr; 182 | break; 183 | case DT_RELSZ: 184 | num_rel_ = dyn->d_un.d_val / sizeof(*rel_); 185 | break; 186 | case DT_SYMTAB: 187 | sym = dyn->d_un.d_ptr; 188 | break; 189 | case DT_STRTAB: 190 | str = dyn->d_un.d_ptr; 191 | break; 192 | case DT_STRSZ: 193 | strsz_ = dyn->d_un.d_val; 194 | break; 195 | case DT_TEXTREL: 196 | textrel_ = true; 197 | break; 198 | } 199 | } 200 | 201 | if (rel && !read_reloc(is, &rel_, rel, num_rel_)) 202 | return false; 203 | 204 | if (plt_rel && !read_reloc(is, &plt_rel_, plt_rel, num_plt_rel_)) 205 | return false; 206 | 207 | symsz_ = str - sym; 208 | if (sym && !read_table(is, (void**)&symtab_, sym, symsz_)) 209 | return false; 210 | 211 | if (str && !read_table(is, (void**)&strtab_, str, strsz_)) 212 | return false; 213 | 214 | /* we should have the string table, store dependency libraries */ 215 | if (strtab_) { 216 | dyn = &dyn_[0]; 217 | for (; dyn->d_tag != DT_NULL; ++dyn) { 218 | if (dyn->d_tag != DT_NEEDED) 219 | continue; 220 | 221 | needed_.push_back(std::string(&strtab_[dyn->d_un.d_val])); 222 | } 223 | } 224 | 225 | return true; 226 | } 227 | 228 | bool ELF::read_reloc(std::ifstream &is, Elf(Rel) **rel, Elf(Addr) pos, 229 | Elf32_Word count) 230 | { 231 | if (*rel) 232 | return true; 233 | 234 | *rel = (Elf(Rel)*)calloc(count, sizeof(Elf(Rel))); 235 | if (!*rel) { 236 | cleanup(); 237 | return false; 238 | } 239 | 240 | is.seekg(pos); 241 | is.read((char*)(*rel), count * sizeof(Elf(Rel))); 242 | 243 | return true; 244 | } 245 | 246 | bool ELF::read_table(std::ifstream &is, void **out, Elf(Addr) pos, 247 | Elf32_Word size) 248 | { 249 | if (*out) 250 | return true; 251 | 252 | *out = calloc(1, size); 253 | if(!*out) { 254 | cleanup(); 255 | return false; 256 | } 257 | 258 | is.seekg(pos); 259 | is.read((char*)(*out), size); 260 | 261 | return true; 262 | } 263 | 264 | void ELF::cleanup() 265 | { 266 | free(ehdr_); 267 | ehdr_ = NULL; 268 | free(phdr_); 269 | phdr_ = NULL; 270 | free(symtab_); 271 | symtab_ = NULL; 272 | free(strtab_); 273 | strtab_ = NULL; 274 | free(dyn_); 275 | dyn_ = NULL; 276 | free(rel_); 277 | rel_ = NULL; 278 | free(plt_rel_); 279 | plt_rel_ = NULL; 280 | } 281 | 282 | } /* namespace armhook */ 283 | -------------------------------------------------------------------------------- /SharedObject.cpp: -------------------------------------------------------------------------------- 1 | #include "SharedObject.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Logger.h" 9 | #include "Process.h" 10 | 11 | namespace armhook { 12 | 13 | SharedObject::SharedObject(string full_path) 14 | : ELF(full_path) 15 | , loadcmds_(NULL) 16 | , ncmds_(0) 17 | , load_size_(0) 18 | , load_start_(0) 19 | , load_bias_(0) 20 | { 21 | } 22 | 23 | SharedObject::~SharedObject() 24 | { 25 | /* free all segment structs */ 26 | while (segments_.size()) { 27 | free(segments_.back()); 28 | segments_.pop_back(); 29 | } 30 | 31 | /* free all loadcmds */ 32 | free(loadcmds_); 33 | loadcmds_ = NULL; 34 | } 35 | 36 | bool SharedObject::Injectable() 37 | { 38 | return ehdr_->e_type == ET_DYN; 39 | } 40 | 41 | bool SharedObject::Inject(Process *proc) 42 | { 43 | if (!ehdr_) { 44 | LOG_ERROR("library needs to be initialized first"); 45 | return false; 46 | } 47 | 48 | if (!Injectable()) { 49 | LOG_ERROR("can only inject ET_DYN libraries"); 50 | return false; 51 | } 52 | 53 | LOG_DEBUG("injecting library: %s", name_.c_str()); 54 | if (!PrepareInjection() || 55 | !ReserveMemory(proc) || 56 | !MapSegments(proc) || 57 | !LinkObject(proc)) 58 | return false; 59 | 60 | return true; 61 | } 62 | 63 | bool SharedObject::set_base(Elf(Addr) base) 64 | { 65 | load_start_ = load_bias_ = base; 66 | 67 | return true; 68 | } 69 | 70 | bool SharedObject::add_segment(Elf(Addr) start, Elf(Addr) end, char *prot) 71 | { 72 | MemorySegment *seg = (MemorySegment*)calloc(1, sizeof(MemorySegment)); 73 | if (!seg) { 74 | LOG_ERROR("couldn't allocate memory for a new segment"); 75 | return false; 76 | } 77 | 78 | seg->start = start; 79 | seg->end = end; 80 | seg->prot = PROT_NONE; 81 | 82 | if (prot[0] == 'r') 83 | seg->prot |= PROT_READ; 84 | if (prot[1] == 'w') 85 | seg->prot |= PROT_WRITE; 86 | if (prot[2] == 'x') 87 | seg->prot |= PROT_EXEC; 88 | 89 | segments_.push_back(seg); 90 | LOG_DEBUG("added segment 0x%08x-0x%08x %4s to %s", start, end, 91 | prot, name_.c_str()); 92 | 93 | return true; 94 | } 95 | 96 | MemorySegment* SharedObject::get_segment(Elf(Addr) addr) 97 | { 98 | std::vector::iterator it = segments_.begin(); 99 | for (; it != segments_.end(); it++) { 100 | if ((*it)->start < addr && addr < (*it)->end) 101 | return (*it); 102 | } 103 | 104 | return NULL; 105 | } 106 | 107 | bool SharedObject::resolve(const char *symbol, Elf(Addr) &out, bool abs) 108 | { 109 | if (!ELF::resolve(symbol, out)) 110 | return false; 111 | 112 | if (abs) 113 | out += load_bias_; 114 | 115 | return true; 116 | } 117 | 118 | bool SharedObject::PrepareInjection() 119 | { 120 | if (!ehdr_) { 121 | LOG_ERROR("ELF header not present"); 122 | return false; 123 | } 124 | 125 | /* can only inject ET_DYN */ 126 | if (ehdr_->e_type != ET_DYN) { 127 | LOG_ERROR("Object is not of type ET_DYN"); 128 | return false; 129 | } 130 | 131 | uint16_t phnum = ehdr_->e_phnum; 132 | loadcmds_ = (LoadCommand*)calloc(phnum, sizeof(*loadcmds_)); 133 | 134 | LoadCommand *cmd = NULL; 135 | for (uint16_t i=0; ip_type) { 139 | case PT_LOAD: 140 | cmd = &loadcmds_[ncmds_++]; 141 | 142 | /* align everything properly */ 143 | cmd->mapstart = PAGE_START(ph->p_vaddr); 144 | cmd->mapend = PAGE_END(ph->p_vaddr + ph->p_filesz); 145 | cmd->dataend = ph->p_vaddr + ph->p_filesz; 146 | cmd->allocend = ph->p_vaddr + ph->p_memsz; 147 | cmd->mapoff = PAGE_START(ph->p_offset); 148 | 149 | cmd->prot = 0; 150 | if (ph->p_flags & PF_R) 151 | cmd->prot |= PROT_READ; 152 | if (ph->p_flags & PF_W) 153 | cmd->prot |= PROT_WRITE; 154 | if (ph->p_flags & PF_X) 155 | cmd->prot |= PROT_EXEC; 156 | break; 157 | } 158 | } 159 | 160 | load_size_ = loadcmds_[ncmds_-1].allocend - loadcmds_[0].mapstart; 161 | 162 | return true; 163 | } 164 | 165 | bool SharedObject::ReserveMemory(Process *proc) 166 | { 167 | uint32_t base; 168 | 169 | PCALL(proc, mmap, &base, 0, load_size_, PROT_NONE, 170 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 171 | LOG_DEBUG("mmap(): 0x%08x, %d, %d, ..., %d, 0x%08x", 172 | base, load_size_, PROT_NONE, -1, 0); 173 | 174 | if ((void*)base == MAP_FAILED) { 175 | LOG_ERROR("couldn't reserve memory mmap return: 0x%08x", base); 176 | return false; 177 | } 178 | 179 | load_start_ = base; 180 | load_bias_ = base - loadcmds_->mapstart; 181 | 182 | return true; 183 | } 184 | 185 | bool SharedObject::MapSegments(Process *proc) 186 | { 187 | /* we need an open file descriptor for the library in the process */ 188 | uint32_t name_addr = 0; 189 | PCALL(proc, malloc, &name_addr, full_path_.length()); 190 | 191 | if (!name_addr) { 192 | LOG_ERROR("couldn't allocate memory inside the process"); 193 | return false; 194 | } 195 | 196 | /* wrtie the path into the allocated memory in the process */ 197 | if (!proc->WriteMemory(name_addr, full_path_.c_str(), 198 | full_path_.length())) { 199 | LOG_ERROR("coudln't write path: %s into process memory", 200 | full_path_.c_str()); 201 | return false; 202 | } 203 | 204 | uint32_t fd = -1; 205 | PCALL(proc, open, (uint32_t*)&fd, name_addr, O_RDONLY, 0); 206 | if ((int32_t)fd < 0) { 207 | LOG_ERROR("process couldn't open library: %d", fd); 208 | return false; 209 | } 210 | 211 | LoadCommand *c = &loadcmds_[0]; 212 | for (uint32_t i=0; i dataend) { 238 | PCALL(proc, mmap, &dummy, dataend, 239 | allocend - dataend, c[i].prot, 240 | MAP_FIXED | MAP_PRIVATE | MAP_ANON, 241 | -1, 0); 242 | 243 | if ((void*)dummy == MAP_FAILED) { 244 | LOG_ERROR("couldn't mmap zeor page: 0x%08x", 245 | dataend); 246 | return false; 247 | } 248 | } 249 | } 250 | 251 | PCALL(proc, close, NULL, fd); 252 | PCALL(proc, free, NULL, name_addr); 253 | 254 | return true; 255 | } 256 | 257 | bool SharedObject::LinkObject(Process *proc) 258 | { 259 | if (plt_rel_) { 260 | if (!FixRelocations(proc, plt_rel_, num_plt_rel_)) { 261 | LOG_ERROR("failed to do DT_JMPREL relocations"); 262 | return false; 263 | } 264 | } 265 | 266 | if (rel_) { 267 | if (!FixRelocations(proc, rel_, num_rel_)) { 268 | LOG_ERROR("failed to do DT_REL relocations"); 269 | return false; 270 | } 271 | } 272 | 273 | if (!ProtectRelocations(proc)) { 274 | LOG_ERROR("couldn't mprotect relro region"); 275 | return false; 276 | } 277 | 278 | return true; 279 | } 280 | 281 | bool SharedObject::FixRelocations(Process *proc, Elf(Rel) *rel, uint32_t count) 282 | { 283 | SharedObject *so = NULL; 284 | 285 | for (uint32_t i=0; ir_info); 287 | uint32_t sym = ELF32_R_SYM(rel->r_info); 288 | char *name = NULL; 289 | 290 | Elf(Addr) rel_pos = rel->r_offset + load_bias_; 291 | Elf(Addr) sym_addr = 0; 292 | Elf(Addr) old_val = 0; 293 | 294 | if (sym) { 295 | name = &strtab_[symtab_[sym].st_name]; 296 | LOG_DEBUG("looking for symbol: %s", name); 297 | 298 | so = proc->lib_find(name, needed_); 299 | if (so) { 300 | so->resolve(name, sym_addr); 301 | LOG_DEBUG("found symbol: %s in %s at 0x%08x", 302 | name, so->name().c_str(), sym_addr); 303 | } else { 304 | /* TODO: handle not found */ 305 | } 306 | } else { 307 | /* TODO: handle this */ 308 | } 309 | 310 | switch (type) { 311 | case R_ARM_JUMP_SLOT: 312 | LOG_DEBUG("R_ARM_JUMP_SLOT [0x%08x] = 0x%08x [%s]", 313 | rel_pos, sym_addr, name); 314 | 315 | if (!proc->WriteMemory(rel_pos, (void*)&sym_addr, 316 | sizeof(sym_addr))) 317 | return false; 318 | break; 319 | case R_ARM_GLOB_DAT: 320 | LOG_DEBUG("R_ARM_GLOB_DAT [0x%08x] = 0x%08x [%s]", 321 | rel_pos, sym_addr, name); 322 | 323 | if (!proc->WriteMemory(rel_pos, (void*)&sym_addr, 324 | sizeof(sym_addr))) 325 | return false; 326 | break; 327 | case R_ARM_ABS32: 328 | LOG_DEBUG("R_ARM_ABS32 [0x%08x] += 0x%08x [%s]", 329 | rel_pos, sym_addr, name); 330 | 331 | old_val = 0; 332 | if (!proc->ReadMemory(rel_pos, (void*)&old_val, 333 | sizeof(old_val))) 334 | return false; 335 | 336 | old_val += sym_addr; 337 | 338 | if (!proc->WriteMemory(rel_pos, (void*)&old_val, 339 | sizeof(old_val))) 340 | return false; 341 | break; 342 | case R_ARM_REL32: 343 | LOG_DEBUG("R_ARM_REL32 [0x%08x] += 0x%08x - 0x%08x [%s]", 344 | rel_pos, sym_addr, rel->r_offset, name); 345 | 346 | old_val = 0; 347 | if (!proc->ReadMemory(rel_pos, (void*)&old_val, 348 | sizeof(old_val))) 349 | return false; 350 | 351 | old_val += sym_addr - rel->r_offset; 352 | 353 | if (!proc->WriteMemory(rel_pos, (void*)&old_val, 354 | sizeof(old_val))) 355 | return false; 356 | break; 357 | case R_ARM_RELATIVE: 358 | LOG_DEBUG("R_ARM_RELATIVE [0x%08x] += 0x%08x", 359 | rel_pos, load_start_); 360 | if (sym) { 361 | LOG_ERROR("ELF32_R_SYM can't be set with ARM_RELATIVE"); 362 | return false; 363 | } 364 | 365 | old_val = 0; 366 | if (!proc->ReadMemory(rel_pos, (void*)&old_val, 367 | sizeof(old_val))) 368 | return false; 369 | 370 | old_val += load_start_; 371 | 372 | if (!proc->WriteMemory(rel_pos, (void*)&old_val, 373 | sizeof(old_val))) 374 | return false; 375 | break; 376 | case R_ARM_COPY: 377 | LOG_ERROR("R_ARM_COPY is not allowed in ET_DYN"); 378 | return false; 379 | break; 380 | default: 381 | break; 382 | } 383 | } 384 | 385 | return true; 386 | } 387 | 388 | bool SharedObject::ProtectRelocations(Process *proc) 389 | { 390 | Elf(Phdr) *ph = &phdr_[0]; 391 | uint16_t phnum = ehdr_->e_phnum; 392 | 393 | uint32_t dummy = 0; 394 | 395 | for (uint16_t i=0; ip_type == PT_GNU_RELRO) { 397 | uint32_t start = PAGE_START(ph->p_vaddr) + load_bias_; 398 | uint32_t end = PAGE_END(ph->p_vaddr + ph->p_memsz) + 399 | load_bias_; 400 | 401 | PCALL(proc, mprotect, &dummy, start, end - start, 402 | PROT_READ); 403 | 404 | if ((int32_t)dummy < 0) 405 | LOG_WARN("failed to mprotect relocations at:" \ 406 | "0x%08x", start); 407 | } 408 | } 409 | 410 | return true; 411 | } 412 | 413 | bool SharedObject::RemapSegments(Process *proc) 414 | { 415 | return RemapSegments(proc, false); 416 | } 417 | 418 | bool SharedObject::RemapSegments(Process *proc, bool add_write) 419 | { 420 | int dummy = 0; 421 | 422 | uint32_t ncmds = ncmds_; 423 | LoadCommand *c = &loadcmds_[0]; 424 | 425 | for (uint32_t i=0; i 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "ELF.h" 20 | #include "SharedObject.h" 21 | #include "Hook.h" 22 | #include "Config.h" 23 | #include "Decoder.h" 24 | #include "Logger.h" 25 | 26 | #define PFUNC_DEFAULT_BODY(n, ...) \ 27 | uint32_t args[] = {__VA_ARGS__}, ret_value; \ 28 | int32_t ret = -1; \ 29 | if (common_functions_.find(#n) == common_functions_.end()) { \ 30 | LOG_ERROR("address of '" #n "unkown for pid %d", pid_); \ 31 | return false; \ 32 | } \ 33 | LOG_EXEC_FAIL(common_functions_[#n], args, sizeof(args)/sizeof(args[0]), \ 34 | &ret_value); \ 35 | \ 36 | if (out) *out = ret_value; \ 37 | return true; 38 | 39 | 40 | #define PFUNC_DEFAULT_IMPL0(n) \ 41 | bool Process::p_##n(uint32_t *out) \ 42 | { PFUNC_DEFAULT_BODY(n, 0); } 43 | 44 | #define PFUNC_DEFAULT_IMPL1(n, a) \ 45 | bool Process::p_##n(uint32_t *out, uint32_t a) \ 46 | { PFUNC_DEFAULT_BODY(n, a); } 47 | 48 | #define PFUNC_DEFAULT_IMPL2(n, a, b) \ 49 | bool Process::p_##n(uint32_t *out, uint32_t a, uint32_t b) \ 50 | { PFUNC_DEFAULT_BODY(n, a, b); } 51 | 52 | #define PFUNC_DEFAULT_IMPL3(n, a, b, c) \ 53 | bool Process::p_##n(uint32_t *out, uint32_t a, uint32_t b, uint32_t c) \ 54 | { PFUNC_DEFAULT_BODY(n, a, b, c); } 55 | 56 | #define PFUNC_DEFAULT_IMPL4(n, a, b, c, d) \ 57 | bool Process::p_##n(uint32_t *out, uint32_t a, uint32_t b, uint32_t c, \ 58 | uint32_t d) \ 59 | { PFUNC_DEFAULT_BODY(n, a, b, c, d); } 60 | 61 | #define PFUNC_DEFAULT_IMPL5(n, a, b, c, d, e) \ 62 | bool Process::p_##n(uint32_t *out, uint32_t a, uint32_t b, uint32_t c, \ 63 | uint32_t d, uint32_t e) \ 64 | { PFUNC_DEFAULT_BODY(n, a, b, c, d, e); } 65 | 66 | #define PFUNC_DEFAULT_IMPL6(n, a, b, c, d, e, f) \ 67 | bool Process::p_##n(uint32_t *out, uint32_t a, uint32_t b, uint32_t c, \ 68 | uint32_t d, uint32_t e, uint32_t f) \ 69 | { PFUNC_DEFAULT_BODY(n, a, b, c, d, e, f); } 70 | 71 | #define LOG_EXEC_FAIL(func, args, nargs, out) \ 72 | if ((ret = Execute(func, args, nargs, out)) < 0) { \ 73 | LOG_ERROR("failed to execute at 0x%08x. Pid: %d, Sig: %d, r15: 0x%08x", \ 74 | func, pid_, ret, out); \ 75 | return false; \ 76 | } 77 | 78 | namespace armhook { 79 | 80 | Process::Process(pid_t pid) 81 | : pid_(pid) 82 | , attached_(false) 83 | {} 84 | 85 | Process::~Process() 86 | { 87 | map::iterator it = libs_.begin(); 88 | 89 | for (; it != libs_.end(); it++) delete (it->second); 90 | 91 | libs_.clear(); 92 | } 93 | 94 | bool Process::Init(string libc_name) 95 | { 96 | if (!InitLibraries()) 97 | return false; 98 | 99 | SharedObject *lib = lib_get(libc_name); 100 | 101 | if (!lib) { 102 | LOG_ERROR("no libc library with name %s", libc_name.c_str()); 103 | return false; 104 | } 105 | 106 | const char *syms[] = {"malloc", "free", "mmap", "mprotect", "open", 107 | "close", "memset"}; 108 | 109 | for (uint32_t i=0; i<(sizeof(syms)/sizeof(syms[0])); i++) { 110 | Elf(Addr) val = 0; 111 | RESOLVE_SYM(lib, syms[i], val, true); 112 | 113 | common_functions_[syms[i]] = val; 114 | } 115 | 116 | return true; 117 | } 118 | 119 | bool Process::InitLibraries() 120 | { 121 | Elf(Addr) start = 0; 122 | Elf(Addr) end = 0; 123 | 124 | char prot[5] = {0}; 125 | char maps[128] = {0}; 126 | char path[256] = {0}; 127 | 128 | snprintf(maps, sizeof(maps), "/proc/%d/maps", pid_); 129 | ifstream is(maps); 130 | if (!is) { 131 | LOG_ERROR("ifstream open /proc/%d/maps failed", pid_); 132 | return false; 133 | } 134 | 135 | string line; 136 | while (getline(is, line)) { 137 | bzero(prot, sizeof(prot)); 138 | bzero(path, sizeof(path)); 139 | 140 | sscanf(line.c_str(), "%lx-%lx %s %*lx %*x:%*x %*u %s", 141 | &start, &end, prot, path); 142 | 143 | if (path[0] != '/') 144 | continue; 145 | 146 | SharedObject *lib = NULL; 147 | if ((lib = lib_get(path))) { 148 | if (!lib->add_segment(start, end, prot)) 149 | LOG_WARN("couldn't add segment at %08x to %s", 150 | start, path); 151 | } else { 152 | lib = new SharedObject(path); 153 | if (lib) { 154 | if (!lib->init() || !lib->set_base(start)) { 155 | LOG_WARN("failed creating library %s", 156 | path); 157 | 158 | delete lib; 159 | continue; 160 | } 161 | 162 | lib->add_segment(start, end, prot); 163 | 164 | libs_[lib->name()] = lib; 165 | } else { 166 | LOG_WARN("failed creating library %s", 167 | path); 168 | } 169 | } 170 | } 171 | 172 | is.close(); 173 | return true; 174 | } 175 | 176 | bool Process::Attach() 177 | { 178 | if (attached_) 179 | return true; 180 | 181 | if (ptrace(PTRACE_ATTACH, pid_, NULL, NULL) < 0) { 182 | int err = errno; 183 | LOG_ERROR("PTRACE_ATTACH with pid %d failed: %s", 184 | pid_, strerror(err)); 185 | return false; 186 | } 187 | 188 | if (!Wait()) 189 | return false; 190 | 191 | attached_ = true; 192 | return true; 193 | } 194 | 195 | bool Process::Detach() 196 | { 197 | bool rc = 0; 198 | 199 | if (!attached_) 200 | return false; 201 | 202 | if (ptrace(PTRACE_DETACH, pid_, NULL, NULL) < 0) { 203 | int err = errno; 204 | LOG_ERROR("PTRACE_DETACH with pid %d failed: %s", 205 | pid_, strerror(err)); 206 | rc = false; 207 | } 208 | 209 | attached_ = false; 210 | 211 | return rc; 212 | } 213 | 214 | bool Process::Wait(int *sig) 215 | { 216 | int status = 0; 217 | 218 | if (waitpid(pid_, &status, 0) < 0) { 219 | int err = errno; 220 | LOG_ERROR("waitpid() on pid %d failed: %s", 221 | pid_, strerror(err)); 222 | return false; 223 | } 224 | 225 | if (sig) 226 | *sig = WSTOPSIG(status); 227 | 228 | if (WIFEXITED(status) || WIFSIGNALED(status)) { 229 | LOG_ERROR("pid %d terminated", pid_); 230 | return false; 231 | } 232 | 233 | return true; 234 | } 235 | 236 | bool Process::Run(int *status) 237 | { 238 | int sig = 0; 239 | 240 | if (!attached_) 241 | return false; 242 | 243 | if (ptrace(PTRACE_CONT, pid_, NULL, NULL) < 0) { 244 | int err = errno; 245 | LOG_ERROR("PTRACE_CONT with pid %d failed: %s", 246 | pid_, strerror(err)); 247 | return false; 248 | } 249 | 250 | Wait(&sig); 251 | 252 | if (status) 253 | *status = sig; 254 | 255 | return true; 256 | } 257 | 258 | bool Process::WriteRegisters(UserRegs *regs) 259 | { 260 | if (!attached_) { 261 | LOG_WARN("WriteRegisters() but not attached"); 262 | return false; 263 | } 264 | 265 | if (ptrace(PTRACE_SETREGS, pid_, NULL, regs) < 0) { 266 | int err = errno; 267 | LOG_ERROR("PTRACE_SETREGS with pid %d failed: %s", 268 | pid_, strerror(err)); 269 | return false; 270 | } 271 | 272 | return true; 273 | } 274 | 275 | bool Process::ReadRegisters(UserRegs *regs) 276 | { 277 | if (!attached_) { 278 | LOG_WARN("ReadRegisters() but not attached"); 279 | return false; 280 | } 281 | 282 | if (ptrace(PTRACE_GETREGS, pid_, NULL, regs) < 0) { 283 | int err = errno; 284 | LOG_ERROR("PTRACE_GETREGS with pid %d failed: %s", 285 | pid_, strerror(err)); 286 | return false; 287 | } 288 | 289 | return true; 290 | } 291 | 292 | bool Process::WriteMemory(uint32_t addr, const void *in, int32_t length, 293 | bool preserve /* = false */) 294 | { 295 | uint32_t value = 0; 296 | uint32_t remain = 0; 297 | int32_t nwrites = length / 4; 298 | 299 | if (!attached_) 300 | return false; 301 | 302 | if ((remain = length % 4)) 303 | nwrites++; 304 | 305 | for (int32_t i=0; i 4) { 431 | WriteMemory(call.uregs[13], (void*)&args[4], (nargs-4)*4); 432 | } 433 | 434 | if (!WriteRegisters(&call)) { 435 | return -1; 436 | } 437 | 438 | int32_t sig = 0; 439 | if (!Run(&sig)) { 440 | return -1; 441 | } 442 | 443 | memset(&call, 0x00, sizeof(call)); 444 | if (!ReadRegisters(&call)) { 445 | return -1; 446 | } 447 | 448 | if (sig != SIGSEGV || call.uregs[15] != 0x4) { 449 | rc = -sig; 450 | *ret = call.uregs[15]; 451 | } else { 452 | *ret = call.uregs[0]; 453 | } 454 | 455 | /* restore old registers */ 456 | if (!WriteRegisters(&old)) { 457 | rc = -1; 458 | } 459 | 460 | return rc; 461 | } 462 | 463 | bool Process::Inject(string path) 464 | { 465 | if (!attached_) 466 | return false; 467 | 468 | /* full path required */ 469 | if (path.find("/") != 0) { 470 | LOG_ERROR("full path required for: %s", path.c_str()); 471 | return false; 472 | } 473 | 474 | if (lib_get(path)) { 475 | LOG_INFO("library already injected: %s", path.c_str()); 476 | return true; 477 | } 478 | 479 | SharedObject *lib = new SharedObject(path); 480 | if (!lib || !lib->init()) { 481 | LOG_ERROR("couldn't create SharedObject for %s", path.c_str()); 482 | return false; 483 | } 484 | 485 | if (!lib->Injectable()) { 486 | LOG_ERROR("library isn't injectable"); 487 | return false; 488 | } 489 | 490 | if (!lib_check_deps(lib)) { 491 | LOG_ERROR("missing library dependencies"); 492 | return false; 493 | } 494 | 495 | if (!lib->Inject(this)) { 496 | LOG_ERROR("failed to inject library"); 497 | return false; 498 | } 499 | 500 | /* add it to our library map */ 501 | libs_[lib->name()] = lib; 502 | 503 | LOG_INFO("library %s injected", path.c_str()); 504 | return true; 505 | } 506 | 507 | bool Process::PrepareHooking() 508 | { 509 | if (!lib_get(Config::Instance()->helper()) && 510 | !Inject(Config::Instance()->helper())) 511 | return false; 512 | 513 | SharedObject *helper = lib_get(Config::Instance()->helper()); 514 | 515 | Elf(Addr) p_setup = 0; 516 | RESOLVE_SYM(helper, "setup", p_setup, true); 517 | 518 | uint32_t args[] = {0x80000000, 30}; 519 | uint32_t loc = args[0], out = 0; 520 | Execute(p_setup, args, 2, &out); 521 | 522 | if ((out & loc) != loc) { 523 | LOG_ERROR("failed to setup trampoline at 0x%08x", loc); 524 | return false; 525 | } 526 | 527 | LOG_DEBUG("helper setup successful"); 528 | 529 | return true; 530 | } 531 | 532 | bool Process::InsertHooks(const std::vector &hooks) 533 | { 534 | bool ret_value = false; 535 | 536 | for (std::vector::const_iterator it = hooks.begin(); 537 | it != hooks.end(); it++) { 538 | InsertHook((*it)); 539 | } 540 | 541 | return ret_value; 542 | } 543 | 544 | bool Process::InsertHook(Hook *hook) 545 | { 546 | if (!attached_) 547 | return false; 548 | 549 | /* check if helper library is injected */ 550 | SharedObject *helper = lib_get(Config::Instance()->helper()); 551 | if (!helper) { 552 | LOG_ERROR("armhook helper library %s isn't injected", 553 | Config::Instance()->helper()); 554 | return false; 555 | } 556 | 557 | /* get function addresses that will help setting up the hook */ 558 | Elf(Addr) p_hook_add = 0; 559 | RESOLVE_SYM(helper, "hook_add", p_hook_add, true); 560 | 561 | Elf(Addr) p_clearcache = 0; 562 | RESOLVE_SYM(helper, "clearcache", p_clearcache, true); 563 | 564 | uint32_t location = hook->location(); 565 | 566 | if (hook->relative()) { 567 | SharedObject *base = lib_get(hook->base()); 568 | if (!base) { 569 | LOG_ERROR("hook base %s is not mapped into memroy", 570 | hook->base()); 571 | return false; 572 | } 573 | 574 | LOG_INFO("hook base [%s]: 0x%08x, offset: 0x%08x", 575 | hook->base(), base->load_start(), hook->location()); 576 | 577 | location += base->load_start(); 578 | } 579 | 580 | MemorySegment *seg = NULL; 581 | for (map::iterator it = libs_.begin(); 582 | it != libs_.end(); it++) { 583 | seg = (it->second)->get_segment(location); 584 | if (seg) break; 585 | } 586 | 587 | if (!seg) { 588 | LOG_ERROR("memory location 0x%08x doesn't seem to be mapped", 589 | location); 590 | return false; 591 | } 592 | 593 | if (!(seg->prot & PROT_EXEC)) 594 | LOG_WARN("segment isn't mapped executable, protection: %d", 595 | seg->prot); 596 | 597 | /* check if the library is injected that contains the handler */ 598 | SharedObject *handler = lib_get(hook->library()); 599 | if (!handler && !Inject(hook->library())) { 600 | LOG_ERROR("handler library %s couldn't be injected", 601 | hook->library()); 602 | return false; 603 | } 604 | 605 | handler = lib_get(hook->library()); 606 | 607 | /* get the address of the handler function */ 608 | Elf(Addr) p_handler = 0; 609 | RESOLVE_SYM(handler, hook->handler(), p_handler, true); 610 | 611 | uint8_t detour[64] = {0}, detour_size = sizeof(detour); 612 | if (!hook->GetDetour(detour, &detour_size)) { 613 | LOG_ERROR("couldn't retrieve detour stub for hook"); 614 | return false; 615 | } 616 | 617 | LOG_DEBUG("detour is %d bytes long", detour_size); 618 | 619 | /* get bytes at location and check how many need to be saved */ 620 | uint8_t bytes[128] = {0}, save_amount = 0; 621 | 622 | /* don't forget to read from a 2 byte aligned address */ 623 | if (!ReadMemory((location & 0xfffffffe), bytes, sizeof(bytes))) { 624 | LOG_ERROR("couldn't read at location 0x%08x", location); 625 | return false; 626 | } 627 | 628 | Decoder *decoder = new Decoder(); 629 | if (!decoder->BytesToSave(bytes, (location & 1) ? false : true, 630 | detour_size, &save_amount) || save_amount < detour_size) { 631 | LOG_ERROR("failed to get amount of bytes to save: %d", 632 | save_amount); 633 | return false; 634 | } 635 | delete decoder; 636 | 637 | if (save_amount > detour_size) 638 | LOG_INFO("detour size is smaller then prolog bytes to save"); 639 | 640 | /* use helper library to set the values for the array */ 641 | int32_t h_idx = -1, ret = -1; 642 | uint32_t args[] = {p_handler, location, save_amount}; 643 | LOG_EXEC_FAIL(p_hook_add, args, sizeof(args)/sizeof(args[0]), 644 | (uint32_t*)&h_idx); 645 | 646 | if (h_idx < 0) { 647 | LOG_ERROR("register hook failed: %d", h_idx); 648 | return false; 649 | } 650 | 651 | uint8_t padding = save_amount - detour_size; 652 | if (padding % 2) { 653 | LOG_ERROR("misaligned function prolog"); 654 | return false; 655 | } 656 | 657 | uint8_t nops[16]; 658 | if (padding && !hook->GetNops(nops, padding)) { 659 | LOG_ERROR("failed to retrieve nops for padding"); 660 | return false; 661 | } 662 | 663 | /* write nop padding first (if required) */ 664 | if (padding && !WriteMemory((location & 0xfffffffe), nops, padding)) { 665 | LOG_ERROR("couldn't write padding of size %d to 0x%08x", 666 | padding, (location & 0xffffffe)); 667 | return false; 668 | } 669 | 670 | /* now overwrite the function prolog, mind alignment */ 671 | if (!WriteMemory((location & 0xfffffffe) + padding, detour, 672 | detour_size, true)) { 673 | LOG_ERROR("couldn't write to memory at 0x%08x", 674 | (location & 0xffffffe) + padding); 675 | return false; 676 | } 677 | 678 | LOG_INFO("address: 0x%08x hooked with detour handler: %s", 679 | location, hook->handler()); 680 | 681 | uint32_t dummy = 0; 682 | args[0] = seg->start; 683 | args[1] = seg->end; 684 | 685 | int32_t out = -1; 686 | LOG_EXEC_FAIL(p_clearcache, args, 2, &dummy); 687 | LOG_DEBUG("cache cleared for hook"); 688 | 689 | return true; 690 | } 691 | 692 | /* default implementations for calling a libc function inside the process */ 693 | PFUNC_DEFAULT_IMPL1(malloc, size); 694 | PFUNC_DEFAULT_IMPL1(free, ptr); 695 | PFUNC_DEFAULT_IMPL6(mmap, addr, len, prot, flags, fd, offset); 696 | PFUNC_DEFAULT_IMPL3(mprotect, addr, len, prot); 697 | PFUNC_DEFAULT_IMPL3(open, pathname, flags, mode); 698 | PFUNC_DEFAULT_IMPL1(close, fd); 699 | PFUNC_DEFAULT_IMPL3(memset, s, c, n); 700 | 701 | SharedObject* Process::lib_get(string name) 702 | { 703 | if (name.find_last_of("/") != string::npos) 704 | name = name.substr(name.find_last_of("/")+1); 705 | 706 | map::iterator it = libs_.find(name); 707 | 708 | if (it == libs_.end()) 709 | return NULL; 710 | 711 | return it->second; 712 | } 713 | 714 | SharedObject* Process::lib_find(const char *symbol, 715 | const vector &needed) 716 | { 717 | Elf(Addr) loc; 718 | SharedObject *lib = NULL; 719 | 720 | vector::const_iterator it = needed.begin(); 721 | for (; it != needed.end(); it++) { 722 | lib = lib_get((*it)); 723 | lib->resolve(symbol, loc); 724 | 725 | if (loc) 726 | return lib; 727 | } 728 | 729 | return NULL; 730 | } 731 | 732 | bool Process::lib_check_deps(SharedObject *lib) 733 | { 734 | vector::const_iterator it = lib->needed().begin(); 735 | 736 | for (; it != lib->needed().end(); it++) { 737 | if (lib_get((*it)) == NULL) { 738 | LOG_ERROR("dependency library: %s not loaded", 739 | (*it).c_str()); 740 | return false; 741 | } 742 | } 743 | 744 | return true; 745 | } 746 | 747 | } /* namespace armhook */ 748 | --------------------------------------------------------------------------------