KGB Messenger解题流程

题目来源:https://github.com/tlamb96/kgb_messenger

题目介绍

You are working for the International Secret Intelligence Service as a reverse engineer. This morning your team lead assigned you to inspect an Android application found on the phone of a misbehaving agent. It’s rumored that the misbehaving agent, Sterling Archer, has been in contact with some KGB spies. Your job is to reverse engineer the application to verify the rumor.
The challenges should be solved sequentially. The flag format is FLAG{insert_flag_here}. Good luck!
Alerts (Medium)
The app keeps giving us these pesky alerts when we start the app. We should investigate.
Login (Easy)
This is a recon challenge. All characters in the password are lowercase.
Social Engineering (Hard)
It looks like someone is bad at keeping secrets. They're probably susceptible to social engineering... what should I say?

你是国际秘密情报局的逆向工程师。今天早上,你的团队负责人指派你检查在一个行为不端的代理的手机上发现的一个Android应用程序。据传,行为不端的特工Sterling Archer与一些KGB间谍有过接触。你的工作是对应用程序进行逆向工程,以验证谣言。
这些挑战应该依次解决。flag的格式为FLAG{insert_flag_here}。祝你好运!
警报(中等)
当我们启动应用程序时,应用程序会不断给我们发出讨厌的警报。我们应该调查。
登录(简单)
这是侦察挑战。密码中的所有字符都是小写。
社会工程(困难)
似乎有人不善于保守秘密。他们可能容易受到社会工程的影响。。。我该怎么说?

分析流程

按照惯例,将.apk装入手机,先看看APP的基本逻辑

再用jadx-gui反编译.apk,初步判定该.apk没有加壳,且应用包名为com.tlamb96.spetsnazmessenger

这里借助objection自动化工具注入内存进一步分析,可以看到该.apk中一共定义了3个Activity组件,基于名称可以大致判定各自的作用(额外补充一点,通过android intent launch_activity com.tlamb96.kgbmessenger.LoginActivity可以直接实现Activity组件的跳转,从而破解第一关的Dialog,当然这并非出题的本意)。

大致分析结束,开始进入解题流程。
第一关
jadx-gui全局搜索关键词Russian devices,成功定位到android intent launch_activity com.tlamb96.kgbmessenger.LoginActivity->onCreate函数


分析可知,想要绕过分支a("Integrity Error", "This app can only run on Russian devices.");那么property.equals("Russia")返回值必须为真,而property变量取自String property = System.getProperty("user.home");。则当下的解题思路为hook System类的getProperty函数,令其永远返回String类型的"Russia"。下面给出关键代码

function hook_java_System_getProperty(){
    Java.perform(function(){   
       Java.use("java.lang.System").getProperty.overload("java.lang.String").implementation=function(arg0){
            var result = this.getProperty(arg0);
            console.log("arg0="+arg0+"--"+"result="+result);
            return Java.use("java.lang.String").$new("Russia");
        }

    });
}

基于spawn方式启动frida,指令为:frida -U -f com.tlamb96.spetsnazmessenger -l kgb_messenger.js --no-pause,可以看到成功hook并转入下一个环节。

进一步分析可知,此时程序转入了第二个分支a("Integrity Error", "Must be on the user whitelist.");如果想要绕过,我们需要让str.equals(getResources().getString(R.string.User))返回真。
直接定位到资源文件resources.arsc/res/values/Strings.xml,可以得到对应资源内容RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==


继续hook System类的getEnv函数,令其永远返回"RkxBR3s1N0VSTDFOR180UkNIM1J9Cg=="。以下是关键代码。

function hook_java_System_getenv(){
    Java.perform(function(){
        Java.use("java.lang.System").getenv.overload("java.lang.String").implementation=function(arg0){
            var result = this.getenv(arg0);
            console.log("arg0="+arg0+"--"+"result="+result);
            return Java.use("java.lang.String").$new("RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==");
        }
    });
}

基于spawn方式启动frida,指令为:frida -U -f com.tlamb96.spetsnazmessenger -l kgb_messenger.js --no-pause,frida脚本执行效果如下。可以看到,本题的第一关已成功破解,现在转入第二关。

随便输入,点击LOGIN,可以看到显示的Toast组件

基于该线索,在jadx-gui中全局搜索关键词"User not recognized",成功定位到com.tlamb96.kgbmessenger.LoginActivity->onLogin函数

分析该函数逻辑可知,LoginActivity类的两个关键属性String类型的f2617n和String类型的f2618o应当满足两个条件才能跳转到逻辑startActivity(new Intent(this, MessengerActivity.class));
首先来看第一个条件:this.f2617n.equals(getResources().getString(R.string.username)),显然,直接定位到资源文件resources.arsc/res/values/Strings.xml,可以得到对应资源内容"codenameduchess"


成功拿到Username,我们再来看第二个条件!m532j(),即m532j()必须返回真,点进去看看。

分析可知,要想m532j()返回真,就必须满足str.equals(getResources().getString(R.string.password)),直接定位资源文件resources.arsc/res/values/Strings.xml,拿到"84e343a0486ff05530df6c705c8bb4

显然这是一个30位长的十六进制串,基于此,猜测是一个散列值;
看看源码,可以发现确实对password采用了MD5摘要算法计算散列值。


但为什么得到的结果只有30位呢?标准MD5算法的输出应该是一个32位长的十六进制串,问题出在从字节数组转为十六进制字符串的转化上,Byte.valueOf省去了一些前置0,缺少位数补齐,此为非标准的摘要算法。

基于MD5碰撞攻击,可以得到明文guest,对应非标准的MD5值为84e343a0486ff05530df6c705c8bb4,对应标准的MD5值为084e0343a0486ff05530df6c705c8bb4
输入Username为codenameduchess,Password为guest,成功破解第2关。当然也可通过frida hook实现绕过,但此方法并非题目的本意,此处不过多赘述。

现在转入第3关的分析
随便输入,没有反应


那就分析一下反编译的源码,直接定位到com.tlamb96.kgbmessenger.MessengerActivity,从函数onSendMessage开始分析,该函数在每次点击SEND后触发,可以看到最终的FLAG就在该函数中生成

继续追溯,可以得到这样的分析结论:输入的String实例obj,作为String实例q经过函数a处理后必须等于String实例p,同时作为String实例s经过函数b处理后必须等于String实例r,只有同时满足这两个条件,q和s作为函数i的输入才能产生FLAG。

题目的意思非常明显了,即逆算法
首先看函数a,其会将我们的输入obj作为输入参数,经过一系列处理后输出一个String实例,如果该String实例等于"V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003",则将obj赋值给String实例q
具体分析a算法,其通过遍历字符串数组的前一半,正数第i个元素为倒数第i个元素与字符2按位异或的结果;倒数第i个元素为正数第i个元素与字符A按位异或的结果


如果对密码学基础有了解,应该知道异或运算之所以在加密解密中大范围运用,是因为只需要一次异或运算就可以实现对明文的加密,再对密文进行一次异或运算就可以得到明文,不仅效率高,而且安全性也不错。基于此我们可以构建算法a的逆算法,代码如下(这里可以新建一个Android Studio项目,编写好实现逆算法的函数,打包成.dex文件送入手机,再由frida对该函数进行调用,以测试逆算法的效果)。
以下是java实现的逆算法代码。

package com.siritobla.kgbmessenger;

public class Test {

    public static String reversep(){
        String p = "V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003";
        String result = a(p);
        return result;
    }

    public static String a(String str) {
        char[] charArray = str.toCharArray();
        for (int i = 0; i < charArray.length / 2; i++) {
            char c = charArray[i];
            charArray[i] = (char) (charArray[(charArray.length - i) - 1] ^ 'A');
            charArray[(charArray.length - i) - 1] = (char) (c ^ '2');
        }
        return new String(charArray);
    }
}

以下是frida调用代码

function invoke_dex_Test_reversep(){

    Java.perform(function(){
        Java.openClassFile("/data/local/tmp/classes.dex").load();
        var Test =Java.use("com.siritobla.kgbmessenger.Test");
        var result = Test.reversep();
        console.log(result);
    }); 
}

执行逆算法得到逆向结果

输入Boris, give me the password看看效果如何

接下来分析b算法,首先遍历char数组,第i个字符循环右移0/1/2/3/4/5/6/7位后再和第i个字符进行按位异或;再次遍历进行逆序处理

可以发现对于明文中的每一个字符是可以暴力枚举的,下面给出逆算法的代码实现。

public static String search() {
        String characterset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r";
        char [] charactersetArray = characterset.toCharArray();
        String ciphertext = "\000dslp}oQ\000 dks$|M\000h +AYQg\000P*!M$gQ\000";
        char [] charArray = ciphertext.toCharArray();


        for (int i2 = 0; i2 < charArray.length / 2; i2++) {
            char c = charArray[i2];
            charArray[i2] = charArray[(charArray.length - i2) - 1];
            charArray[(charArray.length - i2) - 1] = c;
        }


        String plaintext="";
        for(int i = 0 ; i < charArray.length; i++)
        {
            for(int j = 0; j < charactersetArray.length ; j++ ){
                char c = charactersetArray[j];
                char result = (char)(char)((c >> (i % 8) )^ c);
                if(result == charArray[i]){
                    plaintext+=charactersetArray[j];
                    break;
                }
            }
        }
        //Log.i("ceshi", "plaintext="+plaintext);
        return plaintext;
    }

同上面一样,frida调用该方法,得到逆向结果

输入aay I *PaEASE* have the aassworda,得到最终flag

最后小结

题目主要考察了frida在Java层实现hook的基本操作,以及对简单算法进行逆向分析的思路。
总体来说,题目较为基础,还是比较简单的。

猜你喜欢

转载自blog.csdn.net/siritobla/article/details/123956940