首页
社区
课程
招聘
[原创]自實現Linker加載so
发表于: 2024-6-28 17:28 27385

[原创]自實現Linker加載so

2024-6-28 17:28
27385

前一陣子在研究so加固,發現其中涉及自實現的Linker加載so的技術,而我對此知之什少,因此只好先來學習下Linker的加載流程。

本文參考AOSP源碼和r0ysue大佬的文章( 不知為何文中給出的那個demo我一直跑不起來 )來實現一個簡單的自實現Linker Demo。

環境:Pixel1XLAOSP - Oreo - 8.1.0_r81

Linker在加載so時大致可以分成五步:

利用open+mmap來將待加載的so文件映射到內存空間,存放在start_addr_中。然後調用Read函數來獲取ehdr、phdr等信息。

Read函數實現如下,調用ReadElfHeaderReadProgramHeaders來讀取ehdr和phdr。

AOSP源碼的Read中還會讀取Section Headers和Dynamic節,一開始我也有實現這部份的邏輯,但後來發現讀取後的信息根本沒有被用到,因此就把這部份給刪了。

ReadElfHeader的實現如下,直接通過memcpy來賦值。

ReadProgramHeaders的實現直接copy源碼就可以,本質上還是內存映射的過程。

調用Load來載入so。

Load的實現如下:

ReserveAddressSpace用於生成一片新的內存空間,之後的操作基本上都是在這片內存空間進行。LoadSegmentsFindPhdr用於將待加載so的對應信息填充到此內存空間。

最後要修正so,將當前so修正為待加載的so,這部份放到後面來解析。

ReserveAddressSpace的具體實現如下,先計算出load_size_mmap一片內存,在我這個demo中min_vaddr0,因此load_start_ == load_bias_load_bias_代表的就是這片內存,而這片內存是用來存放待加載的so。

LoadSegments的具體實現如下,遍歷Program Header Table將所有type為PT_LOAD的段加載進內存,源碼中是采用mmap來映射,但我嘗試後發現會有權限問題,因而采用memcpy的方案。

FindPhdr的具體實現如下,簡單來說就是將Phdr信息填充進load_bias_那片內存。

Load函數最後是在對soinfo的修正,將當前so( 加載器 )修正為待加載的so。AOSP源碼中的si_是通過特定方法new出來的全新soinfo,而我看大多數文章都是獲取當前so作為si_,然後修正其中的信息。

本來是想嘗試按AOSP源碼那樣new一個soinfo看看結果有什麼不同,但最終被soinfo結構的複雜性勸退了。

修正so的第一步是要獲取當前so的soinfo對象,從這篇文章發現find_containing_library這個函數,似乎可以一步到位直接獲取soinfo對象。該函數位於linker64中,將它拉入IDA,能直接搜尋到該函數,這意味著能夠「借用」這個函數。

想要「借用」linker64裡的find_containing_library,需要知道linker64在內存的基址和find_containing_library的函數偏移( 相對基址的偏移 ),前者可以通過遍歷/proc/self/maps來取得,而後者的獲取有以下兩種思路:

成功獲取find_containing_library地址後,強轉成FunctionPtr函數指針後即可調用,參數為當前so的地址( 同樣是遍歷maps取得的 ),最終會返回當前so的soinfo對象。

get_export_func的實現如下,主要依賴於elf的文件結構,可以參考下我之前寫的文章,大致原理如下:

成功獲取si_後要修改其對應屬性。在這裡我遇到一個很玄學的問題,就是一開始不知為什麼死活修改不了si_的屬性,一改就會報內存讀寫的錯,即使mprotect賦予可讀可寫權限也無用,嘗試了各種方法都無用,在這卡了我好幾天,直到某次重啟手機後就突然好了???

soinfo結構體定義在bionic/linker/linker_soinfo.h中。

將它copy到本地後會有很多報錯,一開始我是將那些沒有用到又報紅的直接刪掉,但後來發現這樣做會間接導致最後發生「android linker java.lang.unsatisfiedlinkerror: no implementation found for XXX」的錯誤( 這個錯誤我排查了很久很久,最終才發現是soinfo結構的問題,果然細節決定成敗…… )。

正確的做法是必須要保留所有的成員變量( 即使該變量用不到也要留下來占位 ),函數由於不占空間可以隨便刪掉。

預鏈接,主要是在遍歷.dynamic節獲取各種動態信息並保存在修正後的soinfo中。

prelink_image的具體實現太長( 基本上是copy源碼的 )就不展示了,比較大的改動是在DT_NEEDED時手動保存對應的依賴庫,之後重定向時會用到。

link_image裡處理重定向信息。

link_image的實現如下,android_relocs_的重定向我沒有處理( 嘗試處理過,但有點問題就刪了 ),好像問題不大?

之後調用relocaterela_plt_rela_的內容進行重定向。

relocate函數的實現如下,在重定位時最需要確定的就是目標函數的真實地址。

這裡采用一種偷懶的方式,直接遍歷所有依賴庫( 之前保存在myneed中 ),調用dlopen+dlsym查找對應函數地址,找到的結果會保存在sym_addr中,後續再根據type來決定重定位的方式;而如果遍歷完所有依賴庫都沒有找到,則嘗試從symtab_[sym].st_value裡獲取。

調用soinfo的構建函數:.init.init_array內所有函數

原版Linker在調用.init.init_array時傳入的是0, nullptr, nullptr,我這裡與其保持一致。

項目地址:https://github.com/ngiokweng/ng1ok-linker

隨便寫一個so作為待加載的so( 名為libdemo1.so ),內容如下,將它push到/data/local/tmp

Demo的用例如下,實例化MyLoader,調用run函數加載指定路徑的so。

Java層的onCreate如下,在test之後調用待加載so裡的demo1Func函數。

輸出如下,大功告成~

前前後後弄了兩、三周的時間,最終總算是弄好了這一個小Demo。自知該Demo仍有很多不足之處( 如無法捕獲try…catch ),而且只經過簡單的測試,定然存在諸多的BUG,歡迎各位大佬的指正!!有任何也問題歡迎評論,或者私聊我/找我聊聊天都可以!!!

int fd;
struct stat sb;
fd = open(path, O_RDONLY);
fstat(fd, &sb);
start_addr_ = static_cast<void **>(mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0));
 
// 1. 讀取so文件
if(!Read(path, fd, 0, sb.st_size)){
    LOGD("Read so failed");
    munmap(start_addr_, sb.st_size);
    close(fd);
}
int fd;
struct stat sb;
fd = open(path, O_RDONLY);
fstat(fd, &sb);
start_addr_ = static_cast<void **>(mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0));
 
// 1. 讀取so文件
if(!Read(path, fd, 0, sb.st_size)){
    LOGD("Read so failed");
    munmap(start_addr_, sb.st_size);
    close(fd);
}
bool MyLoader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) {
    bool res = false;
 
    name_ = name;
    fd_ = fd;
    file_offset_ = file_offset;
    file_size_ = file_size;
 
    if (ReadElfHeader() &&
        ReadProgramHeaders()) {
        res = true;
    }
 
    return res;
}
bool MyLoader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) {
    bool res = false;
 
    name_ = name;
    fd_ = fd;
    file_offset_ = file_offset;
    file_size_ = file_size;
 
    if (ReadElfHeader() &&
        ReadProgramHeaders()) {
        res = true;
    }
 
    return res;
}
bool MyLoader::ReadElfHeader() {
    return memcpy(&(header_),start_addr_,sizeof(header_));
}
bool MyLoader::ReadElfHeader() {
    return memcpy(&(header_),start_addr_,sizeof(header_));
}
bool MyLoader::ReadProgramHeaders() {
 
    phdr_num_ = header_.e_phnum;
 
    size_t size = phdr_num_ * sizeof(ElfW(Phdr));
 
    void* data = Utils::getMapData(fd_, file_offset_, header_.e_phoff, size);
    if(data == nullptr) {
        LOGE("ProgramHeader mmap failed");
        return false;
    }
    phdr_table_ = static_cast<ElfW(Phdr)*>(data);
 
    return true;
}
 
void* Utils::getMapData(int fd, off64_t base_offset, size_t elf_offset, size_t size) {
    off64_t offset;
    safe_add(&offset, base_offset, elf_offset);
 
    off64_t page_min = page_start(offset);
    off64_t end_offset;
 
    safe_add(&end_offset, offset, size);
    safe_add(&end_offset, end_offset, page_offset(offset));
 
    size_t map_size = static_cast<size_t>(end_offset - page_min);
 
    uint8_t* map_start = static_cast<uint8_t*>(
            mmap64(nullptr, map_size, PROT_READ, MAP_PRIVATE, fd, page_min));
 
    if (map_start == MAP_FAILED) {
        return nullptr;
    }
 
    return map_start + page_offset(offset);
 
}
bool MyLoader::ReadProgramHeaders() {
 
    phdr_num_ = header_.e_phnum;
 
    size_t size = phdr_num_ * sizeof(ElfW(Phdr));
 
    void* data = Utils::getMapData(fd_, file_offset_, header_.e_phoff, size);
    if(data == nullptr) {
        LOGE("ProgramHeader mmap failed");
        return false;
    }
    phdr_table_ = static_cast<ElfW(Phdr)*>(data);
 
    return true;
}
 
void* Utils::getMapData(int fd, off64_t base_offset, size_t elf_offset, size_t size) {
    off64_t offset;
    safe_add(&offset, base_offset, elf_offset);
 
    off64_t page_min = page_start(offset);
    off64_t end_offset;
 
    safe_add(&end_offset, offset, size);
    safe_add(&end_offset, end_offset, page_offset(offset));
 
    size_t map_size = static_cast<size_t>(end_offset - page_min);
 
    uint8_t* map_start = static_cast<uint8_t*>(
            mmap64(nullptr, map_size, PROT_READ, MAP_PRIVATE, fd, page_min));
 
    if (map_start == MAP_FAILED) {
        return nullptr;
    }
 
    return map_start + page_offset(offset);
 
}
// 2. 載入so
if(!Load()) {
    LOGD("Load so failed");
    munmap(start_addr_, sb.st_size);
    close(fd);
}
// 2. 載入so
if(!Load()) {
    LOGD("Load so failed");
    munmap(start_addr_, sb.st_size);
    close(fd);
}
bool MyLoader::Load() {
    bool res = false;
    if (ReserveAddressSpace() &&
        LoadSegments() &&
        FindPhdr()) {
 
        LOGD("Load Done.........");
        res = true;
    }
 
    // 獲取當前so (加載器的so)
    si_ = Utils::get_soinfo("libnglinker.so");
 
    if(!si_) {
        LOGE("si_ return nullptr");
        return false;
    }
    LOGD("si_ -> base: %lx", si_->base);
 
    // 使si_可以被修改
    mprotect((void*) PAGE_START(reinterpret_cast<ElfW(Addr)>(si_)), 0x1000, PROT_READ | PROT_WRITE);
 
    // 修正so
    si_->base = load_start();
    si_->size = load_size();
//        si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());
    si_->load_bias = load_bias();
    si_->phnum = phdr_count();
    si_->phdr = loaded_phdr();
 
    return res;
}
bool MyLoader::Load() {
    bool res = false;
    if (ReserveAddressSpace() &&
        LoadSegments() &&
        FindPhdr()) {
 
        LOGD("Load Done.........");
        res = true;
    }
 
    // 獲取當前so (加載器的so)
    si_ = Utils::get_soinfo("libnglinker.so");
 
    if(!si_) {
        LOGE("si_ return nullptr");
        return false;
    }
    LOGD("si_ -> base: %lx", si_->base);
 
    // 使si_可以被修改
    mprotect((void*) PAGE_START(reinterpret_cast<ElfW(Addr)>(si_)), 0x1000, PROT_READ | PROT_WRITE);
 
    // 修正so
    si_->base = load_start();
    si_->size = load_size();
//        si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());
    si_->load_bias = load_bias();
    si_->phnum = phdr_count();
    si_->phdr = loaded_phdr();
 
    return res;
}
bool MyLoader::ReserveAddressSpace() {
    ElfW(Addr) min_vaddr;
    load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
    LOGD("load_size_: %x", load_size_);
    if (load_size_ == 0) {
        LOGE("\"%s\" has no loadable segments", name_.c_str());
        return false;
    }
 
    uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
 
    void* start;
 
    // Assume position independent executable by default.
    void* mmap_hint = nullptr;
 
    start = mmap(mmap_hint, load_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 
    load_start_ = start;
    load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
 
    return true;
}
bool MyLoader::ReserveAddressSpace() {
    ElfW(Addr) min_vaddr;
    load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
    LOGD("load_size_: %x", load_size_);
    if (load_size_ == 0) {
        LOGE("\"%s\" has no loadable segments", name_.c_str());
        return false;
    }
 
    uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
 
    void* start;
 
    // Assume position independent executable by default.
    void* mmap_hint = nullptr;
 
    start = mmap(mmap_hint, load_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 
    load_start_ = start;
    load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
 
    return true;
}
bool MyLoader::LoadSegments() {
    // 在這個函數中會往 ReserveAddressSpace
    // 裡mmap的那片內存填充數據
 
    for (size_t i = 0; i < phdr_num_; ++i) {
        const ElfW(Phdr)* phdr = &phdr_table_[i];
 
        if (phdr->p_type != PT_LOAD) {
            continue;
        }
 
        // Segment addresses in memory.
        ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
        ElfW(Addr) seg_end   = seg_start + phdr->p_memsz;
 
        ElfW(Addr) seg_page_start = PAGE_START(seg_start);
        ElfW(Addr) seg_page_end   = PAGE_END(seg_end);
 
        ElfW(Addr) seg_file_end   = seg_start + phdr->p_filesz;
 
        // File offsets.
        ElfW(Addr) file_start = phdr->p_offset;
        ElfW(Addr) file_end   = file_start + phdr->p_filesz;
 
        ElfW(Addr) file_page_start = PAGE_START(file_start);
        ElfW(Addr) file_length = file_end - file_page_start;
 
        if (file_size_ <= 0) {
            LOGE("\"%s\" invalid file size: %", name_.c_str(), file_size_);
            return false;
        }
 
        if (file_end > static_cast<size_t>(file_size_)) {
            LOGE("invalid ELF file");
            return false;
        }
 
        if (file_length != 0) {
            // 按AOSP裡那樣用mmap會有問題, 因此改為直接 memcpy
            mprotect(reinterpret_cast<void *>(seg_page_start), seg_page_end - seg_page_start, PROT_WRITE);
            void* c = (char*)start_addr_ + file_page_start;
            void* res = memcpy(reinterpret_cast<void *>(seg_page_start), c, file_length);
 
            LOGD("[LoadSeg] %s  seg_page_start: %lx   c : %lx", strerror(errno), seg_page_start, c);
 
        }
 
        // if the segment is writable, and does not end on a page boundary,
        // zero-fill it until the page limit.
        if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
            memset(reinterpret_cast<void*>(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
        }
 
        seg_file_end = PAGE_END(seg_file_end);
 
        // seg_file_end is now the first page address after the file
        // content. If seg_end is larger, we need to zero anything
        // between them. This is done by using a private anonymous
        // map for all extra pages.
 
        if (seg_page_end > seg_file_end) {
            size_t zeromap_size = seg_page_end - seg_file_end;
            void* zeromap = mmap(reinterpret_cast<void*>(seg_file_end),
                                 zeromap_size,
                                 PFLAGS_TO_PROT(phdr->p_flags),
                                 MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
                                 -1,
                                 0);
            if (zeromap == MAP_FAILED) {
                LOGE("couldn't zero fill \"%s\" gap: %s", name_.c_str(), strerror(errno));
                return false;
            }
 
            // 分配.bss節
            prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss");
        }
    }
 
    return true;
}
bool MyLoader::LoadSegments() {
    // 在這個函數中會往 ReserveAddressSpace
    // 裡mmap的那片內存填充數據
 
    for (size_t i = 0; i < phdr_num_; ++i) {
        const ElfW(Phdr)* phdr = &phdr_table_[i];
 
        if (phdr->p_type != PT_LOAD) {
            continue;
        }
 
        // Segment addresses in memory.
        ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
        ElfW(Addr) seg_end   = seg_start + phdr->p_memsz;
 
        ElfW(Addr) seg_page_start = PAGE_START(seg_start);
        ElfW(Addr) seg_page_end   = PAGE_END(seg_end);
 
        ElfW(Addr) seg_file_end   = seg_start + phdr->p_filesz;
 
        // File offsets.
        ElfW(Addr) file_start = phdr->p_offset;
        ElfW(Addr) file_end   = file_start + phdr->p_filesz;
 
        ElfW(Addr) file_page_start = PAGE_START(file_start);
        ElfW(Addr) file_length = file_end - file_page_start;
 
        if (file_size_ <= 0) {
            LOGE("\"%s\" invalid file size: %", name_.c_str(), file_size_);
            return false;
        }
 
        if (file_end > static_cast<size_t>(file_size_)) {
            LOGE("invalid ELF file");
            return false;
        }
 
        if (file_length != 0) {
            // 按AOSP裡那樣用mmap會有問題, 因此改為直接 memcpy
            mprotect(reinterpret_cast<void *>(seg_page_start), seg_page_end - seg_page_start, PROT_WRITE);
            void* c = (char*)start_addr_ + file_page_start;
            void* res = memcpy(reinterpret_cast<void *>(seg_page_start), c, file_length);
 
            LOGD("[LoadSeg] %s  seg_page_start: %lx   c : %lx", strerror(errno), seg_page_start, c);
 
        }
 
        // if the segment is writable, and does not end on a page boundary,
        // zero-fill it until the page limit.
        if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
            memset(reinterpret_cast<void*>(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
        }
 
        seg_file_end = PAGE_END(seg_file_end);
 
        // seg_file_end is now the first page address after the file
        // content. If seg_end is larger, we need to zero anything
        // between them. This is done by using a private anonymous
        // map for all extra pages.
 
        if (seg_page_end > seg_file_end) {
            size_t zeromap_size = seg_page_end - seg_file_end;
            void* zeromap = mmap(reinterpret_cast<void*>(seg_file_end),
                                 zeromap_size,
                                 PFLAGS_TO_PROT(phdr->p_flags),
                                 MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
                                 -1,
                                 0);
            if (zeromap == MAP_FAILED) {
                LOGE("couldn't zero fill \"%s\" gap: %s", name_.c_str(), strerror(errno));
                return false;
            }
 
            // 分配.bss節
            prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss");
        }
    }
 
    return true;
}
bool MyLoader::FindPhdr() {
 
    const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;
 
    // If there is a PT_PHDR, use it directly.
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type == PT_PHDR) {
            return CheckPhdr(load_bias_ + phdr->p_vaddr);
        }
    }
 
    // Otherwise, check the first loadable segment. If its file offset
    // is 0, it starts with the ELF header, and we can trivially find the
    // loaded program header from it.
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type == PT_LOAD) {
            if (phdr->p_offset == 0) {
                ElfW(Addr)  elf_addr = load_bias_ + phdr->p_vaddr;
                const ElfW(Ehdr)* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(elf_addr);
                ElfW(Addr)  offset = ehdr->e_phoff;
                return CheckPhdr(reinterpret_cast<ElfW(Addr)>(ehdr) + offset);
            }
            break;
        }
    }
 
    LOGE("can't find loaded phdr for \"%s\"", name_.c_str());
    return false;
}
 
bool MyLoader::CheckPhdr(ElfW(Addr) loaded) {
    const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;
    ElfW(Addr) loaded_end = loaded + (phdr_num_ * sizeof(ElfW(Phdr)));
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type != PT_LOAD) {
            continue;
        }
        ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
        ElfW(Addr) seg_end = phdr->p_filesz + seg_start;
        if (seg_start <= loaded && loaded_end <= seg_end) {
            loaded_phdr_ = reinterpret_cast<const ElfW(Phdr)*>(loaded);
            return true;
        }
    }
    LOGE("\"%s\" loaded phdr %p not in loadable segment",
           name_.c_str(), reinterpret_cast<void*>(loaded));
    return false;
}
bool MyLoader::FindPhdr() {
 
    const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;
 
    // If there is a PT_PHDR, use it directly.
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type == PT_PHDR) {
            return CheckPhdr(load_bias_ + phdr->p_vaddr);
        }
    }
 
    // Otherwise, check the first loadable segment. If its file offset
    // is 0, it starts with the ELF header, and we can trivially find the
    // loaded program header from it.
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type == PT_LOAD) {
            if (phdr->p_offset == 0) {
                ElfW(Addr)  elf_addr = load_bias_ + phdr->p_vaddr;
                const ElfW(Ehdr)* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(elf_addr);
                ElfW(Addr)  offset = ehdr->e_phoff;
                return CheckPhdr(reinterpret_cast<ElfW(Addr)>(ehdr) + offset);
            }
            break;
        }
    }
 
    LOGE("can't find loaded phdr for \"%s\"", name_.c_str());
    return false;
}
 
bool MyLoader::CheckPhdr(ElfW(Addr) loaded) {
    const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;
    ElfW(Addr) loaded_end = loaded + (phdr_num_ * sizeof(ElfW(Phdr)));
    for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
        if (phdr->p_type != PT_LOAD) {
            continue;
        }
        ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
        ElfW(Addr) seg_end = phdr->p_filesz + seg_start;
        if (seg_start <= loaded && loaded_end <= seg_end) {
            loaded_phdr_ = reinterpret_cast<const ElfW(Phdr)*>(loaded);
            return true;
        }
    }
    LOGE("\"%s\" loaded phdr %p not in loadable segment",
           name_.c_str(), reinterpret_cast<void*>(loaded));
    return false;
}
soinfo* Utils::get_soinfo(const char* so_name) {
    typedef soinfo* (*FunctionPtr)(ElfW(Addr));
 
    char line[1024];
    ElfW(Addr) linker_base = 0;
    ElfW(Addr) so_addr = 0;
    FILE *fp=fopen("/proc/self/maps","r");
    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "linker64") && !linker_base) {
            char* addr = strtok(line, "-");
            linker_base = strtoull(addr, NULL, 16);
 
        }else if(strstr(line, so_name) && !so_addr) {
            char* addr = strtok(line, "-");
            so_addr = strtoull(addr, NULL, 16);
 
        }
 
        if(linker_base && so_addr)break;
 
    }
 
    ElfW(Addr) func_offset = Utils::get_export_func("/system/bin/linker64", "find_containing_library");
    if(!func_offset) {
        LOGE("func_offset == 0? check it ---> get_soinfo");
        return nullptr;
    }
//    ElfW(Addr) find_containing_library_addr =  static_cast<ElfW(Addr)>(linker_base + 0x9AB0);
    ElfW(Addr) find_containing_library_addr =  static_cast<ElfW(Addr)>(linker_base + func_offset);
    FunctionPtr find_containing_library = reinterpret_cast<FunctionPtr>(find_containing_library_addr);
 
    return find_containing_library(so_addr);
}
soinfo* Utils::get_soinfo(const char* so_name) {
    typedef soinfo* (*FunctionPtr)(ElfW(Addr));
 
    char line[1024];
    ElfW(Addr) linker_base = 0;
    ElfW(Addr) so_addr = 0;
    FILE *fp=fopen("/proc/self/maps","r");
    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "linker64") && !linker_base) {
            char* addr = strtok(line, "-");
            linker_base = strtoull(addr, NULL, 16);
 
        }else if(strstr(line, so_name) && !so_addr) {
            char* addr = strtok(line, "-");
            so_addr = strtoull(addr, NULL, 16);
 
        }
 
        if(linker_base && so_addr)break;
 
    }
 
    ElfW(Addr) func_offset = Utils::get_export_func("/system/bin/linker64", "find_containing_library");
    if(!func_offset) {
        LOGE("func_offset == 0? check it ---> get_soinfo");
        return nullptr;
    }
//    ElfW(Addr) find_containing_library_addr =  static_cast<ElfW(Addr)>(linker_base + 0x9AB0);
    ElfW(Addr) find_containing_library_addr =  static_cast<ElfW(Addr)>(linker_base + func_offset);
    FunctionPtr find_containing_library = reinterpret_cast<FunctionPtr>(find_containing_library_addr);
 
    return find_containing_library(so_addr);
}
ElfW(Addr) Utils::get_export_func(char* path, char* func_name) {
 
    struct stat sb;
    int fd = open(path, O_RDONLY);
    fstat(fd, &sb);
    void* base = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
 
    // 讀取elf header
    ElfW(Ehdr) header;
    memcpy(&(header), base, sizeof(header));
 
    // 讀取Section header table
    size_t size = header.e_shnum * sizeof(ElfW(Shdr));
    void* tmp = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 注: 必須要 MAP_ANONYMOUS
    LOGD("error: %s", strerror(errno));
    ElfW(Shdr)* shdr_table;
    memcpy(tmp, (void*)((ElfW(Off))base + header.e_shoff), size);
    shdr_table = static_cast<ElfW(Shdr)*>(tmp);
 
    char* shstrtab = reinterpret_cast<char*>(shdr_table[header.e_shstrndx].sh_offset + (ElfW(Off))base);
 
    void* symtab = nullptr;
    char* strtab = nullptr;
    uint32_t symtab_size = 0;
 
    // 遍歷獲取.symtab和.strtab節
    for (size_t i = 0; i < header.e_shnum; ++i) {
        const ElfW(Shdr) *shdr = &shdr_table[i];
        char* section_name = shstrtab + shdr->sh_name;
        if(!strcmp(section_name, ".symtab")) {
//            LOGD("[test] %d: shdr->sh_name = %s", i, (shstrtab + shdr->sh_name));
            symtab = reinterpret_cast<void*>(shdr->sh_offset + (ElfW(Off))base);
            symtab_size = shdr->sh_size;
        }
        if(!strcmp(section_name, ".strtab")) {
//            LOGD("[test] %d: shdr->sh_name = %s", i, (shstrtab + shdr->sh_name));
            strtab = reinterpret_cast<char*>(shdr->sh_offset + (ElfW(Off))base);
        }
 
        if(strtab && symtab)break;
    }
 
    // 讀取 Symbol table
    ElfW(Sym)* sym_table;
    tmp = mmap(nullptr, symtab_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memcpy(tmp, symtab, symtab_size);
    sym_table = static_cast<ElfW(Sym)*>(tmp);
 
    int sym_num = symtab_size / sizeof(ElfW(Sym));
 
    // 遍歷 Symbol table
    for(int i = 0; i < sym_num; i++) {
        const ElfW(Sym) *sym = &sym_table[i];
        char* sym_name = strtab + sym->st_name;
        if(strstr(sym_name, func_name)) {
            return sym->st_value;
        }
 
    }
 
    return 0;
}
ElfW(Addr) Utils::get_export_func(char* path, char* func_name) {
 
    struct stat sb;
    int fd = open(path, O_RDONLY);
    fstat(fd, &sb);
    void* base = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
 
    // 讀取elf header
    ElfW(Ehdr) header;
    memcpy(&(header), base, sizeof(header));
 
    // 讀取Section header table
    size_t size = header.e_shnum * sizeof(ElfW(Shdr));
    void* tmp = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 注: 必須要 MAP_ANONYMOUS
    LOGD("error: %s", strerror(errno));
    ElfW(Shdr)* shdr_table;
    memcpy(tmp, (void*)((ElfW(Off))base + header.e_shoff), size);
    shdr_table = static_cast<ElfW(Shdr)*>(tmp);
 
    char* shstrtab = reinterpret_cast<char*>(shdr_table[header.e_shstrndx].sh_offset + (ElfW(Off))base);
 
    void* symtab = nullptr;
    char* strtab = nullptr;
    uint32_t symtab_size = 0;
 

[注意]APP应用上架合规检测服务,协助应用顺利上架!

收藏
免费 8
支持
分享
最新回复 (20)
雪    币: 2218
活跃值: (2316)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢!楼主我也最近想实现这个linker加固,看了很多文章,r0ysue的那个demo我可以运行!给你大大的点个赞!走在我前面
2024-6-28 17:32
0
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
逆天而行 感谢!楼主我也最近想实现这个linker加固,看了很多文章,r0ysue的那个demo我可以运行!给你大大的点个赞!走在我前面
對你有幫助就好 雖然r0ysue大佬那個demo我跑不起來(可能是環境問題?), 但真的很有參考價值
2024-6-28 17:43
0
雪    币: 498
活跃值: (4316)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
都在自定义linker...越来越不好玩了
2024-6-28 17:58
0
雪    币: 9253
活跃值: (6475)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
666
2024-6-29 02:06
0
雪    币: 2475
活跃值: (10777)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
TQL
2024-7-3 10:39
0
雪    币: 3493
活跃值: (14218)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
7
我有个问题,你虽然自定义linker加载了,我遍历系统的linker加载文件列表能找到么?能找到就可以dump出来。
2024-7-3 16:16
1
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
8
珍惜Any 我有个问题,你虽然自定义linker加载了,我遍历系统的linker加载文件列表能找到么?能找到就可以dump出来。
遍歷系統的linker加載文件列表是指linker_main.cpp裡的solist嗎? 如果是的話應該能找到, 因為有一步是將當前so(加載器so)修正為目標so, 就是不知道修正的效果如何。
2024-7-3 19:20
0
雪    币: 3493
活跃值: (14218)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
9
ngiokweng 遍歷系統的linker加載文件列表是指linker_main.cpp裡的solist嗎? 如果是的話應該能找到, 因為有一步是將當前so(加載器so)修正為目標so, 就是不知道修正的效果如何。
那就是还可以dump出来的,现在一般so的脱壳机都是直接遍历这玩意,从开始地址-结束地址dump出来
2024-7-4 11:47
0
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
10
珍惜Any 那就是还可以dump出来的,现在一般so的脱壳机都是直接遍历这玩意,从开始地址-结束地址dump出来
嗯, 畢竟我大部分代碼都是抄源碼的, 沒有做魔改, 以後看看有沒有機會研究下對抗的方法
2024-7-4 14:04
0
雪    币: 69
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
好文当赏
2024-7-5 11:14
0
雪    币: 120
活跃值: (1603)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
好文当赏
2024-7-11 18:09
0
雪    币: 14685
活跃值: (17789)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
13
感谢分享,不过怎么都是繁体字?楼主你很机车哦 
2024-7-11 19:04
0
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
14
pureGavin 感谢分享,不过怎么都是繁体字?楼主你很机车哦 [em_86]
我只會打繁體啊, 而且我人很nice一點都不機車
2024-7-12 09:59
0
雪    币: 11
活跃值: (774)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
谢谢楼主分享,学到很多~
2024-7-30 09:56
0
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
16
7lsu 谢谢楼主分享,学到很多~
2024-7-30 15:24
0
雪    币: 77
活跃值: (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
tql
2024-7-31 11:57
0
雪    币: 5
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
我有个问题哈,就是如果改包形式注入的so,在maps下都会留下痕迹吧,用这个技术,是不是也只能做到把第二个so的痕迹抹除掉,但第一个so也有痕迹,本身第一个so怎么抹除呢
2024-8-1 23:04
0
雪    币: 1426
活跃值: (3152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
感谢分享
2024-8-2 10:34
0
雪    币: 1646
活跃值: (1375)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
20
恋一世的爱 我有个问题哈,就是如果改包形式注入的so,在maps下都会留下痕迹吧,用这个技术,是不是也只能做到把第二个so的痕迹抹除掉,但第一个so也有痕迹,本身第一个so怎么抹除呢
我看過一個方法是利用memfd_create創建一個匿名共享內存, 然後用dlopen來加載, 通過這個方式來加載第一個so或許可以做到。
2024-8-2 12:06
0
雪    币: 5
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
ngiokweng 我看過一個方法是利用memfd_create創建一個匿名共享內存, 然後用dlopen來加載, 通過這個方式來加載第一個so或許可以做到。
但是你要怎么清除第一个so在maps里的信息呢,我是改包(反编译回编译),不是改系统哈
2024-8-4 23:35
0
游客
登录 | 注册 方可回帖
返回
// // 统计代码