吾愛破解 - LCG - LSG |安卓破解|病毒分析|huihengkj.com

 找回密碼
 注冊[Register]

QQ登錄

只需一步,快速開始

搜索
查看: 2983|回復: 50

[Android 原創] Android so(ELF)文件解析

  [復制鏈接]
樓主
windy_ll 發表于 2020-10-12 09:21 回帖獎勵

一、前言

    so文件是啥?so文件是elf文件,elf文件后綴名是.so,所以也被chang常稱之為so文件,elf文件是linux底下二進制文件,可以理解為windows下的PE文件,在Android中可以比作dll,方便函數的移植,在常用于保護Android軟件,增加逆向難度。解析elf文件有啥子用?最明顯的兩個用處就是:1、so加固;2、用于frIDA(xposed)的檢測!  

    本文使用c語言,編譯器為vscode。如有錯誤,還請斧正!!!  

    PS:該文已經首發于某公眾號,介意者勿噴!!!500彩票邀请码  


二、SO文件整體格式

    so文件大體上可分為四部分,一般來說從上往下是ELF頭部->Pargarm頭部->節區(Section)->節區頭,其中,除了ELF頭部在文件位置固定不變外,其余三部分的位置都不固定。整體結構圖可以參考非蟲大佬的那張圖,圖片如下:500彩票邀请码  

1.png  

    解析語言之所以選擇c語言,有兩個原因:1、做so加固的時候可以需要用到,這里就干脆用c寫成一個模板,哪里需要就哪里改,不像上次解析dex文件的時候用python寫,結果后面寫指令還原的時候需要用的時候在寫一遍c版本代價太大了;2、在安卓源碼中,有個elf.h文件,這個文件定義了我們解析時需要用到的所有數據結構,并且給出了參考注釋,是很好的參考資料。elf.h文件路徑如下:500彩票邀请码  

2.png


三、解析ELF頭部

    ELF頭部數據格式在elf.h文件中已經給出,如下圖所示:500彩票邀请码  

3.png  

  每個字段解釋如下:500彩票邀请码  

    1、e_ident數組:前4個字節為.ELF,是elf標志頭,第5個字節為該文件標志符,為1代表這是一個32位的elf文件,后面幾個字節代表版本等信息。
    2、e_type字段:表示是可執行文件還是鏈接文件等,安卓上的so文件就是分享文件,一般該字段為3,詳細請看下圖。
    3、e_machine字段:該字段標志該文件運行在什么機器架構上,例如ARM。
    4、e_version字段:該字段表示當前so文件的版本信息,一般為1.
    5、e_entry字段:該字段是一個偏移地址,為程序啟動的地址。
    6、e_phoff字段:該字段也是一個偏移地址,指向程序頭(Pargram Header)的起始地址。
    7、e_shoff字段:該字段是一個偏移地址,指向節區頭(Section Header)的起始地址。
    8、e_flags字段:該字段表示該文件的權限,常見的值有1、2、4,分別代表read、write、exec。
    9、e_ehsize字段:該字段表示elf文件頭部大小,一般固定為52.
    10、e_phentsize字段:該字段表示程序頭(Program Header)大小,一般固定為32.
    11、e_phnum字段:該字段表示文件中有幾個程序頭。
    12、e_shentsize:該字段表示節區頭(Section Header)大小,一般固定為40.
    13、e_shnum字段:該字段表示文件中有幾個節區頭。
    14、e_shstrndx字段:該字段是一個數字,這個表明了.shstrtab節區(這個節區存儲著所有節區的名字,例如.text)500彩票邀请码的節區頭是第幾個。  

  e_type具體值(相關值后面有英文注釋,這里就不再添加中文注釋了):  

4.png500彩票邀请码  

  解析代碼如下:  

struct DataOffest parseSoHeader(FILE *fp,struct DataOffest off)
{
    Elf32_Ehdr header;
    int i = 0;

    fseek(fp,0,SEEK_SET);
    fread(&header,1,sizeof(header),fp);
    printf("ELF Header:\n");
    printf("    Header Magic: ");
    for (i = 0; i < 16; i++)
    {
        printf("%02x ",header.e_ident[i]);
    }
    printf("\n");
    printf("    So File Type: 0x%02x",header.e_type);
    switch (header.e_type)
    {
    case 0x00:
        printf("(No file type)\n");
        break;
    case 0x01:
        printf("(Relocatable file)\n");
        break;
    case 0x02:
        printf("(Executable file)\n");
        break;
    case 0x03:
        printf("(Shared object file)\n");
        break;
    case 0x04:
        printf("(Core file)\n");
        break;
    case 0xff00:
        printf("(Beginning of processor-specific codes)\n");
        break;
    case 0xffff:
        printf("(Processor-specific)\n");
        break;
    default:
        printf("\n");
        break;
    }
    printf("    Required Architecture: 0x%04x",header.e_machine);
    if (header.e_machine == 0x28)
    {
        printf("(ARM)\n");
    }
    else
    {
        printf("\n");
    }
    printf("    Version: 0x%02x\n",header.e_version);
    printf("    Start Program Address: 0x%08x\n",header.e_entry);
    printf("    Program Header Offest: 0x%08x\n",header.e_phoff);
    off.programheadoffset = header.e_phoff;
    printf("    Section Header Offest: 0x%08x\n",header.e_shoff);
    off.sectionheadoffest = header.e_shoff;
    printf("    Processor-specific Flags: 0x%08x\n",header.e_flags);
    printf("    ELF Header Size: 0x%04x\n",header.e_ehsize);
    printf("    Size of an entry in the program header table: 0x%04x\n",header.e_phentsize);
    printf("    Program Header Size: 0x%04x\n",header.e_phnum);
    off.programsize = header.e_phnum;
    printf("    Size of an entry in the section header table: 0x%04x\n",header.e_shentsize);
    printf("    Section Header Size: 0x%04x\n",header.e_shnum);
    off.sectionsize = header.e_shnum;
    printf("    String Section Index: 0x%04x\n",header.e_shstrndx);
    off.shstrtabindex = header.e_shstrndx;
    return off;
}

四、程序頭(Program Header)解析

    程序頭在elf.h文件中的數據格式是Elf32_Phdr,如下圖所示:500彩票邀请码  

5.png  

  每個字段解釋如下:  

    1、p_type字段:該字段表明了段(Segment)類型,例如PT_LOAD類型,具體值看下圖,實在有點多,沒辦法這里寫完。
    2、p_offest字段:該字段表明了這個段在該so文件的起始地址。
    3、p_vaddr字段:該字段指明了加載進內存后的虛擬地址,我們靜態解析時用不到該字段。
    4、p_paddr字段:該字段指明加載進內存后的實際物理地址,跟上面的那個字段一樣,解析時用不到。
    5、p_filesz字段:該字段表明了這個段的大小,單位為字節。
    6、p_memsz字段:該字段表明了這個段加載到內存后使用的字節數。
    7、p_flags字段:該字段跟elf頭部的e_flags一樣,指明了該段的屬性,是可讀還是可寫。
    8、p_align字段:該字段用來指明在內存中對齊字節數的。  

  p_type字段具體取值:  

6.png500彩票邀请码  

  解析代碼:  

struct DataOffest parseSoPargramHeader(FILE *fp,struct DataOffest off)
{
    Elf32_Half init;
    Elf32_Half addr;
    int i;
    Elf32_Phdr programHeader;

    init = off.programheadoffset;
    for (i = 0; i < off.programsize; i++)
    {
        addr = init + (i * 0x20);
        fseek(fp,addr,SEEK_SET);
        fread(&programHeader,1,32,fp);
        switch (programHeader.p_type)
        {
        case 2:
            off.dynameicoff = programHeader.p_offset;
            off.dynameicsize = programHeader.p_filesz;
            break;
        default:
            break;
        }
        printf("\n\nSegment Header %d:\n",(i + 1));
        printf("    Type of segment: 0x%08x\n",programHeader.p_type);
        printf("    Segment Offset: 0x%08x\n",programHeader.p_offset);
        printf("    Virtual address of beginning of segment: 0x%08x\n",programHeader.p_vaddr);
        printf("    Physical address of beginning of segment: 0x%08x\n",programHeader.p_paddr);
        printf("    Num. of bytes in file image of segment: 0x%08x\n",programHeader.p_filesz);
        printf("    Num. of bytes in mem image of segment (may be zero): 0x%08x\n",programHeader.p_memsz);
        printf("    Segment flags: 0x%08x\n",programHeader.p_flags);
        printf("    Segment alignment constraint: 0x%08x\n",programHeader.p_align);
    }
    return off;
}

五、節區頭(Section Header)解析

    節區頭在elf.h文件中的數據結構為Elf32_Shdr,如下圖所示:  

7.png  

  每個字段解釋如下:  

    1、sh_name字段:該字段是一個索引值,是.shstrtab表(節區名字字符串表)的索引,指明了該節區的名字。
    2、sh_type字段:該字段表明該節區的類型,例如值為SHT_PROGBITS,則該節區可能是.text或者.rodata,至于具體怎么區分,當然看sh_name字段。具體取值看下圖。
    3、sh_flags字段:跟上面的一樣,就不再細說了。
    4、sh_addr字段:該字段是一個地址,是該節區加載進內存后的地址。
    5、sh_offset字段:該字段也是一個地址,是該節區在該so文件中的偏移地址。
    6、sh_size字段:該字段表明了該節區的大小,單位是字節。
    7、sh_link和sh_info字段:這兩個字段只適用于少數節區,我們這里解析用不到,感興趣的可以去看官方文檔。
    8、sh_addralign字段:該字段指明在內存中的對齊字節。
    9、sh_entsize字段:該字段指明了該節區中每個項占用的字節數。  

  sh_type取值:  

8.png500彩票邀请码  

  解析代碼:  

struct DataOffest parseSoSectionHeader(FILE *fp,struct DataOffest off,struct ShstrtabTable StrList[100])
{
    Elf32_Half init;
    Elf32_Half addr;
    Elf32_Shdr sectionHeader;
    int i,id,n;
    char ch;
    int k = 0;

    init = off.sectionheadoffest;
    for (i = 0; i < off.sectionsize; i++)
    {
        addr = init + (i * 0x28);
        fseek(fp,addr,SEEK_SET);
        fread(§ionHeader,1,40,fp); 
        switch (sectionHeader.sh_type)
        {
        case 2:
            off.symtaboff = sectionHeader.sh_offset;
            off.symtabsize = sectionHeader.sh_size;
            break;
        case 3:
            if(k == 0)
            {
                off.stroffset = sectionHeader.sh_offset;
                off.strsize = sectionHeader.sh_size;
                k++;
            }
            else if (k == 1)
            {
                off.str1offset = sectionHeader.sh_offset;
                off.str1size = sectionHeader.sh_size;
                k++;
            }
            else
            {
                off.str2offset = sectionHeader.sh_offset;
                off.str2size = sectionHeader.sh_size;
                k++;
            }
            break;
        default:
            break;
        }
        id = sectionHeader.sh_name;
        printf("\n\nSection Header %d\n",(i + 1));
        printf("    Section Name: ");
        for (n = 0; n < 50; n++)
        {
            ch = StrList[id].str[n];
            if (ch == 0)
            {
                printf("\n");
                break;
            }
            else
            {
                printf("%c",ch);
            }
        }
        printf("    Section Type: 0x%08x\n",sectionHeader.sh_type);
        printf("    Section Flag: 0x%08x\n",sectionHeader.sh_flags);
        printf("    Address where section is to be loaded: 0x%08x\n",sectionHeader.sh_addr);
        printf("    Offset: 0x%x\n",sectionHeader.sh_offset);
        printf("    Size of section, in bytes: 0x%08x\n",sectionHeader.sh_size);
        printf("    Section type-specific header table index link: 0x%08x\n",sectionHeader.sh_link);
        printf("    Section type-specific extra information: 0x%08x\n",sectionHeader.sh_info);
        printf("    Section address alignment: 0x%08x\n",sectionHeader.sh_addralign);
        printf("    Size of records contained within the section: 0x%08x\n",sectionHeader.sh_entsize);
    }
    return off;
}

六、字符串節區解析

    PS:從這里開始網上的參考資料很少了,特別是參考代碼,所以有錯誤的地方還請斧正;因為以后的so加固等只涉及到幾個節區,所以只解析了.shstrtab.strtab.dynstr.text.symtab.dynamic節區!!!  

    在elf頭部中有個e_shstrndx字段,該字段指明了.shstrtab節區頭部是文件中第幾個節區頭部,我們可以根據這找到.shstrtab節區的偏移地址,然后讀取出來,就可以為每個節區名字賦值了,然后就可以順著鎖定剩下的兩個字符串節區。  

    在elf文件中,字符串表示方式如下:字符串的頭部和尾部用標示字節00標志,同時上一個字符串尾部標識符00作為下一個字符串頭部標識符。例如我有兩個緊鄰的字符串分別是ab,那么他們在elf文件中16進制為00 97 00 98 00500彩票邀请码  

  解析代碼如下(PS:因為編碼問題,第一次打印字符串表沒問題,但填充進sh_name就亂碼,所以這里只放上解析.shstrtab的代碼,但剩下兩個節區節區代碼一樣):  

void parseStrSection(FILE *fp,struct DataOffest off,int flag)
{
    int total = 0;
    int i;
    int ch;
    int mark;
    Elf32_Off init;
    Elf32_Off addr;
    Elf32_Word count;

    mark = 1;

    if (flag == 1)
    {
        count = off.strsize;
        init = off.stroffset;
    }
    else if (flag == 2)
    {
        count = off.str1size;
        init = off.str1offset;
    }
    else
    {
        count = off.str2size;
        init = off.str2offset;
    }

    printf("String Address==>0x%x\n",init);
    printf("String List %d:\n\t[1]==>",flag);

    for (i = 0; i < count; i++)
    {

        addr = init + (i * 1);

        fseek(fp,addr,SEEK_SET);
        fread(&ch,1,1,fp);

        if (i == 0 && ch == 0)
        {
            continue;
        }
        else if (ch != 0)
        {
            printf("%c",ch);
        }
        else if (ch == 0 && i !=0)
        {
            printf("\n\t[%d]==>",(++mark));
        }
    }
    printf("\n");

}

七、.dynamic解析

    .dynamicelf.h文件中的數據結構是Elf32-Dyn,如下圖所示:  

9.png  

    第一個字段表明了類型,占4個字節;第二個字段是一個共用體,也占四個字節,描述了具體的項信息。解析代碼如下:  

void parseSoDynamicSection(FILE *fp,struct DataOffest off)
{
    int dynamicnum;
    Elf32_Off init;
    Elf32_Off addr;
    Elf32_Dyn dynamicData;
    int i;

    init = off.dynameicoff;
    dynamicnum = (off.dynameicsize / 8);

    printf("Dynamic:\n");
    printf("\t\tTag\t\t\tType\t\t\tName/Value\n");

    for (i = 0; i < dynamicnum; i++)
    {
        addr = init + (i * 8);
        fseek(fp,addr,SEEK_SET);
        fread(&dynamicData,1,8,fp);
        printf("\t\t0x%08x\t\tNOPRINTF\t\t0x%x\n",dynamicData.d_tag,dynamicData.d_un);
    }

}

八、.symtab解析

    該節區是該so文件的符號表,它在elf.h文件中的數據結構是Elf32_Sym,如下所示:  

10.png  

  每個字段解釋如下:500彩票邀请码  

    1、st_name字段:該字段是一個索引值,指明了該項的名字。
    2、st_value字段:該字段表明了相關聯符號的取值。
    3、stz-size字段:該字段指明了每個項所占用的字節數。
    4、st_info和st_other字段:這兩個字段指明了符號的類型。
    5、st_shndx字段:相關索引。  

  解析代碼如下(PS:由于亂碼問題,索引手動固定了地址測試,有興趣的挨個解析字符應該可以解決亂碼問題):500彩票邀请码  

void parseSoDynamicSection(FILE *fp,struct DataOffest off)
{
    int dynamicnum;
    Elf32_Off init;
    Elf32_Off addr;
    Elf32_Dyn dynamicData;
    int i;

    init = off.dynameicoff;
    dynamicnum = (off.dynameicsize / 8);

    printf("Dynamic:\n");
    printf("\t\tTag\t\t\tType\t\t\tName/Value\n");

    for (i = 0; i < dynamicnum; i++)
    {
        addr = init + (i * 8);
        fseek(fp,addr,SEEK_SET);
        fread(&dynamicData,1,8,fp);
        printf("\t\t0x%08x\t\tNOPRINTF\t\t0x%x\n",dynamicData.d_tag,dynamicData.d_un);
    }

}

    void parseSymtabSection(FILE *fp,struct DataOffest off)
    {
        Elf32_Off init;
        Elf32_Off addr;
        Elf32_Word count;
        Elf32_Sym symtabSection;
        int k,i;

        init = off.symtaboff;
        count = off.symtabsize;

        printf("SymTable:\n");

        for (i = 0; i < count; i++)
        {
            addr = init + (i * 16);
            fseek(fp,addr,SEEK_SET);
            fread(&symtabSection,1,16,fp);
            printf("Symbol Name Index: 0x%x\n",symtabSection.st_name);
            printf("Value or address associated with the symbol: 0x%08x\n",symtabSection.st_value);
            printf("Size of the symbol: 0x%x\n",symtabSection.st_size);
            printf("Symbol's type and binding attributes: %c\n",symtabSection.st_info);
            printf("Must be zero; reserved: 0x%x\n",symtabSection.st_other);
            printf("Which section (header table index) it's defined in: 0x%x\n",symtabSection.st_shndx);
        }

    }

九、.text解析

    PS:這部分沒代碼了,只簡單解析一下,因為解析arm指令太麻煩了,估計得寫個半年都不一定能搞定,后續寫了會同步更新在github!!!500彩票邀请码  

    .text節區存儲著可執行指令,我們可以通過節區頭部的名字鎖定.text的偏移地址和大小,找到該節區后,我們會發現這個節區存儲的就是arm機器碼,直接照著指令集翻譯即可,沒有其他的結構。通過ida驗證如下:500彩票邀请码  

11.png  


十、代碼測試相關截圖

12.png500彩票邀请码  

13.png  

14.png  

15.png500彩票邀请码  

16.png  


十一、frida反調試和后序

    frida反調試最簡單的就是檢查端口,檢查進程名,檢查so文件等,但最準確以及最復雜的是檢查匯編指令,我們知道frida是通過一個大調整實現hook,而跳轉的指令就那么幾條,我們是否可以通過檢查每個函數第一條指令來判斷是否有frida了!!!(ps:簡單寫一下原理,拉開寫就太多了,這里感謝某大佬和我討論的這個話題!!!)  

    本來因為這個so文件解析要寫到明年去了,沒想到看起來代碼量大,但實際要用到的地方代碼量很少。。。  

    源碼github鏈接:

免費評分

參與人數 38吾愛幣 +35 熱心值 +35 收起 理由
hlw1995 + 1 + 1 用心討論,共獲提升!
酷檸 + 1 謝謝@Thanks!
brIckZ + 1 學習了!很有幫助
jeanbood + 1 我很贊同!
AyangLe + 1 + 1 我很贊同!
wapj007 + 1 + 1 我很贊同!
laughingsir38 + 1 + 1 熱心回復!
簡單メ傳說 + 1 + 1 用心討論,共獲提升!
victos + 1 + 1 熱心回復!
不諳世事的騷年 + 1 + 1 用心討論,共獲提升!
azcolf + 1 + 1 用心討論,共獲提升!
DemoCc10 + 1 + 1 謝謝@Thanks!
zhoumeto + 1 + 1 用心討論,共獲提升!
云之夢歌 + 1 我很贊同!
九重桂妖 + 1 我很贊同!
lookerJ + 1 我很贊同!
blywq + 1 + 1 謝謝@Thanks!
poisonbcat + 1 + 1 謝謝@Thanks!
Mr_Blake + 1 + 1 謝謝@Thanks!
陳世界 + 1 + 1 我很贊同!
穿透骨頭撫摸妳 + 1 + 1 鼓勵轉貼優秀軟件安全工具和文檔!
zsq + 1 + 1 謝謝@Thanks!
fengbolee + 1 + 1 用心討論,共獲提升!
Allyn0303 + 1 + 1 謝謝@Thanks!
xuanxiaomai + 1 + 1 我很贊同!
siuhoapdou + 1 謝謝@Thanks!
獨行風云 + 1 + 1 謝謝@Thanks!
zhczf + 1 + 1 我很贊同!
woyucheng + 1 + 1 謝謝@Thanks!
yixi + 1 + 1 謝謝@Thanks!
女蘿巖 + 1 + 1 我很贊同!
笙若 + 1 + 1 謝謝@Thanks!
阿薩德話噶 + 1 謝謝@Thanks!
小朋友呢 + 2 + 1 熱心回復!
李玉風我愛你 + 2 + 1 完全看不懂
sblpp + 1 + 1 謝謝@Thanks!
asq56747277 + 1 + 1 謝謝@Thanks!
nstar1221 + 1 + 1 謝謝@Thanks!

查看全部評分

發帖前要善用論壇搜索500彩票邀请码功能,那里可能會有你要找的答案或者已經有人發布過相同內容了,請勿重復發帖。

推薦
zhy_ng 發表于 2020-10-19 16:52
雖然知道樓主說的東西,但一下子還不是全明白,及如何使用,所以請問樓主,如何才能不被檢測到是否裝了xposed框架或別的shell操作軟件,需要修改哪里?
推薦
laoda1228 發表于 2020-10-19 19:36
好專業, 完全看不懂。其實我就好奇.so的文件如何編輯,但等我百度完能打開了發現,更不會了。
4#
魚遇雨欲語 發表于 2020-10-12 09:27
5#
nstar1221 發表于 2020-10-12 10:52
正好在找so的資料
6#
刀大喵 發表于 2020-10-12 11:35
膜拜大神 不曉得啥時候我才能全看懂
7#
一人之下123456 發表于 2020-10-12 12:36
感謝分享,學習了
8#
xixicoco 發表于 2020-10-12 12:55
老大,牛逼,受教了,多謝
9#
luqer 發表于 2020-10-12 15:01
也難怪so逆向困難~ 這學習了。
10#
mitchzh 發表于 2020-10-12 15:25
厲害了,做個標記,后面學習
11#
gunxsword 發表于 2020-10-12 16:17
寫的不錯,贊一個!
12#
fateonlyzero 發表于 2020-10-12 16:52
很詳細的樣子
您需要登錄后才可以回帖 登錄 | 注冊[Register]

本版積分規則 警告:本版塊禁止灌水或回復與主題無關內容,違者重罰!

快速回復 收藏帖子 返回列表 搜索

RSS訂閱|小黑屋|處罰記錄|聯系我們|吾愛破解 - LCG - LSG ( )

GMT+8, 2020-10-22 07:53

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回復 返回頂部 返回列表

500彩票邀請碼-彩經網