国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android 逆向學習詳解及實例

2019-12-12 05:17:33
字體:
來源:轉載
供稿:網(wǎng)友

         斷斷續(xù)續(xù)的總算的把android開發(fā)和逆向的這兩本書看完了,雖然沒有java,和android開發(fā)的基礎,但總體感覺起來還是比較能接收的,畢竟都是觸類旁通的。當然要深入的話還需要對這門語言的細節(jié)特性和奇技淫巧進行挖掘。

  這里推薦2本書,個人覺得對android開發(fā)入門和android逆向入門比較好的教材:

  《google android 開發(fā)入門與實戰(zhàn)》

  《android 軟件安全與逆向分析》

  1. 我對android逆向的認識

  因為之前有一些windows逆向的基礎,在看android逆向的時候感覺很多東西都是能共通的。但因為android程序本身的特性,還是有很多不同的地方。

  1.1 反編譯

  android程序使用java語言編寫,從java到android虛擬機(Dalvik)的dex代碼(可以看成是android虛擬機的機器碼)需要一個中間語言的轉換過程。類似.NET的IL中間虛擬指令。而我們知道,.NET的IL中間代碼之所以能很容易的"反編譯"回C#源代碼,是因為除了IL中間語言,還包含了大量的META元數(shù)據(jù),這些元數(shù)據(jù)使我們可以很容易的一一對應的反編譯回C#的源代碼。java的中間語言.class文件也是類似的道理,我們可以使用工具直接從dex機器碼反編譯回java源代碼。

  1.2 逆向分析手段

  windows的逆向分析中,我們可以使用OD或者C32ASM來分析匯編指令(當然OD還可以動態(tài)調試),或者使用IDA + F5(hex Ray反編譯插件)來靜態(tài)的分析源代碼(C/C++)

  在android逆向分析過程中:

  1) 我們可以使用ApkTool(本質上是BakSmali反匯編引擎)對apk文件進行反匯編,得到各個類、方法、資源、布局文件...的smali代碼,我們可以直接通過閱讀smali代碼來分析程序的代碼流,進行關鍵點的修改或者代碼注入。

  2) 我們可以從apk中提取.dex文件,使用dex2jar工具對dex進行反匯編,得到jar包(java虛擬指令),然后使用jd-gui等工具再次反編譯,得到java源代碼,從源碼級的高度來審計代碼,更快的找到關鍵點函數(shù)或者判斷,然后再回到smali層面,對代碼進行修改。這種方法更傾向于輔助性的,最終的步驟我們都要回到smali層面來修改代碼。

  3) 使用IDA Pro直接分析APK包中的.dex文件,找到關鍵點代碼的位置,記下文件偏移量,然后直接對.dex文件進行修改。修改完之后把.dex文件重新導入apk中。這個時候要注意修改dex文件頭中DexHeader中的checksum字段。將這個值修復后,重新導入apk中,并刪除apk中的META-INF文件夾,重新簽名即可完成破解。

  1.3 android與C的結合

  在學習android逆向的時候感覺遇到的最難的問題就是分析原生代碼,即JNI代碼。開發(fā)者使用android NDK編寫C/C++代碼供android的java代碼調用(通過java的代碼轉接層來完成接口的轉換)。

  使用android NDK編寫的C/C++代碼最終會生成基于ARM的ARM ELF可執(zhí)行文件,我們想要分析軟件的功能就必須掌握另一項技能,ARM匯編,ARM匯編個人感覺雖然和x86匯編類似,不過由于IDA Pro對ARM匯編沒有反編譯功能以及貌似沒有工具能動態(tài)調試ARM代碼(我網(wǎng)上沒找到),導致我們只能直接硬看ARM代碼,加上往往伴隨著復雜的密碼學算法等等,導致對Native Code的逆向相對來說比較困難,對基本功的要求比較高。

  1.4 關于分析android程序

  1) 了解程序的AndroidManifest.xml。在程序中使用的所有activity(交互組件)都需要在AndroidManifest.xml文件中手動聲明。包括程序啟動時默認啟動的主activity,通過研究這個AndroidManifest.xml文件,我們可以知道該程序使用了多少的activity,主activity是誰,使用了哪些權限,使用了哪些服務,做到心中有數(shù)。

  2) 重點關注Application類

  這本來和1) AndroidManifest.xml是一起的,但是分出來說是因為這個思路和windows下的逆向思路有相通之處。

  在windows exe的數(shù)據(jù)目錄表中如果存在TLS項,那程序在加載后會首先執(zhí)行這個TLS中的代碼,執(zhí)行完之后才進行main主程序入口。

  在android 中Application類比程序中其他的類啟動的都要早。

  3) 定位關鍵代碼

  3.1) 信息反饋法(關鍵字查找法)

  通過運行程序,查找程序UI中出現(xiàn)的提示消息或標題等關鍵字,到String.xmlzhong中查找指定字符串的di,然后到程序中查找指定的id即可。

  3.2) 特征函數(shù)法

  這種做法的原理和信息反饋法類似,因為不管你提示什么消息,就必然會調用相應的API函數(shù)來顯示這個字符串,例如Toast.MakeText().show()

  例如在程序中搜索Toast就有可能很快地定位到調用代碼

  3.3) 代碼注入法

  代碼注入法屬于動態(tài)調試的方法,我們可以手動修改smali反匯編代碼,加入Log輸入,配合LogCat來查看程序執(zhí)行到特定點時的狀態(tài)數(shù)據(jù)。

  3.4) 棧跟蹤法

  棧跟蹤法屬于動態(tài)調試方法,從原理上和我們用OD調試時查看call stack的思想類似。我們可以在smali代碼中注入輸出運行時的棧跟蹤信息,然后查看棧上的函數(shù)調用序列來理解方法的執(zhí)行流程(因為每個函數(shù)的執(zhí)行都會在棧上留下記錄)

  3.5) Method Profiling

  Method Profiling,方法剖析(這是書上的叫法,我更愿意叫BenchMark測試法),它屬于一種動態(tài)調試方法,它主要用于熱點分析和性能優(yōu)化。在DDMS中有提供這個功能,它除了可記錄每個函數(shù)所占用的CPU時間外,還能夠跟蹤所有的函數(shù)調用關系。

  1.5 關于android的代碼混淆和加殼

  java語言編寫的代碼本身就很容易被反編譯,google為此在android 2.3的SDK中正式加入了ProGuard代碼混淆工具,只要正確的配置好project.properties與proguard.cfg兩個文件即可使用ProGuard混淆軟件。

  java語言由于語言自身的特殊性,沒有外殼保護這個概念,只能通過混淆方式對其進行保護。對android NDK編寫的Native Code倒是可以進行加殼,但目前貌似只能進行ups的壓縮殼保護

  2. CrackMe_1 分析學習

  2.1 運行一下程序,收集一些基本信息

  只有一個輸入框,那說明這個驗證碼的輸入來自別的地方,因為我們知道,不管你的加密算法是啥,總是要有一個函數(shù)輸入源的,我們在UI界面上輸入的相當于是結果,而輸入源應該來自于別的地方,計算完之后和我們在UI上輸入的結果進行對比,大致是這個思路。

  2.2 分析

  使用apktool反編譯apk文件。查看AndroidManifest.xml文件。了解到主activity為:Main。

  接著我們從apk中提取.dex文件。用dex2jar->jd-gui來查看java源代碼。

  看到里面很多的a,b,c方法,基本上可以判定是配ProGuard混淆了,不過問題也不大,雖然顯示的是無意義的函數(shù)名但是不影響我們分析代碼流程。

  2.2.1 類b的分析

  從OnCreate()的代碼來看,我們首先從類b開始分析:

  類 b 提供了一個公共的構造函數(shù) public b(Context paramContext),  一個私有的成員函數(shù)private String b(),  以及一個公有成員函數(shù) public final void a()。

  b(): 通過TelephonyManager獲取設備相關的一些信息,然后通過PackageManager獲取到自身的簽名。然后把這些字符串拼接起來返回給調用者。

TelephonyManager localTelephonyManager = (TelephonyManager)this.a.getSystemService("phone");   String str1 = localTelephonyManager.getDeviceId();   String str2 = localTelephonyManager.getLine1Number();   String str3 = localTelephonyManager.getDeviceSoftwareVersion();   String str4 = localTelephonyManager.getSimSerialNumber();   String str5 = localTelephonyManager.getSubscriberId();   Object localObject = "";   PackageManager localPackageManager = this.a.getPackageManager();   try   {    String str6 = localPackageManager.getPackageInfo("com.lohan.crackme1", 64).signatures[0].toCharsString();    localObject = str6;    return str1 + str2 + str3 + str4 + str5 + (String)localObject;   }     a():   SharedPreferences localSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.a);   SharedPreferences.Editor localEditor;   if (!localSharedPreferences.contains("machine_id"))    localEditor = localSharedPreferences.edit();   try   {    localEditor.putString("machine_id", b());    localEditor.commit();    return;   } 

  a()調用方法b()獲取字符串,然后通過SharedPreferences.Editor將這個字符串值存儲到鍵machine_id,可以理解為機器碼。也就是說,這個加密函數(shù)的輸入是本機的機器碼。

  經(jīng)過上面的分析,類b對外提供方法a,功能就是生成"機器碼"并存儲到系統(tǒng)中,對應的鍵為machine_id。

  2.2.2 類c的分析

  類c提供的方法較多,我們逐個分析。

  1) 構造函數(shù)

Java代碼

public c(Context paramContext) {   a = paramContext;   b = "f0d412b5530e1f9841aab434d989cc77";   c = "4ec407446b872351e613111339daae9"; } 

  把參數(shù)環(huán)境上下文Context本地化,并聲明了兩個字符串。

  2) public static boolean b()

Java代碼

MessageDigest localMessageDigest = MessageDigest.getInstance("MD5"); localMessageDigest.update(paramString.getBytes(), 0, paramString.length()); return new BigInteger(1, localMessageDigest.digest()).toString(16); 

  通過MessageDigest計算paramString 的MD5值。

  3) public static boolean b()

Java代碼

PackageManager localPackageManager = a.getPackageManager();   try   {    String str = b(new String(localPackageManager.getPackageInfo("com.lohan.crackme1", 64).signatures[0].toChars()));    if (!str.equals(b))    {     boolean bool = str.equals(c);     if (!bool);    }    else    {     return false;    }   } 

  通過 getPackageManager 獲取自身的簽名,如果簽名與構造函數(shù)中的兩個字符串b(f0d412b5530e1f9841aab434d989cc77)或者c(4ec407446b872351e613111339daae9)任意一個相等,那么返回false,否則返回true。

  4) public static int a(String paramString)

Java代碼

try {  if (b())   return 0;  SharedPreferences localSharedPreferences = PreferenceManager.getDefaultSharedPreferences(a);  if (b(localSharedPreferences.getString("machine_id", "")).equals(paramString))  {   if (b())    return 0;   SharedPreferences.Editor localEditor = localSharedPreferences.edit();   localEditor.putString("serial", paramString);   localEditor.commit();   return 1;  } }

  可以看出這段代碼的功能為計算機器碼的 MD5,如果與傳入的參數(shù)paramString一致,那么通過SharedPreferences存入到serial(機器碼的MD5值paramString)字段中。 當然還有調用b方法進行一些判斷,自身的簽名不能是已知的兩個。

  5) public static boolean a()

Java代碼

SharedPreferences localSharedPreferences = PreferenceManager.getDefaultSharedPreferences(a); if (!localSharedPreferences.contains("serial"))  return false; String str = localSharedPreferences.getString("serial", ""); if (str.equals(""))  return false; return a(str) >= 0;

  這個其實就是上面的 int a(String paramString)的包裝函數(shù),通過SharedPreferences獲取serial字段(機器碼的MD5值),并傳給這個方法,返回相應的返回值(判斷結果)。

  2.2.3 類a分析

  可以看到,類a是一個CountDownTimer:

  Schedule a countdown until a time in the future, with regular notifications on intervals along the way. Example of showing a 30 second countdown in a text field:(android Developer)

  從onFinish函數(shù)我們看出這個類的功能是倒計時6秒,然后調用c.a(),也就是判斷我們輸入的serial是否等于"機器碼"的MD5值。如果不能通過,就設置TextView內容提示注冊。

  2.2.4 類Main分析

  1) 在onCreate(),先初始化b和c的類。然后調用b.a()生成并存儲"機器碼",然后調用c.a(),也就是判斷是否已經(jīng)存儲了serial,并判斷是否能通過算法校驗。如果不能通過,則什么都不做,這就是啟動時檢測注冊狀態(tài)的做法,即如果你之前已經(jīng)注冊了,那在之后的登錄后就會自動識別出來,但是我們如果是第一次啟動且沒有注冊,那這里就什么也不做。

  如果能通過,則調用自身的方法a()。而自身的方法a()又調用了c.b()方法,即檢查我們輸入的serial和機器碼的MD5值是否相同,如果相同則什么也不做,如果不同就把下面的按鈕和TextView等UI控件給隱藏了。并啟動倒計時類a.start()。即二次驗證。

  ps:

  這里要注意的是,由于程序使用了ProGuard來混淆代碼,所以用jd-gui翻譯出來的代碼全都是從a,b,c開始計數(shù),而且經(jīng)常是變量、類、方法的命名混合了起來。我們在看java代碼的時候遇到難懂的地方要結合smali代碼一起看,這樣才能獲取比較準確的對程序代碼流的把握。

  2) public void onClick(View paramView)

Java代碼

if (c.a(((EditText)findViewById(2131034114)).getText().toString()) == 0) {  Toast.makeText(this, 2130968577, 0).show();  return; } Toast.makeText(this, 2130968578, 0).show(); 

  判斷我們通過UI輸入的serial是否和"機器碼"的MD5值相同,如果不相同則彈出提示Invalid serial!(可以通過ID值反查出對應的字符串),如果相同則彈出Thanks for purchasing!

  通過以上分析,我們來綜合一下思路:

  程序啟動時會做一些初始化的工作,然后生成本地對應的機器碼并保存在SharedPreferences中。

  檢查當前的SharedPreferences中是否已經(jīng)保存了serial鍵值對,并檢查正確性,即檢查是否上一次已經(jīng)注冊了。如果沒有這個鍵值對,說明還沒注冊,如果存在這個鍵值對且正確性也符合,代碼接下來會繼續(xù)檢查APK自身的簽名是否為代碼中定義的那兩個,如果相等則什么都不做(即依然不通過檢查),如果不等則代碼繼續(xù)執(zhí)行倒計時6秒的類a, 6秒后再次檢查一次serial鍵值對。

  對于那個按鈕點擊事件,onClick(),它獲取用戶通過UI輸入的serial,并檢測是否和"機器碼"的MD5值相等,如果相等則存進SharedPreferences中的鍵值對中。

  以上基本就是這個程序的代碼思路了。我們可以看到,作者這里使用了雙重保護的思路,即不僅要你輸入的serial相同,而且對你的APK的簽名也有限制。

  3. 破解思路

  3.1 單純的破解,用代碼注入的方法得到注冊碼。

  經(jīng)過分析,我們知道應該在b.smali的155行:

  move-result-object v2 這里代碼注入,因為這個b()的作用就是獲取當前"機器碼"(注意,這里獲取的是沒有MD5之前的"機器碼",因為程序中的MD5都是臨時算出來的)

  我們在這里加入:

  const-string v3, "SN"

  invoke-static {v3, v2}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I

  重新回編譯smalli代碼。

  在命令行中執(zhí)行 adb logcat -s SN:v  ,然后再啟動程序

  會在命令行中看到一大串字符串,這些字符串就是我們要的機器碼

  將這些字符串計算MD5值之后,就可以完成破解了。

  3.2 讀取程序對應的文件

  我們知道,所謂的SharedPreferences本質上是保存在當前程序空間下的/data/data/<package name>/shared_prefs/<package name>_preferences.xml文件中的。

  我們可以通過adb連接上去,直接讀取這個文件的內容。

  可以看到,和我們通過代碼注入的方式得到的機器碼是相同的。

  3.3 編寫注冊機

  這種方法是最好的,編寫注冊機要求我們對目標程序的代碼有全盤的認識,然后模擬原本的算法或者逆向原本的算法寫出注冊機

  我們用Eclipse重新生成一個新的工程 com.lohan.crackme。注意,工程的報名必須和目標程序的包名一致,這樣我們的注冊機運行后得到的APK簽名才會是一樣的。

  核心算法如下:

Java代碼

@Override protected void onCreate(Bundle savedInstanceState)  {   super.onCreate(savedInstanceState);   setContentView(R.layout.activity_main);   setTitle("crackMe1_keyGen");   final Context context = getApplicationContext();      //獲取UI控件   txt_machineCode = (TextView) findViewById(R.id.machineCode);   txt_apkSig = (TextView) findViewById(R.id.apkSig);   txt_serial = (TextView) findViewById(R.id.serial);   btn_Go = (Button) findViewById(R.id.ok);      //設置監(jiān)聽事件   btn_Go.setOnClickListener(new OnClickListener(){     public void onClick(View v)     {       //計算機器碼       TelephonyManager localTelephonyManager = (TelephonyManager) context.getSystemService("phone");       String str1 = localTelephonyManager.getDeviceId();       String str2 = localTelephonyManager.getLine1Number();       String str3 = localTelephonyManager.getDeviceSoftwareVersion();       String str4 = localTelephonyManager.getSimSerialNumber();       String str5 = localTelephonyManager.getSubscriberId();       Object localObject = "";       PackageManager localPackageManager = context.getPackageManager();       try       {        String str6 = localPackageManager.getPackageInfo("com.lohan.crackme1", 64).signatures[0].toCharsString();        localObject = str6;        String str_result = str1 + str2 + str3 + str4 + str5 + (String)localObject;        //得出機器碼        txt_machineCode.setText(str_result);                //計算當前APK的簽名        txt_apkSig.setText(str6);                //計算注冊碼        MessageDigest localMessageDigest = null;          try {           localMessageDigest = MessageDigest.getInstance("MD5");         } catch (NoSuchAlgorithmException e) {           // TODO Auto-generated catch block           e.printStackTrace();         }         localMessageDigest.update(str_result.getBytes(), 0, str_result.length());        String str_serial = new BigInteger(1, localMessageDigest.digest()).toString(16);        txt_serial.setText(str_serial);       }       catch (PackageManager.NameNotFoundException localNameNotFoundException)       {        while (true)         localNameNotFoundException.printStackTrace();       }      }    }); 

  破解結果

  APK:

  http://pan.baidu.com/s/1qsygp

  4. 總結

  至此,這個android的CrackeMe_1就算破解完成了。這段時間的android學習也算暫時告一段落,移動無線安全是未來的新方向,在不遠的將來,基于android平臺的各種應用和軟件不僅僅是手機甚至是各種的互聯(lián)終端都將進入人們的視野,無線安全的研究應該也會慢慢成為熱點。

  我也希望下次再研究android安全的時候能有更深入的認識和體會。

        有興趣的同學可以看下本文,謝謝大家對本站的支持!

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 西贡区| 越西县| 南郑县| 甘泉县| 民丰县| 双峰县| 红桥区| 周口市| 华蓥市| 青河县| 桓仁| 肃宁县| 浙江省| 天镇县| 元氏县| 林西县| 鹿泉市| 镇坪县| 龙海市| 朝阳县| 页游| 惠东县| 舟山市| 格尔木市| 古浪县| 天长市| 洛南县| 梅河口市| 马边| 富阳市| 忻州市| 区。| 万州区| 项城市| 确山县| 手游| 滨州市| 福贡县| 徐水县| 西乌珠穆沁旗| 桓台县|