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...