//weibo: @少仲
題目下載地址: http://bbs.pediy.com/showthread.php?t=197462
0x1 第一題
驗證密碼,顯示錯誤,直接連上DDMS看log,看到了一堆亂碼.
直接反編譯apk,看看加解密流程.
先看34行所說,找到對話框的提示,然後具體看看傳遞值的定義
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
requestWindowFeature(1);
setContentView(2130903064);
final EditText localEditText = (EditText)findViewById(2131034173);
((Button)findViewById(2131034174)).setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
String str1 = localEditText.getText().toString();
String str2 = MainActivity.this.getTableFromPic();
String str3 = MainActivity.this.getPwdFromPic();
Log.i("lil", "table:" + str2);
Log.i("lil", "pw:" + str3);
String str4 = "";
try
{
str4 = MainActivity.bytesToAliSmsCode(str2, str1.getBytes("utf-8"));
Log.i("lil", "enPassword:" + str4);
if ((str3 != null) && (!str3.equals("")) && (str3.equals(str4)))
{
//猜測這裏是正確的提示框,具體看showDialog函數
MainActivity.this.showDialog();
return;
}
}
catch (UnsupportedEncodingException localUnsupportedEncodingException)
{
for (;;)
{
localUnsupportedEncodingException.printStackTrace();
}
//對話框提示
AlertDialog.Builder localBuilder = new AlertDialog.Builder(MainActivity.this);
//這個是具體傳遞的值,在反編譯的R.java中搜索一下可以知道是錯誤信息
localBuilder.setMessage(2131361809);
localBuilder.setTitle(2131361808);
localBuilder.setPositiveButton(2131361811, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymous2DialogInterface, int paramAnonymous2Int)
{
paramAnonymous2DialogInterface.dismiss();
}
});
localBuilder.show();
}
}
}
}
}
再看24行的猜測是否正確.
private void showDialog()
{
AlertDialog.Builder localBuilder = new AlertDialog.Builder(this);
//傳遞的值就是正確的提示框
localBuilder.setMessage(2131361810);
localBuilder.setTitle(2131361808);
localBuilder.setPositiveButton(2131361811, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
paramAnonymousDialogInterface.dismiss();
}
});
localBuilder.show();
}
現在已經明確具體的位置,可以看看生成正確對話框提示的條件是什麼,看第21行的註釋,最重要的就是str3和str4要相等,所以要理清str1.str2.str3.str4.
String str1 = localEditText.getText().toString();
String str2 = MainActivity.this.getTableFromPic();
String str3 = MainActivity.this.getPwdFromPic();
str4 = MainActivity.bytesToAliSmsCode(str2, str1.getBytes("utf-8"));
str1是輸入的字符串密碼,str2和str3都是讀取圖片資源裏預設的密碼.str4就要具體分析bytesToAliSmsCode函數.
private static String bytesToAliSmsCode(String paramString, byte[] paramArrayOfByte)
{
StringBuilder localStringBuilder = new StringBuilder();
for (int i = 0;; i++)
{
if (i >= paramArrayOfByte.length) {
return localStringBuilder.toString();
}
localStringBuilder.append(paramString.charAt(0xFF & paramArrayOfByte[i]));
}
}
通過分析可以看出,用戶輸入的str1轉換成數組傳遞,並用i作爲下標獲取paramArrayOfByte數據,再把取出來的數據0xFF & paramArrayOfByte[i]做運算.運算的結果累加返回.說白了就是從圖片資源中獲取密碼錶,然後輸入密碼找到索引號,然後給出ASCII值,aliCodeToBytes就是解密函數.直接寫個java代碼解密就好.
(1).方法一
package calc;
public class calc
{
private static byte[] aliCodeToBytes(String paramString1, String paramString2)
{
byte[] arrayOfByte = new byte[paramString2.length()];
for (int i = 0;; i++)
{
if (i >= paramString2.length())
{
return arrayOfByte;
}
arrayOfByte[i] = ((byte)paramString1.indexOf(paramString2.charAt(i)));
}
}
public static void main(String args[])
{
String table = "一乙二十丁廠七卜人入八九幾兒了力乃刀又三於幹虧士工土才寸下大丈與萬上小口巾山千乞川億個勺久凡及夕丸麼廣亡門義之屍弓己已子衛也女飛刃習叉馬鄉豐王井開夫天無元專雲扎藝木五支廳不太犬區歷尤友匹車巨牙屯比互切瓦止少日中岡貝內水見午牛手毛氣升長仁什片僕化仇幣仍僅斤爪反介父從今兇分乏公倉月氏勿欠風丹勻烏鳳勾文六方火爲鬥憶訂計戶認心尺引醜巴孔隊辦以允予勸雙書幻玉刊示末未擊打巧正撲扒功扔去甘世古節本術可丙左厲右石布龍平滅軋東卡北佔業舊帥歸且旦目葉甲申叮電號田由史只央兄叼叫另叨嘆四生失禾丘付仗代仙們儀白仔他斥瓜乎叢令用甩印樂";
String password = "義弓麼丸廣之";
String strResult = new String(aliCodeToBytes(table, password));
System.out.println(strResult);
}
}
密碼就是581026.
(2).方法二
python版.
# -*- coding: UTF-8 -*-
table = u'一乙二十丁廠七卜人入八九幾兒了力乃刀又三於幹虧士工土才寸下大丈與萬上小口巾山千乞川億個勺久凡及夕丸麼廣亡門義之屍弓己已子衛也女飛刃習叉馬鄉豐王井開夫天無元專雲扎藝木五支廳不太犬區歷尤友匹車巨牙屯比互切瓦止少日中岡貝內水見午牛手毛氣升長仁什片僕化仇幣仍僅斤爪反介父從今兇分乏公倉月氏勿欠風丹勻烏鳳勾文六方火爲鬥憶訂計戶認心尺引醜巴孔隊辦以允予勸雙書幻玉刊示末未擊打巧正撲扒功扔去甘世古節本術可丙左厲右石布龍平滅軋東卡北佔業舊帥歸且旦目葉甲申叮電號田由史只央兄叼叫另叨嘆四生失禾丘付仗代仙們儀白仔他斥瓜乎叢令用甩印樂'
password = u'義弓麼丸廣之'
for result in password:
print chr(table.find(result))
(3).方法三
方法三並非正規解法,而是在我已經知道答案的情況下,巧妙地通過判斷數字1-9在表中的位置得到的.通過app特有的lil標籤,用adb logcat過濾信息.
輸入數字123456789,得到對應的字符.
· 1 -麼
· 2 -廣
· 3 -亡
· 4 -門
· 5 -義
· 6 -之
· 7 -屍
· 8 -弓
· 9 -己
再根據”義弓麼丸廣之”,可得581026
0x2 第二題
(1).方法一:
上來直接反編譯看看
關鍵函數securityCheck在so裏.IDA載入後,習慣性的先看字符串信息
難道答案如此簡單?測試後發現不對,估計是運行後修改了.所以再看看代碼.
我現在的思路就是調用print函數,打印修改後真正的密碼,所以要在關鍵比較的地方patch.
LDRB R3,[R2] -> MOV R1,R0
LDRB R1,[R0] -> BL LOC_1280
使用010editor修改opcode,修改後so的F5的C代碼
重打包簽名後運行,崩潰前打印了結果.
(2).方法二:
方法一是一個比較取巧的方法,方法二則就真正的動態調試解決問題.直接在securitycheck函數裏下斷點,然後調試的時候apk會自動關閉,此時猜測到程序會有反調試.所以只能從JNI_Onload入手.
(1).執行android_server
(2).端口轉發adb forward tcp:23946 tcp:23946
(3).調試模式啓動程序adb shell am start -D -n com.yaotong.crackme/.MainActivity
(4).IDA附加到crackme進程
(5).Debugger->Debugger Option,選擇suspendLibrary Load/Unload
(6).在DDMS中查看調試端口
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600
(7).在IDA中運行
IDA成功中斷
(8).ctrl+s查找libcrackme.so的基地址
我這裏是AB825000
(9).函數地址=基地址+偏移地址
基地址已經找到,偏移地址就是靜態分析中可以看到,JNI_Onload的偏移是0x1B9C,所以地址=0x1B9C+ 0xAB825000=0xAB826B9C,跳轉到該地址下斷
(10).單步執行,找到反調試的地方
多次測試,在這裏斷開,我們F7跟進看看
(11).跟進後發現是一個創建線程的函數,猜測是創建線程監控ida
這裏猜測方法就是TracerPid,如果把BEQ改成B,就能跳過檢測,或者直接把創建線程的函數給nop掉,不創建監控函數就可以繞過.爲了方便調試,我這裏把標誌位Z修改爲1,讓它跳轉.然後線程無法創建,成功過掉反調試.
(12).在securityCheck處下斷點,動態調試可以獲取密碼
SecurityCheck地址=0x11A8+0xAB825000=0xAB8261A8
關於反調試.
值得一提的是它的方法是檢測TracerPid的值.在非調試狀態下,TracerPid的值爲0.調試狀態下它的值就是調試器的pid.它具體實現的步驟應該是創建一個線程,在新線程裏循環讀取/proc/xxx/status文件裏的TracerPid字段,當它不爲0的時候,kill掉調試進程.這樣達到反調試目的.附上一個反調試代碼.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
inline void _check(void)
{
char status[0x64] = { '\0' };
FILE *fd = 0;
char buf[0x200] = { '\0' };
char *offset = NULL;
char tracer[0x200] = { '\0' };
pid_t tracer_pid = 0;
printf("main\n");
memset(status, 0, 0x64);
snprintf(status, 0x64, "/proc/%d/status", getpid());
fd = fopen(status, "r");
if (fd == 0)
{
printf("open status failed\n");
return;
}
while (1)
{
offset = NULL;
memset(buf, 0, 0x200);
if (fgets(buf, 0x200, fd) == NULL) break;
offset = strstr(buf, "TracerPid");
if (offset != NULL) break;
}
fclose(fd);
if (offset == NULL) return;
memset(tracer, 0, 0x200);
sscanf(offset, "%s %d", tracer, &tracer_pid);
printf("tracer_pid=%d\n", tracer_pid);
if (tracer_pid != 0)
{
printf("kill %d\n", tracer_pid);
kill(tracer_pid, 9);
printf("%s\n", strerror(errno));
}
}
void *check_main(void *argv)
{
while (1)
{
_check();
sleep(3);
printf("No Tracer\n");
}
}
int main(int argc, char const *argv[])
{
printf("main\n");
pthread_t thread;
pthread_create(&thread, 0, check_main, 0);
// TODO ..
//
while (1)
{
printf("main sleep\n");
sleep(1);
}
return 0;
}
0x3 第三題
apk被加殼了.由於對dex脫殼之類的完全不瞭解,只好拿到了別人已經dump完整的dex文件,進行學習分析…看了一把so文件應該是有ptrace反調試,nop掉應該不難.經過分析,核心部分在於b類和e類.e類是一個類莫斯電碼轉換.b類的核心函數是run方法.其流程爲:
1. 獲取用戶輸入的內容
2. 調用e.a()解碼
3. 使用AES key解密
4. 要求電碼前兩個字符hashcode等於3618且兩個字節相加等於168
5. 從類e,a中獲得f標籤爲”7e”,”lp”.可得後四位”7elp”.
6. 其餘aes,sha1,base64計算均爲混淆視聽的運算
for ( size_t i = 33; i < 127; ++i )
{
for ( size_t j = 33; j < 127; ++j )
{
String x =String.valueOf((char)j)+String.valueOf((char)i);
if (x.hashCode()==3618 && (i+j) == 168)
{
System.out.println(x);
System.out.println(j+i);
}
}
}
可以枚舉出前兩位爲”s5”或者”qs”.經過測試結果爲”s57e1p”.通過莫斯電碼轉換可得答案” ... ____ ___. . ..___ .__.”
0x4 總結
第一題是apk逆向的入門題目,考了一把反編譯的基本知識,只要大概理清楚關係,都能搞定.
第二題是動態調試的入門題目,以及反調試入門.
第三題難度和前兩道題比難度陡增,反調試+動態調試+殼.
看來還是要繼續加強逆向方面的學習,以及加固方面的研究.