一次crackme.apk逆向的分析

目录

  • 一次crackme.apk逆向的分析
    • 系统环境
    • 分析过程
    • 总结

一次crackme.apk逆向的分析

系统环境

分析软件:crackme.apk(文尾附下载链接)
分析工具:ApkIDE 3.3.2
软件环境:夜神模拟器 6.2.5.3
JDK版本:1.8.0_191

分析过程

  1. 安装软件:将软件图标直接拖进模拟器屏幕,自动进行安装
    一次crackme.apk逆向的分析_第1张图片
  2. 运行软件:
    一次crackme.apk逆向的分析_第2张图片
    此时要注意软件运行界面上的文本布局模式返回值等,这些都是分析时可能会运用到的信息。
    可以看到软件运行界面包含一个输入框和一个check按钮,当输入值是,若不正确则返回错误信息。
    一次crackme.apk逆向的分析_第3张图片
  3. 在ApkIDE中装载apk文件:
    一次crackme.apk逆向的分析_第4张图片
    此时以错误信息的提示文本 错误 为切入点,在搜索拦内输入“错误”,并转化为Unicode进行查找。
    查找结果
    得到查找结果,根据查找路径打开文件锁定位置。
    打开文件可以查看smali语句:
    一次crackme.apk逆向的分析_第5张图片
    此处的iget-object为赋值语句,将p0值赋予v1
    invoke-direct为函数调用语句,调用**->**后方的函数
    在输出错误的上方存在这两个操作,我们就可以大胆推测软件在此处获取了用户输入后,调用函数来进行判断。
    而此处的V函数经过查找就在M.smali文件中,但是因为smali文件对于阅读分析来说并不是十分友好,我们在确定了位置后可以通过反编译出的java代码进行分析。
  4. 查看java源代码:
    点击java小咖啡图标可以反编译出apk文件的源代码,
// 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

输入软件返回正确,分析成功!
一次crackme.apk逆向的分析_第6张图片

总结

逆向分析是我研究生阶段的研究方向,是一个需要耐心的过程,收集信息,阅读代码,进行分析…反复循环需要花费大量的时间,目前自己处于起步阶段,希望自己能静下心来坚持下去,有问题的朋友也欢迎联系我一起讨论,共同进步。

你可能感兴趣的:(网络空间安全,逆向分析)