這是2016年zctf的一道Android題目,樣本和本文用到的部分工具在文章末尾可以下載
0x01 第一部分 靜態分析
安裝運行apk,需要輸入用戶名和密碼,用戶名為zctf,用jeb工具反編譯
文件結構如下:
如圖 assets目錄中有三個文件,bottom、flag.bin、key.db,還有一個JNIclass庫。
程序開始,先檢查密碼的長度(至少4位),然后調用auth方法驗證用戶名密碼是否正確,如果正確則執行OpenNewActivity方法
auth方法的第二個參數是用戶名密碼的組合,第三個為另一個方法databaseopt(),看名字應該是讀取數據庫,看下這個方法:
注:關于查找2131099679對應的字符串
先把數字轉換成16進制:0x7F06001F,然后在public.xml中找到對應索引,在去strings.xml中查看字符串
先把key.db從assets目錄拷貝到應用目錄中,然后調用sql語句查詢id為0對應字段的值
打開數據庫查到key為zctf2016
繼續看auth的實現:
先將用戶名密碼組成的字符串反轉,利用前面的key加密,然后和assets中的flag_bin做比較,如果相等就返回1
看下encrypt方法看到是DES加密,而且還提供了解密方法,所以可以直接解密出用戶名密碼:
java代碼解密:
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
byte[] buffer = readbytes("flag.bin");
System.out.println(buffer.length);
byte[] result = decrypt(buffer, "zctf2016");
System.out.println(new StringBuffer(new String(result)).reverse()
.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] readbytes(String filedir) throws IOException {
FileInputStream fis = new FileInputStream(new File(filedir));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] cache = new byte[1024];
int len = 0;
while ((len = fis.read(cache)) != -1) {
baos.write(cache, 0, len);// 因為一般最后一次read不能裝滿cache,用這種方式確保不會將cache末尾空數據寫入數據流中
}
fis.close();
baos.close();
return baos.toByteArray();
}
運行打印輸出:
16
zctf{Notthis}
所以密碼為{Notthis}
繼續往下看OpenNewActivity方法:
打開了一個新的activity,把密碼也傳過去了
看app.class的內容:
Activity啟動后先接收到傳過來的密碼,然后調用CheckOperatorNameAndroid方法檢測調試器,接著調用了dataProvider.add方法(native方法),如果返回結果為1就退出,如果不為1則調用pushthebottom和dataProvider.sayHelloInc。最終的flag應該就藏在sayHelloInc中
再仔細分析,先看CheckOperatorNameAndroid中啟動的定時任務this.task:
獲得當前activity的taskid,如果不為0就退出。(當前activity的taskid肯定不為0,所以一定會退出)
add方法是native方法,實現在libJNIclass.so中,用IDA打開F5查看
打開/proc/pid/status,讀取每一條信息到v5和v4中,計算v4的值進行反調試檢測
pushthebottom方法是將assets中的bottom文件拷貝到應用安裝目錄中:
最后看下native方法sayHelloInc,用IDA打開查看
sayHelloInc只有1個String參數,修改前兩個參數的類型為JavaVM和JNIEnv
這里調用了sub_14B0函數,其實就是上面的add調用的函數,用來反調試。然后就是打開bottom文件讀到內存中,調用DES_Decrypt解密數據到v24中,接著free釋放內存,所以需要在這個地方把解密的數據拷貝出來。
應用執行的整個流程如下:
0x02 第二部分 動態調試
前面的就是整個程序的靜態分析的過程,接下來需要從程序中拿到flag。
用戶名密碼已經通過解密算法計算出來了,還需要解決的問題有:
定時任務結束程序、add反調試和sub_14B0反調試。
在定時任務中先判斷getTaskId()是否為0,如果不為0就退出程序,解決方法主要有兩種:
i. 反編譯修改smali代碼的if語句繞過
ii. 用hook修改getTaskId的返回值
下面用xposed hook的方式修改getTaskId和add的返回值繞過干擾,相關代碼:
public class HookGetTaskId implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!loadPackageParam.packageName.equals("com.zctf.app")) return;
findAndHookMethod("android.app.Activity", loadPackageParam.classLoader, "getTaskId", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
return 0;
}
});
findAndHookMethod("com.zctf.app.JNIclass", loadPackageParam.classLoader, "add", int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
return 0;
}
});
// findAndHookMethod("android.app.Activity", loadPackageParam.classLoader, "getTaskId", new XC_MethodHook() {
//
//
// @Override
// protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// XposedBridge.log("before gettaskid:" + param.getResult());
// param.setResult(0);
// }
//
// @Override
// protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// XposedBridge.log("after gettaskid:" + param.getResult());
// param.setResult(0);
// }
// });
}
}
用replaceHookedMethod更省事,直接替換方法內容,修改返回值也行。
安裝xposed,重啟手機,運行apk就可以正常進入到sayHelloInc函數中,接下來就需要動態調試從內存中找flag。
在dvm.so中的open函數上下斷點,讓程序可以在加載jniclass.so的時候停下來,運行程序輸入用戶名密碼后,so文件加載到內存中,然后在sayHelloInc處下斷點,調試。
F8單步運行到反調試的地方,然后修改返回值R0為0,繞過反調試
單步運行結合F5查看偽代碼調試,直到解密出數據到內存中:
DES_Decrypt函數的第一個參數存的是解密前的數據,第二個參數是數據的大小對應R11+0x100為0x1338,第三個參數是解密的密鑰對應R8為{Notthis,第四個參數存的是解密后的數據對應R7,v23即0x77f9d008地址開始的就是解密后的數據,看到是png格式,在hex中選中數據保存為d.png
或者使用腳本導出:
auto fp,addr_start,addr_now,size,addr_end;
addr_start = R7;
size = 0x1338;
fp = fopen("d:\d.png","wb");
for(addr_now=addr_start;addr_now<addr_start+size;addr_now++)
fputc(Byte(addr_now),fp);
到導出后打開圖片發現沒有flag,用stegsolve打開按下邊的’>’分層查看,最后在Red Plane層找到flag
apk樣本和stegsolve工具下載:http://pan.baidu.com/s/1nvHOOJZ