教程:利用Aide在Android设备上构建原生模组

来自Minecraft基岩版开发Wiki
  • Nmod是一种用来更改Minecraft的工具。具体实现是使用 Cydia Substrate 来Hook掉libminecraftpe.so里的函数。
  • 本教程不讲解c++的语法,因此你需要一定的c++基础
  • 本教程的目的是获取到编译完成的so文件,如何进行使用参见InnerCore原生模组教程
  • so的文件位于

    ./libs/armeabi-v7a

    目录下

链接[编辑]

第一步:环境配置[编辑]

  1. 确认你当前Android设备的版本
  2. 进入 Aide高级版 官网并下载
  3. 下载好后,进入app页面(这里以2.8.3版本为例)
  4. 点击右上角
  5. 点击更多
  6. 点击设置
  7. 点击构建&运行
  8. 点击管理Native代码支持包
  9. 如果你的 Android设备版本 < 13.0 请选择在线安装,如果 Android设备版本 >= 13.0 请指定NDK路径,手动安装

第二步:下载模板[编辑]

  1. 如果你有能力自己通过内置模板进行改编,可跳过本步骤,否则请下载我给出的Native模板,此模板在原有的基础上进行了一定程度的改编,你可以以此为基础,创建你自己的原生模组

内容讲解[编辑]

Native模板中的库[编辑]

hook.h

//
// Created by zheka on 18/07/19.
//

#include <functional>

#ifndef HORIZON_HOOK_H
#define HORIZON_HOOK_H

typedef long long int64_t;

namespace SubstrateInterface {
    /** change protection mode of given memory address and surrounding pages
     * - address - address to change protection
     * - offset - offset in pages before page, that contains address
     * - size - size in pages
     * - mode - protection mode
     * returns 0 on success or error code
     * */
    int protect(void* address, int offset, int size, int mode);

    /**
     * technical hook function, use HookManager::addCallback
     * - origin - function to hook address
     * - hook - function to replace
     * - result - pointer, to pass original hooked function
     * returns true on success
     */
    bool hook(void *origin, void *hook, void **result);
}

/**
 * core namespace for callback creation
 */
namespace HookManager {
    enum CallbackType {
        // usual listener, does not create any special conditions
        LISTENER = 0,

        // called as target, will force all RETURN callbacks to be called after it
        REPLACE = 4
    };

    enum CallbackTarget {
        // called before target call and cannot be prevented
        CALL = 0,

        // called after "CALL" callbacks, can be prevented by ones before it
        ACTION = 1,

        // called just before target call if its not prevented, cannot be prevented by other such callback
        TARGET = 2,

        // called after target or replace callback is called to process return value and change it if required. RETURN | REPLACE combination is illegal
        RETURN = 3
    };

    enum CallbackParams {
        // should be passed, if callback returns result, otherwise engine will ignore it
        RESULT = 16,

        // should be passed, if callback requires controller, HookManager::CallbackController* will be passed as first parameter
        CONTROLLER = 32
    };

    // priority for adding callbacks, greater priority = earlier call inside one CallbackTarget
    enum CallbackPriority {
        PRIORITY_MIN = -10,
        PRIORITY_LESS = -5,
        PRIORITY_DEFAULT = 0,
        PRIORITY_GREATER = 5,
        PRIORITY_MAX = 10
    };

    // not implemented for now
    struct CallbackAddStatus {

    };

    /**
     * used to access callback logic, result, status, ect.
     * will be passed as first parameter, if CONTROLLER flag is given
     */
    struct CallbackController {
        // returns, if callback was prevented
        bool isPrevented();

        // returns, if callback was replaced
        bool isReplaced();

        // returns, if contains result from previous calls
        bool hasResult();

        // returns saved result
        void* getResult();

        // prevents callback, all future calls wont happen, this will force all RETURN callbacks to execute right after
        void prevent();

        // replaces callback, prevents target and TARGET callbacks from being called, equivalent to REPLACE flag effect
        void replace();

        // returns pointer to target function
        void* getTarget();

        // calls target with given params and casts result to R, usage: int result = controller->call<int>(1, "a");
        template<typename R, typename... ARGS>
        R call (ARGS... args)  {
            return ((R(*)(ARGS...)) getTarget())(args ...);
        }

        template<typename R, typename... ARGS>
        R callAndReplace (ARGS... args)  {
            replace();
            return ((R(*)(ARGS...)) getTarget())(args ...);
        }
    };

    // technical struct
    struct Hook {
    public:
        void* getCaller();
        void* getTarget();
        void* getAddress();
    };


    template<typename R, typename... ARGS>
    class CallInterface;

    /*
     * represents interface to call specified function, its callback or target
     * CallInterface is immune to future addCallback calls for its target method
     */
    template<typename R, typename... ARGS>
    class CallInterface<R(ARGS...)> {
    public:
        // calls target (original) function
        R target(ARGS... args);

        // calls function callback, or original function, if no callbacks exist
        R hook(ARGS... args);

        // equivalent to hook(), but as operator
        R operator()(ARGS... args);

        // creates same interface for other type
        template<typename T>
        CallInterface<T>* cast();

        // returns target (original) function address
        void* getTargetCaller();

        // returns hook function address
        void* getHookCaller();

        void* getAddrHookCaller();
    };

    /*
     * returns call interface for given function, call interface allows to call both callback and original function and immune to creating callbacks of this method
     * usage:
     *  auto method = HookManager::getCallInterface<void*(int, int)>(...);
     */
    template<typename T>
    CallInterface<T>* getCallInterface(void* addr);

    // -- function pointers --
    /*
     * adds func as callback for address with given params and priority
     * - addr - address to add
     * - func - function pointer, cast to void*
     * - flags - all flags are described above, default combination is CALL | LISTENER
     * - priority - higher priority - earlier call, default is DEFAULT_PRIORITY (0)
     * */
    CallbackAddStatus* addCallback(void* addr, void* func, int flags, int priority);
    CallbackAddStatus* addCallback(void* addr, void* func, int flags);
    CallbackAddStatus* addCallback(void* addr, void* func);


    // -"- with usage of LAMBDA()
    CallbackAddStatus* addCallback(void* addr, int64_t lambda, int flags, int priority);
    CallbackAddStatus* addCallback(void* addr, int64_t lambda, int flags);
    CallbackAddStatus* addCallback(void* addr, int64_t lambda);
}

#define LAMBDA(ARGS, CODE, VALUES, ...) ((int64_t) new std::function<void ARGS>([VALUES] ARGS CODE))



#endif //HORIZON_HOOK_H

该库提供了两种hook方法

  • SubstrateInterface
  • HookManager
  • 如果你想写独立的原生模组,推荐使用SubstrateInterface,HookManager在多数情况下应用于与Java和JavaScript的交互中
  • 值得一提的是,在比较新的版本中HookManager方法可能存在报错情况

mod.h

//
// Created by zheka on 18/07/15.
//

#include <jni.h>
#include <functional>
#include <vector>
#include <fstream>
#include <map>
#include "definitions.h"

#ifndef HORIZON_MOD_H
#define HORIZON_MOD_H

class Module;

/* represents mod library instance */
class ModLibrary {
public:
    // library handle ptr
    void* handle = nullptr;

    // initialization result
    int result = 0;

    // returns list of all modules
    std::vector<Module*> getModules();
};

/*
 * Modules are main structural units of most mod libraries, that can receive and handle events, contain information etc.
 * - Custom module class must extend Module class or some of its subclasses
 * - Modules must be instantiated in library entry point (MAIN {...})
 * - All initialization logic, including adding callbacks, must happen inside module's initialize() method
 *
 * Properties:
 * - Modules can have name IDs and inherit other modules, this will be represented in UI to show hierarchy
 * - Module name ID is used to search its description in manifest
 *
 * Tips:
 * - Modules are used to divide all code into separate logic pieces
 * - You should have global variables of module instances, created in MAIN {...}
 * - All global variables should be put inside modules and accessed through its instances
 * - All API events should be processed inside modules through addListener
 * - Modules also used for profiling and crash handling
 */
class Module {
private:
    ModLibrary* library = NULL;
    Module* parent = NULL;
    const char* id = NULL;
    bool _isInitialized = false;

    std::map<std::string, std::vector<std::function<void()>*>> listeners;

public:
    // receives parent or name ID or both, root module must have name ID
    Module(Module* parent, const char* id);
    Module(const char* id);
    Module(Module* parent);

    // adds mod listener method, that will be called upon given event
    template <typename T>
    void addListener(std::string event, std::function<T> const& function);

    // invokes all listeners for given event
    template <typename... ARGS>
    void onEvent(std::string event, ARGS... args);

    // all initialization must happen here
    virtual void initialize();

    // separate method for java initialization
    virtual void initializeJava(JNIEnv* env);

    // returns parent module
    Module* getParent();
    // returns name ID
    const char* getNameID();
    // returns type, that used inside UI
    virtual const char* getType();
    // used to create separate mod log, used, if current profiled section belongs to this module
    virtual std::ofstream* getLogStream();

    bool isInitialized();
    // returns mod library, this module belongs to
    ModLibrary* getLibrary();
};

/*
 * same class as module, but interpreted as mod inside UI, root module of your mod should implement this class
 */
class Mod : public Module {
public:
    Mod(Module *parent, const char *id);
    Mod(Module *parent);
    Mod(const char *id);
};


namespace ModuleRegistry {
    // returns all modules for given name ID
    std::vector<Module*> getModulesById(std::string id);

    // invokes event with given name and args for all modules, if filter function returned true
    template <typename... ARGS>
    void onFilteredEvent(std::string event, std::function<bool(Module*)> filter,  ARGS... args);

    // invokes event with given name and args for all modules
    template <typename... ARGS>
    void onEvent(std::string event, ARGS... args);

    // invokes event with given name and args for all modules with given name ID
    template <typename... ARGS>
    void onTargetEvent(std::string module, std::string event, ARGS... args);

    // invokes method for all modules
    void onAction(std::function<void(Module*)> action);
};

namespace SignalHandler {
    // initializes signal handles inside given process, this usually happens automatically
    void initialize();
}



#define JNI_VERSION JNI_VERSION_1_4

/**
 * describes library entry point
 * - There must be only one entry point per library
 * - In most cases used only for module instantiation
 * - Inside its body there are variables ModLibrary* library - instance of this mod library and int* result - pointer to initialization result (0 is OK)
 * MAIN {
 *
 * }
 */

#define NO_JNI_MAIN \
            void __entry(ModLibrary* library, int* result); \
            int __mod_main(ModLibrary* library) {\
                int result = 0; \
                SignalHandler::initialize();\
                __entry(library, &result);\
                return result;\
            }\
            void __entry(ModLibrary* library, int* result)

#define MAIN \
            NO_JNI_MAIN
            


#endif //HORIZON_MOD_H
  • 该库主要负责原生模组之间的隔离和原生模组的运行
  • 本库已经过编者的修改,你可以重载JNI_OnLoad方法来获取jvm
  • 一定要注意,每个原生模组的Module名称不可相同,否则会出现闪退情况

symbol.h

//
// Created by zheka on 18/07/19.
//

#ifndef HORIZON_SYMBOL_H
#define HORIZON_SYMBOL_H

#include "definitions.h"

/*
 * represents dynamic library handle
 * */
class DLHandle {
private:
    const char* name = "<unknown>";

    void* handle = nullptr;

    // if dlsym failed, uses elf elf_dlsym instead
    bool elf_support = true;

public:
    void* symbol(const char* name);
};


/*
 * interface to access dynamic libraries and symbols
 * */
namespace DLHandleManager {
    DLHandle* getHandle(const char* name);

    /*
     * initializes dynamic library handle, that can be further accessed by SYMBOL macros
     * name - full library name
     * key - name to access handle from SYMBOL, equals to name by default
     * flags - flags, that are passed to dlopen, RTLD_LAZY by default
     * support_elf, if dlsym fails, tries internal method, based on ELF format, true by default
     * */
    DLHandle* initializeHandle(const char* name, const char* key, int flags, bool support_elf);
    DLHandle* initializeHandle(const char* name, int flags, bool support_elf);
    DLHandle* initializeHandle(const char* name, const char* key, int flags);
    DLHandle* initializeHandle(const char* name, int flags);
    DLHandle* initializeHandle(const char* name, const char* key);
    DLHandle* initializeHandle(const char* name);

    // used in macros
    void* _symbol(DLHandle* handle, const char* symbol);
    void* _symbol(const char* dlname, const char* symbol);
}

// converts any type to (void*)
#define ADDRESS(X) ((void*) X)

// returns symbol address, if search failed, returns NULL and writes error to log
// HANDLE - DLHandle* or string, representing dynamic library to search ("mcpe" represents minecraft pe library)
// NAME - symbol name
#define SYMBOL(HANDLE, NAME) (DLHandleManager::_symbol(HANDLE, NAME))

// converts function pointer to (void*)
#define FUNCTION(X) ((void*) ((unsigned long long) &(X)))

#endif //HORIZON_SYMBOL_H
  • 这个库是原生模组想要运行的关键库,你可以通过该库获取so文件的Handle,从而进行操作

其它Hook方法[编辑]

dlfcn库[编辑]

  • 如果你熟悉dlfcn库的话,可以通过dlopen,dlsym函数之间的配合,通过函数名称实现Hook
  • 如果你熟悉内存地址,也可以省略符号名,使用内存地址进行Hook(此方法编者未研究)

头文件[编辑]

  • 如果你接触过ModdedPE,并了解它的模组编写方式也可以使用头文件的方法进行Hook