Markdown哪兒都好就是插圖太麻煩,盡量用語(yǔ)言描述
Overview
題目給的是一個(gè)Demo小程序,安裝在手機(jī)或者虛擬機(jī)之后看是一個(gè)看圖程序,一共三張黃圖圖,預(yù)覽版只可以看一張,輸入key之后可以看另外兩張,提供了一個(gè)輸入key的窗口,輸入錯(cuò)誤之后會(huì)提示錯(cuò)誤(廢話),想必正確的key就是題目的flag.
Analyse
首先例行的對(duì)給的apk進(jìn)行基礎(chǔ)操作,我的習(xí)慣是同時(shí)恢復(fù)Java代碼和Smali代碼.分析的時(shí)候Java代碼比較方便,出錯(cuò)或者是需要重新打包dex的時(shí)候使用Smali.逆向之后發(fā)現(xiàn)程序沒(méi)有l(wèi)ib目錄,且Java代碼只經(jīng)過(guò)簡(jiǎn)單混淆,沒(méi)有報(bào)錯(cuò),說(shuō)明單純分析Java代碼邏輯即可.
從輸入錯(cuò)誤key時(shí)候給出的報(bào)錯(cuò)語(yǔ)句入手,Your licence key is incorrect...! Please try again with another.
這句在源程序中出現(xiàn)兩次,都在/edu/sharif/ctf/activities/MainActivity.java
之中,查看代碼關(guān)鍵位置.
public void onClick(DialogInterface paramDialogInterface, int paramInt)
{
if (KeyVerifier.isValidLicenceKey(this.val$userInput.getText().toString(), MainActivity.this.app.getDataHelper().getConfig().getSecurityKey(), MainActivity.this.app.getDataHelper().getConfig().getSecurityIv()))
{
MainActivity.this.app.getDataHelper().updateLicence(2014);
MainActivity.isRegisterd = true;
MainActivity.this.showAlertDialog(this.val$context, "Thank you, Your application has full licence. Enjoy it...!");
return;
}
MainActivity.this.showAlertDialog(this.val$context, "Your licence key is incorrect...! Please try again with another.");
}
發(fā)現(xiàn)關(guān)鍵邏輯位于KeyVerifier.isValidLicenceKey函數(shù)中,對(duì)所在文件進(jìn)行分析,會(huì)發(fā)現(xiàn)實(shí)質(zhì)性調(diào)用的是encrypt函數(shù),該函數(shù)需要三個(gè)參數(shù).
public static boolean isValidLicenceKey(String paramString1, String paramString2, String paramString3)
{
return encrypt(paramString1, paramString2, paramString3).equals("29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84");
}
public static String encrypt(String paramString1, String paramString2, String paramString3)
{
try
{
SecretKeySpec localSecretKeySpec = new SecretKeySpec(hexStringToBytes(paramString2), "AES");
Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
localCipher.init(1, localSecretKeySpec, new IvParameterSpec(paramString3.getBytes()));
String str = bytesToHexString(localCipher.doFinal(paramString1.getBytes()));
return str;
}
catch (Exception localException)
{
localException.printStackTrace();
}
return "";
}
由代碼可以發(fā)現(xiàn),加密使用AES算法,對(duì)稱加密就可逆,現(xiàn)在需要找出使用的三個(gè)參數(shù)paramString1,paramString2,paramString3,即可逆推出輸入的值.回到MainActivity中考察傳遞三個(gè)參數(shù)的位置作分析,
KeyVerifier.isValidLicenceKey(this.val$userInput.getText().toString(), MainActivity.this.app.getDataHelper().getConfig().getSecurityKey(), MainActivity.this.app.getDataHelper().getConfig().getSecurityIv())
第一個(gè)參數(shù)是從UI中讀入的用戶輸入,即用戶輸入的key,第二個(gè)是SecurityKey,第三個(gè)是SecurityIv,通過(guò)調(diào)用進(jìn)一步分析源碼包括程序帶的SQLite數(shù)據(jù)庫(kù)可以找到后兩者的值,而AES加密之后的值應(yīng)該是
29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84
這里使用一個(gè)奇技淫巧不去進(jìn)一步考察代碼,而是修改Smali代碼,在使用三個(gè)值之前將它們從Log中打印出來(lái),條用位置位于class Ledu/sharif/ctf/activities/MainActivity$4
之中
.line 211
.local v0, "iv":Ljava/lang/String;
invoke-static {v3, v3}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-static {v3, v2}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-static {v3, v0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-static {v3, v2, v0}, Ledu/sharif/ctf/security/KeyVerifier;->isValidLicenceKey(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z
move-result v1
重新打包dex,插入apk中,簽名,運(yùn)行之后隨便輸入點(diǎn)東西,通過(guò)Logcat可以截取三條Log,tag都是隨便輸入的內(nèi)容,后兩條即SecurityKey,SecurityIv
SecurityKey = 37eaae0141f1a3adf8a1dee655853714
SecurityIv = a5efdbd57b84ca36
encrypted = 29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84
Solve
經(jīng)過(guò)以上分析,已經(jīng)可以基本確定解決問(wèn)題方法,即利用KeyVerifier中的代碼,更改Cipher工作模式為解密,接觸答案并ASCII化為字符串,即為key(flag).完整代碼如下所示
package com.company;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) {
// write your code here
String encrypted = "29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84";
String securityKey = "37eaae0141f1a3adf8a1dee655853714";
String securityIv = "a5efdbd57b84ca36";
String result = decrypt(encrypted, securityKey, securityIv);
System.out.println(result);
}
public static String bytesToHexString(byte[] paramArrayOfByte) {
StringBuilder localStringBuilder = new StringBuilder();
int i = paramArrayOfByte.length;
for (int j = 0; ; j++) {
if (j >= i)
return localStringBuilder.toString();
int k = paramArrayOfByte[j];
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = Integer.valueOf(k & 0xFF);
localStringBuilder.append(String.format("%02x", arrayOfObject));
}
}
public static String decrypt(String paramString1, String paramString2, String paramString3) {
try {
SecretKeySpec localSecretKeySpec = new SecretKeySpec(hexStringToBytes(paramString2), "AES");
Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
localCipher.init(Cipher.DECRYPT_MODE, localSecretKeySpec, new IvParameterSpec(paramString3.getBytes()));
byte[] bytes = localCipher.doFinal(hexStringToBytes(paramString1));
String flag = "";
for (byte b : bytes) {
flag += (char) b;
}
return flag;
} catch (Exception localException) {
localException.printStackTrace();
}
return "";
}
public static byte[] hexStringToBytes(String paramString) {
int i = paramString.length();
byte[] arrayOfByte = new byte[i / 2];
for (int j = 0; ; j += 2) {
if (j >= i)
return arrayOfByte;
arrayOfByte[(j / 2)] = (byte) ((Character.digit(paramString.charAt(j), 16) << 4) + Character.digit(paramString.charAt(j + 1), 16));
}
}
}
運(yùn)行得到結(jié)果
Flag:fl-ag-IS-se-ri-al-NU-MB-ER