Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86381178

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

前言本次測試對比是為了呈現incinerator與PNFSofteware出品的JEB以及國內出品GDA在Android逆向工程能力的對比,從而讓大家更好更直觀的了解相關的詳細信息

本次測試對比的產品信息如下:

Incinerator: 1.0.0

JEB:3.19.1.202005071620

GDA:3.9.0

注:文章編寫日期與發佈時間有一定時間間隔,所以以下內容並不代表各產品的後續性能指標。

反編譯器對循環結構的還原能力測試Test1第一個測試中,設計了循環頭和鎖節點都為二路條件循環結構,為了測試循環結構化分析能力,多嵌套了幾個if語句(代碼標號為基本塊號)。程序簡單如下:

publicvoidtest1(inty,inta){

while(y0){

if(a10){

if(a100){

a=a*5;

break;

}else{

y=y/a;

}

}

}

}

}

流程图.png

編寫testdemo將代碼段生成apk後,並分別使用JEB、GDA、Incinerator來進行反編譯操作,從而進行代碼可讀性和語義準確性上的對比,如下圖所示:

95f762e41272c0aa3a7fe3914c2bdf56

test1

通過上述對比可以看出在語義準確性上,JEB發生了語義錯誤,在a 100時,丟失了a *=5的代碼塊,Incinerator與GDA保持了語義的準確性。

在代碼可讀性上,三者相差不大可讀性都很好。 Incinerator在if-else上做了相應的優化,可讀性略有提升。

在代碼還原度上,Incinerator做了對應優化,GDA重複聲明了a、y變量,其他方面最為接近源碼。而JEB存在代碼塊丟失。

反編譯器語義準確性代碼可讀性代碼還原度Incinerator高高JEB×高中GDA高高Test2接下來看看他們對雙層循環的結構化分析的能力,設計一個雙層循環,在內層循環break出外層循環,實際上基本塊5即if(a 10),不僅會是內存循環的鎖節點,也會是外層循環的鎖節點。並且該鎖節點為二路條件節點,其一個分支路徑回到內層循環,另外一個分支結構回到外層循環。一般對循環結構算法都是循環頭-鎖節點一一對應,因此處理過程中可能會復雜化該類結構。代碼實現非常簡單如下:

publicvoidtest2(inty,inta){

while(y0){

while(a0){

if(a10){

break;

}

}

}

}

attachBaseContext(this);

}重新編譯apk後,再進行反編譯後,如下圖所示:

f61654c8fad1346041bba769daa7b737

test2

通過上述對比可以看出在語義準確性上,Incinerator、JEB保持了語義的準確性,都識別除了雙重循環,GDA僅有函數聲明,丟失了整個函數的代碼塊。

在代碼可讀性上,Incinerator優化了if-else組合,JEB在if中加入continue省略else語句,兩者可讀性都很好。

在代碼還原度上,Incinerator、JEB除了各自在if-else上的優化,還原度都很高。

反編譯器語義準確性代碼可讀性代碼還原度Incinerator高高JEB高高GDA×低低Test3這一段代碼在退出循環的”if(a10)”語句中內嵌了另外一個if語句,這會導致內層循環的鎖節點發生變化,並且給內層循環添加了一個跟隨節點,另外代碼做了稍稍的改動。如下:

publicvoidtest3(inty,inta){

while(y0){

while(a50){

a=a+1;

y=y+1;

if(a10){

if(a100){

a=a*5;

break;

}else{

y=y/a;

}

}

}

this.attachBaseContext(this);

}

}繼續編譯成apk,再進行反編譯操作,如下圖所示:

8dd35acc6d19712de616aed4a9777f32

test3

通過對比可以看出在語義準確性上,Incinerator、JEB仍然保持了語義的準確性,GDA重複聲明了a、y變量,並且繼續丟失函數內部的代碼塊。

在代碼可讀性上,Incinerator、JEB保持很好的代碼可讀性,JEB使用了continue來分割嵌套的if。

在代碼還原度上,Incinerator最為接近源碼,JEB改為使用continue來分割嵌套的if。

反編譯器語義準確性代碼可讀性代碼還原度Incinerator高高JEB高高GDA×低低Test4在內層循環的第一個if-else結構上添加一個後隨節點,並且最後break出內層循環到外層循環。並且將a=a*5語句後的break改成continue。代碼如下:

publicvoidtest4(inty,inta){

while(y0){

y++;

while(a50){

a=a+1;

y=y+1;

if(a10){

if(a100){

a=a*5;

continue;

}else{

y=y/a;

}

}

y=a*y;

break;

}

this.attachBaseContext(this);

}

}同樣編譯成apk後再反編譯,如下圖所示:

6002ea5b38852a4bf5870e1aee4ad462

test4

通過上述對比可以看出

在語義準確性上,Incinerator在a *=5; 後面丟失了continue,在y *=a; 後面丟失了退出循環的break;JEB保持了語義的正確性;GDA重複聲明變量,也丟失了函數內的代碼塊。

在代碼可讀性上,Incinerator、JEB可讀性都很好。

在代碼還原度上,Incinerator與源碼最為相似,但是丟失了continue、break;JEB使用continue分開了if-else,將else後面的y /=a,與y *=a合併為新的y=y/a * a,並加入break,還原度上有了一定的改變。

反編譯器語義準確性代碼可讀性代碼還原度Incinerator×高中JEB高中GDA×低低Test5這次在“if(a10)”內部加入switch,在a為11、12時,執行“a=a * 5”,並continue返回內循環while,a為13時,執行“a=a * 6”,繼續往下執行,並不退出,a為14時,執行“a=a * 7” 退出switch, 與default中加入if-else,代碼如下:

publicvoidtest5(inty,inta){

while(y0){

y++;

while(a50){

a=a+1;

y=y+1;

if(a10){

switch(a){

case11:

case12:

a=a*5;

continue;

case13:

a=a*6;

case14:

a=a*7;

break;

default:

if(a100){

a=a*5;

continue;

}else{

y=y/a;

}

}

}

y=a*y;

break;

}

this.attachBaseContext(this);

}

}最後編譯成apk後反編譯,如下圖所示:

9423c2d3183b388f644c6fadcfbf5295

test5

通過上述對比可以看出在語義準確性上,Incinerator在switch將a為11、12、default中的continue錯誤表達為break,丟失了y *=a後面退出內循環的break;JEB保持了語義的正確性在,但在label_18的break之後,多了兩句無用的代碼a *=8;continue;GDA沒有識別出內循環,使用if與goto做處理,switch中a為11、12時多了break,沒有識別出a=14,且在default中,執行完y=y/a後繼續執行'a=a * 7'。

在代碼可讀性上,Incinerator識別出雙循環、switch-case可讀性上最好,JEB、GDA多次出現goto,在代碼可讀性上存在一定的影響。

在代碼還原度上,Incinerator與源碼最為相似,但在節點的退出上存在一定的問題,JEB、GDA在代碼的還原度上膨脹比較大。

反編譯器語義準確性代碼可讀性代碼還原度Incinerator×高中JEB中中GDA×中低調試能力測試逆向工程工具針對Apk可調試,對於研究人員來說有著極大的幫助,而對於已經發布後的應用再進行調試的話,可調試的前提條件會比較苛刻,如:設備是否root、調試屬性是否開啟、能否重打包等,這些因素都會影響著是否能夠調試,而影響調試功能的好壞、支持與否,取決於:能否stepover、stepinto、breakpoint,能否獲取/修改變量值等,這些因素都體現著調試器是否好用。所以我們從上述多個維度,對Incinerator、JEB的調試做下簡單對比,但因GDA不支持調試,所以下面的內容無法針對GDA進行測試對比。

這裡直接使用Android Studio自帶的example:Login Activity進行對比,如圖1-2所示:

72a03afc6836285c34d5e3234bf992fc

圖1

e1ecd5a7f6c45018c44dc7ea23ea0b3c

圖2

在登錄驗證的位置做細微修改,讓它基本不可能登錄成功,然後再分別使用JEB、Incinerator進行分析登錄過程,並繞過登錄限制。修改的代碼與登錄失敗,如圖3-4所示:

67e985a816171ca2bfbc7045086c456e

圖3

c686e78245ee860d6f9a03acecbcf3af

圖4

調試設備:Nexus 5X

系統版本:7.1.2

root狀態:已root

主要測試功能:

下斷點

步進

步過

跑至光標

顯示與修改變量值

免debugger屬性調試

smali調試

偽代碼調試

JEB調試首先手動安裝編譯好的apk,然後使用JEB反編譯對應apk,點擊JEB上的start. 如圖5所示:

f59a75d9a167571550761a7f9e0d45c7

圖5

出來Attach界面,因為應用還沒啟動,所以並沒有看到Processes中有進程列表,如圖6所示:

1d6c2a933eae2f3f45a43b8dfbfdaf61

圖6

通過命令(adb shell am start -D -n com.testdemo3/.ui.login.LoginActivity)啟動進程,JEB點擊(Refresh Machines List)刷新列表,看到已經跑起的測試案例,如圖7所示:

a7a1022cd33cdd32a34526e7950dc177

圖7

點擊Attach後,發現無法debug,按提示指app沒有開啟debuggable屬性或者設備沒有root,建議使用模擬器、root設備或者重打包app。 (實際上設備已root),如圖8所示:

14eb8cc4bcc1153f20d81ec3c48e0728

圖8

我們在AndroidManifest中加入android:debuggable='true'並重新編譯(如果是第三方的app只能嘗試用apktool等工具進行重打包,有簽名、完整性等校驗的話再想辦法將其繞過)

現在可以成功Attach上去。

我們使用JEB反編譯登錄界面的activity:LoginActivity,在按鈕的點擊觸發代碼中加入斷點,如圖9所示:

cb7e6b9d77ccdd3d0940a62ccbbe1773

圖9

因不支持在偽代碼中加入斷點,我們切換回smali(快捷鍵Q),在onClick的第一行按Ctrl+B加入斷點,如圖10所示:

2973f6abcb0812c148d737436de4b6d2

圖10

操作app,輸入帳號密碼,點擊:SIGN IN OR REGISTER,在JEB中成功觸發斷點,如圖11所示:

JEB此處有個優勢,它的佈局可以根據個人喜好隨意拖拉

4e5e64f1ce1c439f2938500955500b94

圖11

當前顯示的變量似乎有些異常,我們此時忽略他,鼠標放到00000040 處,點擊JEB的'Run to line',成功跳到指定行,如圖12所示:

5e176b7f9fb8c43f2b0c2a4abbda242f

圖12

JEB一開始將所有變量當作int類型處理,我們分析代碼,可以知道此處的V1、V2是輸入的帳號與密碼,類型是String,因此,我們點擊V1、V2中的Type將int改為String,如圖13所示:

c5573234eacd57bc2809142639d00c88

圖13

v1、v2成功修正類型,對應的值也成功顯示出我們測試輸入的帳號密碼。

在JEB中點擊步進(Step Into)到LoginViewModel的login方法,如圖14所示:

7a36c5e8a7cabc39b6d6a50302369f5b

圖14

成功步進LoginViewModel的login方法,但是在這裡可以看到,剛才修改的類型(此處對應的是p1、p2)又重新變回了int。

根據smali可知道,接下來會調用LoginRepository的login方法,隨後返回Result

我們再繼續點擊兩次步進(Step Into)進入LoginRepository的login方法,如圖15所示:

9d65fb40f8f67ea74cea4507b3dcf0d4

圖15

在此方法中,它會繼續將帳號密碼傳給LoginDataSource的login方法,返回Result

繼續步進(Step Into)兩次,進入LoginDataSource的log