Dalvik位元組碼自篡改原理及實現

2021-09-11 11:17:20 字數 3376 閱讀 9064

leonnewton · 2016/06/14 10:32

author:leonnewton

早在2023年,bluebox就發布了乙個可以在執行時篡改dalvik位元組碼的demo。之後,國內有很多對這個demo進行分析的資料,分析得也非常詳細。但是看了很多資料以後,感覺缺少從實現的角度可以給學習的同學進行練手的資料。因此,本文從實現的角度,解釋篡改的原理並實現篡改的過程,並附上實現的源**。希望可以幫助想自己實現著玩的同學。

**:dalvik位元組碼自篡改原理及實現

由於dex檔案被對映為唯讀的,因此從dex檔案自己的**裡來修改自己的位元組碼是不行的。但是native**和dvm卻執行在相同的比位元組碼低的層次,所以可以通過native**來修改位元組碼。在修改位元組碼之前,用mprotect把位元組碼所在的記憶體重新對映為可寫,然後把新的位元組碼覆蓋原來的位元組碼即可。

而在應用程式的程序空間,有一段是載入的odex檔案,odex的0x28偏移開始的地方就是dex檔案。

在得到dex檔案在記憶體中的位址之後,通過查詢dex一系列相應的結構,最終找到存放指令的位址,把新的指令覆蓋原來的指令即可。

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

首先是需要確定method的名字,也就是method字串,再確定method所在class的名字,也就是class字串。這確定了要修改的方法。

把2個字串到dexstringid結構的位置進行搜尋,進而得到2個字串在dexstringid中的索引序號。

對於class字串,再通過dexstrngid索引序號,到dextypeid中搜尋,得到class字串在dextypeid中的索引序號。

還是class字串,得到dextypeid中的索引序號後,再到dexclassdef中搜尋,就可以得到class的dexclassdef的位置。其中classdataoff欄位記錄了dexclassdata偏移,這個結構裡可以找到dexmethod結構。

而對於method字串,找到dexstringid索引後,再結合上面class的dextypeid,可以確定dexmethodid的索引序號。

現在我們知道了dexmethodid的索引序號,也知道了存放dexmethod的位置,直接搜尋就可以得到我們要找的dexmethod。這個結構裡的codeoff指向了存放指令的位置。

首先寫乙個應用,應用的testadd類有乙個add方法,這個方法**裡進行的是乘法,計畫篡改位元組碼後,執行的時候進行的是加法。如下:

#!cpp

public int add(int a, int b)

複製**

篡改後返回a + b的值。

讀取/proc/self/maps,搜尋odex檔案的位址,從而得到odex的起始位址和結束位址。

#!cpp

file *fp;

fp = fopen("/proc/self/maps", "r");

if(fp!=null)}}

fclose ( fp);

}複製**

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

#!cpp

dowhile(!findmagic( (void *)(search_start_page + 40) ) ); //findmagic是搜尋dex檔案開頭的maigc number

複製**

這樣我們就得到了dex檔案的起始位址search_start_position。

在dexheader找到stingid的偏移,根據stringid結構指示的字串位址,一項一項匹配是否是我們要找的字串,具體見注釋。

#!cpp

int getstridx(int search_start_position, char *target_string,int size )

}}else

return index;

}複製**

這幾個過程都很相似,這裡只放typeid,其他的**見鏈結。

解析過程見注釋。

#!cpp

signed int gettypeidx(int search_start_position, int stridx)

return -1;

} return result;

}複製**

根據methodidx和dexclassdef的位址,搜尋所有的dexmethod結構,找到要修改的指令。

具體見注釋。

#!cpp

int getcodeitem(int search_start_position, int class_def_item_address, int methodidx)

--directmethodssize;

dexmethod_start_address = skipuleb128(2, dexmethod_accessflagsstart_address);//如果上面不是要找的dexmethod,跳過accessflags和codeoff欄位

}while ( directmethodssize );

result = 0;

}//跟上面的邏輯是一樣的

if ( virtualmethodsize )

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

memcpy(code_insns_address,&inject,6);//將位元組碼複製過去

複製**

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

位元組碼與常量池和JVM記憶體原理

1.jvm主要包括了圖中的三塊,分別是方法區,堆,以及執行緒獨有的區域。2.其中方法區中包括了類變數,類資訊,方法資訊以及常量池。1.常量池以表的形式存在 2.常量池用於儲存編譯期間生成的字面量和符號引用。值得注意的是,執行期間產生的新的常量也可被儲存到常量池中,例如string中的intern方法...

JVM位元組碼執行引擎和動態繫結原理

編譯期就確定了需要多大的區域性變數表,多深的運算元棧,這些資訊全在位元組碼中。只有位於棧頂的棧幀才有效,稱為當前棧幀,所對應的方法就是當前正在執行的方法。容量以變數槽slot為單位,slot記憶體大小隨著需求而變化並且不固定。方法執行時jvm使用區域性變數表完成引數值到引數列表的傳遞過程。slot可...

自抽樣演算法原理及python實現

後續補充 採用自抽樣方式對資料進行選擇 coding utf 8 引入資料庫包 import pymysql 引入操作excel包 import xlrd import pandas as pd import matplotlib.pyplot as plt import matplotlib im...