【Android逆向】破解看雪9月算法破解第一题

android,逆向,破解,算法,第一 · 浏览次数 : 20

小编点评

** Java 代码** ```java public class MainActivityHandler { public void register() { System.out.println("hooked i = " + i); // Your registration logic } } ``` ** Python 代码** ```python import base64 username = input("请输入用户名(长度小于等于20): ") if len(username) > 20: print("输入长度超过20,请重新输入") elif len(username) < 6: print("输入长度小于6,请重新输入") else: b64_byt = base64.b64encode(username.encode('utf-8')) print("密码:", b64_byt) ``` ** 解释** * Java 代码定义了一个 `MainActivityHandler` 类,该类拥有 `register()` 方法,这是注册回调函数的入口点。 * 对于 Python 代码,它使用 `base64` 模块对字符串进行编码和解码。 * 代码首先获取用户输入的用户名,然后检查其长度。 * 如果用户名长度超过 20 个字符,则打印输入长度超过 20 的提示信息。 * 如果用户名长度小于 6 个字符,则打印输入长度小于 6 个字符的提示信息。 * 如果用户名长度正确,则使用 `base64` 模块对用户名进行编码,并将编码后的字符串打印出来。 * 如果用户名正确,则将编码后的字符串打印出来。

正文

1. 安装apk到手机

2. 随意输入账号和密码,点击register,报错crackme1:ERROR

3. 将apk拖入到jadx中进行观察

    public native String register(String str);

    static {
        System.loadLibrary("native-lib");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        this.edt_code = (EditText) findViewById(R.id.edt_code);
        this.edt_username = (EditText) findViewById(R.id.edt_username);
        Button button = (Button) findViewById(R.id.btn_register);
        this.btn_register = button;
        button.setOnClickListener(new View.OnClickListener() { // from class: com.r0ysue.first.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String obj = MainActivity.this.edt_username.getText().toString();
                String obj2 = MainActivity.this.edt_code.getText().toString(); // password
                if (obj.equals("")) {
                    Toast.makeText(MainActivity.this, "用户名不能为空", 0).show();
                    return;
                }
                String register = MainActivity.this.register(obj);
                if (register.equals(obj2) && !register.equals("Error!")) {
                    Toast.makeText(MainActivity.this, "SUCCESS!", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "ERROR!", 0).show();
                }
            }
        });
    }

那么很明显就是账号通过native函数处理后与密码进行比较来判断成功与失败

4. 加压缩apk, 拿到so文件,丢入到IDA中进行静态分析看看

__int64 __fastcall Java_com_r0ysue_first_MainActivity_register(JNIEnv *env, jobject object, void *name)
{
  const char *char_arr; // x21
  JNIEnv t_env; // x8
  __int128 *ret_chars; // x1
  unsigned int str_len; // w0
  __int128 v10[6]; // [xsp+0h] [xbp-90h] BYREF
  int v11; // [xsp+60h] [xbp-30h]
  __int64 v12; // [xsp+68h] [xbp-28h]

  v12 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  char_arr = (*env)->GetStringUTFChars(env, name, 0LL);
  if ( strlen(char_arr) - 6 < 0xF )
  {
    v11 = 0;
    memset(v10, 0, sizeof(v10));
    str_len = strlen(char_arr);
    base64_encode((const unsigned __int8 *)char_arr, str_len, (char *)v10);
    (*env)->ReleaseStringUTFChars(env, name, char_arr);
    t_env = *env;
    ret_chars = v10;
  }
  else
  {
    (*env)->ReleaseStringUTFChars(env, name, char_arr);
    t_env = *env;
    ret_chars = (__int128 *)"Error!";
  }
  return (__int64)t_env->NewStringUTF(env, (const char *)ret_chars);
}

这里可以看到 输入的字符串长度必须大于6 且小于20才可以,并且会进行base64_encode处理,这里是不是正真base64呢? 打开函数看看,整理后可得

unsigned __int8 *__fastcall base64_encode(unsigned __int8 *char_arr_p, unsigned int length, char *ret_v10)
{
  unsigned __int8 *t_char; // x8
  int v4; // w13
  int v5; // w12
  __int64 i; // x10
  char t_tbale_char; // w14
  int v8; // w15
  __int64 v9; // x13
  int step; // w14
  unsigned __int64 tt_Char; // x11
  int v12; // w9
  int v13; // w10
  int v14; // w9

  if ( length )
  {
    t_char = char_arr_p;
    v4 = 0;
    v5 = 0;
    LODWORD(char_arr_p) = 0;
    i = length;
    do
    {
      tt_Char = *t_char;
      if ( v5 == 2 )
      {
        t_tbale_char = word_8FC[((unsigned int)tt_Char >> 6) & 0xFFFFFFC3 | (4 * (v4 & 0xF))];
        v5 = 0;
        v8 = (_DWORD)char_arr_p + 1;
        v9 = tt_Char & 0x3F;
        ret_v10[(unsigned int)char_arr_p] = t_tbale_char;
        step = 2;
      }
      else if ( v5 == 1 )
      {
        v5 = 2;
        v9 = (16 * v4) & 0x30LL | (tt_Char >> 4);
        step = 1;
        v8 = (int)char_arr_p;
      }
      else
      {
        v9 = tt_Char >> 2;
        step = 1;
        v8 = (int)char_arr_p;
        v5 = 1;
      }
      char_arr_p = (unsigned __int8 *)(unsigned int)((_DWORD)char_arr_p + step);
      ret_v10[v8] = word_8FC[v9];
      --i;
      ++t_char;
      v4 = tt_Char;
    }
    while ( i );
    if ( v5 == 2 )
    {
      v14 = (_DWORD)char_arr_p + 1;
      ret_v10[(unsigned int)char_arr_p] = word_8FC[(4 * (int)tt_Char) & 0x3CLL];
      char_arr_p = (unsigned __int8 *)(unsigned int)((_DWORD)char_arr_p + 2);
      ret_v10[v14] = 61;
    }
    else if ( v5 == 1 )
    {
      v12 = (_DWORD)char_arr_p + 1;
      v13 = (_DWORD)char_arr_p + 2;
      ret_v10[(unsigned int)char_arr_p] = word_8FC[(16 * (int)tt_Char) & 0x30LL];
      char_arr_p = (unsigned __int8 *)(unsigned int)((_DWORD)char_arr_p + 3);
      ret_v10[v12] = 61;
      ret_v10[v13] = 61;
      ret_v10[(unsigned int)char_arr_p] = 0;
      return char_arr_p;
    }
    ret_v10[(unsigned int)char_arr_p] = 0;
  }
  else
  {
    char_arr_p = 0LL;
    *ret_v10 = 0;
  }
  return char_arr_p;
}

这里有一个关键的变量 word_8FC,点进去看看

.rodata:00000000000008FC 41 42 43 44 45 46 47 48 49 4A+aAbcdefghijklmn DCB "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
.rodata:00000000000008FC 4B 4C 4D 4E 4F 50 51 52 53 54+                                        ; DATA XREF: base64_encode(uchar const*,uint,char *)+4↑o
.rodata:00000000000008FC 55 56 57 58 59 5A 61 62 63 64+     

可以看到一个标准的base64编解码表"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

由此可以推测,这就是一个标准的base64编码函数

5. 用frida调用验证一下

function main() {
    Java.perform(function () {

        var MainActivityHandler = Java.use('com.r0ysue.first.MainActivity')

        console.log('1111')
        if (MainActivityHandler != undefined) {
            console.log('2222' )
            MainActivityHandler.register.implementation = function (str) {
                console.log('hooked i = ' + str)
                var ret = this.register(str)
                console.log('hooked ret = ' + ret)

                return ret
            }
        }

    })

}

setTimeout(main, 1000)

日志

ooked i = dfdd
hooked ret = Error!
hooked i = dfddfggv
hooked ret = ZGZkZGZnZ3Y=
hooked i = 1234567
hooked ret = MTIzNDU2Nw==                                                                                                                                                                                                           
hooked i = 1234567
hooked ret = MTIzNDU2Nw==
hooked i = 12345678
hooked ret = MTIzNDU2Nzg=
hooked i = ddddddd
hooked ret = ZGRkZGRkZA==
hooked i = dddd
hooked ret = Error!
hooked i = ddddd
hooked ret = Error!
hooked i = dddddd
hooked ret = ZGRkZGRk
hooked i = dddddd
hooked ret = ZGRkZGRk
hooked i = dddddd
hooked ret = ZGRkZGRk

使用在线编解码工具测试,确实是正确的

6. 使用python开发注册机

import base64
 
while True:
    username = input("请输入用户名(长度小于等于20): ")
    if len(username) > 20:
        print("输入长度超过20,请重新输入")
    elif len(username) < 6:
        print("输入长度小于6,请重新输入")
    else:
        b64_byt = base64.b64encode(username.encode('utf-8'))
        print("密码:", b64_byt)

与【Android逆向】破解看雪9月算法破解第一题相似的内容:

【Android逆向】破解看雪9月算法破解第一题

1. 安装apk到手机 2. 随意输入账号和密码,点击register,报错crackme1:ERROR 3. 将apk拖入到jadx中进行观察 public native String register(String str); static { System.loadLibrary("nativ

【Android逆向】破解看雪9月算法破解第三题

这题的目标是算法还原,并写出注册机 1. 9月份算法第一题.apk 安装到手机 2. 随意输入账号密码,提示错误 3. apk拖入到jadx中 public native boolean register(String str, String str2); static { System.loadL

【Android逆向】破解看雪9月算法破解第二题

1. apk安装到手机,一样的界面,随便输入一样的报错 2. apk拖入到jadx重看看 public native String sha1(String str); static { System.loadLibrary("native-lib"); } /* JADX INFO: Access

【Android逆向】破解看雪 test1.apk

1. 获取apk,并安装至手机 apk 获取地址: https://www.kanxue.com/work-task_read-800624.htm adb install -t test1.apk # 这个apk必须加-t ,否则会报错 2. 只有一个输入框,随便输入内容,提示壮士继续加油 3.

【Android逆向】破解看雪test3.apk方案一

1. test3.apk 安装到手机 2. 发现其实际逻辑和之前的test2.apk基本一致,逆向so查看到加入了一些检查逻辑 代码: jstring __fastcall fuck(JNIEnv *env, jclass jcls, jstring str_) { ...... if ( !str

【Android逆向】破解看雪test3.apk方案二

方案二就是要hook那三个条件,不让追加字符串变成false v20 = "REAL"; clazz = _JNIEnv::FindClass(env, "android/os/Build"); fieldID = _JNIEnv::GetStaticFieldID(env, clazz, "FIN

【Android逆向】破解黑宝宝apk,绕过签名校验

这是52pojie的一道题,实现输入任何密码都可以登录成功 他知道你最近在学习Android逆向 他想在游戏上线前让你测试一下他新加的签名验证是否能防住别人的破解。 下面是李华编写的黑宝宝apk 链接:https://pan.baidu.com/s/1h6pX2ARE3qtiKiYbcnJ-3g 密

【Android逆向】frida 破解 jwxdxnx02.apk

apk 路径: https://pan.baidu.com/s/1cUInoi 密码:07p9 这题比较简单,主要是用于练习frida 1. 安装apk到手机 需要输入账号密码 2. 使用jdax 查看apk package hfdcxy.com.myapplication; import andr

[Android逆向]Exposed 破解 jwxdxnx02.apk

使用exposed 遇到了一些坑,这里记录一下 源码: package com.example.exposedlesson01; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.X

[Android 逆向]frida 破解 切水果大战原版.apk

1. 手机安装该apk,运行,点击右上角礼物 提示 支付失败,请稍后重试 2. apk拖入到jadx中,待加载完毕后,搜素失败,找到疑似目标类MymmPay的关键方法payResultFalse 4. adb logcat 或者androidstudio 查看该进程的日志,发现以下日志 com.mf