简介

在第一篇文章 使用 libiblessing-core 独立开发 Scanner 中我们介绍了基于 iblessing-core 独立开发一个 Scanner 的方法,本文将介绍如何将其以 dylib 的形式发布为插件,从而可以被 iblessing 壳程序直接加载和运行,成为 iblessing 的一部分。

工程环境搭建

上一篇文章中我们实现了一个简单地 objdump,他是通过依赖 iblessing-core 编译出的可执行文件,我们这里采用相同的代码,新建一个 dylib 工程,Xcode 环境配置请参考第一节中的内容 https://www.yuque.com/yansugu/nkwc1m/wth52l#Oq1GS 不同的是这里我们新建的是一个 MacOS Dynamic Library 工程。

代码编写和构建

这里我们不再以 main 为入口,而是写一个继承 Scanner 的类,通过 iblessing 加载和统一调度,类的定义如下:

  1. #include <iostream>
  2. #include <iblessing-core/v2/vendor/capstone/capstone.h>
  3. #include <iblessing-core/v2/iblessing-core.hpp>
  4. #include <iblessing-core/scanner/dispatcher/ScannerDispatcher.hpp>
  5. #include <iblessing-core/v2/util/termcolor.h>
  6. #include <iblessing-core/v2/util/StringUtils.h>
  7. #define UnicornStackTopAddr 0x300000000
  8. using namespace std;
  9. using namespace iblessing;
  10. class ObjDumpScanner : public Scanner {
  11. public:
  12. ObjDumpScanner(std::string name, std::string desc): Scanner(name, desc) {}
  13. virtual ~ObjDumpScanner() {};
  14. virtual int start();
  15. };
  16. int ObjDumpScanner::start() {
  17. // ...
  18. return IB_SUCCESS;
  19. }

此外,我们需要通过全局单例 ScannerDispatcher 将其注册到 iblessing,这里我们可以通过 constructor 实现:

__attribute__((constructor))
static void onRegister() {
    ScannerDispatcher::getInstance()->registerScanner("otool", []() {
        return new ObjDumpScanner("otool", "obj dump tool");
    });
}

接下来,在 start 中拿到 iblessing 注入的 inputPath 和 options 即可:

int ObjDumpScanner::start() {
    string filePath = this->inputPath;
     shared_ptr<MachO> macho = MachO::createFromFile(filePath);
    assert(macho->loadSync() == IB_SUCCESS);
    ib_section_64 *textSect = macho->context->fileMemory->textSect;
    assert(textSect != nullptr);
    printf("[+] find __TEXT,__text at 0x%llx\n", textSect->addr);
    // ...
}

完整代码如下:

//
//  otool_scanner.cpp
//  otool-scanner
//
//  Created by Soulghost on 2021/5/3.
//

#include <iostream>
#include <iblessing-core/v2/vendor/capstone/capstone.h>
#include <iblessing-core/v2/iblessing-core.hpp>
#include <iblessing-core/scanner/dispatcher/ScannerDispatcher.hpp>

#include <iblessing-core/v2/util/termcolor.h>
#include <iblessing-core/v2/util/StringUtils.h>

#define UnicornStackTopAddr      0x300000000

using namespace std;
using namespace iblessing;

class ObjDumpScanner : public Scanner {
public:
    ObjDumpScanner(std::string name, std::string desc): Scanner(name, desc) {}

    virtual ~ObjDumpScanner() {};
    virtual int start();
};

int ObjDumpScanner::start() {
    string filePath = this->inputPath;
    shared_ptr<MachO> macho = MachO::createFromFile(filePath);
    assert(macho->loadSync() == IB_SUCCESS);
    ib_section_64 *textSect = macho->context->fileMemory->textSect;
    assert(textSect != nullptr);
    printf("[+] find __TEXT,__text at 0x%llx\n", textSect->addr);

    shared_ptr<Memory> memory = Memory::createFromMachO(macho);
    assert(memory->loadSync() == IB_SUCCESS);

    shared_ptr<Objc> objc = Objc::create(macho, memory);
    objc->loadClassList();
    objc->loadCategoryList();

    shared_ptr<Dyld> dyld = Dyld::create(macho, memory, objc);
    dyld->doBindAll();

    objc->realizeClasses([&](ObjcClassRuntimeInfo *info, uint64_t current, uint64_t total) {
#ifndef XcodeDebug
        fprintf(stdout, "\r\t[*] realize classes %lld/%lld (%.2f%%)", current, total, 100.0 * current / total);
        fflush(stdout);
#else
        if (current % 1000 == 0) {
            fprintf(stdout, "\r\t[*] realize classes %lld/%lld (%.2f%%)", current, total, 100.0 * current / total);
            fflush(stdout);
        }
#endif
    });

    // setup engine
    uc_engine *uc = memory->virtualMemory->getEngine();
    uint64_t unicorn_sp_start = UnicornStackTopAddr;
    uc_reg_write(uc, UC_ARM64_REG_SP, &unicorn_sp_start);
    // set FPEN on CPACR_EL1
    uint32_t fpen;
    uc_reg_read(uc, UC_ARM64_REG_CPACR_EL1, &fpen);
    fpen |= 0x300000; // set FPEN bit
    uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpen);

    shared_ptr<VirtualMemoryV2> vm2 = memory->virtualMemory;
    shared_ptr<SymbolTable> symtab = macho->context->symtab;
    shared_ptr<ObjcRuntime> rt = objc->getRuntime();

    // dis all
    csh handle;
    assert(cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle) == CS_ERR_OK);
    // enable detail
    cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);

    // setup unicorn virtual memory
    uint64_t addr = textSect->addr;
    uint64_t end = textSect->addr + textSect->size;
    string last_mnemonic;
    while (addr < end) {
        bool success;
        uint32_t code = vm2->read32(addr, &success);
        if (!success) {
            cout << termcolor::yellow << "[-] Warn: Failed to read data from";
            cout << StringUtils::format(" 0x%llx", addr);
            cout << termcolor::reset << endl;
            last_mnemonic = "";
            addr += 4;
            continue;
        }

        cs_insn *insn = nullptr;
        size_t count = cs_disasm(handle, (uint8_t *)&code, 4, addr, 0, &insn);
        if (count != 1) {
            cout << termcolor::yellow << "[-] Warn: Failed to disassemble from";
            cout << StringUtils::format(" 0x%llx", addr);
            cout << termcolor::reset << endl;
            addr += 4;
            last_mnemonic = "";
            continue;
        }

        uc_emu_start(uc, addr, addr + 4, 0, 1);
        uc_emu_stop(uc);

        Symbol *sym = symtab->getSymbolByAddress(addr);
        if (sym && sym->name.size() > 0) {
            printf("%s:\n", sym->name.c_str());
        }

        string comment;
        uint64_t resolvedAddress = 0;
        uint64_t branchAddress = 0;
        if (strcmp(last_mnemonic.c_str(), "adrp") == 0 &&
            strcmp(insn->mnemonic, "add") == 0) {
            if (UC_ERR_OK != uc_reg_read(uc, insn->detail->arm64.operands[1].reg, &resolvedAddress)) {
                resolvedAddress = 0;
            }
        }
        if (strcmp(last_mnemonic.c_str(), "adrp") == 0 &&
            strcmp(insn->mnemonic, "ldr") == 0) {
            if (UC_ERR_OK != uc_reg_read(uc, insn->detail->arm64.operands[0].reg, &resolvedAddress)) {
                resolvedAddress = 0;
            }
        }
        if (strcmp(insn->mnemonic, "b") == 0 ||
            strcmp(insn->mnemonic, "bl") == 0 ||
            strncmp(insn->mnemonic, "b.", 2) == 0 ||
            strncmp(insn->mnemonic, "bl.", 2) == 0 ||
            strcmp(insn->mnemonic, "cbz") == 0 ||
            strcmp(insn->mnemonic, "cbnz") == 0) {
            branchAddress = insn->detail->arm64.operands[0].imm;
        }

        if (resolvedAddress > 0) {
            pair<string, string> segInfo = vm2->querySegInfo(resolvedAddress);
            if (segInfo.second == "__objc_selrefs") {
                bool success;
                uint64_t selAddr = vm2->read64(resolvedAddress, &success);
                if (success) {
                    char *sel = vm2->readString(selAddr, 1000);
                    if (sel) {
                        comment = "Objc selector ref: " + string(sel);
                        free(sel);
                    }
                }
            } else if (segInfo.second == "__objc_methname") {
                char *sel = vm2->readString(resolvedAddress, 1000);
                if (sel) {
                    comment = "Objc selector ref: " + string(sel);
                    free(sel);
                }
            } else if (segInfo.second == "__cfstring") {
                char *content = vm2->readAsCFStringContent(resolvedAddress);
                if (content) {
                    comment = StringUtils::format("Objc cfstring ref: @\"%s\"", content);
                    free(content);
                }
            } else if (segInfo.second == "__objc_classrefs") {
                uint64_t classAddr = vm2->read64(resolvedAddress, NULL);
                Symbol *sym = symtab->getSymbolByAddress(classAddr);
                if (sym) {
                    comment = "Objc class ref: " + sym->name;
                }
            }
        }

        if (branchAddress > 0) {
            Symbol *sym = symtab->getSymbolByAddress(branchAddress);
            if (sym) {
                if (strncmp(sym->name.c_str(), "_objc_msgSend", strlen("_objc_msgSend")) == 0) {
                    ObjcClassRuntimeInfo *info = nullptr;
                    uint64_t classAddr = 0;
                    uc_reg_read(uc, UC_ARM64_REG_X0, &classAddr);
                    if (classAddr != 0) {
                        info = rt->getClassInfoByAddress(addr);
                    }

                    char *sel = NULL;
                    uint64_t selAddr = 0;
                    uc_reg_read(uc, UC_ARM64_REG_X1, &selAddr);
                    if (selAddr != 0) {
                        sel = vm2->readString(selAddr, 1000);
                    }

                    comment = "Objc message: ";
                    string prefix, classExpr, selExpr;
                    if (info && sel) {
                        ObjcMethod *method = rt->inferNearestMethod("?", info->className, string(sel));
                        if (method) {
                            prefix = method->isClassMethod ? "+" : "-";
                        } else {
                            prefix = "?";
                        }
                    } else {
                        prefix = "?";
                    }

                    bool superCall = false;
                    if (strncmp(sym->name.c_str(), "_objc_msgSendSuper", strlen("_objc_msgSendSuper")) == 0) {
                        superCall = true;
                    }

                    if (info) {
                        if (superCall) {
                            classExpr = StringUtils::format("[%s super]", info->className.c_str());
                        } else {
                            classExpr = info->className;
                        }
                    } else {
                        string expr;
                        Symbol *sym = symtab->getSymbolByAddress(classAddr);
                        if (sym) {
                            prefix = "+";
                            if (sym->name.rfind("_OBJC_") != -1 &&
                                sym->name.rfind("_$_") != -1) {
                                expr = StringUtils::split(sym->name, '$')[1].substr(1);
                            } else {
                                expr = sym->name;
                            }
                        } else {
                            expr = "x0";
                        }
                        if (superCall) {
                            classExpr = "[" + expr + " super]";
                        } else {
                            classExpr = expr;
                        }
                    }

                    if (sel) {
                        selExpr = StringUtils::format("%s", sel);
                    } else {
                        selExpr = "?";
                    }

                    if (sel) {
                        free(sel);
                    }

                    comment += StringUtils::format("%s[%s %s]", prefix.c_str(), classExpr.c_str(), selExpr.c_str());
                } else {
                    if (sym->isStub) {
                        comment = StringUtils::format("symbol stub for: %s", sym->name.c_str());
                    } else {
                        comment = StringUtils::format("%s %s", insn->mnemonic, sym->name.c_str());
                    }
                }
            }
        }

        printf("%016llx      %-8s %s", insn->address, insn->mnemonic, insn->op_str);
        if (comment.length() > 0) {
            printf(" ; %s\n", comment.c_str());
        } else {
            printf("\n");
        }
        last_mnemonic = insn->mnemonic;
        addr += 4;
    }
    return IB_SUCCESS;
}

__attribute__((constructor))
static void onRegister() {
    ScannerDispatcher::getInstance()->registerScanner("otool", []() {
        return new ObjDumpScanner("otool", "obj dump tool");
    });
}

此时即可构建出 dylib,例如我们这里构建的是 libotool-scanner.dylib。

调试插件

将插件 dylib 化以后,想要调试有两个办法,一个是拉 iblessing 源码将其作为 builtin scanner 直接调试,另一个则是写一个简单地加载器本地调试,这里我们介绍后者。

像第一篇文章介绍的那样,在插件工程中新建一个可执行文件的 Target,然后依赖 libiblessing-core ,加载插件并手动调度 ScannerDispatcher 即可:
image.png
加载器的代码也很简单,就是通过 dlopen 加载插件然后手动 start 它:

#include <iostream>
#include <dlfcn.h>
#include <iblessing-core/scanner/dispatcher/ScannerDispatcher.hpp>

using namespace std;
using namespace iblessing;

int main(int argc, const char * argv[]) {
    ScannerDispatcher *dispatcher = ScannerDispatcher::getInstance();
    printf("count of scanners %lu\n",  dispatcher->allScanners().size());
    void *handle = dlopen("/Users/soulghost/Downloads/libotool-scanner.dylib", RTLD_NOW);
    printf("%p count of scanners %lu %s\n", handle, dispatcher->allScanners().size(), dlerror());

    Scanner *s = dispatcher->getInstance()->prepareForScanner("otool", {}, "/Users/soulghost/Downloads/ipa/MobileSlideShow", "");
    s->start();
    return 0;
}

最后就可以愉快的调试了,如果遇到了搜索路径、运行时问题可以参考第一篇文章的解决方案。
image.png