初識Frida--Android逆向之Java層hook (一)
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控制元件以及對應的監聽器。
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中。
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次,...... :(
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));
//反過來當然是輸咯
}elseif(MainActivity.this.m-MainActivity.this.n==1) {
MainActivity.this.cnt=0;
tv3.setText("LOSE +0");
//一樣則打平
}elseif(MainActivity.this.m==MainActivity.this.n) {
tv3.setText("DRAW +"+String.valueOf(MainActivity.this.cnt));
//我布 cup:剪刀
}elseif(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中真正執行程式碼。
importfrida, sys
//hook程式碼,採用javascript編寫
jscode="""
//javascript程式碼,重點
"""
//自定義回撥函式
defon_message(message, data):
ifmessage['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庫的原始碼的絕對路徑。
接下來就來具體看看這幾句程式碼做了什麼事情。
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方法。
defget_usb_device(timeout=0):
return_get_device(lambdadevice: device.type=='tether', timeout)
在_get_device中,通過get_device_manager()例項化DeviceManager類,並呼叫該類中的enumerate_devices()方法。
def_get_device(predicate, timeout):
mgr=get_device_manager() //獲取裝置管理
deffind_matching_device(): //尋找匹配裝置
usb_devices=[devicefordeviceinmgr.enumerate_devices()ifpredicate(device)]
iflen(usb_devices) >0:
returnusb_devices[0]
else:
returnNone
device=find_matching_device()
...省略
get_device_manager()程式碼
defget_device_manager():
global_device_manager
if_device_managerisNone:
from.importcore
_device_manager=core.DeviceManager(_frida.DeviceManager())
return_device_manager
DeviceManager中enumerate_devices()方法,可以看到enumerate_devices()方法實際上是返回了一個Device()類的例項化物件List。
classDeviceManager(object):
def__init__(self, impl):
self._impl=impl
def__repr__(self):
returnrepr(self._impl)
//返回了一個Device()類的例項化。
defenumerate_devices(self):
return[Device(device)fordeviceinself._impl.enumerate_devices()]
defadd_remote_device(self, host):
returnDevice(self._impl.add_remote_device(host))
defremove_remote_device(self, host):
self._impl.remove_remote_device(host)
defget_device(self, device_id):
devices=self._impl.enumerate_devices()
ifdevice_idisNone:
returnDevice(devices[0])
fordeviceindevices:
ifdevice.id==device_id:
returnDevice(device)
raise_frida.InvalidArgumentError("unable to find device with id %s"%device_id)
defon(self, signal, callback):
self._impl.on(signal, callback)
defoff(self, signal, callback):
self._impl.off(signal, callback)
繼續跟進Device類中的,就找到了attach()方法。在attach方法這是設定斷點,看看傳入的資料。
接下來提供的“應用完整名”是通過self._pid_of()函式去找到對應的程式號pid,然後將pid後通過Session類初始化。到此第一句程式碼過程就算是明白了,最終得到的是一個對應程式號pid的Session例項化物件process。
classDevice(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):
returnrepr(self._impl)
...節省空間刪除部分方法,詳細內容可自行檢視原始碼
defkill(self, target):
self._impl.kill(self._pid_of(target))
//返回了一個Session的例項化物件
defattach(self, target):
returnSession(self._impl.attach(self._pid_of(target)))
definject_library_file(self, target, path, entrypoint, data):
returnself._impl.inject_library_file(self._pid_of(target), path, entrypoint, data)
definject_library_blob(self, target, blob, entrypoint, data):
returnself._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data)
defon(self, signal, callback):
self._impl.on(signal, callback)
defoff(self, signal, callback):
self._impl.off(signal, callback)
def_pid_of(self, target):
ifisinstance(target, numbers.Number):
returntarget
else:
returnself.get_process(target).pid
第二句,緊接著process.create_script(jscode),可以看到它返回一個Script類的例項化,引數不確定。
defcreate_script(self,*args,**kwargs):
returnScript(self._impl.create_script(*args,**kwargs))
跟進Script類,可以找到on()方法,在on方法中可以設定自定義回撥函式。
classScript(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)
...節省空間刪除部分類方法,詳細內容可自行檢視原始碼
defload(self):
self._impl.load()
//設定自定義回撥函式
defon(self, signal, callback):
ifsignal=='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函式的返回值是解出問題最簡單的方法。
public nativeintcalc();
那怎麼獲取calc()函式的返回值呢,這個函式在MainActivity類中,直接引用該類下的calc()方法,不就ok了嗎,原理是這樣,下面就來構造一下Javascript程式碼。
//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指令碼。
importfrida, sys
defon_message(message, data):
ifmessage['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()
接下來執行一下,看看能否成功。
步驟如下:
啟動模擬器,使用adb push將對應架構的frida-server檔案push到模擬器中
/data/local/tmp目錄下。
adb shell 進入/data/local/tmp目錄,啟動frida-server。
開啟埠轉發
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
啟動應用後,在命令列等執行python指令碼。
因為hook的是應用的onCreate方法,執行python指令碼的前提是應用首先啟動,這樣才能attach到該應用,所以還得返回模擬器桌面重新啟動應用,這樣它才會執行hook的onCreate()方法,結果如下。
方法二:修改cnt的值為1000
第二種思路也比較簡單,我們需要修改cnt的值,但如果直接修改cnt的初始值為1000的話,在遊戲中可能存在不確定因素,比如輸了會置0,贏了cnt值就變成1001了,所以還得控制一下輸贏,而輸贏的條件是電腦出什麼,所以最終hook的方法就在onClick中。
從onClick()中可以知道,控制輸贏的在於修改this.n 和 this.m的值,再來看看原始碼。
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程式碼怎麼寫吧
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程式碼
importfrida, sys
defon_message(message, data):
ifmessage['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反彙編出來的程式碼。可以看到,calc()函式就單純的返回了int值7。
0x04 總結
一般分析流程
1.反編譯apk,分析程式碼尋找hook點。
2.編寫js程式碼,呼叫類的方法或者替換。
3.在python中執行即可。
下面一篇會更詳細介紹frida的使用。
本文由看雪論壇 ghostmazeW 原創
轉載請註明來自看雪社群
相關文章
- 初識Frida--Android逆向之Java層hook (二)2018-06-12AndroidJavaHook
- hook初識之inline hook2024-04-19Hookinline
- 一.初識Java2020-12-27Java
- 初識NIO之Java小Demo2018-08-21Java
- 羽夏逆向指引—— Hook2022-03-28Hook
- iOS逆向 程式碼注入+Hook2019-12-04iOSHook
- iOS逆向學習筆記 - 彙編(一) - 初識彙編2018-05-16iOS筆記
- 初識LinkedList底層原理2018-03-22
- Java初識2024-11-21Java
- 初識Java2024-07-06Java
- 初識Java Java基礎知識2020-06-23Java
- CreateProcess逆向分析-3環使用者層逆向分析(一)2022-03-11
- iOS逆向之旅(進階篇) — HOOK(Logos)2018-10-26iOSHookGo
- iOS逆向之旅(進階篇) — HOOK(FishHook)2018-10-26iOSHook
- C++逆向 可變引數Hook2022-04-21C++Hook
- 前端筆記之JavaScript(一)初識JavaScript2019-03-20前端筆記JavaScript
- 初識 Java 註解2018-11-16Java
- 【Java基礎】01初識Java2024-08-03Java
- 初識Kotlin之集合2019-05-18Kotlin
- Hook技術之Hook Activity2019-02-17Hook
- iOS逆向之旅(進階篇) — HOOK(Method Swizzling)2018-10-26iOSHook
- iOS 逆向 - Hook / fishHook 原理與符號表2019-11-12iOSHook符號
- 初識Java與RabbitMQ(三)2020-03-20JavaMQ
- 初識Java類和物件2020-10-20Java物件
- 初識dagger(一)2019-03-02
- 初識 webpack (一)2019-12-31Web
- 初識Django(一)2018-12-28Django
- 一、初識Netty2024-10-02Netty
- RocketMq(一)初識2021-04-20MQ
- RabbitMQ系列之---初識RabbitMQ2019-05-09MQ
- 逆向基礎(十三) JAVA (一)2020-08-19Java
- JS 逆向之 Hook,吃著火鍋唱著歌,突然就被麻匪劫了!2021-10-04JSHook
- 【JS 逆向百例】Fiddler 外掛 Hook 實戰,某創幫登入逆向2021-10-12JSHook
- Java 函數語言程式設計(一)初識篇2018-08-15Java函數程式設計
- Java開發學習(一)----初識Spring及其核心概念2022-05-28JavaSpring
- Docker初認識(一)2019-06-18Docker
- kafka初認識(一)2021-10-18Kafka
- 初識Javaweb之Servlet以及Tomcat2018-08-11JavaWebServletTomcat