简介
在第一篇文章 使用 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 加载和统一调度,类的定义如下:
#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 0x300000000using 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() {// ...return IB_SUCCESS;}
此外,我们需要通过全局单例 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 即可:
加载器的代码也很简单,就是通过 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;
}
最后就可以愉快的调试了,如果遇到了搜索路径、运行时问题可以参考第一篇文章的解决方案。
