[羊城杯 2021]Ez_android

反编译APP

image-20230420124310331

首先就是输入账号密码,正确后才能进入下一个函数的判断,但是这个getKeyAndRedirect需要联网获取key,才能进行下一步的加密验证。

image-20230420124350850

获取到key之后就可以进行下一步计算

image-20230420124450904

这里就是获取flag的关键代码了。

private boolean checkFlag(String arg3) {
        return new String(EncodeUtils.encode(arg3.getBytes(StandardCharsets.UTF_8), false, this.key.getBytes(StandardCharsets.UTF_8))).equals("3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P");
    }

也就是加密后需要输入的flag跟上面的字符串一致。这里的输入的账号密码很重要,因为需要后续输入密码来请求key,我们可以先过一遍流程,已知的账号为admin,加密后的密码为:c232666f1410b3f5010dc51cec341f58。而这个字符串是md5加密后再每一位减1得到,也就是需要把上面的再加1。结果就是:c33367701511b4f6020ec61ded352059。解密可以得到密码为:654321。

再把这个密码提交到平台上得到key:TGtUnkaJD0frq61uCQYw3-FxMiRvNOB/EWjgVcpKSzbs8yHZ257X9LldIeh4APom

image-20230420132711887

需要计算出来的结果为:3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P

这开头熟悉的乘除法和下面的计算过程,应该算法是base64的编码过程,其中key就是替换了原本的字符。

image-20230420150602353

可以从Java中把base64解码的代码抠出来本地执行,代码过长不贴出来了,运行后显示如下,需要把前缀进行替换。

image-20230420181201727

[CISCN 2022 东北]crackme_Android

找到onCreate,onClick在其中,那就直接找关键函数,flag长38位,去掉前后的位数,中间的字符为32位,且需要把每四位进行一次看似是md5的加密,最后拼接的结果为:8393931a16db5a00f464a24abe24b17a9040b57d9cb2cbfa6bdc61d12e9b51f2789e8a8ae9406c969118e75e9bc65c4327fbc7c3accdf2c54675b0ddf3e0a6099b1b81046d525495e3a14ff6eae76eddfa1740cd6bd483da0f7684b2e4ec84b371f07bf95f0113eefab12552181dd832af8d1eb220186400c494db7091e402b0

image-20230421105414513

md5算法中的传参是字符的每四位

image-20230421105704911

先把上面的字符串分割为8段解密

8393931a16db5a00f464a24abe24b17a   //4aea
9040b57d9cb2cbfa6bdc61d12e9b51f2   //146e
789e8a8ae9406c969118e75e9bc65c43   //9dc7
27fbc7c3accdf2c54675b0ddf3e0a609   //365e
9b1b81046d525495e3a14ff6eae76edd   //4ec9
fa1740cd6bd483da0f7684b2e4ec84b3   //31f5
71f07bf95f0113eefab12552181dd832   //4728
af8d1eb220186400c494db7091e402b0   //4822

再把解密出来的拼接,加上flag的前缀即可。

[鹏城杯 2022]baby_re

反编译后发现是一个JNI的题,需要传入一个chararray的值

image-20230421154034979

并且这个值再亦或后需要等于[0x77, 9, 40, 44, 106, 83, 0x7E, 0x7B, 33, 87, 0x71, 0x7B, 0x70, 93, 0x7D, 0x7F, 41, 82, 44, 0x7F, 39, 3, 0x7E, 0x7D, 0x77, 87, 0x2F, 0x7D, 33, 6, 44, 0x7F, 0x70, 0, 0x7E, 0x7B, 0x73, 24]

直接拖到IDA里面,找到调用函数,这个函数只有按位异或key这一个操作,但是key不知道,v5是传入的array数组。v6是数组长度。

image-20230421154120686

点击查找,发现key是一个int类型的4位数组,第一个值0x56,函数列里有一个hide_key,这个函数是init_array内加载,就是给了一个key的原始数组,再进行异或替换。

image-20230421154458480

key原始值是:[0x56,0x57,0x58,0x59]

key异或后的值是:[0x11, 0x65, 0x49, 0x4b]

image-20230421154410345

编写脚本还原,上面的*(v5 + 4 * i)并不代表参数加值,而且指针变量,存储是地址值,int是4字节。

out = [0x77, 9, 40, 44, 106, 83, 0x7E, 0x7B, 33, 87, 0x71, 0x7B, 0x70, 93, 0x7D, 0x7F, 41, 82, 44, 0x7F, 39, 3, 0x7E, 0x7D, 0x77, 87, 0x2F, 0x7D, 33, 6, 44, 0x7F, 0x70, 0, 0x7E, 0x7B, 0x73, 24];
key = [0x11, 0x65, 0x49, 0x4b]
​
a = ''
for i in range(0, len(out)):
    c = key[i % 4] ^ out[i]
    a = a + chr(c)
    print(a)

结果是:flag{6700280a84487e46f76f2f60ce4ae70b}

[HGAME 2022 week1]flagchecker

反编译后查看内容,是一个需要进行RC4加密,然后再进行base64编码输出的过程。

image-20230423103021841

解密代码

#coding:utf-8
​
from Crypto.Cipher import ARC4
import base64
​
def rc4_decrypt(key, data):
    cipher = ARC4.new(key)
    return cipher.decrypt(base64.b64decode(data))
​
key = b'carol'
data = b'mg6CITV6GEaFDTYnObFmENOAVjKcQmGncF90WhqvCFyhhsyqq1s='
​
decrypted_data = rc4_decrypt(key, data)
print(decrypted_data)

[SWPU 2019]ThousandYearsAgo

反编译APP失败,解压缩发现dex异常,在res下发现所谓的真正的APP,一个txt文件,但是是压缩包,打开发现是APP目录格式,修改后缀打开即可。

image-20230423150327583

Java层没有发现有用的东西,jni层调用了两个函数,反编译libnative-lib.so。

jni内有JNI_Onload函数,其中做了一个函数调用的判断。检查是否能获取到环境变量,然后再去检查是否存在MainActivity,然后调用stringFromJNI。

其中还有一个StringFromJNI,这个函数就是一个提示,会返回flag是flag{WeLcome_to-SWPU}}加密的结果

直接还原方法stringFromJNI

#include <stdio.h>
#include <string.h>
​
int main() {
    size_t i;
    char a1[35] = "flag{WeLcome_to-SWPU}}";
    int a2 = 5;
    for ( i = 0; i < strlen(a1); ++i ) {
        if ( a1[i] < 0x41 || a1[i] > 0x5A ) {
            if ( a1[i] >= 0x61 && a1[i] <= 0x7A )
                a1[i] = (a1[i] + a2 - 97) % 26 + 97;
        } else {
            a1[i] = (a1[i] + a2 - 65) % 26 + 65;
        }
    }
    printf(a1);
    return 0;
}

结果是:kqfl{BjQhtrj_yt-XBUZ}},再加上flag的前后缀即可。

[鹤城杯 2021]AreYouRich

反编译发现是一个计算过程,这个关于账号密码没有提示,比如账号qweasdzxcr,计算出来的密码就是qweasdzxcr_SUGCQFXZAP@001

image-20230424141047967

在UserActivity内,有一个判断余额是否大于499999999的计算,大于则购买成功返回flag,还原后是flag{~1fHrTY8Y@_61$H*rPf6n3y!!},但是这个flag并不正确,说明token不对。

public static void main(String[] args) throws Exception {
        byte[] arr_b = {0x40, 0x30, 0x30, 49};
        byte[] arr_b1 = "qweasdzxcr".getBytes();
        for(int v = 0; v < arr_b1.length; ++v) {
            arr_b1[v] = (byte)(arr_b1[v] ^ 34);
        }
​
        String p = new String(arr_b1) + new String(arr_b);
        String ss = "qweasdzxcr" + "_" + p + "_" + System.currentTimeMillis(); 
        System.out.println(ss);
​
        String s;
        byte[] arr_b0 = {102, 108, 97, 103, 0x7B};
        byte[] arr_b11 = {0x7D};
        byte[] arr_b22 = new byte[]{15, 70, 3, 41, 1, 0x30, 35, 0x40, 58, 50, 0, 101, 100, 99, 11, 0x7B, 52, 8, 60, 0x77, 62, 0x73, 73, 17, 16};
        byte[] arr_b3 = ss.getBytes();
        if(25 > arr_b3.length) {
            s = "";
        }
        else {
            for(int v = 0; v < 25; ++v) {
                arr_b22[v] = (byte)(arr_b22[v] ^ arr_b3[v]);
            }
​
            s = new String(arr_b0) + new String(arr_b22) + new String(arr_b11);
        }
​
        System.out.println(s);
}

然后再去查看另一个计算余额的过程,发现其中有涉及到token,就是账号密码的组合值,这个token至少是一个固定值,才可以正确计算出flag,先逆推出token。

public static void main(String[] args) throws Exception {
​
        int[] arr_v = new int[1];
        byte[] arr_bq = new byte[]{81, -13, 84, -110, 72, 77, (byte)0xA0, 77, 0x20, (byte)0x8D, -75, -38, -97, 69, (byte)0xC0, 49, 8, -27, 56, 0x72, -68, -82, 76, -106, -34};
        byte[] arr_b2 = "5FQ5AaBGbqLGfYwjaRAuWGdDvyjbX5nH".getBytes();
        byte[] arr_b3 = new byte[0x100];
        for(int v = 0; v < 0x100; ++v) {
            arr_b3[v] = (byte)v;
        }
​
        if(arr_b2.length == 0) {
            arr_b3 = null;
        }
        else {
            int v2 = 0;
            int v3 = 0;
            for(int v1 = 0; v1 < 0x100; ++v1) {
                v3 = (arr_b2[v2] & 0xFF) + (arr_b3[v1] & 0xFF) + v3 & 0xFF;
                byte b = arr_b3[v1];
                arr_b3[v1] = arr_b3[v3];
                arr_b3[v3] = b;
                v2 = (v2 + 1) % arr_b2.length;
            }
        }
        String string;
​
        int v4 = Math.min(25, arr_bq.length);
        int v5 = 16;
        int v7 = 0;
        int v8 = 0;
        char[] chrCharArray = new char[25];
        for(int v6 = 0; v6 < v4; ++v6) {
            v7 = v7 + 1 & 0xFF;
            v8 = (arr_b3[v7] & 0xFF) + v8 & 0xFF;
            byte b1 = arr_b3[v7];
            arr_b3[v7] = arr_b3[v8];
            arr_b3[v8] = b1;
​
            chrCharArray[v6] = (char) (arr_b3[(arr_b3[v7] & 0xFF) + (arr_b3[v8] & 0xFF) & 0xFF] ^ arr_bq[v6]);
        }
​
        System.out.println(chrCharArray);
​
    }

得到的token为:vvvvipuser_TTTTKRWQGP@001,账号密码就出来了,再把这个token放进去计算flag。

public static void main(String[] args) throws Exception {
​
        String s;
        byte[] arr_b0 = {102, 108, 97, 103, 0x7B};
        byte[] arr_b11 = {0x7D};
        byte[] arr_b22 = new byte[]{15, 70, 3, 41, 1, 0x30, 35, 0x40, 58, 50, 0, 101, 100, 99, 11, 0x7B, 52, 8, 60, 0x77, 62, 0x73, 73, 17, 16};
        byte[] arr_b3 = "vvvvipuser_TTTTKRWQGP@001".getBytes();
        if(25 > arr_b3.length) {
            s = "";
        }
        else {
            for(int v = 0; v < 25; ++v) {
                arr_b22[v] = (byte)(arr_b22[v] ^ arr_b3[v]);
            }
​
            s = new String(arr_b0) + new String(arr_b22) + new String(arr_b11);
        }
​
        System.out.println(s);
    }

得到flag:flag{y0u_h@V3_@_107_0f_m0n3y!!}

[鹤城杯 2021]designEachStep

这个APP流程有点麻烦,但不难,就是需要把输入的字符串分为三部分,每8个字符跟特定的数据比对,相同则下一步,也就是可以跟据特定的数据来获取那8个字符串,首先是data.bin文件前8个字节,然后是des解密后再用Inflater压缩获取前8位跟输入字符串的中间8位一致,des再解密后LZ4解压缩获取最后字符串的8位。反编译代码太长这里不贴,直接写计算代码:

下面的data是我手动解压后,不然还需要再进行一次解压缩。

public static void getEncodeStr() throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        byte[] des_decry;
        byte[] arr_b14;
        byte[] arr_b10 = new byte[0];
        byte[] arr_b3 = null;
        try (FileInputStream fis = new FileInputStream("E:\\Java_File\\File\\src\\data")) {
            byte[] header = new byte[8];
            int bytesRead = fis.read(header, 0, 8);
            byte[] data = new byte[fis.available()];
            bytesRead = fis.read(data);
​
            Cipher cipher0 = Cipher.getInstance("DES");
            cipher0.init(2, j(header));
            des_decry = cipher0.doFinal(data);
​
            Inflater inflater = new Inflater();
            inflater.setInput(des_decry);
            ByteArrayOutputStream byteArrayOutputStream1 = new ByteArrayOutputStream(des_decry.length);
            try {
                byte[] des_decry2 = new byte[0x400];
                while (!inflater.finished()) {
                    byteArrayOutputStream1.write(des_decry2, 0, inflater.inflate(des_decry2));
                }
                byteArrayOutputStream1.close();
            } catch (Exception exception0) {
                System.out.println("error");
            }
​
            byte[] output = new byte[des_decry.length];
            int resultLength = inflater.inflate(output);
            inflater.end();
            byte[] result = byteArrayOutputStream1.toByteArray();
            byte[] arr_b12 = new byte[0];
            if (result.length >= 8) {
                arr_b12 = new byte[8];
                System.arraycopy(result, 0, arr_b12, 0, 8);
                byte[] arr_b13 = new byte[result.length - 8];
                System.arraycopy(result, 8, arr_b13, 0, result.length - 8);
                Cipher cipher1 = Cipher.getInstance("DES");
                cipher1.init(2, j(arr_b12));
                arr_b14 = cipher1.doFinal(arr_b13);
                arr_b10 = arr_b14;
            }
​
            LZ4SafeDecompressor lZ4SafeDecompressor0 = LZ4Factory.safeInstance().safeDecompressor();
            byte[] arr_b15 = new byte[arr_b10.length * 5];
            int v1 = lZ4SafeDecompressor0.decompress(arr_b10, 0, arr_b10.length, arr_b15, 0);
            byte[] arr_b16 = new byte[v1];
            System.arraycopy(arr_b15, 0, arr_b16, 0, v1);
            byte[] arr_b17 = new byte[0];
            if (v1 >= 8) {
                arr_b17 = new byte[8];
                System.arraycopy(arr_b16, 0, arr_b17, 0, 8);
                int v2 = v1 - 8;
                byte[] arr_b18 = new byte[v2];
                System.arraycopy(arr_b16, 8, arr_b18, 0, v2);
                Cipher cipher2 = Cipher.getInstance("DES");
                cipher2.init(2, j(arr_b17));
                arr_b3 = cipher2.doFinal(arr_b18);
            }
            byte[] m = mergeBytes(header, arr_b12, arr_b17);
​
            System.out.println(new String(m));
        } catch (DataFormatException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static byte[] mergeBytes(byte[]... bytes) {
        int length = 0;
        for (byte[] b : bytes) {
            length += b.length;
        }
        byte[] result = new byte[length];
        int destPos = 0;
        for (byte[] b : bytes) {
            System.arraycopy(b, 0, result, destPos, b.length);
            destPos += b.length;
        }
        return result;
    }
    
    public static Key j(byte[] arr_b) {
        byte[] arr_b1 = new byte[8];
        for(int v = 0; v < arr_b.length && v < 8; ++v) {
            arr_b1[v] = arr_b[v];
        }
​
        return new SecretKeySpec(arr_b1, "DES");
    }

得到flag的值:DE5_c0mpr355_m@y_c0nfu53

[GFCTF 2021]easy_low

反编译得到一个类似账号密码判断的流程,但是需要逆推出

image-20230427175818488

输入的账号也就是字符串s进行encode操作

image-20230427175901885

字符串计算前后相等,这样可以爆破出账号s,得到结果为:LOHILMNMLKHILKHI

public static int encode(byte[] arr_b) {
        byte[] arr_b2 = new byte[16];
        byte[] ast = "qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM".getBytes();
​
        for(int v1 = 0; v1 < 16; ++v1) {
            for(int x = 0; x < ast.length; ++x) {
                arr_b2[v1] = (byte)((ast[x] + arr_b[v1]) % 61);
​
                arr_b2[v1] = (byte)(arr_b2[v1] * 2 - v1);
                if (arr_b2[v1] == ast[x]) {
                    System.out.println((char) arr_b2[v1]);
                }
            }   //LOHILMNMLKHILKHI
        }
        return 0;
    }

然后再去计算密码,可得到结果:nmjknoloni_@0011

public static void main(String[] args) throws Exception {
​
        byte[] arr_b = {0x40, 0x30, 0x30, 49, 49};
        byte[] arr_b1 = encode("LOHILMNMLKHILKHI", new byte[]{23, 22, 26, 26, 25, 25, 25, 26, 27, 28, 30, 30, 29, 30, 0x20, 0x20});
        for(int v = 0; v < arr_b1.length; ++v) {
            arr_b1[v] = (byte)(arr_b1[v] ^ 34);
        }
​
        char[] arr_c = new String(arr_b1).toCharArray();
        String s2 = "";
        for(int v1 = 0; v1 < 10; ++v1) {
            s2 = s2 + arr_c[v1];
        }
        System.out.println(s2 + "_" + new String(arr_b));  //nmjknoloni_@0011
​
    }

然后找到b类,这个类应该才是最后的验证,其中有个f0.oho,这个是JNI层函数调用,这里的O().v跟上面的计算过程一样,都是encode的过程。所以输入的参数为:密码,用户名,拼接字段。

image-20230427183126137

这里直接贴最后的WP,不要问,问就是没还原出来。。。

#include <bits/stdc++.h>
using namespace std;
int main() {
  char key1[]={"nNjLnHlL"};
  int count=1;
  int key[7];//nNjLnHlL
  for (int i = 0; i < 6; ++i) {
    key[i] = key1[count];
    count++;
  }
  unsigned int ks[6]={0x5d950ef2,0x86cca2de,0xc039bbf4,0xc5948102,0xaed55e9c,0x89f14377};
  unsigned int k=0,bk=0;
  unsigned int p[4];
  for(int i=5;i>=0;i--)
    if(i>0) ks[i]^=ks[i-1];
  for(int i=0;i<24;i+=4){
    k=ks[i/4];
    k=(1<<key[i/4])^k;
    k=((k>>16)) | ((~(k<<16))&0xffff0000); 
    k=((k<<key[i/4])) | (k>>(32-key[i/4]));
    for(int j=0; j<4; j++) printf("%c", *((char*)&k+3-j));
  }
  return 0;
}

结果就是:WelCOme_To_mAkaBakA!BrO!





# Android逆向  

tocToc: