Realize so packing based on linker and supplement loading so from dex

Realize so packing technology based on linker (supplement)

Overview

The previous article showed you an overall method of packing an so file, and gave a relatively simple example. Later, I implemented writing so in dex and then obtained the address of so by hooking key functions. But in the process of actual use, I found a problem, that is, some variables in the got table use page alignment instructions through plt calls, which requires our so to be on a piece of corresponding memory, as shown in the figure below, and inline Hook is also very difficult to implement, and dex is loaded into memory in a read-only manner. I also need to hook key functions to change its attributes. Based on these three problems, I wrote this Supplement 1: so address page is
correct There is redundant data between the first PT_LOAD and the second PT_LOAD
2: Implement the inlinehook in libart.so
3: Hook the dex loading function to change the read-only permission
4: Correct the getsoinfo function

so address page to its & excess data

Since there is a need to fetch the start of the page during the use of variables or functions like the above (such as the strlen function), we must ensure that the offset %0x1000 of so in memory is equal to 0, so I imagined it directly in dex It is not advisable to connect the content of so at the end, and the so we attach to the end of dex is a linking view, the data is on the offset relative to the file, and the execution of so requires Execution View, and the lr function jumps in the code The physical address offset relative to the starting address of so is used, so forcibly using a static so file will make the address of the calling function incorrect, and I don’t want to use mmap again to load the so that has been loaded into memory, and then make it out separately Loading, so I thought of a compromise method, using the system to directly dump the Execution View of this so through the soinfo at the end of ElfReader::Load. This step can be done with frida or ida, or with what I wrote in the last article Loading function (of course, this is better because it will not trigger any anti-debugging, because I did not call the functions in init_arry and JNI_Onloade), in the demo I used the first method (I don’t want to write and load a bit tired...), use The idc script dumps a piece of memory from ida

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); 
}
}

Just copy and paste the obtained so to the end of dex. Since it needs to be corrected, it needs to be filled with 0. Of course, if you want to prevent people from seeing it, add garbled characters. The subsequent so should also be encrypted and only decrypted when it is used. I There is no encryption here, just add 0 and normal copy, and finally complete a series of parameters such as filesize and checksum. Since the

section header is removed after the Execution View, they are all 0. Will this solve the alignment problem? The answer is yes, because the function MapFileAtAddress on the way of loading dex by DexClassloader uses page alignment, so after we fill in 0, so must also be page alignment. After finishing here, write so and plug-in dex into

one b.txt, and then load it directly with DexClassLoader. During the period, I imitated the writing method of the ctf question given by Mr. Han Bing before, and got b.txt into the resource directory and then copied it to the cache directory when using it.

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

This part of the preparatory work is complete.

Implement inlinehook in libart.so

Then at the same time, the second problem arises, which is how to locate the first address of my real so. Since I added so to the end of dex, then I can access my so through dex+offset, then the question arises, how to get the first address of dex? There are two ways here, the first one is to search from the maps file, but this is not very elegant, and it was not adopted in the end. In the end, I borrowed the shelling idea of ​​Mr. Han Bing and hooked it on the key path of LoadClass. I chose the LoadMethod function. Its first parameter is the DexFile object of the c layer. hook framework? I feel that there are too many features and the value of learning is lost. It is better to write one by myself. First, I think about it. Since it is a hook LoadMethod, should I use the got table hook to simply get it done? After a few glances, I found that there is no got table This function is 555..., then you can only challenge the inline hook.

Find the address of the LoadMethod function

Since the LoadMethod function is an export function, we can find its address from the export table of libart.so, and we can find it through the segment of type PT_DYNAMIC. Here, since the previous article has introduced the elf file format, we only paste a little bit here The code does not introduce too much

   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<header.e_phnum;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<header.e_phnum;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<ElfW(Addr)*>((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<Elf64_Sym *>((
                            (char *) startr + dd.d_un.d_ptr-phof));
                }
            }
        }


    }

It is easy to get the information of the export table, symbol table, and string table. Next, you only need to imitate the writing method of the soinfo::gnu_lookup function in the Android source code to obtain the function address of our exported symbol. Here I directly use symbol_name The .gnu_hash function is extracted and implemented. His method of calculating hash is very simple, and it only takes 5 lines of code

  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);

In this way, we finally get the index of the symbol we passed in in the symbol table, so as long as we get the st_value in it, it is the address offset

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

Rough implementation of inline hook

Because the level is too low, I can't understand the big guy's thinking... so I can only think about how to realize inline hook first, so I put forward the following questions: 1. First conceive
an overall idea, how to realize each of the hooks we need Functions can jump to our own function execution?
This problem can use one instruction to be an address of b, but there is another problem. This address has a range requirement. If it is not in the same so, it is difficult to meet this limit requirement. This is in https://armconverter.com/ It can be verified, so this method is not possible, then there is only one method left. The value of a register of br or blr, according to the online information (the article of the really big guy can only understand this point) x17 and x16 It is not often used (I only saw it in the Android source code art_jni_dlsym_lookup_stub), then you need to assign and br to replace the first few instructions of the function. The code is as follows, a total of 16 bits are used to jump to my own code, so To save these four instructions, I don’t know if there is a better way to save the global variables I use for the instructions here. I use blr because I have to jump back so I can’t use br. Using br will not save the x30 register. It will directly jump back to the upper-level function, and the original function will not be executed, but there is another problem, that is, my 4 instructions may overwrite the push instruction of x30, so when x30 recovers later, it will not be able to Restore x30, so I decided to execute my operation after the push instruction of x30. The specific method is to scan the instruction if it meets STX29, X30, [SP,#0x80]this similarity, and then write our jump instruction in its last bit. If it is scanned directly to the end , then there is no other way but to start from the beginning, the conversion of hexadecimal and assembly can be found from https://armconverter.com/ It is not calculated here, as shown in the figure below

    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);//自己函数的地址


I have successfully jumped to the function I wrote, so now I am thinking about how to write my own shell function. There is a problem, that is, arm64 cannot operate pc, so how do I control where to jump when I come back? I set my sights on x30, after executing the RET assembly, the pc will jump to x30 (that is, lr under 32 bits), then as long as I specify x30, it will be fine, because 4 instructions are used above and I am in the second instruction Jump, so x30+8 is needed. I found that if the naked function is not written in this section, it will automatically add protection to the x30 register, so I chose the naked function, and then the protection of the register will be fine.

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");

}

Here is the next question, how do we execute our own function, and how to execute the original 4 instructions again, here I choose to make another layer of packaging, and then write a jump, this will not use the naked function, And the instruction b 相对地址can be used directly, because it is in the same so, since I don't know who is in front of them in the memory, so I wrote a judgment, and the code to restore the occupied instruction is also written in this wrapper function

    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);

In this wrapper function, there are mainly two aspects, so the simple code comes out
1. Execute the hook function we passed in outside
2. Restore the occupied instructions

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);

}

Such a simple inlinehook framework is finished, and when I couldn't wait to try it out, I found a big problem, that is, a single hook is fine, but it is not enough to hook two functions at the same time, because I used a lot The new input will overwrite the old input, 5555..., and my requirement is just to hook 2 functions. I am so mad, so I can only do one thought, how to improve this framework, and try not to A big change, because there are too many above, then if the global variable is used, it can only be stored in the form of an array, and directly use a global variable ns to store our index.


    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++;

Then there is another problem when using it, that is, the calling order may be different from the order of the hooks we wrote ourselves, so how to determine which index the function we call belongs to is another problem. Here I directly use the previous x30 to Make sure, because x30 comes from the original function, so it will not be too different from the original function. Use this to determine which index the called function belongs to

void plts(){
    
    
    unsigned long addr=0;
    int uu;
    __asm__("mov %[input_n], x17\r\n"
    :[result_m] "=r" (addr)
    :[input_n] "r" (addr)
    );
    int yy=0;
    for(yy=0;yy<ns;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","");
}

Then our framework is complete here. In fact, there is still a problem that I have not solved, that is, how to change the parameters. I can only use the stack to solve the problem of changing parameters (it is a failure to think about how to solve it), then it can be completed. Our hook is used to get the first address of dex, just write the hook in 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 loading function to change read-only permissions

So now there is only one problem left, which is how to load the dex into the memory. Here I imitate the ctf competition questions of Mr. Han Bing. Just take the java code and use it directly. It is the code in the first section above, but here is a The problem is that DexClassLoader loads the dex file into the memory in a read-only manner. We need to be readable, writable and executable, so we need to change the permissions, but there is another problem here. When calling mprotect, it reports an error directly Permission denied. I know why I have been stuck here for a long time, and finally found the answer in https://blog.csdn.net/earbao/article/details/120308836. Here we are required to take the hook MapFileAtAddress and change its third function to PROT_WRITE|PROT_READ|PROT_EXEC, this That's why I just wanted to optimize my inlinehook framework. I hooked two functions, one is LoadMethod and MapFileAtAddress, and the code is changed as follows

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

Fix getsoinfo function

In this way, the program design is completed. I tried it without any problems. I happily went to the big guy to try it. I didn’t expect to be slapped in the face, and it crashed. After learning that he is bullhead, I picked up my 82 Nexus 5x, after debugging, I found that the soinfo pointer directly returned a very large value. Hey, it seems that the linker of different models of mobile phones is different. I opened ida to see it __dl_g_soinfo_handles_map. It’s different. I used offset in my last article, so there will be this problem. As shown in the picture below, the left is 0xfa460 and the right is 0xfd460.

Then this is very annoying and uncomfortable. The content of the previous article has limitations. It is not possible to target a certain type of mobile phone. If I want to solve the problem, I still start with the inline function soinfo_from_handle. Then I will take a look at how it is implemented. eee... If I don’t understand it, I still look at the assembly Well, it's better to compile friendly.

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;
    }
 }

The assembly is simpler, this part of the content is easy to understand, just get the last x0, then what did he do 1. First take the
value of __dl_g_soinfo_handles_map
2. Then compare the value with the next word Save and get the rest operation
3. Take the result of the previous step and multiply it by 8, then take the value in the value of __dl_g_soinfo_handles_map firstly and add this offset
4. Take the value in the address twice and add 0x18
5. Finally take the address in it again The value is the soninfo pointer

.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

The translation into the final c code is as follows, so that the adaptation problem of different mobile phones just now can be solved

    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<uint64 *>(soinfo);

Then the next question arises, how to get __dl_g_soinfo_handles_map.

__dl_g_soinfo_handles_map is the value of the symbol in the linker, but not the value of the exported symbol. Based on this, I did not find its corresponding symbol in the dynamic segment. What should I do if there are only a few symbols in the following figure? Header table index, directly open /system/bin/linker64 file to read its section header table, which is much simpler than reading sections, the code is as follows

   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;
    }

Summarize

In this way, the solution to the above four problems is completed. It is relatively basic, mainly an analysis of the elf file format. I just provided an example without encrypting the data. Under normal circumstances, I think the so information in the dex should also be encrypted. The effect of encryption protection is stronger, and my hook seems unstable, and the chance of crashing, thank you for watching

Guess you like

Origin blog.csdn.net/u010559109/article/details/120728774
SO?