天天看點

初識Frida--Android逆向之Java層hook (二)

目錄

  • 初識Frida--Android逆向之Java層hook (二)
  •          apk的安裝與分析
  •                  流程分析
  •                  hook點分析
  •          JavaScript代碼構造與執行
  •                  0x00 hook getMac()
  •                  0x01 計算秘鑰Key
  •                  0x02 調用showPremium擷取答案
  •          總結

今天繼續一個新的示例,同樣采用CTF作為例子,難度稍微加大了一點,如果對Frida基本的使用還不是很了解,建議先看看之前的文章初識Frida--Android逆向之Java層hook (一)

部落格同步:通路

文章涉及到的知識點:

  • 怎麼使用javascript執行個體化類并調用類方法
  • 怎麼在"jscode"中增加自定義javascript方法
  • 怎麼較為靈活的hook類方法

apk的安裝與分析

示例下載下傳:whyshouldIpay

下載下傳apk後安裝,一樣還是先來看看是什麼功能,這是一個比較簡單的驗證程式,簡單的使用後,了解到

PREMIUM CONETNT

内容需要輸入License驗證後才能檢視。那估計

PREMIUM CONETNT

按鈕中的内容應該就是答案了吧。

初識Frida--Android逆向之Java層hook (二)

流程分析

使用jadx将apk反編譯出來,分析,在AndroidManifest.xml中找到了啟動的Activity是

LauncherActivity

初識Frida--Android逆向之Java層hook (二)

找到其中驗證的主要代碼

verifyClick

,分析如下:

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

public void verifyClick(View v) {

/

/

第一個驗證,将輸入的Licese通過網絡驗證,但這個肯定是通不過的,這是一個可能需要繞過的點。

try

{

InputStream 

in

=

new URL(

"http://broken.license.server.com/query?license="

+

((EditText) findViewById(R.

id

.text_license)).getText().toString()).openConnection().getInputStream();

StringBuilder responseBuilder 

=

new StringBuilder();

byte[] b 

=

new byte[

];

while

(

in

.read(b) > 

) {

responseBuilder.append(b);

}

String response 

=

responseBuilder.toString();

/

/

網絡驗證需要伺服器傳回 

"LICENSEKEYOK"

,才能進行下一步

if

(response.equals(

"LICENSEKEYOK"

)) {

/

/

當網絡驗證成功後,生成激活秘鑰,并寫入到preferences檔案中

String activatedKey 

=

new String(MainActivity.xor(getMac().getBytes(), response.getBytes()));

Editor editor 

=

getApplicationContext().getSharedPreferences(

"preferences"

).edit();

editor.putString(

"KEY"

, activatedKey);

editor.commit();

/

/

這樣便成功激活

new Builder(this).setTitle((CharSequence) 

"Activation successful"

).setMessage((CharSequence) 

"Activation successful"

).setIcon(

17301543

).show();

return

;

}

new Builder(this).setTitle((CharSequence) 

"Invalid license!"

).setMessage((CharSequence) 

"Invalid license!"

).setIcon(

17301543

).show();

} catch (Exception e) {

new Builder(this).setTitle((CharSequence) 

"Error occured"

).setMessage((CharSequence) 

"Server unreachable"

).setNeutralButton((CharSequence) 

"OK"

, null).setIcon(

17301543

).show();

}

}

verifyClick

中可以知道生成激活秘鑰的算法是

MainActivity.xor

1

String activatedKey 

=

new String(MainActivity.xor(getMac().getBytes(), response.getBytes()));

來到

MainActivity

中,檢視該方法,看上去筆算起來還是比較麻煩。

1

2

3

4

5

6

7

public static byte[] xor(byte[] val, byte[] key) {

byte[] o 

=

new byte[val.length];

for

(

int

=

; i < val.length; i

+

+

) {

o[i] 

=

(byte) (val[i] ^ key[i 

%

key.length]);

}

return

o;

}

接下來當程式被激活成功後,點選

PREMIUM CONETNT

按鈕,會調用

MainActivity

中的方法,可以看到它将MAC,以及生成的Key發送到了

MainActivity

中。

1

2

3

4

5

6

public void showPremium(View view) {

Intent i 

=

new Intent(this, MainActivity.

class

);

i.putExtra(

"MAC"

, getMac());

i.putExtra(

"KEY"

, getKey());

startActivity(i);

}

MainActivity

onCreate

方法中,看到了最終答案生成的native方法

stringFromJNI(key, mac)

1

2

3

4

5

6

7

8

9

10

11

12

13

protected void onCreate(Bundle savedInstanceState) {

/

/

擷取Intent傳遞過來的值

String key 

=

getIntent().getStringExtra(

"KEY"

);

String mac 

=

getIntent().getStringExtra(

"MAC"

);

if

(key 

=

=

"

" || mac == "

") {

key 

=

"";

mac 

=

"";

}

super

.onCreate(savedInstanceState);

setContentView((

int

) R.layout.activity_main);

/

/

調用native函數,算出答案

((TextView) findViewById(R.

id

.sample_text)).setText(stringFromJNI(key, mac));

}

好,現在源代碼分析基本上能夠理清楚了,大概的過程就是這樣。

  1. 輸入License,進行驗證
  2. 通過網絡驗證擷取傳回值“LICENSEKEYOK”後,然後調用

    MainActivity.xor

    在本地

    preferences

    檔案中生成秘鑰,激活成功。
  3. 本地擷取MAC位址及秘鑰Key傳入

    MainActivity

    得出答案。

hook點分析

接下來重點就是要尋找hook點,經過剛才解題流程的分析,得出hook思路如下:

  1. 擷取getMac()函數的傳回值,與“LICENSEKEYOK"字元串進行xor運算得出秘鑰Key.
  2. hook getKey方法,讓它不從

    preferences

    檔案讀取Key,而是我們自己構造。
  3. hook 

    verifyClick

    ,讓它調用

    showPremium

    方法

JavaScript代碼構造與執行

0x00 hook getMac()

先來一個簡單的示例,看看getMac()方法傳回的的是什麼,采用的方法是hook 

showPremium

,這樣就能通過點選

PREMIUM CONETNT

按鈕直接得到

getMac()

的傳回值。

JavaScript代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

js_code 

=

'''

Java.perform(function(){

var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity');

//hook showPremium進而友善直接點選按鈕得出Mac值

hook_Activity.showPremium.implementation = function(v){

//因為showPremium,getMac()均在LauncherActivity類中,所有直接通過this就能直接調用getMac()方法

var Key = this.getKey();

var Mac = this.getMac();

send(Key);

send(Mac);

}

});

'''

完整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

28

29

import

frida,sys

def

on_message(message, data):

if

message[

'type'

=

=

'send'

:

print

(

"[*] {0}"

.

format

(message[

'payload'

]))

else

:

print

(message)

js_code 

=

'''

Java.perform(function(){

var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity');

hook_Activity.showPremium.implementation = function(v){

var Key = this.getKey();

var Mac = this.getMac();

send(Key);

send(Mac);

}

});

'''

session 

=

frida.get_usb_device().attach(

"de.fraunhofer.sit.premiumapp"

)

script 

=

session.create_script(js_code)

script.on(

'message'

,on_message)

script.load()

sys.stdin.read()

運作看看結果:

初識Frida--Android逆向之Java層hook (二)

0x01 計算秘鑰Key

接下來開始真正第一步的hook,将mac值與“LICENSEKEYOK"通過

MainActivity.xor

擷取秘鑰Key。那就直接hook 

getKey

方法吧,這樣可以自己來構造秘鑰Key。

仔細分析,會發現在這一步中可能會遇到下面的問題:

  1. 怎麼調用xor方法。
  2. java是強類型語言,javascript是弱類型語言,怎麼将javascript參數進行類型轉換并傳遞到java語言中。

怎麼将javascript參數進行類型轉換并傳遞到java語言中?其實方法很簡單,既然java是強類型語言,那就根據它要求的類型傳遞對應參數即可,看看它參數的類型。

1

2

3

4

5

6

7

public static byte[] xor(byte[] val, byte[] key) {

byte[] o 

=

new byte[val.length];

for

(

int

=

; i < val.length; i

+

+

) {

o[i] 

=

(byte) (val[i] ^ key[i 

%

key.length]);

}

return

o;

}

那麼,在javascript代碼中,先準備一個将字元串類型轉換為byte[]類型的方法

stringToBytes

,再通過執行個體化

MainActivity

類的方式調用

xor()

,然後還需要一個将byte[]回轉為String的方法,因為秘鑰key是Sting類型的。

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

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

js_code 

=

'''

//字元串轉換byte[]的方法

stringToBytes = function(str) { 

var ch, st, re = [];

for (var i = 0; i < str.length; i++ ) {

ch = str.charCodeAt(i); 

st = [];                

do { 

st.push( ch & 0xFF ); 

ch = ch >> 8;         

}   

while ( ch ); 

re = re.concat( st.reverse() );

return re; 

}

//将byte[]轉成String的方法

function byteToString(arr) { 

if(typeof arr === 'string') { 

return arr; 

var str = '', 

_arr = arr; 

for(var i = 0; i < _arr.length; i++) { 

var one = _arr[i].toString(2), 

v = one.match(/^1+?(?=0)/); 

if(v && one.length == 8) { 

var bytesLength = v[0].length; 

var store = _arr[i].toString(2).slice(7 - bytesLength); 

for(var st = 1; st < bytesLength; st++) { 

store += _arr[st + i].toString(2).slice(2); 

str += String.fromCharCode(parseInt(store, 2)); 

i += bytesLength - 1; 

} else { 

str += String.fromCharCode(_arr[i]); 

return str; 

}

//hook 代碼

Java.perform(function(){

var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity');

var MainActivity = Java.use('de.fraunhofer.sit.premiumapp.MainActivity')

var LicenseStr = "LICENSEKEYOK";

//hook getKey()方法,直接構造密碼,而不從preferences讀取

hook_Activity.getKey.implementation = function(){

//擷取Mac

var Mac = this.getMac();

//執行個體化MainActivity

var instance = MainActivity.$new();

//類型轉換

var MacByte =stringToBytes(Mac);

var LicenseByte = stringToBytes(LicenseStr);

send("MacByte:"+MacByte)

send("LicenseByte:"+LicenseByte)

//調用執行個體化對象的xor方法

xorResult = instance.xor(MacByte,LicenseByte);

send(xorResult);

//類型回轉

var Key = byteToString(xorResult)

send(Key);

return Key;

}

hook_Activity.verifyClick.implementation = function(view){

this.showPremium(view);

}

});

'''

接下來,執行看看,能不能擷取秘鑰Key。

不知道怎麼啟動模拟器中的frida-server,以及端口轉發,可以先看看初識Frida--Android逆向之Java層hook (一)

啟動python腳本,在模拟器中直接點選PREMIUM CONTENT,即可看到執行結果。

初識Frida--Android逆向之Java層hook (二)

0x02 調用showPremium擷取答案

前面2個步驟,可以說是已經完成90%了,接下來隻需要在hook一個能夠觸發showPremium方法的即可。方法就随意了,這裡采用hook verifyClick的方式,這樣點選app上的

VERIFY

按鈕,觸發verifyClick方法去調用showPremium,進而獲得最終答案。

1

2

3

hook_Activity.verifyClick.implementation 

=

function(view){

this.showPremium(view);

}

啟動腳本,點選app上的

VERIFY

按鈕看看執行結果:

初識Frida--Android逆向之Java層hook (二)

完整python代碼:下載下傳

總結

通過上面的例子,可以學習在java層怎麼使用frida實作:

  1. 任意類方法調用。
  2. 任意類方法重實作。

    以及學會怎麼構造和使用自定義javascript方法。

    當然這還僅僅隻是一個開始.....

https://bbs.pediy.com/thread-227233.htm