<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/mobile/16677

            Author:leonnewton

            0x00 前言


            早在2013年,bluebox就發布了一個可以在運行時篡改Dalvik字節碼的Demo。之后,國內有很多對這個Demo進行分析的資料,分析得也非常詳細。但是看了很多資料以后,感覺缺少從實現的角度可以給學習的同學進行練手的資料。因此,本文從實現的角度,解釋篡改的原理并實現篡改的過程,并附上實現的源代碼。希望可以幫助想自己實現著玩的同學。

            代碼:

            Dalvik字節碼自篡改原理及實現

            0x01 篡改的原理


            由于Dex文件被映射為只讀的,因此從Dex文件自己的代碼里來修改自己的字節碼是不行的。但是native代碼和DVM卻運行在相同的比字節碼低的層次,所以可以通過native代碼來修改字節碼。在修改字節碼之前,用mprotect把字節碼所在的內存重新映射為可寫,然后把新的字節碼覆蓋原來的字節碼即可。

            而在應用程序的進程空間,有一段是加載的odex文件,odex的0x28偏移開始的地方就是Dex文件。

            1.png

            在得到Dex文件在內存中的地址之后,通過查找Dex一系列相應的結構,最終找到存放指令的地址,把新的指令覆蓋原來的指令即可。

            0x02 搜索method指令位置的過程


            找到了Dex文件的位置,需要經過幾個查找步驟,最終定位到method存放指令的位置。具體的過程如圖所示:

            2.png

            1. 首先是需要確定method的名字,也就是method字符串,再確定method所在class的名字,也就是class字符串。這確定了要修改的方法。
            2. 把2個字符串到DexStringId結構的位置進行搜索,進而得到2個字符串在DexStringId中的索引序號。
            3. 對于class字符串,再通過DexStrngId索引序號,到DexTypeId中搜索,得到class字符串在DexTypeId中的索引序號。
            4. 還是class字符串,得到DexTypeId中的索引序號后,再到DexClassDef中搜索,就可以得到class的DexClassDef的位置。其中classDataOff字段記錄了DexClassData偏移,這個結構里可以找到DexMethod結構。
            5. 而對于method字符串,找到DexStringId索引后,再結合上面class的DexTypeId,可以確定DexMethodId的索引序號。
            6. 現在我們知道了DexMethodId的索引序號,也知道了存放DexMethod的位置,直接搜索就可以得到我們要找的DexMethod。這個結構里的codeoff指向了存放指令的位置。

            0x03 實驗的設計


            首先寫一個應用,應用的TestAdd類有一個add方法,這個方法代碼里進行的是乘法,計劃篡改字節碼后,運行的時候進行的是加法。如下:

            #!cpp
            public int add(int a, int b){
                    int c;
                    c = a * b;
                    return c;       
                }
            

            篡改后返回a + b的值。

            0x04 代碼實現


            搜索Dex文件開頭

            讀取/proc/self/maps,搜索odex文件的地址,從而得到odex的起始地址和結束地址。

            #!cpp
            FILE *fp;    
               fp = fopen("/proc/self/maps", "r");    
               if(fp!=NULL)
                  {
                char line [ 2048 ];
                while ( fgets ( line, sizeof line, fp ) != NULL ) 
                           {
                              if (strstr(line, "[email protected]@com.example.selfmodify") != NULL) //找到odex文件的名字
                                  {
                                     if (strstr(line, "classes.dex") != NULL) 
                                         {
                                    s = strchr(line, '-');
                                            if (s == NULL) 
                                                LOGD(" Error: string NULL");
                                           *s++ = '\0';
                                    start = (void *)strtoul(line, NULL, 16);
                                    end = (void *)strtoul(s, NULL, 16);
            
                                    LOGD(" startAddress = %x", (unsigned int)start);//得到odex起始地址
                                    LOGD(" endAddress = %x", (unsigned int)end);//得到odex結束地址
                                         }
                                   }
                             }
                    fclose ( fp);
                  }
            

            其實直接在起始地址的那一頁應該就是odex的開頭,但是這里還是模擬從后面往前面搜索,search_start_page是odex結束地址的那一頁。

            #!cpp
            do{
                search_start_page -= page_size; //page_size是sysconf得到的頁大小
                search_start_position = search_start_page + 40;  //加40是因為Dex在odex偏移0x28處
                }while(!findmagic( (void *)(search_start_page + 40) ) ); //findmagic是搜索Dex文件開頭的maigc number
            

            這樣我們就得到了Dex文件的起始地址search_start_position。

            得到method、class字符串的DexStringId序號

            在DexHeader找到StingId的偏移,根據StringId結構指示的字符串地址,一項一項匹配是否是我們要找的字符串,具體見注釋。

            #!cpp
            int getStrIdx(int search_start_position, char *target_string,int size )
            {
            int index;
            int stringidsoff;
            int stringdataoff;
            int *stringaddress;
            int string_num_mutf8;
            
            if(*(int *)(search_start_position+56))//StringId個數不為0
            {
               index = 0;
               stringidsoff = search_start_position + *(int *)(search_start_position+60) //StringId在內存的地址
            
               while(1)
            {
              stringdataoff = *(int *)stringidsoff;
              stringidsoff +=4; //指向下一個StringId結構
              stringaddress = (int *)(search_start_position + stringdataoff); //字符串數據在內存的地址
              string_num_mutf8 = 0; //首先記錄的是后續字符串占的字節數
              if( readUleb128(stringaddress,(int)&string_num_mutf8) == size && !strncmp((char *)stringaddress + string_num_mutf8,target_string,size) )
            //這里readUleb128讀取的是記錄后面字符串數據占的字節數;strncmp比較的是字符串的內容
                   break; //找到了就結束搜索
               ++index; //記錄索引序號
              if(*(int *)(search_start_position+56) <= index ) //搜索次數大于StringId數目則結束
                 { index = -1;break;}
            }
            }else
            {
            index = -1;
            }
            return index;
            }
            

            得到typeId、methodId序號,ClassDef地址

            這幾個過程都很相似,這里只放typeId,其他的代碼見鏈接。

            解析過程見注釋。

            #!cpp
            signed int  getTypeIdx(int search_start_position, int strIdx)
            {
              int typeIdsSize; 
              int typeIdsOff;
              int typeid_to_stringid; 
              signed int result; 
              int next_typeIdsOff; 
              int next_typeid_to_stringid; 
            
              typeIdsSize = *(int *)(search_start_position + 64);//typeId的個數
              if ( !typeIdsSize )
                return -1;
              typeIdsOff = search_start_position + *(int *)(search_start_position + 68);//typeId在內存的地址
              typeid_to_stringid = *(int *)typeIdsOff; //指向StringId的索引序號
              result = 0;
              next_typeIdsOff = typeIdsOff + 4; //下一個typeId項
              if ( typeid_to_stringid != strIdx )
              {
                while ( 1 )
                {
                  ++result;//記錄索引序號
                  if ( result == typeIdsSize ) //搜索完所有的typeId項
                    break;
                  next_typeid_to_stringid = *(int *)next_typeIdsOff;
                  next_typeIdsOff += 4; //下一typeId項
                  if ( next_typeid_to_stringid == strIdx )//找到了指定StringId的typeId項
                    return result;
                }
                return -1;
              }
              return result;
            }
            

            得到字節碼存放的地址

            根據methodIdx和DexClassDef的地址,搜索所有的DexMethod結構,找到要修改的指令。

            具體見注釋。

            #!cpp
            int getCodeItem(int search_start_position, int class_def_item_address, int methodIdx)
            {
              int *classDataOff; 
              int staticFieldsSize; 
              int *classDataOff_new_start; 
              int instanceFieldsSize; 
              int directMethodsSize; 
              int virtualMethodSize;
              int *after_skipstaticfield_address; 
              int *DexMethod_start_address; 
              int result; 
              int DexMethod_methodIdx; 
              int *DexMethod_accessFlagsstart_address;
              int Uleb_bytes_read; 
              int tmp;
            
              classDataOff = (int *)(*(int *)(class_def_item_address + 24) + search_start_position);//得到DexClassData項的地址(class_data_item)
              Uleb_bytes_read = 0; //每次讀取的Uleb格式的字節數
              staticFieldsSize = readUleb128(classDataOff, (int)&Uleb_bytes_read); //靜態字段的個數
              classDataOff_new_start = (int *)((char *)classDataOff + Uleb_bytes_read); //讓地址前進,指向下一個要讀取的項
              instanceFieldsSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);//實例字段個數
              classDataOff_new_start = (int *)((char *)classDataOff_new_start + Uleb_bytes_read);
              directMethodsSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);//直接方法個數
              classDataOff_new_start  = (int *)((char *)classDataOff_new_start + Uleb_bytes_read);
              virtualMethodSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);//虛方法個數
              after_skipstaticfield_address = skipUleb128(2 * staticFieldsSize, (int *)((char *)classDataOff_new_start + Uleb_bytes_read));//地址繼續前進,跳過靜態字段占的空間
              DexMethod_start_address = skipUleb128(2 * instanceFieldsSize, after_skipstaticfield_address);//繼續前進,跳過實例字段后就是直接方法字段的起始地址
              result = 0;
              if ( directMethodsSize )//直接方法字段存在
              {
                DexMethod_methodIdx = 0;
                do
                {  
                  DexMethod_methodIdx = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);//直接方法的methodIdx
                  DexMethod_accessFlagsstart_address = (int *)((char *)DexMethod_start_address + Uleb_bytes_read);//地址指向accessFlags字段
                  if ( DexMethod_methodIdx == methodIdx )//如果是要查找的methodIdx
                  {
                    readUleb128(DexMethod_accessFlagsstart_address, (int)&Uleb_bytes_read);//讀取accessFlags字段
                    return readUleb128((int *)((char *)DexMethod_accessFlagsstart_address + Uleb_bytes_read), (int)&Uleb_bytes_read) +  search_start_position;//接著就是讀取codeoff字段,加上起始地址,就是DexCode在內存中的地址
                  }
                  --directMethodsSize;
                  DexMethod_start_address = skipUleb128(2, DexMethod_accessFlagsstart_address);//如果上面不是要找的DexMethod,跳過accessFlags和codeOff字段
                }while ( directMethodsSize );
                result = 0;
              }
            
            
            //跟上面的邏輯是一樣的
              if ( virtualMethodSize )
              {
                DexMethod_methodIdx = 0;
                do
                {  
                  DexMethod_methodIdx = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);
                  DexMethod_accessFlagsstart_address = (int *)((char *)DexMethod_start_address + Uleb_bytes_read);
                  if ( DexMethod_methodIdx == methodIdx )
                  {
                    readUleb128(DexMethod_accessFlagsstart_address, (int)&Uleb_bytes_read);
                    return readUleb128((int *)((char *)DexMethod_accessFlagsstart_address + Uleb_bytes_read), (int)&Uleb_bytes_read) +  search_start_position;
                  }
                  --virtualMethodSize;
                  DexMethod_start_address = skipUleb128(2, DexMethod_accessFlagsstart_address);
                }while ( virtualMethodSize );
                result = 0;
              }
              return result;
            }
            

            改變權限并復制字節碼

            codeItem_address是上面我們找到的DexCode開始的地址。

            #!cpp
            void *codeinsns_page_address = (void *)(codeItem_address + 16 - (codeItem_address + 16) % (unsigned int)page_size );//找到指令開始的地址,然后計算那一內存頁的地址。
            mprotect(codeinsns_page_address,page_size,3);//改為可寫
            char inject[]={0x90,0x00,0x02,0x03,0x0f,0x00};
            memcpy(code_insns_address,&inject,6);//將字節碼復制過去
            

            0x04 結果


            可以看到各個字段讀取的結果,本來應該是1*2=2,修改字節碼之后為1+2=3了。

            3.png

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线