<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    基于linker實現so加殼補充-------從dex中加載so

    VSole2021-11-07 16:14:54

    簡介

    之前的文章給大家展示了一個so文件加殼的整體方法,而且給了一個比較簡單的例子,后來我實現了一下將so寫在dex中然后通過hook關鍵函數的方式得到so的地址。

    但是在實際使用過程中我發現了一個問題,就是有些got表中的變量,通過plt調用使用了頁對其指令,這就要求我們的so必須在一塊對其的內存上,如下圖,并且inline hook也好難實現,而且dex是以只讀的方式加載到內存中的,我還需要hook掉關鍵函數改掉它的屬性,基于這三個問題,寫了這篇補充。

    1、so地址頁對其&第一個PT_LOAD和第二個PT_LOAD之間有多余占位數據

    2、實現libart.so中的inlinehook

    3、hook dex加載函數更改只讀權限

    4、修正getsoinfo函數

    so地址頁對齊&多余數據

    由于像上面那種變量或者函數(例如strlen函數)在使用過程中,存在需要取頁開始的情況,我們就必須保證so在內存中的偏移%0x1000等于0,所以我之前設想的直接在dex末尾接so內容的方式就不可取了。

    而且我們附在dex末尾的so是linking view,數據都在相對于文件的偏移上面,而so的執行需要Execution View,代碼中lr這種函數跳轉用的都是相對于so起地址的物理地址偏移,所以強行用靜態的so文件會使調用函數的地址不對。

    而我又不想再次使用mmap將已經加載到內存中的so,再搞出來單獨裝載,所以想了一個折中的辦法,利用系統通過ElfReader::Load結束的soinfo直接將此so的Execution View 整體dump出來。

    這一步可以用frida也可以用ida,或者用我上篇文章寫的裝載函數(當然這種更好因為不會觸發任何反調試,因為我沒有調用init_arry和JNI_Onloade當中的函數),在演示demo中我采用了第一種方式(不想寫裝載了有點累...),用idc腳本從ida里面dump一段內存。

    static main(void){auto fp, begin, end, dexbyte;fp = fopen("d:\\1.so", "wb");begin = 0x0000007366280000;end = begin + 0x0036000;for ( dexbyte = begin; dexbyte < end;dexbyte ++ ){fputc(Byte(dexbyte), fp);}}
    

    搞下來的so直接復制粘貼到dex末尾即可,由于需要對齊,所以需要補0,當然如果想不讓人看出來就補亂碼,后面so也應該加密,只有在使用的時候再解密,我這里沒有加密,只是補0和正常的復制,最后把filesize和checksum等一系列參數補充完整即可。

    由于Execution View后面都是節頭去掉的部分所以都是0。這樣就能解決對齊的問題嘛?答案是可以的,因為在DexClassloader加載dex的路上的函數MapFileAtAddress,是用的頁對齊,所以我們補完0之后so也一定是頁對齊的。

    搞定完這里,把so和插件dex都寫入一個b.txt,然后直接用DexClassLoader加載起來就好,期間我模仿之前寒冰老師出的ctf題的寫法,將b.txt搞到了資源目錄然后使用的時候再復制到cache目錄。

    copyAssetAndWrite("b.txt",getApplicationContext()); ....DexClassLoader loader=new DexClassLoader(path,"/sdcard","/sdcard",context.getClassLoader());
    

    這樣這部分的準備工作就完成了。

    實現libart.so中的inlinehook

    那么同時又產生了第二個問題,就是如何定位我的真正so的首地址。既然我將so加到了dex的末尾,那么我就可以通過dex+偏移的方式來訪問我的so,那么問題又來了如何搞到dex的首地址呢?

    這里有2種辦法,第一種就是從maps文件里面搜索,但是這樣又不是很優雅,最終沒有采納。我最后是借鑒了寒冰老師的脫殼的思路,在LoadClass的關鍵路徑上hook來搞定,我選擇了LoadMethod函數,它的第一個參數就是c層的DexFile對象,那么hook搞定呢,用現成的hook框架?

    感覺特征太多而且就失去了學習的價值,還是自己寫一個來的實在,首先想一個,既然是hook LoadMethod那么是不是用got表hook,來簡單的搞定,掃了好幾眼發現got表種沒有這個函數555...,那么就只能挑戰linline hook了。

    尋找LoadMethod函數地址

    由于LoadMethod函數是導出函數,所以我們可以從libart.so的導出表里面找到它的地址,通過類型為PT_DYNAMIC的段就能找到,這里由于上篇文章已經介紹了elf的文件格式所以這里只貼一點代碼不做過多的介紹。

     char line[1024];    int *startr;    int *end;    int n=1;    FILE *fp=fopen("/proc/self/maps","r");    while (fgets(line, sizeof(line), fp)) {//從maps種掃描libart.so的地址        if (strstr(line, libname) ) {            __android_log_print(6,"r0ysue","");            if(n==1){                startr = reinterpret_cast<int *>(strtoul(strtok(line, "-"), NULL, 16));                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));            }            else{                strtok(line, "-");                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));            }            n++;        }    }    size_t   gnu_nbucket_ = 0;    // skip symndx    uint32_t     gnu_maskwords_ = 0;    uint32_t  gnu_shift2_ =0;    ElfW(Addr)*  gnu_bloom_filter_= nullptr;    uint32_t*  gnu_bucket_ = nullptr;    uint32_t*  gnu_chain_ = nullptr;//導出表4項    int phof=0;    Elf64_Ehdr header;    memcpy(&header,startr,sizeof(Elf64_Ehdr));    uint64 rel= 0;    size_t size=0;    long* plt= nullptr;    char* strtab_= nullptr;    Elf64_Sym* symtab_= nullptr;    Elf64_Phdr cc;    memcpy (&cc,((char*)(startr)+header.e_phoff),sizeof(Elf64_Phdr));    for(int y=0;y        memcpy(&cc, (char *) (startr) +header.e_phoff+sizeof(Elf64_Phdr) * y, sizeof(Elf64_Phdr));        if(cc.p_type==6) {//程序頭偏移            phof=cc.p_paddr-cc.p_offset;        }    }    for(int y=0;y        memcpy(&cc, (char *) (startr) +header.e_phoff+sizeof(Elf64_Phdr) * y, sizeof(Elf64_Phdr));        if(cc.p_type==2) {//p_type=2代表找到了動態段            Elf64_Dyn dd;            for(y=0;y==0||dd.d_tag!=0;y++) {                memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn)+0x1000,                       sizeof(Elf64_Dyn));                 if(dd.d_tag==0x6ffffef5){//找到了 DT_GNU_HASH段也就是導出表                     gnu_nbucket_ = reinterpret_cast<uint32_t*>((char*)startr + dd.d_un.d_ptr-phof)[0];                    // skip symndx                    gnu_maskwords_ = reinterpret_cast<uint32_t*>((char*)startr + dd.d_un.d_ptr-phof)[2];                    gnu_shift2_ = reinterpret_cast<uint32_t*>((char*)startr + dd.d_un.d_ptr-phof)[3];                     gnu_bloom_filter_ = reinterpret_cast((char*)startr + dd.d_un.d_ptr + 16-phof);                    gnu_bucket_ = reinterpret_cast<uint32_t*>(gnu_bloom_filter_ + gnu_maskwords_);                    // amend chain for symndx = header[1]                    gnu_chain_ = reinterpret_cast<uint32_t *>( gnu_bucket_ +                                                               gnu_nbucket_-reinterpret_cast<uint32_t *>(                                                                       (char *) startr +                                                                       dd.d_un.d_ptr-phof)[1]);                 }                if(dd.d_tag==5 ){//得到字符串表的首地址                    strtab_=reinterpret_cast< char*>((char *) startr+dd.d_un.d_ptr-phof);                }                if(dd.d_tag==6 ){//得到符號表的首地址                    symtab_= reinterpret_cast((                            (char *) startr + dd.d_un.d_ptr-phof));                }            }        }      }
    

    很容易就得到了導出表、符號表、字符串表的信息,接下來只需要模仿安卓源碼中soinfo::gnu_lookup函數的寫法來,獲取我們的導出符號的函數地址就好,這里我直接將symbol_name.gnu_hash函數抽出來實現了一下,他這種計算hash的方式很簡單,也就5行代碼。

    char* name_=symname;  uint32_t h = 5381;  const uint8_t* name = reinterpret_cast<const uint8_t*>(name_);  while (*name != 0) {      h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c  }//實現symbol_name.gnu_hash  int index=0;  uint32_t h2 = h >> gnu_shift2_;  uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8;  uint32_t word_num = (h / bloom_mask_bits) & gnu_maskwords_;  ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];  n = gnu_bucket_[h % gnu_nbucket_];//模仿安卓源碼直接抄  do {        Elf64_Sym * s = symtab_ + n;        char * sb=strtab_+ s->st_name;       if (strcmp(sb ,reinterpret_cast<const char *>(name_)) == 0 ) {            break;      }  } while ((gnu_chain_[n++] & 1) == 0);
    

    這樣最終得到了我們傳入符號在符號表當中的索引,那么只要拿到當中的st_value就是地址偏移了。

    void* mysym=(char *)startr+sb->st_value-phof
    

    粗略的實現inline hook

    由于水平太低有點看不懂大佬的思路.......,只能先自己思考一下如何實現inline hook了,由此提出以下幾個問題:

    1、首先構想一個整體的思路,如何實現每個我們需要hook的函數都能跳轉到我們自己的函數執行?

    這個問題可以用一條指令就是b一個地址,但是又有一個問題,這個地址是有范圍要求的,如果不在同一個so當中很難滿足這個界限要求,這一點在https://armconverter.com/ 中可以得到驗證,所以這種方式是不可以的。

    那么就只剩一種方式了br或blr一個寄存器的值,根據網上資料(實在太菜大佬的文章只能看懂這一點)x17和x16是不經常使用的(我只在安卓源碼art_jni_dlsym_lookup_stub中看到了使用),那么就需要賦值和br來替換函數的前幾條指令代碼如下,一共用了16位來跳轉到我自己的代碼,所以要把這四條指令保存下來。

    這里保存指令我用的全局變量不知道有沒有更好的方式,這里面我用的blr因為我還要跳回來所以不能用br,用br不會保存x30寄存器,就會直接跳回上一級函數,就不會執行原函數了,但是這樣又有一個問題,就是我這4條指令可能會覆蓋x30的入棧指令那么在之后的x30恢復的時候就會無法恢復x30,所以我決定在x30的入棧指令之后執行我的操作。

    具體做法是掃描指令如果滿足STX29, X30, [SP,#0x80]這種類似的,才在他的后一位寫入我們的跳轉指令 ,如果直接掃到了最后,那么沒辦法了只能從開頭開始,16進制和匯編的轉換都可以從https://armconverter.com/ 當中找到這里就不在計算了,如下圖:

     int s=0;    for(n=0;*(int*)((char *)startr+sb->st_value-phof+n)!=0xd65f03c0;n=n+4){        int code=*(int*)((char *)startr+sb->st_value-phof+n);        if(code>>32==0xa9&&(code&0xfff)==0xbfd){          __android_log_print(6,"r0ysue","%x",n);            s=1;            break;        }     }    if(s=1){        n=n+4;        }    else{        n=0;    }one= *(int*)((char *)startr+sb->st_value-phof+n);two= *(int*)((char *)startr+sb->st_value-phof+n+4);three[ns=*(int*)((char *)startr+sb->st_value-phof+n+8);four[]=*(int*)((char *)startr+sb->st_value-phof+n+12); *(int*)((char *)startr+sb->st_value-phof+n)=0x58000051;//*(int*)((char *)startr+sb->st_value-phof+n+4)=0xd63f0220;*(long**)((char *)startr+sb->st_value-phof+n+8)= reinterpret_cast<long*>(myloadmethod);//自己函數的地址
    

    成功的跳到我寫的函數,那么現在就構思如何寫自己的殼函數了,有一個問題,就是arm64不能操作pc,所以我要如何控制回來的時候跳到哪里呢?

    我把目光盯向了x30,執行完RET這條匯編之后,pc會跳到x30處(也就是32位下的lr),那么只要我指定x30就好了,由于上面用了4條指令而我是在第2條指令跳轉的,所以要把x30+8,這一段我發現如果不用裸函數寫,他會自動加上對x30寄存器的保護,所以我選擇了裸函數,之后就是對寄存器的保護就好了。

    void  __attribute((naked)) myloadmethod(){    asm("add x30,x30,8");//指定跳回去的位置    asm("sub SP, SP, #0x100");//申請棧    asm("stp X29, X30, [SP,#0x10]");//保護寄存器    asm("stp X0, X1, [SP,#0x20]");    asm("stp X2, X3, [SP,#0x30]");    asm("stp X4, X5, [SP,#0x40]");    asm("stp X6, X7, [SP,#0x50]");    asm("stp X8, X9, [SP,#0x60]");    asm("stp X10, X11, [SP,#0x70]");    asm("stp X12, X13, [SP,#0x80]");    asm("stp X14, X15, [SP,#0x90]");    asm("ldp X16, X17, [SP,#0x10]");//保存x30到x17,當然這里用mov或者ldr都行我復制粘貼省事了    asm("mov X16,SP");//這里參數處理我們想好就先用棧替代    asm("mov x0,x0");//占位,會改成BL _Z4pltsv,也就是我們自己寫的第二個函數    asm("ldp X8, X9, [SP,#0x60]");//寄存器恢復    asm("ldp X10, X11, [SP,#0x70]");    asm("ldp X12, X13, [SP,#0x80]");    asm("ldp X14, X15, [SP,#0x90]");    asm("ldp X0, X1, [SP,#0x20]");    asm("ldp X2, X3, [SP,#0x30]");    asm("ldp X4, X5, [SP,#0x40]");    asm("ldp X6, X7, [SP,#0x50]");    asm("ldp X29, X30, [SP,#0x10]");    asm("add SP, SP, #0x100");    asm("mov x0,x0");//之前在原函數中占位的第1條指令    asm("mov x0,x0");//之前在原函數中占位的第2條指令    asm("mov x0,x0");//之前在原函數中占位的第3條指令    asm("mov x0,x0");//之前在原函數中占位的第4條指令    asm("RET"); }
    

    這里就是下一個問題了,我們如何執行我們自己的函數,又如何將原來的4條指令執行一遍,這里我選擇了再做一層包裝,再寫一個跳轉,這會就不用裸函數了,而且指令直接用b 相對地址就可以了,因為是在同一個so當中,由于我不知道在內存中他倆誰在前面,所以寫了個判斷,而且恢復占用指令的代碼也寫在了這個包裝函數里面。

    int code;if((long)myloadmethod>(long)plts){    int off=(long)myloadmethod-(long)plts+48;    code=0x97ffffff-off/4;}else{    int off=(long)plts-(long)myloadmethod-48;    code=off/4|0x94000000;} *(int*)((char *)myloadmethod+52)= reinterpret_cast<int>(code);
    

    在這個包裝函數中,主要有2方面的內容,于是簡單的代碼就出來了。

    1、執行我們在外面傳入的hook函數

    2、恢復占用的指令

    void plts(){    func st=(func)fc;    st();    memcpy((char*)myloadmethod+0x60,&one,4);    memcpy((char*)myloadmethod+0x64,&two,4);    memcpy((char*)myloadmethod+0x68,&three,4);    memcpy((char*)myloadmethod+0x6c,&four,4); }
    

    這樣一個簡單的inlinehook框架就寫完了,就在我迫不及待的去試一下的時候,發現有個大問題,就是單個hook是沒啥問題,但是要同時hook2個函數就不行了。

    因為我用了大量的全局變量,新的輸入會覆蓋舊的輸入,5555...,而我的需求正好是hook 2個函數,氣死我了,那么只能做一個思考了,如何完善這個框架,而且盡量做到不大改,因為上面太多了,那么既用了全局變量,就只能以數組的形式存儲使用了,直接用一個全局變量ns存儲我們的索引。

     one[ns]= *(int*)((char *)startr+sb->st_value-phof+n);    two[ns]= *(int*)((char *)startr+sb->st_value-phof+n+4);    three[ns]=*(int*)((char *)startr+sb->st_value-phof+n+8);     four[ns]=*(int*)((char *)startr+sb->st_value-phof+n+12);    funtab[ns]= reinterpret_cast<long>((char *) startr + sb->st_value - phof);ns++;
    

    那么在使用的時候又有一個問題,就是調用順序可能和我們自己寫的hook的順序不太一樣,所以如何確定我們調用的函數屬于哪一個索引又是一個問題,這里我直接用之前的x30來確定,因為x30來自與原函數,所以與原函數不會差距太大,用這個來判斷調用的函數屬于哪個索引。

    void plts(){    unsigned long addr=0;    int uu;    __asm__("mov %[input_n], x17\r"    :[result_m] "=r" (addr)    :[input_n] "r" (addr)    );    int yy=0;    for(yy=0;yy        if(addr-(unsigned long)funtab[yy]<0x100){            break;        }     }    func st=(func)fc[yy];    st();    memcpy((char*)myloadmethod+0x60,&one[yy],4);    memcpy((char*)myloadmethod+0x64,&two[yy],4);    memcpy((char*)myloadmethod+0x68,&three[yy],4);    memcpy((char*)myloadmethod+0x6c,&four[yy],4);    __android_log_print(6,"r0ysue","");}
    

    那么這里我們的框架就完善了,其實還有個問題我沒有解決,就是如何更改參數,我這里只能先用棧來解決更改參數的問題(沒想好如何解決是個敗筆),那么就可以完成我們的hook來獲得dex首地址了,hook寫在init里面就好。

    void io(void* a,void*b){    long** dex= static_cast<long **>(b);     size= reinterpret_cast<size_t>(*(dex + 2));    long* begin=(*(dex+1));    if(size==0x392000&&fl==0) {//是我們自己大小的dex而且只取一次        fl=1;     realel= reinterpret_cast<long*>(begin );//拿到真正的dex地址      jiake();//釋放so,加殼函數上篇文章講過了,所以這里不再贅述,只需注意修正got表中的變量的值即可    }} void _init(){    mainfun("_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE", "libart.so",            reinterpret_cast<void *>(io));}
    

    hook dex加載函數更改只讀權限

    那么現在就只剩下一個問題了,就是如何將dex加載進內存中,這里模仿寒冰老師的ctf賽題,java代碼直接拿過來用就好,就是上面第一節的代碼,但是這里有一個問題,DexClassLoader是以只讀的方式將dex文件加載到內存中,我們需要可讀可寫可執行,所以需要改權限。

    但是這里又遇到了一個問題,調用mprotect的時候直接報錯,Permission denied,我也不知道為啥在這里卡了好久,最后在 https://blog.csdn.net/earbao/article/details/120308836 中找到了答案,這里就要求我們取hook MapFileAtAddress將其第三個函數改為PROT_WRITE|PROT_READ|PROT_EXEC,這就是我剛才要優化我的inlinehook 框架的原因,我hook了2個函數一個是LoadMethod和MapFileAtAddress,更改代碼如下:

    void jjj(void* a,void* b,int c,int d){    d=7;    __asm__("str %[input_n], [X16,#0x30]\r"http://用到了剛才保存的寄存器,這里直接用    :[result_m] "=r" (d)    :[input_n] "r" (d)    );}mainfun("_ZN3art6MemMap16MapFileAtAddressEPhmiiilbbPKcPNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEE", "libart.so",            reinterpret_cast<void *>(jjj));
    

    修正getsoinfo函數

    這樣程序就設計完成了,我試沒有任何問題,我興高采烈地去找大佬試一下,沒想到打臉了,直接就崩潰了,得知他是bullhead之后,我就拿起了我的82年的Nexus 5x,調試了一下。

    發現獲得soinfo指針那里直接返回了一個特別大的值,看來不同型號的手機linker不一樣,我打開ida易看果然,不同型號的手機linker中,__dl_g_soinfo_handles_map偏移不一樣,我上篇文章用的是偏移,所以會有這個問題,如下圖,左面是0xfa460右面是0xfd460。

    那么這就很煩了,好難受,上篇文章的內容就有局限性了,只能針對某一型號的手機,那是不可以的。我要解決問題,就還是從soinfo_from_handle這個inline函數入手,這會就看一看他是如何實現的,看不懂還是看匯編吧,還是匯編友好一點。

    static soinfo* soinfo_from_handle(void* handle) {  if ((reinterpret_cast<uintptr_t>(handle) & 1) != 0) {    auto it = g_soinfo_handles_map.find(reinterpret_cast<uintptr_t>(handle));    if (it == g_soinfo_handles_map.end()) {      return nullptr;    } else {      return it->second;    } }
    

    還是匯編簡單一點,這部分內容就很容易理解了,只要拿到最后的x0就好,那么他做了什么呢?

    1、首先取了dl_g_soinfo_handles_map的值

    2、然后將其中的值與他后面一個字節取余操作

    3、取上一步結果乘8然后取首先取了dl_g_soinfo_handles_map的值中的值加這個偏移

    4、取2次地址中的值并且加0x18

    5、最后再取一次它其中地址的值就是soninfo指針了

    .text:000000000000CC5C                 ADRP            X12, #__dl_g_soinfo_handles_map_ptr@PAGE.text:000000000000CC60                 LDR             X12, [X12,#__dl_g_soinfo_handles_map_ptr@PAGEOFF].text:000000000000CC64                 LDR             X8, [X12,#(qword_FA468 - 0xFA460)].text:000000000000CC68                 CBZ             X8, loc_CCF4.text:000000000000CC6C                 SUB             X9, X8, #1.text:000000000000CC70                 AND             X10, X9, X8.text:000000000000CC74                 CBZ             X10, loc_CC90.text:000000000000CC78                 MOV             X11, X19.text:000000000000CC7C                 CMP             X8, X19.text:000000000000CC80                 B.HI            loc_CC94.text:000000000000CC84                 UDIV            X11, X19, X8.text:000000000000CC88                 MSUB            X11, X11, X8, X19.text:000000000000CC8C                 B               loc_CC94.text:000000000000CC90 ; ---------------------------------------------------------------------------.text:000000000000CC90.text:000000000000CC90 loc_CC90                                ; CODE XREF: __dl__Z10do_dlclosePv+5C↑j.text:000000000000CC90                 AND             X11, X9, X19.text:000000000000CC94.text:000000000000CC94 loc_CC94                                ; CODE XREF: __dl__Z10do_dlclosePv+68↑j.text:000000000000CC94                                         ; __dl__Z10do_dlclosePv+74↑j.text:000000000000CC94                 LDR             X12, [X12].text:000000000000CC98                 LDR             X12, [X12,X11,LSL#3].text:000000000000CC9C                 CBZ             X12, loc_CCF4.text:000000000000CCA0.text:000000000000CCA0 loc_CCA0                                ; CODE XREF: __dl__Z10do_dlclosePv+A4↓j.text:000000000000CCA0                                         ; __dl__Z10do_dlclosePv+CC↓j.text:000000000000CCA0                 LDR             X12, [X12].text:000000000000CCA4                 CBZ             X12, loc_CCF4.text:000000000000CCA8                 LDR             X13, [X12,#8].text:000000000000CCAC                 CMP             X13, X19.text:000000000000CCB0                 B.NE            loc_CCC4.text:000000000000CCB4                 LDR             X13, [X12,#0x10].text:000000000000CCB8                 CMP             X13, X19.text:000000000000CCBC                 B.NE            loc_CCA0.text:000000000000CCC0                 B               loc_CCEC.text:000000000000CCC4 ; ---------------------------------------------------------------------------.text:000000000000CCC4.text:000000000000CCC4 loc_CCC4                                ; CODE XREF: __dl__Z10do_dlclosePv+98↑j.text:000000000000CCC4                 CBZ             X10, loc_CCDC.text:000000000000CCC8                 CMP             X13, X8.text:000000000000CCCC                 B.CC            loc_CCE0.text:000000000000CCD0                 UDIV            X14, X13, X8.text:000000000000CCD4                 MSUB            X13, X14, X8, X13.text:000000000000CCD8                 B               loc_CCE0.text:000000000000CCDC ; ---------------------------------------------------------------------------.text:000000000000CCDC.text:000000000000CCDC loc_CCDC                                ; CODE XREF: __dl__Z10do_dlclosePv:loc_CCC4↑j.text:000000000000CCDC                 AND             X13, X13, X9.text:000000000000CCE0.text:000000000000CCE0 loc_CCE0                                ; CODE XREF: __dl__Z10do_dlclosePv+B4↑j.text:000000000000CCE0                                         ; __dl__Z10do_dlclosePv+C0↑j.text:000000000000CCE0                 CMP             X13, X11.text:000000000000CCE4                 B.EQ            loc_CCA0.text:000000000000CCE8                 B               loc_CCF4.text:000000000000CCEC ; ---------------------------------------------------------------------------.text:000000000000CCEC.text:000000000000CCEC loc_CCEC                                ; CODE XREF: __dl__Z10do_dlclosePv+A8↑j.text:000000000000CCEC                 LDR             X0, [X12,#0x18].text:000000000000CCF0                 CBNZ            X0, loc_CC50
    

    翻譯成最終c代碼如下,這樣就能解決剛才的不同手機的適配問題了。

    void* nmm=(char*)startr+realoff;char *next=(char*)nmm+8;unsigned long map= reinterpret_cast<unsigned long>(strstr);unsigned long mapnext=*next;mapnext=map%mapnext;  nmm=(long*)((char*)(*(long*)nmm)+mapnext*8); long* final= reinterpret_cast<long *>((char *) (**(long **) nmm) + 0x18);void* soinfo= reinterpret_cast<void *>(*final);return static_cast(soinfo);
    

    那么又產生了下一個問題,就是如何拿到dl_g_soinfo_handles_map。

    dl_g_soinfo_handles_map是linker中的符號的值,但不是導出符號的值,基于此我沒有在動態段中找到它對應的的符號,如下圖只有寥寥的幾個符號。

    那怎么辦呢,我想到了可以通過節頭表索引,直接打開/system/bin/linker64文件讀它的節頭表,比讀段簡單了許多,代碼如下:

    int fd; void *start; struct stat sb; fd = open("/system/bin/linker64", O_RDONLY); /*打開/etc/passwd */ fstat(fd, &sb); /* 取得文件大小 */ start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); __android_log_print(6, "r0ysue", "%p", start); Elf64_Ehdr header; memcpy(&header, start, sizeof(Elf64_Ehdr)); int secoff = header.e_shoff; int secsize = header.e_shentsize; int secnum = header.e_shnum; int secstr = header.e_shstrndx; Elf64_Shdr strtab; memcpy(&strtab, (char *) start + secoff + secstr * secsize, sizeof(Elf64_Shdr)); int strtaboff = strtab.sh_offset; char strtabchar[strtab.sh_size]; memcpy(&strtabchar, (char *) start + strtaboff, strtab.sh_size); Elf64_Shdr enumsec; int gotoff = 0; int gotsize = 0; int strtabsize = 0; int stroff = 0; for (int n = 0; n < secnum; n++) {     memcpy(&enumsec, (char *) start + secoff + n * secsize, sizeof(Elf64_Shdr));     if (strcmp(&strtabchar[enumsec.sh_name], ".symtab") == 0) {         gotoff = enumsec.sh_offset;         gotsize = enumsec.sh_size;         __android_log_print(6, "r0ysue", "%x", gotsize);     }     if (strcmp(&strtabchar[enumsec.sh_name], ".strtab") == 0) {         stroff = enumsec.sh_offset;         strtabsize = enumsec.sh_size;         __android_log_print(6, "r0ysue", "%x", stroff);     } } int realoff=0; char relstr[strtabsize]; Elf64_Sym tmp; memcpy(&relstr, (char *) start + stroff, strtabsize);  for (int n = 0; n < gotsize; n = n + sizeof(Elf64_Sym)) {     memcpy(&tmp, (char *)start + gotoff+n, sizeof(Elf64_Sym));     if(tmp.st_name!=0&&strstr(relstr+tmp.st_name,"soinfo_handles_map"))         realoff=tmp.st_value; }
    

    總結

    這樣就完成了上面4個問題的解決,比較基礎,主要是對elf文件格式的一個解析,我只是提供了一個例子,沒有對數據進行加密,正常情況下我覺得還應該對dex中的so信息進行加密保護效果才更強,而且我的hook貌似不穩定,又崩潰的幾率,感謝大家觀看。

    charchar函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    無意中看到ch1ng師傅的文章覺得很有趣,不得不感嘆師傅太厲害了,但我一看那長篇的函數總覺得會有更騷的東西,所幸還真的有,借此機會就發出來一探究竟,同時也不得不感慨下RFC文檔的妙處,當然本文針對的技術也僅僅只是在流量層面上waf的繞過。Pre很神奇對吧,當然這不是終點,接下來我們就來一探究竟。前置這里簡單說一下師傅的思路部署與處理上傳war的servlet是?
    記一次網站滲透過程
    2022-09-13 08:37:27
    前幾天記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。
    一、序言 記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。 二、滲透過程 1. 無意點開一個網站,發現網站比較小,且看起來比較老,然后發現logo沒有改,于是乎去百度搜索這個cms,發現有通用漏洞,這里貼一個鏈接:Beecms 通用漏洞(https://lin
    釣魚小技巧-XLM
    2022-01-21 21:30:11
    隨后保存為啟用宏的文檔。而在實戰環境中,我們更關注的是能否執行我們的shellcode。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    最近在分析JDK7u21的Gadgets,有兩個不解之處,閱讀前輩們的文章發現并未提起。1.為什么有的POC入口是LinkedHashSet,有的是HashSet,兩個都可以觸發嗎?
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类