Android逆向技术61——Android逆向吾爱春节中级题目

admin 2024年2月26日21:46:29评论13 views字数 15433阅读51分26秒阅读模式

Android逆向吾爱春节中级题目

背景

记录Android找flag的一个案例,2024吾爱春节题目3。计算最终flag的时候需要把password+uid处理。比如找到了password是23456,自己的uid是20220000,组合起来就是2345620220000。

体验样本

把样本安装到手机上面。

Android逆向技术61——Android逆向吾爱春节中级题目

一个绘图解锁,尝试一下,当然是没那么容易就解锁了。

开始反编译分析

环境:macOS,root的真机pixel3,MT管理,jadx。

通过dump堆栈收集到包名和activity信息。

adb shell dumpsys activity | grep -i run
# 高版本用这个
adb shell dumpsys activity top | grep ACTIVITY
ACTIVITY bin.mt.plus/l.۟ۚۡ۫ 28b8e30 pid=4807
ACTIVITY com.android.launcher3/.uioverrides.QuickstepLauncher f053047 pid=2666
ACTIVITY com.zj.wuaipojie2024_2/.MainActivity 7c20286 pid=8038

包名:com.zj.wuaipojie2024_2

当前页面:MainActivity

根据上面信息在jadx中找到页面的代码:

Android逆向技术61——Android逆向吾爱春节中级题目

开始分析代码。

Android逆向技术61——Android逆向吾爱春节中级题目

1、初始化绘图。

2、错误的回调中打印了绘图密码,然后checkPassword

3、复制classes.dex到私有目录中名字1.dex

4、动态加载dex,应该是调用里面的函数做事情。

String str2 = (String) new DexClassLoader(file.getAbsolutePath(), getDir("dex"0).getAbsolutePath(), null, getClass().getClassLoader()).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.classString.classint[].class).invoke(nullthisstrgetResources().getIntArray(R.array.A_offset));  

这一行代码太长了,我复制出来了。

loadClass("com.zj.wuaipojie2024_2.C")加载了1.dex中的一个类C,调用了isValidate函数,传递的参数是绘图的密码和R.array.A_offset对应的数组。绘图密码是log打印的,可以通过logcat查看。

E/zj595: 012543
E/zj595: 78
E/zj595: 15
E/zj595: 34

尝试几次解锁,可以通过过滤tag得到日志。

查看上面代码复制出来的dex:

File file = new File(getDir("data"0), "1.dex");

进入app的内部目录:

adb shell
dipper:/ $ su
dipper:/ # cd data/data/com.zj.wuaipojie2024_2
dipper:/data/data/com.zj.wuaipojie2024_2 # ls -l
total 12
drwxrwx--x 2 u0_a160 u0_a160       3488 2024-02-22 14:05 app_data
drwxrwx--x 2 u0_a160 u0_a160       3488 2024-02-22 14:05 app_dex
drwxrws--x 2 u0_a160 u0_a160_cache 3488 2024-02-22 14:00 cache
drwxrws--x 2 u0_a160 u0_a160_cache 3488 2024-02-22 14:00 code_cache
dipper:/data/data/com.zj.wuaipojie2024_2 # cd app_data/
dipper:/data/data/com.zj.wuaipojie2024_2/app_data # ls -l
total 16
-rw------- 1 u0_a160 u0_a160 12384 2024-02-22 14:05 1.dex
dipper:/data/data/com.zj.wuaipojie2024_2/app_data #

这个dex中有重要逻辑:loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate")

动态加载这个dex,执行isValidate。经过多次绘制密码后我发现logcat日志有点异常:

2024-02-22 14:09:15.332 8038-8038/? E/zj595: 345
2024-02-22 14:09:15.334 8038-8038/? W/wuaipojie2024_: Failure to verify dex file '/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex'Bad checksum (c607ea12, expected 22dcea4c)
2024-02-22 14:09:15.335 8038-8038/? E/System: Unable to load dex file: /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex
2024-02-22 14:09:15.335 8038-8038/? E/System: java.io.IOException: Failed to open dex files from /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex because: Failure to verify dex file '/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex': Bad checksum (c607ea12, expected 22dcea4c)
        at dalvik.system.DexFile.openDexFileNative(Native Method)
        at dalvik.system.DexFile.openDexFile(DexFile.java:373)
        at dalvik.system.DexFile.<init>(DexFile.java:115)
        at dalvik.system.DexFile.<init>(DexFile.java:88)
        at dalvik.system.DexPathList.loadDexFile(DexPathList.java:438)
        at dalvik.system.DexPathList.makeDexElements(DexPathList.java:387)
        at dalvik.system.DexPathList.<init>(DexPathList.java:166)
        at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:134)
        at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:92)
        at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:55)
        at com.zj.wuaipojie2024_2.MainActivity.checkPassword(MainActivity.java:72)
        at com.zj.wuaipojie2024_2.MainActivity$1.isError(MainActivity.java:47)
        at com.example.gesturelock.GestureUnlock$1.handleMessage(GestureUnlock.java:111)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7876)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

动态加载dex失败了:Failure to verify dex file '/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex': Bad checksum (c607ea12, expected 22dcea4c),这是原因,这是一个的dex。

这可能是不小心故意的?

把assets中dex丢进mt中:

Android逆向技术61——Android逆向吾爱春节中级题目

打开代码中动态加载的类loadClass("com.zj.wuaipojie2024_2.C"),转java,导出。

Android逆向技术61——Android逆向吾爱春节中级题目

导出文件方便分析代码。

下面是导出来的代码:

package com.zj.wuaipojie2024_2;

import android.content.Context;
import android.util.Log;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.HashMap;

public class C {
    public static final String SIGNATURE = "fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117";
    private static final String TAG = "ZJ595";

    public static String isValidate(Context context, String str, int[] iArr) throws Exception {
        try {
            return (String) getStaticMethod(context, iArr, "com.zj.wuaipojie2024_2.A""d", Context.classString.class).invoke(nullcontextstr);
        } catch (Exception e) {
            Log.e(TAG, "咦,似乎是坏掉的dex呢!");
            e.printStackTrace();
            return "";
        }
    }

    private static Method getStaticMethod(Context context, int[] iArr, String str, String str2, Class<?>... clsArr) throws Exception {
        try {
            File fix = fix(read(context), iArr[0], iArr[1], iArr[2], context);
            ClassLoader classLoader = context.getClass().getClassLoader();
            File dir = context.getDir("fixed"0);
            Method declaredMethod = new DexClassLoader(fix.getAbsolutePath(), dir.getAbsolutePath(), null, classLoader).loadClass(str).getDeclaredMethod(str2, clsArr);
            fix.delete();
            new File(dir, fix.getName()).delete();
            return declaredMethod;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static File fix(ByteBuffer byteBuffer, int i, int i2, int i3, Context context) throws Exception {
        try {
            File dir = context.getDir("data"0);
            int intValue = ((Integer) D.getClassDefData(byteBuffer, i).get("class_data_off")).intValue();
            HashMap classData = D.getClassData(byteBuffer, intValue);
            ((int[][]) classData.get("direct_methods"))[i2][2] = i3;
            byte[] encodeClassData = D.encodeClassData(classData);
            byteBuffer.position(intValue);
            byteBuffer.put(encodeClassData);
            byteBuffer.position(32);
            byte[] bArr = new byte[byteBuffer.capacity() - 32];
            byteBuffer.get(bArr);
            byte[] sha1 = Utils.getSha1(bArr);
            byteBuffer.position(12);
            byteBuffer.put(sha1);
            int checksum = Utils.checksum(byteBuffer);
            byteBuffer.position(8);
            byteBuffer.putInt(Integer.reverseBytes(checksum));
            byte[] array = byteBuffer.array();
            File file = new File(dir, "2.dex");
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(array);
            fileOutputStream.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static ByteBuffer read(Context context) {
        try {
            File file = new File(context.getDir("data"0), "decode.dex");
            if (file.exists()) {
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] bArr = new byte[fileInputStream.available()];
                fileInputStream.read(bArr);
                ByteBuffer wrap = ByteBuffer.wrap(bArr);
                fileInputStream.close();
                return wrap;
            }
            return null;
        } catch (Exception unused) {
            return null;
        }
    }
}

这是默认的代码,下面是分析增加注释的代码:

package com.zj.wuaipojie2024_2;

import android.content.Context;
import android.util.Log;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.HashMap;

public class C {
    public static final String SIGNATURE = "fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117";
    private static final String TAG = "ZJ595";
    // checkPassword -> com.zj.wuaipojie2024_2.C.isValidate (密码, A_offset)
    // com.zj.wuaipojie2024_2.A.d (密码, A_offset)
    public static String isValidate(Context context, String str, int[] iArr) throws Exception {
        try {
            return (String) getStaticMethod(context, iArr, "com.zj.wuaipojie2024_2.A""d", Context.classString.class).invoke(nullcontextstr);
        } catch (Exception e) {
            Log.e(TAG, "咦,似乎是坏掉的dex呢!");
            e.printStackTrace();
            return "";
        }
    }

    private static Method getStaticMethod(Context context, int[] iArr, String str, String str2, Class<?>... clsArr) throws Exception {
        try {
            // read 读取app_data -> decode.dex"
            // fix 就是修复dex, 用到传递进来的A_offset, 偏移
            File fix = fix(read(context), iArr[0], iArr[1], iArr[2], context);
            ClassLoader classLoader = context.getClass().getClassLoader();
            // 把修复的dex保存到fixed目录
            File dir = context.getDir("fixed"0);
            // 动态加载dex,加载函数后返回Method
            Method declaredMethod = new DexClassLoader(fix.getAbsolutePath(), dir.getAbsolutePath(), null, classLoader).loadClass(str).getDeclaredMethod(str2, clsArr);
            // 删除修复的dex
            fix.delete();
            new File(dir, fix.getName()).delete();
            return declaredMethod;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static File fix(ByteBuffer byteBuffer, int i, int i2, int i3, Context context) throws Exception {
        try {
            File dir = context.getDir("data"0);
            int intValue = ((Integer) D.getClassDefData(byteBuffer, i).get("class_data_off")).intValue();
            HashMap classData = D.getClassData(byteBuffer, intValue);
            ((int[][]) classData.get("direct_methods"))[i2][2] = i3;
            byte[] encodeClassData = D.encodeClassData(classData);
            byteBuffer.position(intValue);
            byteBuffer.put(encodeClassData);
            byteBuffer.position(32);
            byte[] bArr = new byte[byteBuffer.capacity() - 32];
            byteBuffer.get(bArr);
            byte[] sha1 = Utils.getSha1(bArr);
            byteBuffer.position(12);
            byteBuffer.put(sha1);
            int checksum = Utils.checksum(byteBuffer);
            byteBuffer.position(8);
            byteBuffer.putInt(Integer.reverseBytes(checksum));
            byte[] array = byteBuffer.array();
            File file = new File(dir, "2.dex");
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(array);
            fileOutputStream.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static ByteBuffer read(Context context) {
        try {
            File file = new File(context.getDir("data"0), "decode.dex");
            if (file.exists()) {
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] bArr = new byte[fileInputStream.available()];
                fileInputStream.read(bArr);
                ByteBuffer wrap = ByteBuffer.wrap(bArr);
                fileInputStream.close();
                return wrap;
            }
            return null;
        } catch (Exception unused) {
            return null;
        }
    }
}

再查看A.d的函数:

Android逆向技术61——Android逆向吾爱春节中级题目

把密码传递进来,前后加个?再返回,应该没那么简单,C里面的代码就是修复这个函数。

流程梳理:

1、绘图解锁checkPassword -> 动态加载com.zj.wuaipojie2024_2.C.isValidate (密码, A_offset)。

2、C中反射调用com.zj.wuaipojie2024_2.A.d (密码, A_offset)。

3、动态修复dex,read 读取app_data -> decode.dex"。

4、修复dex, 用到传递进来的A_offset, 偏移。

5、把修复的dex保存到fixed目录。

6、动态加载dex,加载函数后返回Method,删除dex。

现在加载1.dex是坏的,修复的逻辑需要1.dex中的陷入死循环。要是里面的修复的代码可以正常执行,利用它修复dex就好了。

修复dex

思路:创建一个新的app项目,把代码复制过来修复dex。

1、先创建assets目录,把样本的dex复制过来。

Android逆向技术61——Android逆向吾爱春节中级题目

app启动后把dex复制到data中,重命名为1.dex,这一块代码直接拿过来用:

Android逆向技术61——Android逆向吾爱春节中级题目

复制出来:

public void copyDex() {
    try {
        InputStream open = getAssets().open("classes.dex");
        byte[] bArr = new byte[open.available()];
        open.read(bArr);
        File file = new File(getDir("data"0), "1.dex");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(bArr);
        fileOutputStream.close();
        open.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

跑一跑看结果:

Android逆向技术61——Android逆向吾爱春节中级题目
dipper:/data/data/com.example.myapplication/app_data # ls
1.dex  oat

dex复制到内部目录,接着下一步跑fix的代码。在修复代码的时候他读取是decode.dex,我们修改为复制出来的1.dex

Android逆向技术61——Android逆向吾爱春节中级题目

fix函数中修复com.zj.wuaipojie2024_2.A之后的dex命名为2.dex

Android逆向技术61——Android逆向吾爱春节中级题目

跑一次修复试试:

private void test() {
    Toast.makeText(this"test", Toast.LENGTH_SHORT).show();
    // 模拟绘图后输出密码
    isValidate(this"232", getResources().getIntArray(R.array.A_offset),
            "com.zj.wuaipojie2024_2.A");
}

在这里会遇到问题,isValidate第二个参数是密码,这个随意,R.array.A_offset从哪里来?

资源array在开发的时候定义在xml中,在jadx中需要把xml的数据找出来,复制到新项目中。

Android逆向技术61——Android逆向吾爱春节中级题目

这样就补了需要的参数。跑一次修复后去查看是否有2.dex

dipper:/data/data/com.example.myapplication/app_data # ls
1.dex  2.dex  oat

mt打开2.dex转java导出:

package com.zj.wuaipojie2024_2;

import android.content.Context;

public class A {
    private static final String SUCCESS_TAG = "唉!";

    public static boolean b() {
        return false;
    }

    public static String c(String str) {
        return "?" + str + "?";
    }
    // 处理解锁密码的函数
    public static String d(Context context, String str) {
        MainActivity.sSS(str);
        String signInfo = Utils.getSignInfo(context);
        if (signInfo == null || !signInfo.equals("fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117")) {
            return "";
        }
        StringBuffer stringBuffer = new StringBuffer();
        int i = 0;
        while (stringBuffer.length() < 9 && i < 40) {
            int i2 = i + 1;
            String substring = "0485312670fb07047ebd2f19b91e1c5f".substring(i, i2);
            if (!stringBuffer.toString().contains(substring)) {
                stringBuffer.append(substring);
            }
            i = i2;
        }
        return !str.equals(stringBuffer.toString().toUpperCase()) ? "" : "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d";
    }
}

导出的代码中d函数已经被修复了:

Android逆向技术61——Android逆向吾爱春节中级题目

从修复代码中可以得到信息:

1、校验了app的签名,失败就不做处理。

2、经过一些列处理后,判断传递解锁密码是不是一致,如果不一致返回空,如果一致提示一句话:机缘在B.d中。

Android逆向技术61——Android逆向吾爱春节中级题目

这是checkPassword之后执行的代码块,假设我们现在已经知道了密码,直接去B.d找机缘就行了。看看B.d的代码:

Android逆向技术61——Android逆向吾爱春节中级题目

但是B里面和之前A一样都是前后加了?,显然不对的,还记得上面的array

偏移数据中第一次使用了A_offset,后面接着有一个B_offset

<resources>
    <array name="A_offset">
        <item>0</item>
        <item>3</item>
        <item>7908</item>
    </array>
    <array name="B_offset">
        <item>1</item>
        <item>1</item>
        <item>8108</item>
    </array>
</resources>

既然这样,用代码修复一下B类,我们在上一次修复好的2.dex的基础上修复B,这样代码就完整了。

修改read函数把1.dex修改2.dex。

fix函数中保存的dex名字3.dex,执行一次修复:

dipper:/data/data/com.example.myapplication/app_data # ls
1.dex  2.dex  3.dex  oat

把3.dex拿出来,加入到jadx中

Android逆向技术61——Android逆向吾爱春节中级题目

这里就是flag的计算代码了。到此dex修复完成。

获取flag

flag的计算代码是这样的:

public static String d(String str) {
    return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
}

Utils这个工具代码在修复dex中,直接把代码复制到我们的新项目中,他依赖的代码D也复制过来。

现在代码准备好了,这个函数传递str在里面没用到,他提示password+你的uid,+号不要。uid是自己的论坛id,这个已经拿到了。password怎么来?

Android逆向技术61——Android逆向吾爱春节中级题目

把检测密码是否正确代码拿出来,跑一跑。

1、把native函数注释,这个和计算密码逻辑无关。

2、签名也不用检测了。

3、打印stringBuffer。

Android逆向技术61——Android逆向吾爱春节中级题目

得到了锁屏密码:048531267

"机缘是{" + Utils.md5(Utils.getSha1("048531267自己的uid".getBytes())) + "}";

把自己的uid放进去跑一次代码:

Android逆向技术61——Android逆向吾爱春节中级题目

最后

第一次玩这个题目,解题流程有点啰嗦了,祝大家新年好,身体健康,万事如意。

文章中样本及单独创建的项目代码可在星球获取。

Android逆向技术61——Android逆向吾爱春节中级题目

原文始发于微信公众号(安全后厨):Android逆向技术61——Android逆向吾爱春节中级题目

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月26日21:46:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Android逆向技术61——Android逆向吾爱春节中级题目http://cn-sec.com/archives/2526959.html

发表评论

匿名网友 填写信息