Android Crack 2023

【2023春节】解题领红包之三

APK:https://down.52pojie.cn/nUvaFj.7z|zYchSGxanOOx

用jadx反编译出来,其中关键是onCreate中的decrypt

public static final void m19onCreate$lambda0(MainActivity mainActivity, TextView textView, View view) {
        Intrinsics.checkNotNullParameter(mainActivity, "this$0");
        Intrinsics.checkNotNullParameter(textView, "$key");
        MainActivity mainActivity2 = mainActivity;
        mainActivity.jntm(mainActivity2);
        textView.setText(String.valueOf(mainActivity.num));
        if (mainActivity.check() == 999) {
            Toast.makeText(mainActivity2, "快去论坛领CB吧!", 1).show();
            textView.setText(mainActivity.decrypt("hnci}|jwfclkczkppkcpmwckng•", 2));
        }
    }
​

上面有两个部分,其中一是check结果为999,另一个就是解密那个字符串,check这个不需要传入参数,上面设定了get/set,这里直接修改判断即可。

public final int check() {
        int i = this.num + 1;
        this.num = i;
        return i;
    }

修改if-neif-eq

00000036  invoke-virtual      MainActivity->check()I, p0
0000003C  move-result         v0
0000003E  const/16            v1, 999
00000042  if-ne               v0, v1,

第二部分decrypt,这里

public final String decrypt(String str, int i) {
        Intrinsics.checkNotNullParameter(str, "encryptTxt");
        char[] charArray = str.toCharArray();
        Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
        StringBuilder sb = new StringBuilder();
        for (char c : charArray) {
            sb.append((char) (c - i));
        }
        String sb2 = sb.toString();
        Intrinsics.checkNotNullExpressionValue(sb2, "with(StringBuilder()) {\n…     toString()\n        }");
        return sb2;
    }

写个python脚本复原

#coding:utf-8
​
​
list_s = []
def decrypt(str1, int2):
    for i in str1:
        list_s.append(chr(ord(i) - int2))
    return list_s
​
print("".join(decrypt("hnci}|jwfclkczkppkcpmwckng•", 2)))

当然,如果习惯用jeb打开的话就会发现,这个decrypt已经给我们复原好了。

image-20230131175642195

【2023春节】解题领红包之四

APK:https://down.52pojie.cn/JfCdrX.7z | 5dPxREzsOa89

这个题需要知道自己的UID,反编译后可以看到主要的判断逻辑在onCreate

public static final void m19onCreate$lambda0(MainActivity mainActivity, View view) {
        Intrinsics.checkNotNullParameter(mainActivity, "this$0");
        A a = A.INSTANCE;
        EditText editText = mainActivity.edit_uid;
        EditText editText2 = null;
        if (editText == null) {
            Intrinsics.throwUninitializedPropertyAccessException("edit_uid");
            editText = null;
        }
        String obj = StringsKt.trim((CharSequence) editText.getText().toString()).toString();
        EditText editText3 = mainActivity.edit_flag;
        if (editText3 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("edit_flag");
        } else {
            editText2 = editText3;
        }
        if (a.B(obj, StringsKt.trim((CharSequence) editText2.getText().toString()).toString())) {
            Toast.makeText(mainActivity, "恭喜你,flag正确!", 1).show();
        } else {
            Toast.makeText(mainActivity, "flag错误哦,再想想!", 1).show();
        }
    }
​

这里,我们可以得到两个信息,第一个参数是UID,第二个参数是flag,参数传入A类下的B函数中。

public final boolean B(String str, String str2) {
        Intrinsics.checkNotNullParameter(str, "str");
        Intrinsics.checkNotNullParameter(str2, "str2");
        if ((str.length() == 0 && str2.length() == 0) || !StringsKt.startsWith$default(str2, "flag{", false, 2, (Object) null) || !StringsKt.endsWith$default(str2, "}", false, 2, (Object) null)) {
            return false;
        }
        String substring = str2.substring(5, str2.length() - 1);
        Intrinsics.checkNotNullExpressionValue(substring, "this as java.lang.String…ing(startIndex, endIndex)");
        C c = C.INSTANCE;
        MD5Utils mD5Utils = MD5Utils.INSTANCE;
        Base64Utils base64Utils = Base64Utils.INSTANCE;
        String encode = B.encode(str + "Wuaipojie2023");
        Intrinsics.checkNotNullExpressionValue(encode, "encode(str3)");
        byte[] bytes = encode.getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
        return Intrinsics.areEqual(substring, c.cipher(mD5Utils.MD5(base64Utils.encodeToString(bytes)), 5));
    }

上面的函数先对flag进行截取,去掉flag{},只留中间的字符串。然后使用B类下的encode进行处理UID,处理后再进行md5操作,然后跟输入进行比较,相同则代表输入正确,也就是我们需要获取的值。

public static String encode(String str) {
        int length = str.length();
        char[] cArr = new char[length];
        int i = length - 1;
        while (i >= 0) {
            int i2 = i - 1;
            cArr[i] = (char) (str.charAt(i) ^ '5');
            if (i2 < 0) {
                break;
            }
            i = i2 - 1;
            cArr[i2] = (char) (str.charAt(i2) ^ '2');
        }
        return new String(cArr);
    }

这个算法稍微比上面的麻烦一点,本来打算用添加log或者frida来做,发现手机的终端都不能安装,干脆直接把算法实现一遍,需要修改下面的UID为自己的UID。Base64Utils和MD5Utils也可以自己使用编码加密来替代算法。

import org.apache.commons.io.Charsets;
​
public class checkme {
    public static String encode(String arg4) {
        int v0 = arg4.length();
        char[] v1 = new char[v0];
        int v0_1 = v0 - 1;
        while(v0_1 >= 0) {
            int v2 = v0_1 - 1;
            v1[v0_1] = (char)(arg4.charAt(v0_1) ^ 53);
            if(v2 < 0) {
                break;
            }
​
            v0_1 = v2 - 1;
            v1[v2] = (char)(arg4.charAt(v2) ^ 50);
        }
​
        return new String(v1);
    }
​
    public static void main(String[] args) {
        String v6 = encode(UID+"Wuaipojie2023");
        byte[] v6_1 = v6.getBytes(Charsets.UTF_8);
        String v6_2 = Base64Utils.INSTANCE.encodeToString(v6_1);
        String v6_3 = MD5Utils.INSTANCE.MD5(v6_2);
        System.out.println(C.INSTANCE.cipher(v6_3, 5));
    }
​
}

最后算出来的值就是,再加上前后的flag{}。

flag{i4jkj66h8j7i4j7hi6ihf4h02hi062i4}

【2023春节】解题领红包之六

APK:https://down.52pojie.cn/cuKcNU.7z | my4OyfjP5HG2

反编译APK,发现是一个native层的解题,继续看下面的流程,需要把音量调到100-101之间,应该是需要让这个v2不等于0,既可把flag写入到本地的图片上

private final void Check_Volume(double arg6) {
        TextView v0 = this.automedia;
        if(v0 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("automedia");
            v0 = null;
        }
​
        v0.setText(((CharSequence)("当前分贝:" + arg6)));
        int v2 = 0;
        if(Double.compare(84.0, arg6) <= 0 && arg6 <= 99.0) {
            this.xigou(((Context)this));
            return;
        }
​
        if(100.0 <= arg6 && arg6 <= 101.0) {
            v2 = 1;
        }
​
        if(v2 != 0) {
            Toast.makeText(((Context)this), "快去找flag吧", 1).show();
            this.write_img();
        }
    }

其中的write_img函数为

private final void write_img() {
        Closeable v2;
        InputStream v1_1;
        InputStream v0 = this.getAssets().open("aes.png");
        Intrinsics.checkNotNullExpressionValue(v0, "assets.open(\"aes.png\")");
        File v3 = new File(this.getPrivateDirectory(), "aes.png");
        Closeable v0_1 = (Closeable)v0;
        try {
            v1_1 = (InputStream)v0_1;
            v2 = (Closeable)new FileOutputStream(v3);
        }
        catch(Throwable v1) {
            throw v1;
        }
​
        try {
            ByteStreamsKt.copyTo$default(v1_1, ((OutputStream)(((FileOutputStream)v2))), 0, 2, null);
            goto label_27;
        }
        catch(Throwable v1_2) {
        }
​
        try {
            throw v1_2;
        }
        catch(Throwable v3_1) {
        }
​
        try {
            CloseableKt.closeFinally(v2, v1_2);
            throw v3_1;
        label_27:
            CloseableKt.closeFinally(v2, null);
            goto label_34;
        }
        catch(Throwable v1) {
        }
​
        try {
            throw v1;
        }
        catch(Throwable v2_1) {
        }
​
        CloseableKt.closeFinally(v0_1, v1);
        throw v2_1;
    label_34:
        CloseableKt.closeFinally(v0_1, null);
    }

其中在assert下面的aes图片是一个加密的字段,看起来不是写入里面,而是这个图片是显示了加密的flag,这里需要解密出来这个flag图片,大概?

反编译其中的lib52pj.so,发现其中存在JNI_Onload函数,里面貌似是调试自身的反调试和环境检测。

{
  jint v3; // r4
  int v4; // r0
  int v6; // [sp+0h] [bp-18h] BYREF
​
  ptrace(PTRACE_TRACEME, 0, 0, 0);
  v6 = 0;
  v3 = 65542;
  if ( (*vm)->GetEnv(vm, &v6, 65542) )
    return -1;
  v4 = (*(*v6 + 24))(v6, "com/zj/wuaipojie2023_2/MainActivity");
  if ( !v4 )
    return -1;
  if ( (*(*v6 + 860))(v6, v4, methods, 1) < 0 )
    v3 = -1;
  return v3;
}

这个APP的作用就是符合上面的条件后提供这个图片出来,除此之外都没有调用过native函数。

so的反编译这个用x86架构的,看起来清楚一些。其中_mm_add_epi8代表SSE指令的8位加法,意思是r0=a0+b0,r1=a1+b1_mm_loadu_si128代表加载128位的值,s1就是v3和xmmword_2A60相加。而xmmword_2A60的值查看Data是0xFBFEFBFEFBFEFBFEFBFEFBFEFBFEFBFE,strcmp是比较,这里无实际意义。

bool __cdecl get_RealKey(_JNIEnv *a1, int a2, int a3)
{
  const __m128i *v3; // esi
  __m128i s1; // [esp+0h] [ebp-2Ch] BYREF
  char v6; // [esp+10h] [ebp-1Ch]
  unsigned int v7; // [esp+20h] [ebp-Ch]
​
  v7 = __readgsdword(0x14u);
  v3 = a1->functions->GetStringUTFChars(a1, a3, 0);
  if ( strlen(v3->m128i_i8) != 16 )
    return 0;
  v6 = 0;
  s1 = _mm_add_epi8(_mm_loadu_si128(v3), xmmword_2A60);
  return strcmp(s1.m128i_i8, "thisiskey") != 0;
}

编写一个C脚本来还原key

#include <stdio.h>
​
int main() {
    char key[] = "|wfkuqokj4548366";
    char xmmword_2A60[] = { 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE};
    int a = 0;
    char newkey[32];
    for (int i = 0; i < sizeof(key) - 1; i ++)
        if (a % 2 == 0) {
            newkey[i] = key[i] + xmmword_2A60[i];
        } else {
            newkey[i] = key[i] + xmmword_2A60[i + 1];
        }
    printf("%s", newkey);
}

运行的结果是wuaipojie2023114,尝试解密png图片,解密出来是这样的一段值

89504E470D0A1A0A0000000D49484452000002E2000002E204030000006ECDAE0C0000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000030504C5445FEFEFEF6CF75F0BF5FF9DF89ECA34FF3F4EC272B24E26265E0DED98B867C52514EC29F6A6D380EB55633A8A7A6D0C7BCAE84814D000020004944415478DAEC5DBF6FDB481666F30057B77FD6B5AF1980D7C49D00BB484A42100CB81980BB07585DB4B48F4E75C6D9383BA51018D9FD032E5713。。。。。。。

利用脚本进行十六进制转图片操作

import binascii
payload = "89504E470D0A1A0A0000000D49484452000002E2000002E204030000006ECDAE0C0000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000030504C5445FEFEFEF6CF75F0BF5FF9DF89ECA34FF3F4EC272B24。。。。。。"
f=open("1.png","ab")
pic = binascii.a2b_hex(payload.encode())
f.write(pic)
f.close()

得到一个干杯的表情包。。。。用010editor打开查看一下。

可以看到开头是PNG的图片标志头,搜索一下看看是不是里面还带了什么zip或者图片。在1953行那还有一个PNG头,提取后面的图片。是一个1kb大小的二维码,扫码可得

flag{Happy_New_Year_Wuaipojie2023}

【2023春节】解题领红包之七

不要问,问就是不会,现在已经有大佬做出来了。可以看看大佬们的WP:https://www.52pojie.cn/thread-1742121-1-1.html





# Android逆向  

tocToc: