首页
社区
课程
招聘
[原创]看雪CTF_2018 团队赛 第五题 write up(吃我十六字阴阳风水秘术)
2018-12-9 23:32 3223

[原创]看雪CTF_2018 团队赛 第五题 write up(吃我十六字阴阳风水秘术)

2018-12-9 23:32
3223

环境配置

系统 : Windows 7、nexus 5
程序 : CrackMe.KwaiChing.apk
要求 : 输入口令
使用工具 : 称骨算命、Android Studio、jadxapktool

搜集线索

在测试机中安装该apk,发现程序需求一个key,如果以123这样的随机数作为输入的话,会输出failed!!!

 

我们使用apktool反编译这个程序

apktool d CrackMe.KwaiChing.apk

然后在\res\values\strings.xml位置查看字符串信息:

    <string name="me">"
s \u0009\u0009u \u0009\u0009\u0009c \u0009\u0009\u0009\u0009c \u0009\u0009\u0009\u0009\u0009e \u0009\u0009\u0009\u0009\u0009\u0009s \u0009\u0009\u0009\u0009\u0009\u0009\u0009s \u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009! \u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009! 





"</string>
    <string name="notMe">f \u0009a \u0009\u0009i \u0009\u0009\u0009l \u0009\u0009\u0009\u0009e \u0009\u0009\u0009\u0009\u0009d \u0009\u0009\u0009\u0009\u0009\u0009! \u0009\u0009\u0009\u0009\u0009\u0009\u0009! \u0009\u0009\u0009\u0009\u0009\u0009\u0009\u0009!</string>
    <string name="ok">驗證</string>
    <string name="search_menu_title">Search</string>
    <string name="status_bar_notification_info_overflow">999+</string>
    <string name="success00" />
    <string name="success34">"
財穀有餘主得內助富貴之命


此命福氣果如何,僧道門中衣祿多;
離祖出家方為妙,朝晚拜佛念彌陀。



此命推來為人性躁;
與人做事反為不美;
離祖成家;
三番四次自成自立安享福;
直自三十六至四十六;
財不謀而自至;
福不求而自得;
有貴人助;
家庭安寧;
妻宮若要無刑;
猴、豬、羊、蛇不可配;
龍、虎、馬、牛方得安;
雖有二子;
終生帶暗方可.
兄弟六親如冰碳;
在家不得安然;
初限駁雜多端;
勞碌奔波不能聚錢;
常有憂愁.
壽元七十八歲;
死於三月中."</string>

看来存在有一个提示正确和一个提示错误的字符串,还有就是success34的迷之字符串。我搜索了一下,居然发现了存有相关信息的网页

 

看来是一个玄学题目(笑

开始分析

我们运行jadx:

D:\software\jadx-0.8.0(1)\lib>java -jar jadx-gui-0.8.0.jar

定位到p007cn.kwaiching.crackme包下的CrackMe类,发现这就是算法的主流程:

    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_fate_me);
        b();
        this.n = (TextView) findViewById(R.id.fate);
        ((Button) findViewById(R.id.ok)).setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                try {
                    CrackMe.this.a();
                } catch (Exception unused) {
                    CrackMe.this.n.setText(CrackMe.this.getString(R.string.notMe));
                }
            }
        });
    }

来看看a的流程:

    private void a() {
        try {
            c();
            if (this.j == 0 || this.i == 0 || this.h == 0) {
                this.n.setText(getString(R.string.notMe));
                return;
            }
            d();
            a(((e() + f()) + g()) + h());
        } catch (Exception unused) {
            this.n.setText(getString(R.string.notMe));
        }
    }

我们先来查看h函数的部分代码:

   /* JADX WARNING: Removed duplicated region for block: B:17:0x0050 A:{Catch:{ Exception -> 0x005c }} */
    private int h() {
        /*
        r6 = this;
        r0 = 2131165227; // 0x7f07002b float:1.7944665E38 double:1.0529355243E-314;
        r1 = 2131427370; // 0x7f0b002a float:1.8476354E38 double:1.05306504E-314;
        r2 = 0;
        r0 = r6.findViewById(r0);     Catch:{ Exception -> 0x005c }

这样的代码就是jadx的反编译出了点问题,我们需要设置一下选项,让代码的可读性变高。

代码修饰

这里,在jadx的菜单中点击文件->设置->启用反混淆,接着选择
显示不一致的代码。这时,可以发现代码漂亮了不少。

导入as查看

在jadx中查看,不如导入到android stdio(下文中简称为as)这样专业的ide中查看,这样能修改变量名,快速查看引用位置等等。

 

这里,在jadx的菜单中点击文件->另存为 gradle项目,选中一个任意一个空的文件夹,然后保存就可以。

流程分析

在as中,发现主流程是:

    private void m162a() {
        try {
            m166c();
            if (this.year == 0 || this.month == 0 || this.day == 0) {
                this.f125n.setText(getString(C0252R.string.notMe));
                return;
            }
            m167d();
            m163a(((m168e() + m169f()) + m170g()) + m171h());
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
        }
    }

m166c函数的流程是:

    /* renamed from: c */
    private void m166c() {
        try {
            String obj = ((EditText) findViewById(C0252R.id.code)).getText().toString();
            this.year = 0;
            this.month = 0;
            this.day = 0;
            this.year = Integer.parseInt(obj.length() > 4 ? obj.substring(0, 4) : obj);
            if (this.year > 0 && this.year < 189) {
                this.year = 0;
            }
            if (this.year <= 1983 || this.year >= 2007) {
                this.year = 0;
            }
            this.month = Integer.parseInt(obj.length() > 6 ? obj.substring(4, 6) : obj);
            if (this.month < 1 || this.month > 12) {
                this.month = 0;
            }
            if (obj.length() > 8) {
                obj = obj.substring(6, 8);
            }
            this.day = Integer.parseInt(obj);
            if (this.day < 1 || this.day > 31) {
                this.day = 0;
            }
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
        }
    }

稍微分析一下,可知程序把输入限定在:

  1. 年份:1983后,2007前。
  2. 月份:1-12
  3. 日期:1-31

再来看看其他的函数:

    private void m167d() {
        try {
            if (this.year == 1989 || this.year == 2004) {
                this.day = 31;
            }
            if (this.month == 1 || this.month == 4 || this.month == 5 || this.month == 7 || this.month == 10 || this.month == 11 || this.month == 12) {
                this.year = 1999;
            }
            if (this.year <= 1994 && (this.month == 2 || this.month == 6 || this.month == 8)) {
                this.month = 3;
            }
            if (this.year >= 1996 && (this.month == 2 || this.month == 6 || this.month == 8)) {
                this.month = 9;
            }
            if (this.year == 1995 && (this.day > this.month + 2 || this.month == this.day)) {
                this.month = 6;
            }
            this.my_year = this.year;
            this.my_month = this.month;
            this.my_day = this.day;
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
        }
    }

这里其实是将可能的输入范围减少了,比如第一个判断,2004和1989年的所有可选日期都是31号。以此类推。

四个小函数

    private int m168e() {
        try {
            return this.f115d[(this.my_year - 1900) % 60];
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
            return 0;
        }
    }

    /* renamed from: f */
    private int m169f() {
        try {
            return this.f114c[this.my_month - 1];
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
            return 0;
        }
    }

    /* renamed from: g */
    private int m170g() {
        try {
            return this.f113b[this.my_day - 1];
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
            return 0;
        }
    }

    /* JADX WARNING: Removed duplicated region for block: B:17:0x0050 A:{Catch:{ Exception -> 0x005c }} */
    /* Code decompiled incorrectly, please refer to instructions dump. */
    /* renamed from: h */
    private int m171h() {
        try {
            Object obj;
            String other_str = ((EditText) findViewById(C0252R.id.code)).getText().toString();
            other_str = other_str.substring(8, other_str.length());
            int MONTH = this.my_month;
            int i2 = 0;
            while (i2 < this.f124m.length) {
                if (!other_str.equals(this.f124m[i2])) {
                    i2++;
                } else if (MONTH == 2 && other_str.equals(this.f124m[6])) {
                    return 63;
                } else {
                    this.f122k = this.f112a[i2];
                    obj = 1;
                    if (obj == null) {
                        this.f125n.setText(getString(C0252R.string.notMe));
                    }
                    return this.f122k;
                }
            }
            obj = null;
            if (obj == null) {
            }
            return this.f122k;
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
            return 0;
        }
    }
}

最本质的操作都是从固定数组中取值,然后返回相关值。以下是相关数组:

    /* renamed from: a */
    int[] f112a = new int[]{16, 6, 7, 10, 9, 16, 10, 8, 8, 9, 6, 6};
    /* renamed from: b */
    int[] f113b = new int[]{5, 10, 8, 15, 16, 15, 8, 16, 8, 16, 9, 17, 8, 17, 10, 8, 9, 18, 5, 15, 10, 9, 8, 9, 15, 18, 7, 8, 16, 6};
    /* renamed from: c */
    int[] f114c = new int[]{6, 7, 18, 9, 5, 16, 9, 15, 18, 8, 9, 5};
    /* renamed from: d */
    int[] f115d = new int[]{7, 7, 9, 12, 8, 7, 13, 5, 14, 5, 9, 17, 5, 7, 12, 8, 8, 6, 19, 6, 8, 16, 10, 6, 12, 9, 6, 7, 12, 5, 9, 8, 7, 8, 15, 9, 16, 8, 8, 19, 12, 6, 8, 7, 5, 15, 6, 16, 15, 7, 9, 12, 10, 7, 15, 6, 5, 14, 14, 9};
    String[] f124m = new String[]{"23to01", "01to03", "03to05", "05to07", "07to09", "09to11", "11to13", "13to15", "15to17", "17to19", "19to21", "21to23"};

判断函数

这个判断函数,emmm...

    private void m163a(int i) {
        if (i > 34 || i < 34) {
            this.f125n.setText(getString(C0252R.string.notMe));
            return;
        }
        try {
            this.f125n.setText(String.format("%s%s", new Object[]{getString(C0252R.string.me), this.f123l[i]}));
            ((Button) findViewById(C0252R.id.ok)).setEnabled(false);
        } catch (Exception unused) {
            this.f125n.setText(getString(C0252R.string.notMe));
        }
    }

可知需求i值为34了。

逻辑推理

我们知道,作者需求我们输入生辰八字,如果生辰八字按某种特定方式取值相加的结果为34,那么夺旗成功。

 

计算一下遍历所有可能的计算次数:

2007-1983=24
24 * 12 * 31 * 12 = 107136

十万次的运算对于计算机来说不算什么,如果发现可能的结果太多,再添加m167d函数中的判断缩小取值范围,还有m171h函数中的这一句:

MONTH == 2 && other_str.equals(this.f124m[6])

其实表示,月份为2时,时辰索引值不能取6,否则失败。

编写代码

根据以上逻辑,我们做python代码如下:

hour_weight_list = [16, 6, 7, 10, 9, 16, 10, 8, 8, 9, 6, 6]
day_weight_list = [5, 10, 8, 15, 16, 15, 8, 16, 8, 16, 9, 17, 8, 17, 10, 8, 9, 18, 5, 15, 10, 9, 8, 9, 15, 18, 7, 8, 16, 6]
month_weight_list = [6, 7, 18, 9, 5, 16, 9, 15, 18, 8, 9, 5]
# begin at 1960
year_weight_list = [7, 7, 9, 12, 8, 7, 13, 5, 14, 5, 9, 17, 5, 7, 12, 8, 8, 6, 19, 6, 8, 16, 10, 6, 12, 9, 6, 7, 12, 5, 9, 8, 7, 8, 15, 9, 16, 8, 8, 19, 12, 6, 8, 7, 5, 15, 6, 16, 15, 7, 9, 12, 10, 7, 15, 6, 5, 14, 14, 9]

date_list = []
for year_index in range(1984,2007):
    for month_index in range(1,len(month_weight_list)+1):
        for day_index in range(1,len(day_weight_list)+1):
            for hour_index in range(0,len(hour_weight_list)):
                year_weight = year_weight_list[(year_index - 1900) % 60]
                month_weight = month_weight_list[month_index-1]
                day_weight = day_weight_list[day_index-1]
                hour_weight = hour_weight_list[hour_index]

                year = year_index
                month = month_index
                if year == 1989 or year == 2004:
                    continue
                if month == 1 or month == 4 or month == 5 or month == 7 or month == 10 or month == 11 or month == 12:
                    year = 1999
                    year_weight = year_weight_list[(year - 1900) % 60]
                if year <= 1994 and (month == 2 or month == 6 or month == 8):
                    month = 3
                    month_weight = month_weight_list[month-1]
                if year >= 1996 and (month == 2 or month == 6 or month == 8):
                    month = 9
                    month_weight = month_weight_list[month-1]
                if year == 1995 and (day_index > month + 2 or month == day_index):
                    month = 6
                    month_weight = month_weight_list[month-1]

                if ( year_weight + month_weight + day_weight + hour_weight ) == 34:
                    date = str(year_index) + '/' + str(month_index) + '/' + str(day_index) + '/' + str(hour_index)
                    if date_list.count(date) == 0:
                        date_list.append(date)

print "find_times = " + str(len(date_list))
print date_list[0]
print date_list[1]

夺旗成功

最终运算结果如下:

➜  play_ground python count_date.py
find_times = 2
1995/2/3/3
1995/2/3/6

根据月份为2,索引值不能为6的推论,我们得到了唯一的flag1995020305to07,输入flag,程序提示成功,并弹出一段迷之算命结果。

 

95年的小伙,和我同龄,还不错的。不过,果然还是少看些算命,好好学习,天天向上比较好哦。

参考

  1. Android 反编译神器jadx的使用

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-12-10 20:29 被顾言庭编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回