用户登录
用户注册

分享至

bugku-逆向-10、SafeBox(NJCTF)

  • 作者: Callme_爹地
  • 来源: 51数据库
  • 2021-09-05

文章目录

  • 一、反编译查看源代码
  • 二、源代码分析
  • 三、方法1:正向暴力破解
  • 四、方法二:按照筛选条件,逐步缩小范围

先下载软件,发现是个安卓的apk安装包,安装之后打开:

一、反编译查看源代码

只有一个输入框,其他的点不了。应该是要输入某个字符串然后判断是否正确,之后返回flag。
打开apk反编译:

发现有两个Activity,而且代码高度相似,查看AndroidManifest.xml:

多个Activity可以显示多个不同的界面,setContentView就是设置一个Activity的显示界面,使用setContentView可以在Activity中动态切换显示的View,这样,不需要多个Activity就可以显示不同的界面。
其中它们调用的id:2130968603 = 0x7F04001B,2130968604 = 0x7F04001C,
在public.xml中找到相应id对应的资源:


打开layout文件夹,找到activity_main.xml和build.xml文件:


发现两个文件一模一样:

二、源代码分析

再查看两个类的源代码:
MainActivity.class

package com.geekerchina.hi;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity
  extends AppCompatActivity
{
  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130968603);
    ((Button)findViewById(2131427415)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        int i1 = Integer.parseInt(this.val$Et1.getText().toString());
        int k;
        int i;
        int n;
        int j;
        if ((i1 > 10000000) && (i1 < 99999999))
        {
          k = 1;
          i = 10000000;
          n = 1;
          if ((Math.abs(i1 / 1000 % 100 - 36) == 3) && (i1 % 1000 % 584 == 0)) {
            j = 0;
          }
        }
        for (;;)
        {
          int m = n;
          if (j < 4)
          {
            if (i1 / k % 10 != i1 / i % 10) {
              m = 0;
            }
          }
          else
          {
            if (m == 1)
            {
              char c1 = (char)(i1 / 1000000);
              char c2 = (char)(i1 / 10000 % 100);
              char c3 = (char)(i1 / 100 % 100);
              this.val$Et1.setText("NJCTF{" + c1 + c2 + c3 + "f4n}");
            }
            return;
          }
          k *= 10;
          i /= 10;
          j += 1;
        }
      }
    });
  }
  
  public boolean onCreateOptionsMenu(Menu paramMenu)
  {
    getMenuInflater().inflate(2131558400, paramMenu);
    return true;
  }
  
  public boolean onOptionsItemSelected(MenuItem paramMenuItem)
  {
    if (paramMenuItem.getItemId() == 2131427439) {
      return true;
    }
    return super.onOptionsItemSelected(paramMenuItem);
  }
}

androidTest.class

package com.geekerchina.hi;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class androidTest
  extends AppCompatActivity
{
  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130968604);
    ((Button)findViewById(2131427415)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        int i1 = Integer.parseInt(this.val$Et1.getText().toString());
        int k;
        int i;
        int n;
        int j;
        if ((i1 > 10000000) && (i1 < 99999999))
        {
          k = 1;
          i = 10000000;
          n = 1;
          if ((Math.abs(i1 / 1000 % 100 - 36) == 3) && (i1 % 1000 % 584 == 0)) {
            j = 0;
          }
        }
        for (;;)
        {
          int m = n;
          if (j < 3)
          {
            if (i1 / k % 10 != i1 / i % 10) {
              m = 0;
            }
          }
          else
          {
            if (m == 1)
            {
              char c1 = (char)(i1 / 1000000);
              char c2 = (char)(i1 / 10000 % 100);
              char c3 = (char)(i1 / 100 % 100 + 10);
              this.val$Et1.setText("NJCTF{have" + c1 + c2 + c3 + "f4n}");
            }
            return;
          }
          k *= 10;
          i /= 10;
          j += 1;
        }
      }
    });
  }
  
  public boolean onCreateOptionsMenu(Menu paramMenu)
  {
    getMenuInflater().inflate(2131558400, paramMenu);
    return true;
  }
  
  public boolean onOptionsItemSelected(MenuItem paramMenuItem)
  {
    if (paramMenuItem.getItemId() == 2131427439) {
      return true;
    }
    return super.onOptionsItemSelected(paramMenuItem);
  }
}

两个类主要的差别就是:
MainActivity.class

androidTest.class

androidTest相比于MainActivity,就是为了不限制中间4,5两位的数字必须相等,而且出c3有加10再转换成字符。

三、方法1:正向暴力破解

直接用反编译的class代码暴力破解:
MainActivity.java

public class MainActivity {
    public static void main(String[] args) {
        int k;
        int i;
        int n;
        int j;
        for(int i1 = 10000001; i1 < 99999999 ; i1++){
            k = 1;
            i = 10000000;
            n = 1;
            if ((Math.abs(i1 / 1000 % 100 - 36) == 3) && (i1 % 1000 % 584 == 0)) {
                j = 0;
            }else {
                continue;
            }

            for (;;) {
                int m = n;
                if (j < 3) {
                    if (i1 / k % 10 != (i1 / i % 10) ) {
                        //m = 0;
                        //当m = 0 时,表明当前的i1已经不符合条件,直接break;否则程序还会进入下一层循环,m又会重新等于n,导致程序还会继续运行,在j=4时始终会输出
                        break;
                    }
                } else {
                    if (m == 1) {
                        char c1 = (char) (i1 / 1000000);
                        char c2 = (char) (i1 / 10000 % 100);
                        char c3 = (char) (i1 / 100 % 100 + 10);
                        System.out.println(i1);
                        System.out.println("NJCTF{have" + c1 + c2 + c3 + "f4n}");
                    }
                    break;
                }
                k *= 10;
                i /= 10;
                j += 1;
            }
        }
    }
}

运行结果截图:

得到的flag为NJCTF{05#f4n}。
androidTest.java

public class androidTest {
    public static void main(String[] args) {

        int k;
        int i;
        int n;
        int j;
        for(int i1 = 10000001; i1 < 99999999 ; i1++)
        {
            k = 1;
            i = 10000000;
            n = 1;
            if ((Math.abs(i1 / 1000 % 100 - 36) == 3) && (i1 % 1000 % 584 == 0)) {
                j = 0;
            }else {
                continue;
            }

            for (;;)
            {
                int m = n;
                if (j < 3)
                {
                    if (i1 / k % 10 != i1 / i % 10) {
                        m = 0;
                        break;
                    }
                }
                else
                {
                    if (m == 1)
                    {
                        char c1 = (char)(i1 / 1000000);
                        char c2 = (char)(i1 / 10000 % 100);
                        char c3 = (char)(i1 / 100 % 100 + 10);
                        System.out.println(i1);
                        System.out.println("NJCTF{have" + c1 + c2 + c3 + "f4n}");
                    }
                    return;
                }
                k *= 10;
                i /= 10;
                j += 1;
            }
        }
    }
}

运行结果截图:

得到的flag为:NJCTF{have05-f4n}和NJCTF{have05if4n}。
Bugku的提示:flag格式NJCTF{xxx} 并且 xxx只包含[a-z][A-Z][0-9]。
所以真正的flag是NJCTF{have05if4n}。
其实网鼎杯原题的题目提示是:tips:Don’t believe what you saw.
The flag’s format is NJCTF{xxx} and xxx only include [a-z][A-Z][0-9].让我们不要相信看到的,意思就是程序的入口MainActivity这个表面上的类得到的答案是错误的,隐藏在它后面相似的androidTest才是真正的答案。

四、方法二:按照筛选条件,逐步缩小范围

il要满足的条件:
1、首先范围是在10000000到99999999之间,一个8位数
2、之后是满足Math.abs(i1 / 1000 % 100 - 36) == 3 && (i1 % 1000 % 584 == 0):
Math.abs(i1 / 1000 % 100 - 36) == 3:il/1000得到的就是前5位的数字,i1 / 1000 % 100得到的就是第4、5位的数字,Math.abs(i1 / 1000 % 100 - 36) == 3就是第4、5位的数字减36的绝对值是3,所以第4、5位是33或者39,符合条件的数格式为ABC33EFG或者ABC39EFG;
i1 % 1000 % 584 == 0:i1 % 1000,得到的就是后面的第6、7、8位的数字,i1 % 1000 % 584 == 0就是第6、7、8位的数字是584的倍数,第6、7、8位就是一个三位数所以就是584,符合条件的数格式为ABC33584或者ABC39584。
3、这里就是MainActivity和androidTest有差异的地方:
MainActivity.class:

for (;; )
{
  int m = n;
  if (j < 4)
  {
    if (i1 / k % 10 != i1 / i % 10) {
      m = 0;
    }
  }
  else
  {
    if (m == 1)
    {
      char c1 = (char)(i1 / 1000000);
      char c2 = (char)(i1 / 10000 % 100);
      char c3 = (char)(i1 / 100 % 100);
      this.val$Et1.setText("NJCTF{" + c1 + c2 + c3 + "f4n}");
    }
    return;
  }
  k *= 10;
  i /= 10;
  j += 1;
}

androidTest.class:

for (;; )
{
  int m = n;
  if (j < 3)
  {
    if (i1 / k % 10 != i1 / i % 10) {
      m = 0;
    }
  }
  else
  {
    if (m == 1)
    {
      char c1 = (char)(i1 / 1000000);
      char c2 = (char)(i1 / 10000 % 100);
      char c3 = (char)(i1 / 100 % 100 + 10);
      this.val$Et1.setText("NJCTF{have" + c1 + c2 + c3 + "f4n}");
    }
    return;
  }
  k *= 10;
  i /= 10;
  j += 1;
}

这两段代码的主要判断是相似的,在MainActivity类中就是if (j < 4){ if (i1 / k % 10 != i1 / i % 10) { m = 0;}}
k *= 10;
i /= 10;
j += 1;
k的初始值是1,i的初始值是10000000,所以i1 / k % 10就是分别获得第8、7、6、5位上的数字,i1 / i % 10就是分别获得第1、2、3、4位上的数字,再要求它们分别相等,也就是ABCDDCBA的形式,结合上面的条件,符合条件的数为48533584。
48533584经过

char c1 = (char)(i1 / 1000000);//第1、2位数字
char c2 = (char)(i1 / 10000 % 100);//第3、4位数字
char c3 = (char)(i1 / 100 % 100);//第3、4位数字
this.val$Et1.setText("NJCTF{" + c1 + c2 + c3 + "f4n}");

得到的字符串为NJCTF{05#f4n}。

而在androidTest类中,androidTest相比于MainActivity,就是j<4变成j<3,目的是为了不限制中间4,5两位的数字必须相等,这样就只用第8、7、6位上的数字和第1、2、3位的数字分别相等了,也就是ABCDECBA的形式,结合上面的条件得到了符合条件的两个数为48533584和48539584。
48533584和48539584经过

char c1 = (char)(i1 / 1000000);//第1、2位数字
char c2 = (char)(i1 / 10000 % 100);//第3、4位数字
char c3 = (char)(i1 / 100 % 100 + 10);//第5、6位数字再加10
this.val$Et1.setText("NJCTF{have" + c1 + c2 + c3 + "f4n}");

得到的字符串为NJCTF{have05-f4n}和NJCTF{have05if4n}。 其中c3字符有额外加10再转换成字符,是为了让第5、6位上的数字转化的字符#和_变成字符-和i,从而让i在[a-z][A-Z][0-9]的范围内,得到唯一一个满足题目提示flag格式NJCTF{xxx} 并且 xxx只包含[a-z][A-Z][0-9]的flag:NJCTF{have05if4n}。

软件
前端设计
程序设计
Java相关