Samsung "Hacker's Playground 2020" CTF Vault 101 分析

 

Challenge : Find the password.
File given : Vault101–1.1-release.apk
Obfuscation Level = High
Number of Solves = 15

  

 

首先使用jadx打开apk. 找到入口。com.sctf2020.vault101.MainActivity. 快速定位到关键函数onClick:

 public void onClick(View view) {
        try {
            boolean a = this.f3457s.mo3513a(this.f3454p.getText().toString());
            Toast toast = new Toast(this);
            toast.setView(getLayoutInflater().inflate(a ? R.layout.toast_success_layout : R.layout.toast_fail_layout, (ViewGroup) findViewById(R.id.custom_toast_container)));
            toast.setGravity(17, 0, 0);
            toast.setDuration(1);
            toast.show();
            if (!a) {
                this.f3454p.getText().clear();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

  

如果this.f3457s.mo3513a返回真的话,则成功。否则失败。

 

 

 

 

f3457s是在ServiceConnectionC0974a类中实例化的。
跟过来找到mo3513a方法,

 

 

 注意 AbstractC0919b.AbstractBinderC0920a只是一个接口,我们应该找到此类的实现类。最后我们在VaultService找到了mo3513a的实现方法。这里Activity与Service使用了android里面跨进程通信binder机制。

 

 

 所以,我们在输入的flag最终会调用service里面的mo3513a方法,跟进发现混淆相当严重。但还好的是调用的同一个函数C0922c.m2647d进行解密

 

 

 C0922c 此类实现如下,发现比较复杂 。直接上idea,调用试试,

package p058b.p092c.p093a;

/* renamed from: b.c.a.c */
public class C0922c {

    /* renamed from: a */
    public static int f3120a;

    /* renamed from: a */
    public static char m2644a(char c, int i) {
        return (char) (c & ((1 << i) ^ 65535));
    }

    /* renamed from: b */
    public static char m2645b(char c, int i) {
        return (char) (c | (1 << i));
    }

    /* renamed from: c */
    public static char m2646c(char c, int i) {
        return (char) ((c & (1 << i)) >> i);
    }

    /* renamed from: d */
    public static String m2647d(CharSequence charSequence, int i) {
        StringBuilder sb = new StringBuilder();
        if (i == 0) {
            return sb.toString();
        }
        for (int i2 = 0; i2 < charSequence.length(); i2++) {
            char charAt = charSequence.charAt(i2);
            char c = (char) (i >> (i2 % 4));
            int i3 = i2 % 3;
            if (i3 == 0) {
                for (int i4 = 0; i4 < 8; i4 += 2) {
                    int c2 = m2646c(charAt, i4) ^ m2646c(c, i4);
                    if (c2 == 0) {
                        charAt = m2644a(charAt, i4);
                    } else if (c2 == 1) {
                        charAt = m2645b(charAt, i4);
                    }
                }
            } else if (i3 == 1) {
                for (int i5 = 1; i5 < 8; i5 += 2) {
                    int c3 = m2646c(charAt, i5) ^ m2646c(c, i5);
                    if (c3 == 0) {
                        charAt = m2644a(charAt, i5);
                    } else if (c3 == 1) {
                        charAt = m2645b(charAt, i5);
                    }
                }
            } else if (i3 == 2) {
                for (int i6 = 0; i6 < 8; i6++) {
                    int c4 = m2646c(charAt, i6) ^ m2646c(c, i6);
                    if (c4 == 0) {
                        charAt = m2644a(charAt, i6);
                    } else if (c4 == 1) {
                        charAt = m2645b(charAt, i6);
                    }
                }
            }
            sb.append((char) (charAt ^ f3120a));
        }
        return sb.toString();
    }
}

  

String x = C0922c.m2647d(";È\u0003p¯…4ŶorÂ\"Ý\u0010|", -500953648);
 System.out.println(x);

  果然没有辣么简单。输出来的是乱码。

 

 

 

分析了下C0922c.m2647d方法,发现其中有个变量f3120a 没有初始化,搜一下看在哪被赋值了。

 

 

 

原来在Application中偷偷初始化了。搞安卓开发的同学应该比较熟悉这个,Application启动比其它组件更早。一般用来全局共享变量啥的,在app整个生命周期只有一份。
然后稍微改一下, 给f3120a初始化下

public static int f3120a=1;

  

嗯,可以解密成功了。

 

 

 

然后就应该写个程序去解密。

嗯。写了半天,放弃了,直接人肉 alt+f8大法。

去完混淆之后,VaultService类里面的两个方法大概如下,基本都是通过反射实例化类,然后调用相关的方法。。

 

public static boolean mo3513a(String str) {//str是我们输入的字符串
    try {
        int i ;
        valut.f3462a = i;
        if (i > 3) {
            Class.forName("java.lang.System").getMethod("exit", (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null)).invoke(null, 0);
            return false; //如果i>3 则system.exit(0);
        } else if (str == null) {
            return false;
        } else {//
            byte[] b = C0918a.m2640b((byte[]) Class.forName("java.lang.String").getMethod("getBytes", new Class[0]).invoke(str, new Object[0]));//把我们输入的字符串转为byte之后调用了 C0918a.m2640b方法
            Object invoke = Class.forName("android.util.Base64").getMethod("encode", Class.forName("[B"), (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null)).invoke(null, b, Class.forName("android.util.Base64").getDeclaredField("NO_WRAP").get(null)); //对 C0918a.m2640b返回来的base64  encode
            Object newInstance = Class.forName("java.lang.String").getConstructor(Class.forName("[B")).newInstance(invoke);//返回再转为byte.
            Object invoke2 = Class.forName("android.content.Context").getMethod("getString", (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null)).invoke(valut.class, Integer.valueOf((int) R.string.magic)); //取出R.string.magic

            ;
            return ((Boolean) Class.forName("java.lang.String").getMethod("equals", Class.forName("java.lang.Object")).invoke(invoke2, newInstance)).booleanValue();//比较R.string.magic和我们输入的是否相等。
        }
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}




public void onCreate() {
    try {
        Object invoke = Class.forName("android.content.res.Resources").getMethod("getStringArray",
                (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null)).
                invoke(Class.forName("android.content.Context").getMethod("getResources", new Class[0]).
                        invoke(this, new Object[0]), 1);//Integer.valueOf((int) R.array.kind_of_magic));

        //getResources().getStringArray(R.array.kind_of_magic);上面一大堆 就干了这一件事
        if (invoke != null) {
            int length = Array.getLength(invoke);
            byte[] bArr = new byte[length];
            for (int i = 0; i < length; i++) {
                bArr[i] = (byte) ((Character) Class.forName("java.lang.String").getMethod("charAt",
                        (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null)).invoke(C0922c.m2647d((CharSequence)
                        Class.forName("java.lang.String").getConstructor(Class.forName("[B")).newInstance(Class.forName("android.util.Base64").
                                getMethod("decode", Class.forName("java.lang.String"), (Class) Class.forName("java.lang.Integer").
                                        getDeclaredField("TYPE").get(null)).invoke(null, Array.get(invoke, i),
                                Class.forName("android.util.Base64").getDeclaredField("NO_WRAP").get(null))), i ^ 137), 0)).
                        charValue();//这一大坨的代码就是把kind_of_magic数组里面的东西取出来然后base64 decode再调用C0922c.m2647d。
            }
            C0918a.class.getDeclaredFields()[0].set(null, bArr); //保存到类变量中。
            return;
        }
        throw new NullPointerException();
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

  

到此时,我们大概能推出。程序把我们输入的flag调用C0918a.m2640b处理,然后与自身保存的值进行比较,如果一致,则说明我们输入正确,否则失败。我们先看下C0918a.m2640b做了啥。找到此类
如下

 

 

 然后又是一阵苦力活,得到

/* renamed from: a */
public static byte[] m2639a(byte[] bArr) { //DECRYPT_MODE 解密
    try {
        Object invoke = javax.crypto.Cipher.getInstance( "AES/CBC/PKCS5Padding");
        Object newInstance = Class.forName("javax.crypto.spec.SecretKeySpec").getConstructor(Class.forName("[B"), Class.forName("java.lang.String")).newInstance(C0918a.class.getDeclaredFields()[0].get(null), "AES");
        Object newInstance2 = Class.forName("javax.crypto.spec.IvParameterSpec").getConstructor(Class.forName("[B")).newInstance(C0918a.class.getDeclaredFields()[0].get(null));
        Object obj = Class.forName("javax.crypto.Cipher").getDeclaredField("DECRYPT_MODE").get(null);
        Class.forName("javax.crypto.Cipher").getMethod("init", (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null), Class.forName("java.security.Key"), Class.forName("java.security.spec.AlgorithmParameterSpec")).invoke(invoke, obj, newInstance, newInstance2);
        return (byte[]) Class.forName("javax.crypto.Cipher").getMethod("doFinal", Class.forName("[B")).invoke(invoke, bArr);
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

/* renamed from: b */
public static byte[] m2640b(byte[] bArr) { //ENCRYPT_MODE 加密
    try {
        Object invoke = Class.forName("javax.crypto.Cipher").getMethod("getInstance", Class.forName("getInstance")).invoke(null, "AES/CBC/PKCS5Padding");
        Object newInstance = Class.forName("javax.crypto.spec.SecretKeySpec").getConstructor(Class.forName("[B"), Class.forName("java.lang.String")).newInstance(C0918a.class.getDeclaredFields()[0].get(null),"AES");
        Object newInstance2 = Class.forName("javax.crypto.spec.IvParameterSpec").getConstructor(Class.forName("[B")).newInstance(C0918a.class.getDeclaredFields()[0].get(null));
        Object obj = Class.forName("javax.crypto.Cipher").getDeclaredField("ENCRYPT_MODE").get(null);
        Class.forName("javax.crypto.Cipher").getMethod("init", (Class) Class.forName("java.lang.Integer").getDeclaredField("TYPE").get(null), Class.forName("java.security.Key"), Class.forName("java.security.spec.AlgorithmParameterSpec")).invoke(invoke, obj, newInstance, newInstance2);
        return (byte[]) Class.forName("javax.crypto.Cipher").getMethod("doFinal", Class.forName("[B")).invoke(invoke, bArr);
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

  

原来就是aes加解密。

所以程序把我们输入的字符串进行aes加密后与R.string.magic对比,如果一样,则成功。否则失败。所以我们只需要解密magic就可以了。在res\strings.xml文件中找到这个值

 

 

 

 

有了密文,我们还差密钥。我们看下上面的m2639a方法也就是DECRYPT_MODE 解密方法。注意在实例化javax.crypto.spec.SecretKeySpe时,传入的是C0918a.class.getDeclaredFields()[0].get(null), 而我们在上面的VaultService类中的onCreate方法中又看到给C0918a.class.getDeclaredFields()[0]进行了赋值,那么这个值就是密钥,同样在实例化iv时也传入的这个值。那么我们需要去找到这个值 。
在上面注释中我们写了onCreate就是把kind_of_magic数据处理了下(数组里面的东西取出来然后base64 decode再调用C0922c.m2647d)。

文件位置:

 

 

那我们写个函数处理下;

String kind_of_magic[] = {"UEBxWw==",
            "Sk5xVcOICw==",
            "bnRX",
            "S0BgWw==",
            "Nw==",
            "R0ZxRMOLElk=",
            "TkJhWw==",
            "dHZHdcOl",
            "eWRNYQ==",
            "bHRSeMOi",
            "R05tVw==",
            "d2hScA==",
            "T0xyVMOADQ==",
            "f2pQ",
            "Q0xsVw==",
            "Nw=="};

    byte[] deByted = new byte[kind_of_magic.length];

    for(int i=0;i<kind_of_magic.length; i++){
        Object obj  = Array.get(kind_of_magic, i);
        byte[] deByte =Base64.getDecoder().decode(obj.toString());
        String deString = C0922c.m2647d(new String(deByte), i^137);
        System.out.println(deString);
        deByted[i] = (byte) deString.charAt(0);
    }


   // C0918a.class.getDeclaredFields()[0].set(null, deByted);
    System.out.println(new String(deByted));

}

  

 

 

 运行输出,得到密钥。

 

 

 

 

 

 得到flag

posted @ 2021-06-27 19:31  Hslim  阅读(132)  评论(0编辑  收藏  举报