初识Frida--Android逆向之Java层hook (一)
?
?
目錄
- ?????????0x00 文中用到的工具
- ?????????0x01 hook示例的安裝與分析
- ?????????????????安裝
- ?????????????????源代碼分析
- ?????????0x02 frida自帶的Messages機制與進程交互
- ?????????0x03 Javascript代碼構造與執行
- ?????????????????方法一:獲取calc()返回值
- ?????????????????方法二:修改cnt的值為1000
- ?????????0x04 總結
?
?
博客同步:訪問
0x00 文中用到的工具
- Frida
- jadx-gui 一個強大的android反編譯工具
- genymotion模擬器
- Python2.7以及frida-python庫
- radare2 反匯編器
- pycharm
0x01 hook示例的安裝與分析
Frida官網給我們了一個ctf的示例,就以此為例子,開始學習frida在android逆向的使用。
rps.apk?下載地址
安裝
使用genymotion等類似android模擬器安裝好打開,發現這是一個石頭剪刀布的游戲應用,簡單的玩了一下,沒什么特別的,直接分析代碼吧,看看到底想干什么。
源代碼分析
使用jadx-gui反編譯,發現app沒有加殼和混淆,當然一來就加殼和混淆的話對我們就太不友好了,接下分析就簡單了,直接看java代碼。當然也可以使用androidkiller,jeb等其他強大的反編譯工具。
?
在MainActivity中找到OnCreate()方法,可以看到只是簡單的聲明了button控件以及對應的監聽器。
| 1 2 3 4 5 6 7 8 9 10 11 | protected void onCreate(Bundle savedInstanceState) { ??????super.onCreate(savedInstanceState); ??????setContentView(R.layout.activity_main); ??????this.P?=?(Button) findViewById(R.id.button); ??????this.S?=?(Button) findViewById(R.id.button3); ??????this.r?=?(Button) findViewById(R.id.buttonR); ??????this.P.setOnClickListener(this); ??????this.r.setOnClickListener(this); ??????this.S.setOnClickListener(this); ??????this.flag?=?0; ??} |
繼續查看button的onclick方法,可以看出cpu是通過隨機數組出的,其判斷輸贏的方法在this.showMessageTask中。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public void onClick(View v) { ??????if?(this.flag !=?1) { ??????????this.flag?=?1; ??????????((TextView) findViewById(R.id.textView3)).setText(""); ??????????TextView tv?=?(TextView) findViewById(R.id.textView); ??????????TextView tv2?=?(TextView) findViewById(R.id.textView2); ??????????this.m?=?0; ??????????this.n?=?new Random().nextInt(3);??//隨機數0,1,2 ??????????tv2.setText(new String[]{"CPU: Paper",?"CPU: Rock",?"CPU: Scissors"}[this.n]);?//隨機出石頭,剪刀,布 ??????????if?(v?==?this.P) { ??????????????tv.setText("YOU: Paper"); ??????????????this.m?=?0; ??????????} ??????????if?(v?==?this.r) { ??????????????tv.setText("YOU: Rock"); ??????????????this.m?=?1; ??????????} ??????????if?(v?==?this.S) { ??????????????tv.setText("YOU: Scissors"); ??????????????this.m?=?2; ??????????} ??????????this.handler.postDelayed(this.showMessageTask,?1000);//輸贏判斷方法 ??????} ??} |
跟進分析showMessageTask,可以看到如果贏了mainActivity.cnt會+1,但是一旦輸了cnt就會置0,而獲取flag的要求是我們得獲勝1000次,...... :(
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | private final Runnable showMessageTask?=?new Runnable() { ????????public void run() { ????????????TextView tv3?=?(TextView) MainActivity.this.findViewById(R.id.textView3); ????????????MainActivity mainActivity; ????????????//我方:布 CPU:石頭?or?我方:石頭 CUP:剪刀 ,則為贏 ????????????if?(MainActivity.this.n?-?MainActivity.this.m?==?1) { ????????????????mainActivity?=?MainActivity.this; ????????????????mainActivity.cnt++; ????????????????tv3.setText("WIN! +"?+?String.valueOf(MainActivity.this.cnt)); ?????????????//反過來當然是輸咯 ????????????}?else?if?(MainActivity.this.m?-?MainActivity.this.n?==?1) { ????????????????MainActivity.this.cnt?=?0; ????????????????tv3.setText("LOSE +0"); ?????????????//一樣則打平 ????????????}?else?if?(MainActivity.this.m?==?MainActivity.this.n) { ????????????????tv3.setText("DRAW +"?+?String.valueOf(MainActivity.this.cnt)); ?????????????//我布? cup:剪刀 ????????????}?else?if?(MainActivity.this.m < MainActivity.this.n) { ????????????????MainActivity.this.cnt?=?0; ????????????????tv3.setText("LOSE +0"); ????????????}?else?{ ????????????????mainActivity?=?MainActivity.this; ????????????????mainActivity.cnt++; ????????????????tv3.setText("WIN! +"?+?String.valueOf(MainActivity.this.cnt)); ????????????} ????????????//獲勝1000次則能夠獲取flag ????????????if?(1000?==?MainActivity.this.cnt) { ????????????????tv3.setText("SECCON{"?+?String.valueOf((MainActivity.this.cnt?+?MainActivity.this.calc())?*?107)?+?"}"); ????????????} ????????????MainActivity.this.flag?=?0; ????????} ????}; |
簡單分析一下獲取flag需要的條件,總結有3個辦法:
-
分析calc()方法能算出答案,但這個方法在so中,得分析匯編代碼才行,當然可以嘗試使用ida pro,F5查看C代碼分析,前提是算法不難。
-
獲取calc函數的返回值,從而計算答案。
- 還有一個方法就是,直接將MainActivity.this.cnt的值構造成1000。
接下來就用frida,使用后兩種思路來解這個簡單的示例。但在這之前得先了解Frida自帶的Messages機制,了解frida怎么從通過一個python腳本發送和接收message消息是一個提升理解frida的好方法。
0x02 frida自帶的Messages機制與進程交互
先來看看一個Messages的模板,這里用到的語言分別是python和javascript,他們之間的關系是python作為載體,javascript作為在android中真正執行代碼。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import?frida, sys ? //hook代碼,采用javascript編寫 jscode?=?""" //javascript代碼,重點 """ ? //自定義回調函數 def?on_message(message, data): ????if?message['type']?==?'send': ????????print("[*] {0}".format(message['payload'])) ????else: ????????print(message) ? #重點的4行代碼 process?=?frida.get_usb_device().attach('應用完整包名') script?=?process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() |
當然如果是對此簡單的使用,只需要編寫jscode,以及填寫你要hook的應用完整包名就行了,不過如果單純只會用可能在以后會被模板限制,所以一探究竟還是很有必要。
可以在cmd中,使用python終端的help()函數找到frida庫的源代碼的絕對路徑。
接下來就來具體看看這幾句代碼做了什么事情。
| 1 2 3 4 5 | process?=?frida.get_usb_device().attach('應用完整包名') script?=?process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() |
首先使用了frida.get_usb_device(),返回了一個_get_device函數,跟進_get_device方法。
| 1 2 | def?get_usb_device(timeout?=?0): ????return?_get_device(lambda?device: device.type?==?'tether', timeout) |
在_get_device中,通過get_device_manager()實例化DeviceManager類,并調用該類中的enumerate_devices()方法。
| 1 2 3 4 5 6 7 8 9 10 | def?_get_device(predicate, timeout): ????mgr?=?get_device_manager()????????????????//獲取設備管理 ????def?find_matching_device():???????????????//尋找匹配設備 ????????usb_devices?=?[device?for?device?in?mgr.enumerate_devices()?if?predicate(device)] ????????if?len(usb_devices) >?0: ????????????return?usb_devices[0] ????????else: ????????????return?None ????device?=?find_matching_device() ???...省略 |
get_device_manager()代碼
| 1 2 3 4 5 6 | def?get_device_manager(): ????global?_device_manager ????if?_device_manager?is?None: ????????from?.?import?core ????????_device_manager?=?core.DeviceManager(_frida.DeviceManager()) ????return?_device_manager |
DeviceManager中enumerate_devices()方法,可以看到enumerate_devices()方法實際上是返回了一個Device()類的實例化對象List。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class?DeviceManager(object): ????def?__init__(self, impl): ????????self._impl?=?impl ? ????def?__repr__(self): ????????return?repr(self._impl) ? ????//返回了一個Device()類的實例化。 ????def?enumerate_devices(self): ????????return?[Device(device)?for?device?in?self._impl.enumerate_devices()] ? ????def?add_remote_device(self, host): ????????return?Device(self._impl.add_remote_device(host)) ? ????def?remove_remote_device(self, host): ????????self._impl.remove_remote_device(host) ? ????def?get_device(self, device_id): ????????devices?=?self._impl.enumerate_devices() ????????if?device_id?is?None: ????????????return?Device(devices[0]) ????????for?device?in?devices: ????????????if?device.id?==?device_id: ????????????????return?Device(device) ????????raise?_frida.InvalidArgumentError("unable to find device with id %s"?%?device_id) ? ????def?on(self, signal, callback): ????????self._impl.on(signal, callback) ? ????def?off(self, signal, callback): ????????self._impl.off(signal, callback) |
繼續跟進Device類中的,就找到了attach()方法。在attach方法這是設置斷點,看看傳入的數據。
?
?
接下來提供的“應用完整名”是通過self._pid_of()函數去找到對應的進程號pid,然后將pid后通過Session類初始化。到此第一句代碼過程就算是明白了,最終得到的是一個對應進程號pid的Session實例化對象process。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class?Device(object): ????def?__init__(self, device): ????????self.id?=?device.id ????????self.name?=?device.name ????????self.icon?=?device.icon ????????self.type?=?device.type ????????self._impl?=?device ? ????def?__repr__(self): ????????return?repr(self._impl) ? ????...節省空間刪除部分方法,詳細內容可自行查看源碼 ? ????def?kill(self, target): ????????self._impl.kill(self._pid_of(target)) ? ????//返回了一個Session的實例化對象 ????def?attach(self, target): ????????return?Session(self._impl.attach(self._pid_of(target))) ? ????def?inject_library_file(self, target, path, entrypoint, data): ????????return?self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data) ? ????def?inject_library_blob(self, target, blob, entrypoint, data): ????????return?self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data) ? ????def?on(self, signal, callback): ????????self._impl.on(signal, callback) ? ????def?off(self, signal, callback): ????????self._impl.off(signal, callback) ? ????def?_pid_of(self, target): ????????if?isinstance(target, numbers.Number): ????????????return?target ????????else: ????????????return?self.get_process(target).pid |
第二句,緊接著process.create_script(jscode),可以看到它返回一個Script類的實例化,參數不確定。
| 1 2 | def?create_script(self,?*args,?**kwargs): ????????return?Script(self._impl.create_script(*args,?**kwargs)) |
跟進Script類,可以找到on()方法,在on方法中可以設置自定義回調函數。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class?Script(object): ????def?__init__(self, impl): ????????self.exports?=?ScriptExports(self) ? ????????self._impl?=?impl ????????self._on_message_callbacks?=?[] ????????self._log_handler?=?self._on_log ? ????????self._pending?=?{} ????????self._next_request_id?=?1 ????????self._cond?=?threading.Condition() ? ????????impl.on('destroyed',?self._on_destroyed) ????????impl.on('message',?self._on_message) ? ???...節省空間刪除部分類方法,詳細內容可自行查看源碼 ? ????def?load(self): ????????self._impl.load() ? ???//設置自定義回調函數 ????def?on(self, signal, callback): ????????if?signal?==?'message': ????????????self._on_message_callbacks.append(callback) ????????else: ????????????self._impl.on(signal, callback) ? 在IDE中可以看到_on_message_callbacks中存放的on_message函數地址。 |
接下來調用load()方法,在服務端就啟動javascript腳本了,至于在frida-server服務端怎么執行的,可逆向研究一下frida-server,它才是真正的核心。
0x03 Javascript代碼構造與執行
現在就來使用frida實現剛剛試想的方法。
方法一:獲取calc()返回值
第一種思路就是直接獲取calc的返回值,從native函數定義上知道它的返回值是int類型,當然直接獲取calc函數的返回值是解出問題最簡單的方法。
| 1 | public native?int?calc(); |
那怎么獲取calc()函數的返回值呢,這個函數在MainActivity類中,直接引用該類下的calc()方法,不就ok了嗎,原理是這樣,下面就來構造一下Javascript代碼。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //Java.Perform 開始執行JavaScript腳本。 Java.perform(function () { //定義變量MainActivity,Java.use指定要使用的類 ????var MainActivity?=?Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); ????//hook該類下的onCreate方法,重新實現它 ????MainActivity.onCreate.implementation?=?function () { ????????send("Hook Start..."); ????????//調用calc()方法,獲取返回值 ????????var returnValue?=?this.calc(); ????????send("Return:"+returnValue); ????????var result?=?(1000+returnValue)*107; ????????//解出答案 ????????send("Flag:"+"SECCON{"+result.toString()+"}"); ????} }); |
JavaScript代碼就是這樣,如果不是很理解,學習一下JavaScript基礎即可,下面看看完整的python腳本。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import?frida, sys ? def?on_message(message, data): ????if?message['type']?==?'send': ????????print("[*] {0}".format(message['payload'])) ????else: ????????print(message) ? jscode?=?""" Java.perform(function () { ????var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); ????MainActivity.onCreate.implementation = function () { ????????send("Hook Start..."); ????????var returnValue = this.calc(); ????????send("Return:"+returnValue); ????????var result = (1000+returnValue)*107; ????????send("Flag:"+"SECCON{"+result.toString()+"}"); ????} }); """ ? process?=?frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors') script?=?process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() |
接下來運行一下,看看能否成功。
?
步驟如下:
/data/local/tmp目錄下。
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
因為hook的是應用的onCreate方法,執行python腳本的前提是應用首先啟動,這樣才能attach到該應用,所以還得返回模擬器桌面重新啟動應用,這樣它才會執行hook的onCreate()方法,結果如下。
方法二:修改cnt的值為1000
第二種思路也比較簡單,我們需要修改cnt的值,但如果直接修改cnt的初始值為1000的話,在游戲中可能存在不確定因素,比如輸了會置0,贏了cnt值就變成1001了,所以還得控制一下輸贏,而輸贏的條件是電腦出什么,所以最終hook的方法就在onClick中。
從onClick()中可以知道,控制輸贏的在于修改this.n 和 this.m的值,再來看看源代碼。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public void onClick(View v) { ???????if?(this.flag !=?1) { ???????????this.flag?=?1; ???????????((TextView) findViewById(R.id.textView3)).setText(""); ???????????TextView tv?=?(TextView) findViewById(R.id.textView); ???????????TextView tv2?=?(TextView) findViewById(R.id.textView2); ???????????this.m?=?0; ???????????//控制電腦出拳 ???????????this.n?=?new Random().nextInt(3); ???????????tv2.setText(new String[]{"CPU: Paper",?"CPU: Rock",?"CPU: Scissors"}[this.n]); ???????????if?(v?==?this.P) { ???????????????tv.setText("YOU: Paper"); ???????????????this.m?=?0; ???????????} ???????????if?(v?==?this.r) { ???????????????tv.setText("YOU: Rock"); ???????????????this.m?=?1; ???????????} ???????????if?(v?==?this.S) { ???????????????tv.setText("YOU: Scissors"); ???????????????this.m?=?2; ???????????} ???????????this.handler.postDelayed(this.showMessageTask,?1000); ???????} |
來看JavaScript代碼怎么寫吧
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Java.perform(function () { ????var MainActivity?=?Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); ????//hook onClick方法,此處要注意的是onClick方法是傳遞了一個View參數v ????MainActivity.onClick.implementation?=?function (v) { ????????send("Hook Start..."); ????????//調用onClick,模擬點擊事件 ????????this.onClick(v); ????????//修改參數 ????????this.n.value?=?0; ????????this.m.value?=?2; ????????this.cnt.value?=?999; ????????send("Success!") ????} }); |
完整python代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import?frida, sys ? def?on_message(message, data): ????if?message['type']?==?'send': ????????print("[*] {0}".format(message['payload'])) ????else: ????????print(message) ? jscode?=?""" Java.perform(function () { ????var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); ????MainActivity.onClick.implementation = function (v) { ????????send("Hook Start..."); ????????this.onClick(v); ????????this.n.value = 0; ????????this.m.value = 2; ????????this.cnt.value = 999; ????????send("Success!") ????} }); """ ? process?=?frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors') script?=?process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() |
執行python腳本,任意點擊按鈕,答案就出來了。
?
當然,如果so中的calc()函數算法不難的前提,直接使用ida pro或者radare2分析匯編代碼也是可以的。這里給出用radare2反匯編出來的代碼??梢钥吹?#xff0c;calc()函數就單純的返回了int值7。
0x04 總結
- 一般分析流程
1.反編譯apk,分析代碼尋找hook點。
2.編寫js代碼,調用類的方法或者替換。
3.在python中執行即可。
下面一篇會更詳細介紹frida的使用。
https://bbs.pediy.com/thread-227232.htm
總結
以上是生活随笔為你收集整理的初识Frida--Android逆向之Java层hook (一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [原创]基于frida的脱壳工具
- 下一篇: 初识Frida--Android逆向之J