最近,開始記錄一篇關於 Android 逆向分析的 WriteUp 方便有需要的人學習,也歡迎大家相互交流, 發現不
一樣的世界。
一、 signin
考點:反編譯、靜態分析
Topic Link:https://ctf.bugku.com/files/109fa055c682e810684427c123a0833b/sign_in.zip
signin 軟件介紹:
1. 開始界面
2. 當輸入的字符串有誤時,會顯示:Try again.
題目分析:
1. 剛開始直接使用了 Android killer工具進行反編譯,但是不能夠查看Java源碼 (有點不夠意思,有時候Android killer自動化工具並不好用),但是這並不能阻擋我們查看想要的Java源碼 (dex2jar-2.0工具的利用)
2. 先將該APK解壓,提取class.dex到dex2jar-2.0的目錄下,操作class.dex文件得到對應的.jar文件
3. 利用jd-gui-1.4.0.jar工具打開步驟2中的.jar文件
4. 提取我們想要的Java源代碼
package re.sdnisc2018.sdnisc_apk1; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Base64; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private String getFlag() { return getBaseContext().getString(2131427360); } private void showMsgToast(String paramString) { Toast.makeText(this, paramString, 1).show(); } public void checkPassword(String paramString) { if (paramString.equals(new String(Base64.decode(new StringBuffer(getFlag()).reverse().toString(), 0)))) { showMsgToast("Congratulations !"); return; } showMsgToast("Try again."); } protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2131296283); ((Button)findViewById(2131165261)).setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { paramAnonymousView = ((EditText)MainActivity.this.findViewById(2131165253)).getText().toString(); MainActivity.this.checkPassword(paramAnonymousView); } }); } }
分析代碼可知:
1. 在 checkPassword()函數中,判斷用戶的輸入是否正確,與用戶相匹配的字符串是經過getFlag()函數返回的值處理之后的。
2. 將目標定位到getFlag()函數中,獲取其返回值。
分析getFlag()函數代碼片段:
return getBaseContext().getString(2131427360);
函數的返回值和 “2131427360” 有關,該字符串為資源ID,一般存儲在R.java文件中,ID對應的資源一般存儲在strings.xml文件中。
4. 在R.java文件中,查找資源ID
根據資源ID對應的變量名“toString”在strings.xml文件中找到對應的值
5. 將字符串“991YiZWOz81ZhFjZfJXdwk3X1k2XzIXZIt3ZhxmZ”進行反轉(reverse()函數影響),然后進行base64解密
6. 測試輸入正確字符串
get flag:
flag{Her3_i5_y0ur_f1ag_39fbc_}
二、mobile1(gctf)
考點:反編譯、靜態分析
Topic Link:https://ctf.bugku.com/files/7c43d693909d6dbfd7ad7d5a0866548b/gctf_mobile1.apk
mobile1(gctf) 軟件介紹:
1. 開始界面
2. 當輸入的字符串有誤時,會顯示:錯誤!
題目分析:
1. 先將該APK解壓,提取class.dex到dex2jar-2.0的目錄下,操作class.dex文件得到對應的.jar文件
2. 利用jd-gui-1.4.0.jar工具打開步驟1中的.jar文件
3. 提取主要的Java源代碼
package com.example.crackme; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MainActivity extends Activity { private Button btn_register; private EditText edit_sn; String edit_userName; private boolean checkSN(String paramString1, String paramString2) { if (paramString1 != null) { try { if (paramString1.length() == 0) { return false; } if ((paramString2 != null) && (paramString2.length() == 22)) { Object localObject = MessageDigest.getInstance("MD5"); ((MessageDigest)localObject).reset(); ((MessageDigest)localObject).update(paramString1.getBytes()); paramString1 = toHexString(((MessageDigest)localObject).digest(), ""); localObject = new StringBuilder(); int i = 0; while (i < paramString1.length()) { ((StringBuilder)localObject).append(paramString1.charAt(i)); i += 2; } paramString1 = ((StringBuilder)localObject).toString(); boolean bool = ("flag{" + paramString1 + "}").equalsIgnoreCase(paramString2); if (bool) { return true; } } } catch (NoSuchAlgorithmException paramString1) { paramString1.printStackTrace(); } } return false; } private static String toHexString(byte[] paramArrayOfByte, String paramString) { StringBuilder localStringBuilder = new StringBuilder(); int j = paramArrayOfByte.length; int i = 0; while (i < j) { String str = Integer.toHexString(paramArrayOfByte[i] & 0xFF); if (str.length() == 1) { localStringBuilder.append('0'); } localStringBuilder.append(str).append(paramString); i += 1; } return localStringBuilder.toString(); } public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130968601); setTitle(2131099677); this.edit_userName = "Tenshine"; this.edit_sn = ((EditText)findViewById(2131492945)); this.btn_register = ((Button)findViewById(2131492946)); this.btn_register.setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { if (!MainActivity.this.checkSN(MainActivity.this.edit_userName.trim(), MainActivity.this.edit_sn.getText().toString().trim())) { Toast.makeText(MainActivity.this, 2131099678, 0).show(); return; } Toast.makeText(MainActivity.this, 2131099675, 0).show(); MainActivity.this.btn_register.setEnabled(false); MainActivity.this.setTitle(2131099673); } }); } public boolean onCreateOptionsMenu(Menu paramMenu) { getMenuInflater().inflate(2131558400, paramMenu); return true; } }
分析代碼可知:
1. 將主要目光定在checkSN()函數上,分析該函數發現,需要滿足幾個條件:
1. 第一個參數paramString1 !=null&字符串長度 !=0
2. 第二個參數paramString2 !=null&字符串長度 ==22
3. 將第一個參數paramString1 經過MD5加密之后,取其偶數位組成字符串(16位)
4. 第三步里面產生的字符串與 “flag{}”組合(16+6=22)要與參數paramString2相等
2. 有上一步的分析可知,需要查看參數paramString1和參數paramString2的賦值情況,搜索誰調用了checkSN()函數,搜索之后將目標定在onCreate()函數上
public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130968601); setTitle(2131099677); this.edit_userName = "Tenshine"; this.edit_sn = ((EditText)findViewById(2131492945)); this.btn_register = ((Button)findViewById(2131492946)); this.btn_register.setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { if (!MainActivity.this.checkSN(MainActivity.this.edit_userName.trim(), MainActivity.this.edit_sn.getText().toString().trim())) { Toast.makeText(MainActivity.this, 2131099678, 0).show(); return; } Toast.makeText(MainActivity.this, 2131099675, 0).show(); MainActivity.this.btn_register.setEnabled(false); MainActivity.this.setTitle(2131099673); } }); }
分析該函數可知:
1. paramString1="Tenshine"
2. paramString2="用戶輸入的字符串"
3. 既然知道了參數paramString1的值,直接將paramString1進行MD5加密,取其偶數位,與"flag{}"進行組合得到最終FLAG
md5(Tenshine,32) = b9c77224ff234f27ac6badf83b855c76 FLAG:flag{bc72f242a6af3857}
4. 測試輸入正確字符串
get flag:
flag{bc72f242a6af3857}
三、mobile2(gctf)
考點:反編譯、靜態分析、腦洞
Topic Link:https://ctf.bugku.com/files/d1a2520c55a335d83646ce8a724dbebb/eb1fd250-7c32-418c-b287-1b00dcc53852.zip
題目分析
1. 下載下來的是zip格式的文件,不過和APK一樣,於是將其后綴改為.apk,可是卻不能成功安裝,只有先反編譯查看源碼 *_*
2. 先將該壓縮包解壓,提取class.dex到dex2jar-2.0的目錄下,操作class.dex文件得到對應的.jar文件
3. 利用jd-gui-1.4.0.jar工具打開步驟1中的.jar文件
4. 提取主要的Java源代碼
package com.example.mmsheniq; import android.annotation.SuppressLint; import android.app.AlertDialog.Builder; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.telephony.SmsManager; import android.text.Editable; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @SuppressLint({"DefaultLocale"}) public class MainActivity extends ActionBarActivity { Button button1; Button button2; ArrayList<String> packagNameList; EditText pass; private MyReceiver receiver; private boolean detectApk(String paramString) { return this.packagNameList.contains(paramString.toLowerCase()); } private boolean goToNetWork() { ConnectivityManager localConnectivityManager = (ConnectivityManager)getSystemService("connectivity"); if (localConnectivityManager.getNetworkInfo(1).getState() != null) {} while (localConnectivityManager.getNetworkInfo(0).getState() != null) { return true; } return false; } private void initpackagNameList() { this.packagNameList = new ArrayList(); List localList = getPackageManager().getInstalledPackages(0); int i = 0; for (;;) { if (i >= localList.size()) { return; } PackageInfo localPackageInfo = (PackageInfo)localList.get(i); this.packagNameList.add(localPackageInfo.packageName.toLowerCase()); i += 1; } } protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); requestWindowFeature(1); setContentView(2130903064); initpackagNameList(); System.out.println("host��������=============================="); this.receiver = new MyReceiver(null); paramBundle = new IntentFilter("android.intent.action.PACKAGE_ADDED"); paramBundle.addDataScheme("package"); registerReceiver(this.receiver, paramBundle); if (!detectApk("com.example.com.android.trogoogle")) { System.out.println("host��������=============================="); paramBundle = getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk"; retrieveApkFromAssets(this, "com.android.Trogoogle.apk", paramBundle); showInstallConfirmDialog(this, paramBundle); } this.pass = ((EditText)findViewById(2131034176)); this.button1 = ((Button)findViewById(2131034177)); this.button1.setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { if (!MainActivity.this.detectApk("com.example.com.android.trogoogle")) { paramAnonymousView = MainActivity.this.getFilesDir().getAbsolutePath() + "/com.android.Trogoogle.apk"; MainActivity.this.retrieveApkFromAssets(MainActivity.this, "com.android.Trogoogle.apk", paramAnonymousView); MainActivity.this.showInstallConfirmDialog(MainActivity.this, paramAnonymousView); return; } if (!MainActivity.this.goToNetWork()) { Toast.makeText(MainActivity.this, "��������������������������", 0).show(); return; } if (MainActivity.this.pass.getText().toString().length() >= 6) { Toast.makeText(MainActivity.this, "����������������...", 0).show(); Toast.makeText(MainActivity.this, "����������������������", 0).show(); return; } Toast.makeText(MainActivity.this, "����������������������", 0).show(); } }); this.button2 = ((Button)findViewById(2131034178)); this.button2.setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { MainActivity.this.startActivity(new Intent(MainActivity.this, RegisterActivity.class)); } }); } public boolean retrieveApkFromAssets(Context paramContext, String paramString1, String paramString2) { bool = false; try { paramString2 = new File(paramString2); if (paramString2.exists()) { return true; } paramString2.createNewFile(); paramString1 = paramContext.getAssets().open(paramString1); paramString2 = new FileOutputStream(paramString2); byte[] arrayOfByte = new byte['?']; for (;;) { int i = paramString1.read(arrayOfByte); if (i == -1) { paramString2.flush(); paramString2.close(); paramString1.close(); bool = true; break; } paramString2.write(arrayOfByte, 0, i); } return bool; } catch (IOException paramString1) { Toast.makeText(paramContext, paramString1.getMessage(), 2000).show(); paramContext = new AlertDialog.Builder(paramContext); paramContext.setMessage(paramString1.getMessage()); paramContext.show(); paramString1.printStackTrace(); } } public void showInstallConfirmDialog(final Context paramContext, final String paramString) { AlertDialog.Builder localBuilder = new AlertDialog.Builder(paramContext); localBuilder.setIcon(2130837592); localBuilder.setTitle("������������"); localBuilder.setMessage("������������������������������APK��������������������"); localBuilder.setPositiveButton("����", new DialogInterface.OnClickListener() { public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt) { try { paramAnonymousDialogInterface = "chmod 777 " + paramString; Runtime.getRuntime().exec(paramAnonymousDialogInterface); paramAnonymousDialogInterface = new Intent("android.intent.action.VIEW"); paramAnonymousDialogInterface.addFlags(268435456); paramAnonymousDialogInterface.setDataAndType(Uri.parse("file://" + paramString), "application/vnd.android.package-archive"); paramContext.startActivity(paramAnonymousDialogInterface); return; } catch (IOException paramAnonymousDialogInterface) { for (;;) { paramAnonymousDialogInterface.printStackTrace(); } } } }); localBuilder.show(); } private class MyReceiver extends BroadcastReceiver { private MyReceiver() {} public void onReceive(Context paramContext, Intent paramIntent) { System.out.println("MyReceiver ��������=========================="); if (paramIntent.getAction().equals("android.intent.action.PACKAGE_ADDED")) { paramContext.startActivity(new Intent(paramContext, MainActivity.class)); System.out.println(" ������ Ok!=============================="); paramIntent = new Intent("android.intent.action.MAIN"); paramIntent.addFlags(268435456); paramIntent.addCategory("android.intent.category.LAUNCHER"); paramIntent.setComponent(new ComponentName("com.example.com.android.trogoogle", "com.example.com.android.trogoogle.MainActivity")); paramContext.startActivity(paramIntent); System.out.println(" ���� Ok!=============================="); SmsManager.getDefault().sendTextMessage("15918661173", null, " Tro instanll Ok", null, null); System.out.println(" �������� Ok!=============================="); } } } }
5. 分析代碼並沒有找到什么關於flag的線索,同時軟件也不能運行,發現毫無提示,於是猜測flag可能藏在某個文件中,其中可疑的文件主要有strings.xml、AndroidManifest.xml,用工具010 Editor打開進行搜索,發現flag存在於AndroidManifest.xml中
get flag:
flag{8d6efd232c63b7d2}
update + ing