分析软件:crackme.apk(文尾附下载链接)
分析工具:ApkIDE 3.3.2
软件环境:夜神模拟器 6.2.5.3
JDK版本:1.8.0_191
// a.class
//作用:接收输入
package ctf.bobbydylan;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
class a
implements View.OnClickListener
{
a(M paramM, TextView paramTextView) {}
public void onClick(View paramView)
{
try
{
paramView = this.a.getText().toString();
this.b.check(paramView);
paramView = new android/app/AlertDialog$Builder;
paramView.<init>(this.b);
paramView.setMessage("正确").setNeutralButton("OK", null).create().show();
return;
}
catch (Exception paramView)
{
for (;;)
{
new AlertDialog.Builder(this.b).setMessage("错误").setNeutralButton("OK", null).create().show();
}
}
}
}
此处反编译得到的源代码可能语法上有冗余或者错误,但是对于我们分析来说已经足够了,找到a.class中的关键部分,
paramView = this.a.getText().toString();
this.b.check(paramView);
此处就是软件接受输入,并且调用函数进行检查的代码,由此跟踪到check() 方法,该方法位于M.class内部。
// M.class
//作用:进行匹配判断
package ctf.bobbydylan;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import java.io.PrintStream;
public class M
extends T
{
public void check(String paramString)
{
int i = 0;
if (paramString.length() != 16) {
throw new RuntimeException();
}
try
{
str1 = getKey();
arrayOfInt = new int[16];
arrayOfInt[0] = 0;
arrayOfInt[12] = 14;
arrayOfInt[10] = 7;
arrayOfInt[14] = 15;
arrayOfInt[15] = 42;
arrayOfInt[1] = 3;
arrayOfInt[5] = 5;
}
catch (Exception localException1)
{
try
{
String str1;
System.out.println();
arrayOfInt[6] = 15;
arrayOfInt[2] = 13;
arrayOfInt[3] = 19;
arrayOfInt[11] = 68;
arrayOfInt[4] = 85;
arrayOfInt[13] = 5;
arrayOfInt[9] = 7;
arrayOfInt[7] = 78;
arrayOfInt[8] = 22;
if (i < paramString.length()) {
if ((arrayOfInt[i] & 0xFF) != ((paramString.charAt(i) ^ str1.charAt(i % str1.length())) & 0xFF))
{
throw new RuntimeException();
localException1 = localException1;
String str2 = getKey();
System.arraycopy(str2, 0, paramString, 5, 5);
}
}
}
catch (Exception localException2)
{
for (;;)
{
int[] arrayOfInt;
arrayOfInt[5] = 37;
arrayOfInt[1] = 85;
continue;
i++;
}
}
}
}
public String getKey()
{
return "bobbydylan";
}
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130903040);
startService(new Intent(this, P.class));
((Button)findViewById(2131099649)).setOnClickListener(new a(this, (TextView)findViewById(2131099648)));
}
protected void onPause()
{
stopService(new Intent(this, P.class));
super.onPause();
}
}
在M.class中我们要特别关注判断语句以及数据,这些都是一个匹配方法大概率会用到的,能帮助我们有目的的进行分析。
首先看开头的if语句:
if (paramString.length() != 16) {
throw new RuntimeException();
}
明显输入应该为一段16位的密码,否则直接跳出返回错误。
然后再阅读后面的代码段,可以发现一个16位的数组arrayOfInt,猜测此数组即为用于密码匹配的数据,跟踪此数组,观察哪些函数使用了这个数组,找到以下代码段:
if ((arrayOfInt[i] & 0xFF) != ((paramString.charAt(i) ^ str1.charAt(i % str1.length())) & 0xFF))
{
throw new RuntimeException();
localException1 = localException1;
String str2 = getKey();
System.arraycopy(str2, 0, paramString, 5, 5);
}
......
......
......
//T.class
public String getKey()
{
return "bobdylan";
}
此函数通过将paramString内每一位的值与str1中每一位的值进行异或之后与arrayOfInt数组逐位比较,若相同则返回正确,否则抛出错误。
至此我们就找到了核心的密码匹配的代码段,str1也在前面经过getKey方法的处理经过了初始化,此处存在一个小陷阱,注意到M.class文件内部有一个getKey()方法,但这个方法并不是真正被使用到的方法,而是一个障眼法,真正被使用到的getKey()位于T.class内部,这一单可以从代码开头的
extends T
得到。
接下来的工作就是通过得到的信息,分析出密码。
根据异或运算的性质,我们直接以将arrayOfInt与str1进行逐位异或即可得到正确的输入密码。根据代码可以得到:
arrayOfInt[16] = {0, 3, 13, 19, 85, 5, 15, 78, 22, 7, 7, 68, 14, 5, 15, 42}
str1[8] = {‘b’, ‘o’, ‘b’, ‘d’, ‘y’, ‘l’, ‘a’, ‘n’}
密码生成小程序:
#define L 8
#include<stdio.h>
int main(){
int arrayOfInt[16] = {0, 3, 13, 19, 85, 5, 15, 78, 22, 7, 7, 68, 14, 5, 15, 42};
char str[8] = {'b', 'o', 'b', 'd', 'y', 'l', 'a', 'n'};
char psw;
int i;
for (i = 0;i < 16; i++){
psw = arrayOfInt[i]^str[i%L];
printf("%c",psw);
}
return 0;
}
得到密码为:
blow,in the winD
逆向分析是我研究生阶段的研究方向,是一个需要耐心的过程,收集信息,阅读代码,进行分析…反复循环需要花费大量的时间,目前自己处于起步阶段,希望自己能静下心来坚持下去,有问题的朋友也欢迎联系我一起讨论,共同进步。