CTF逆向--安卓篇

题目(来源:Jarvis-OJ):

  1. Androideasy
  2. DD Android Easy
  3. DD - Android Normal
  4. FindPass
  5. Smali
  6. 爬楼梯

Androideasy

使用APKToolBOX中的jadx打开该apk文件找到MainActivity查看主函数,如下所示

package com.a.sample.androidtest;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    private EditText editText;
    private byte[] s = new byte[]{(byte) 113, (byte) 123, (byte) 118, (byte) 112, (byte) 108, (byte) 94, (byte) 99, (byte) 72, (byte) 38, (byte) 68, (byte) 72, (byte) 87, (byte) 89, (byte) 72, (byte) 36, (byte) 118, (byte) 100, (byte) 78, (byte) 72, (byte) 87, (byte) 121, (byte) 83, (byte) 101, (byte) 39, (byte) 62, (byte) 94, (byte) 62, (byte) 38, (byte) 107, (byte) 115, (byte) 106};
    public boolean check() {
        byte[] chars = this.editText.getText().toString().getBytes();
        if (chars.length != this.s.length) {
            return false;
        }
        int i = 0;
        while (i < this.s.length && i < chars.length) {
            if (this.s[i] != (chars[i] ^ 23)) {
                return false;
            }
            i++;
        }
        return true;
    }
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        final Context context = this;
        this.editText = (EditText) findViewById(R.id.edit_text);
        findViewById(R.id.button).setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                if (MainActivity.this.check()) {
                    Toast.makeText(context, "You got the flag!", 1).show();
                } else {
                    Toast.makeText(context, "Sorry your flag is wrong", 1).show();
                }
            }
        });
    }
}

查看onClick函数,其中调用了check函数,check函数中将用户输入的字符串与23进行异或,然后与数组s进行比较,判断其是否一致。所以我们可以写出flag脚本

s = [ 113, 123, 118, 112, 108, 94, 99, 72, 38, 68, 72, 87, 89, 72, 36, 118, 100, 78, 72, 87, 121, 83, 101, 39, 62, 94, 62, 38, 107, 115, 106 ] flag = "" for i in s: flag += chr(i^0x17) print flag

Flag: flag{It_1S_@N_3asY_@nDr0)I)1|d}

DD Android Easy

拖进APKToolBOX的jadx中,找到FlagActivity,如下所示

package com.didi_ctf.flagapp;
import android.os.Bundle;
import android.support.v7.a.d;
import android.view.View;
import android.widget.TextView;
public class FlagActivity extends d {
    private static String m = "com.didi_ctf.flagapp.FlagActivity";
    private static final byte[] p = new byte[]{(byte) -40, (byte) -62, (byte) 107, (byte) 66, (byte) -126, (byte) 103, (byte) -56, (byte) 77, (byte) 122, (byte) -107, (byte) -24, (byte) -127, (byte) 72, (byte) -63, (byte) -98, (byte) 64, (byte) -24, (byte) -5, (byte) -49, (byte) -26, (byte) 79, (byte) -70, (byte) -26, (byte) -81, (byte) 120, (byte) 25, (byte) 111, (byte) -100, (byte) -23, (byte) -9, (byte) 122, (byte) -35, (byte) 66, (byte) -50, (byte) -116, (byte) 3, (byte) -72, (byte) 102, (byte) -45, (byte) -85, (byte) 0, (byte) 126, (byte) -34, (byte) 62, (byte) 83, (byte) -34, (byte) 48, (byte) -111, (byte) 61, (byte) -9, (byte) -51, (byte) 114, (byte) 20, (byte) 81, (byte) -126, (byte) -18, (byte) 27, (byte) -115, (byte) -76, (byte) -116, (byte) -48, (byte) -118, (byte) -10, (byte) -102, (byte) -106, (byte) 113, (byte) -104, (byte) 98, (byte) -109, (byte) 74, (byte) 48, (byte) 47, (byte) -100, (byte) -88, (byte) 121, (byte) 22, (byte) -63, (byte) -32, (byte) -20, (byte) -41, (byte) -27, (byte) -20, (byte) -118, (byte) 100, (byte) -76, (byte) 70, (byte) -49, (byte) -39, (byte) -27, (byte) -106, (byte) -13, (byte) -108, (byte) 115, (byte) -87, (byte) -1, (byte) -22, (byte) -53, (byte) 21, (byte) -100, (byte) 124, (byte) -95, (byte) -40, (byte) 62, (byte) -69, (byte) 29, (byte) 56, (byte) -53, (byte) 85, (byte) -48, (byte) 25, (byte) 37, (byte) -78, (byte) 11, (byte) -110, (byte) -24, (byte) -120, (byte) -82, (byte) 6, (byte) -94, (byte) -101};
    private static final byte[] q = new byte[]{(byte) -57, (byte) -90, (byte) 53, (byte) -71, (byte) -117, (byte) 98, (byte) 62, (byte) 98, (byte) 101, (byte) -96, (byte) 36, (byte) 110, (byte) 77, (byte) -83, (byte) -121, (byte) 2, (byte) -48, (byte) 94, (byte) -106, (byte) -56, (byte) -49, (byte) -80, (byte) -1, (byte) 83, (byte) 75, (byte) 66, (byte) -44, (byte) 74, (byte) 2, (byte) -36, (byte) -42, (byte) -103, (byte) 6, (byte) -115, (byte) -40, (byte) 69, (byte) -107, (byte) 85, (byte) -78, (byte) -49, (byte) 54, (byte) 78, (byte) -26, (byte) 15, (byte) 98, (byte) -70, (byte) 8, (byte) -90, (byte) 94, (byte) -61, (byte) -84, (byte) 64, (byte) 112, (byte) 51, (byte) -29, (byte) -34, (byte) 126, (byte) -21, (byte) -126, (byte) -71, (byte) -31, (byte) -24, (byte) -60, (byte) -2, (byte) -81, (byte) 66, (byte) -84, (byte) 85, (byte) -91, (byte) 10, (byte) 84, (byte) 70, (byte) -8, (byte) -63, (byte) 26, (byte) 126, (byte) -76, (byte) -104, (byte) -123, (byte) -71, (byte) -126, (byte) -62, (byte) -23, (byte) 11, (byte) -39, (byte) 70, (byte) 14, (byte) 59, (byte) -101, (byte) -39, (byte) -124, (byte) 91, (byte) -109, (byte) 102, (byte) -49, (byte) 21, (byte) 105, (byte) 0, (byte) 37, Byte.MIN_VALUE, (byte) -57, (byte) 117, (byte) 110, (byte) -115, (byte) -86, (byte) 56, (byte) 25, (byte) -46, (byte) -55, (byte) 7, (byte) -125, (byte) 109, (byte) 76, (byte) 104, (byte) -15, (byte) 82, (byte) -53, (byte) 18, (byte) -28, (byte) -24};
    private TextView n;
    private TextView o;
    private String i() {
        int i;
        int i2 = 0;
        byte[] bArr = new byte[p.length];
        for (i = 0; i < bArr.length; i++) {
            bArr[i] = (byte) (p[i] ^ q[i]);
        }
        byte b = bArr[0];
        i = 0;
        while (bArr[b + i] != (byte) 0) {
            i++;
        }
        byte[] bArr2 = new byte[i];
        while (i2 < i) {
            bArr2[i2] = bArr[b + i2];
            i2++;
        }
        return new String(bArr2);
    }
    public void onClickTest(View view) {
        if (this.n.getText().toString().equals(i())) {
            this.o.setText(R.string.flag_result_yes);
        } else {
            this.o.setText(R.string.flag_result_no);
        }
    }
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_flag);
        this.n = (TextView) findViewById(R.id.flag_entry);
        this.o = (TextView) findViewById(R.id.flag_result);
    }
}

这里首先就是将用户输入的字符串和i()函数的返回值进行对比,若正确,则说明用户输入的为flag,所以我们只要分析i()就可得到flag。查看函数后,首先该函数将两个数组进行异或,然后去第一个元素为首地址,一直往后读到0为止,这之间的字符串就是flag,下面是获得flag的脚本

p = [ -40, -62, 107, 66, -126, 103, -56, 77, 122, -107, -24, -127, 72, -63, -98, 64, -24, -5, -49, -26, 79, -70, -26, -81, 120, 25, 111, -100, -23, -9, 122, -35, 66, -50, -116, 3, -72, 102, -45, -85, 0, 126, -34, 62, 83, -34, 48, -111, 61, -9, -51, 114, 20, 81, -126, -18, 27, -115, -76, -116, -48, -118, -10, -102, -106, 113, -104, 98, -109, 74, 48, 47, -100, -88, 121, 22, -63, -32, -20, -41, -27, -20, -118, 100, -76, 70, -49, -39, -27, -106, -13, -108, 115, -87, -1, -22, -53, 21, -100, 124, -95, -40, 62, -69, 29, 56, -53, 85, -48, 25, 37, -78, 11, -110, -24, -120, -82, 6, -94, -101 ]
 q = [-57, -90, 53, -71, -117, 98, 62, 98, 101, -96, 36, 110, 77, -83, -121, 2, -48, 94, -106, -56, -49, -80, -1, 83, 75, 66, -44, 74, 2, -36, -42, -103, 6, -115, -40, 69, -107, 85, -78, -49, 54, 78, -26, 15, 98, -70, 8, -90, 94, -61, -84, 64, 112, 51, -29, -34, 126, -21, -126, -71, -31, -24, -60, -2, -81, 66, -84, 85, -91, 10, 84, 70, -8, -63, 26, 126, -76, -104, -123, -71, -126, -62, -23, 11, -39, 70, 14, 59, -101, -39, -124, 91, -109, 102, -49, 21, 105, 0, 37, -128, -57, 117, 110, -115, -86, 56, 25, -46, -55, 7, -125, 109, 76, 104, -15, 82, -53, 18, -28, -24 ]
 arr1 = []
 flag = ""
 for i in range(len(p)):
     arr1.append(p[i]^q[i])
 k = arr1[0]
 i1 = 0
 while 1:
     if arr1[k+i1] == 0:
         break
     i1 += 1
 for i in range(i1):
     flag += chr(arr1[k+i])
 
 print flag
Flag:DDCTF-3ad60811d87c4a2dba0ef651b2d93476@didichuxing.com

DD - Android Normal

首先使用APKToolBOX中的jadx打开apk文件,找到主函数,如下所示

发现只要用户输入的flag和stringFromJNI函数的返回值一致,则输出正确。又看到该函数来自hello-libs中,因此解压apk文件,在解压后的目录中(DD - Android Normal\DDCTF-Normal\lib\arm64-v8a)找到该库文件,并拖进IDA中即可找到该函数,如下所示:

__int64 __fastcall Java_com_didictf_hellolibs_MainActivity_stringFromJNI(__int64 a1)
{
  __int64 v1; // x19
  __int64 v2; // ST00_8
  __int64 v3; // x20
  unsigned int v4; // w21
  __int64 v5; // x0
  __int64 v6; // x8
  signed int v7; // w10
  unsigned __int8 *v8; // x11
  unsigned int v9; // w9
  int v10; // t1
  int v11; // w20
  __int64 v12; // x8
  __int64 result; // x0
  char v14[184]; // [xsp+8h] [xbp-1A8h]
  __int128 v15; // [xsp+C0h] [xbp-F0h]
  __int128 v16; // [xsp+D0h] [xbp-E0h]
  __int128 v17; // [xsp+E0h] [xbp-D0h]
  __int128 v18; // [xsp+F0h] [xbp-C0h]
  __int128 v19; // [xsp+100h] [xbp-B0h]
  __int128 v20; // [xsp+110h] [xbp-A0h]
  __int128 v21; // [xsp+120h] [xbp-90h]
  __int128 v22; // [xsp+130h] [xbp-80h]
  __int128 v23; // [xsp+140h] [xbp-70h]
  __int128 v24; // [xsp+150h] [xbp-60h]
  __int64 v25; // [xsp+160h] [xbp-50h]
  v1 = a1;
  v2 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v3 = GetTicks();
  v4 = 0;
  do
    gpower(v4++);
  while ( v4 != 32 );
  v5 = GetTicks();
  __android_log_print(4LL, "hell-libs::", "calculation time: %lu", v5 - v3);
  v15 = xmmword_A40;
  v16 = xmmword_A50;
  v17 = xmmword_A60;
  v18 = xmmword_A70;
  v21 = xmmword_AA0;
  v22 = xmmword_AB0;
  v6 = 0LL;
  v19 = xmmword_A80;
  v20 = xmmword_A90;
  v23 = xmmword_AC0;
  v24 = xmmword_AD0;
  do
  {
    *(&v25 + v6) = byte_B96[v6 + 160] ^ byte_AE0[v6 + 160];
    ++v6;
  }
  while ( v6 != 22 );
  v7 = -1;
  v8 = &v15 + (v15 >> 1);
  v9 = -2;
  do
  {
    v10 = *v8++;
    ++v9;
    ++v7;
  }
  while ( v10 );
  if ( v7 < 1 )
  {
    v12 = 0LL;
  }
  else
  {
    v11 = v9 + 1;
    memcpy(v14, &v15 + (v15 >> 1), v9 + 1LL);
    v12 = v11;
  }
  v14[v12] = 0;
  result = (*(*v1 + 1336LL))(v1, v14);
  *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  return result;
}

可以看到这里的v15-v24是一连串非常大的数据,双击其中一个数据,来到一下界面

对这里的数据点击r,查看一下这些数据对应的字符,忽然发现这里隐藏着flag

尝试读取出来,在模拟器中运行程序,并提交该flag,提示正确

Flag:DDCTF-397a90a3267641658bbc975326700f4b@didichuxing.com

FindPass

拖进jadx中,找到主函数中的关键代码,如下所示

看到底部Flag==flag{Key},我们知道上面的那个if就必须为真,接着找到fkey,就在最开始的位置,可以看到它应该是用户输入的字符串,而ekey则就是R.string.fkey这个字符串

这里只有一个ID号

接下来我们把APK拖进Android Killer这个软件,因为它可以对整个工程进行字符串搜索,在搜索框中输入fkey

然后我们在string.xml中找到该字符串,为“Tr43Fla92Ch4n93”,继续看接下来的代码

这里它读取了src.jpg中的每个字节到数组cha中,然后对ekey中每个字节做了相应的操作,若这个字节在偶数位置,则减temp2,若在奇数位置,则加temp2,这里需要注意的是,char类型的范围是-128到127,如果使用python的话需要加上范围限制。基本算法分析结束,首先将apk解压,在里面找到src.jpg这个文件,使用十六进制编辑器打开,把数据复制下来,保存到txt文件中,如下所示

下面试获取flag脚本

#coding:utf-8 ekey = [ord(i) for i in "Tr43Fla92Ch4n93"] cha = [] f = open("src.txt",'r') data = f.read() for i in range(0x400*3): cha.append(int(data[3*i:3*i+2],16)) flag = "" for i in range(len(ekey)): # 注意char类型是-128到127 if cha[ekey[i]] <128: temp2 = cha[ekey[i]] % 10 else: temp2 = (-(cha[ekey[i]]%128))%10 if i%2==1: ekey[i] += temp2 else: ekey[i] -= temp2 for i in ekey: flag += chr(i) print flag

Flag:Qv49CmZB2Df4jB-

Smali

这里提供了一个smali文件,之前做的时候我是专门查了smali的语法来分析,虽然说分析出来了,不过后来发现了一个效率更高的方法。首先打开Smali2JavaUI这个软件,点击file选择处理单个smali文件,直接就能将其转换为java代码,代码如下

/**
  * Generated by smali2java 1.0.0.558
  * Copyright (C) 2013 Hensence.com
  */
package net.bluelotus.tomorrow.easyandroid;
import android.util.Base64;
import java.io.PrintStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.GeneralSecurityException;
public class Crackme {
    private String str2 = "cGhyYWNrICBjdGYgMjAxNg==";
    public Crackme() {
        GetFlag("sSNnx1UKbYrA1+MOrdtDTA==");
    }
    private String GetFlag(String p1) {
        byte[] "content" = Base64.decode(p1.getBytes(), 0x0);
        String "kk" = new String(Base64.decode(str2.getBytes(), 0x0));
        System.out.println(decrypt("content", "kk"));
        return null;
    }
    private String decrypt(byte[] p1, String p2) {
        String "m" = 0x0;
        try {
            byte[] "keyStr" = p2.getBytes();
            SecretKeySpec "key" = new SecretKeySpec("keyStr", "AES");
            Cipher "cipher" = Cipher.getInstance("AES/ECB/NoPadding");
            "cipher".init(0x2, "key");
            byte[] "result" = "cipher".doFinal(p1);
            return "m";
        } catch(NoSuchPaddingException "e") {
            "e".printStackTrace();
        }
        return  "m";
    }
}

这里可以看到首先将str2进行base64解码,然后将其作为key,使用AES的ECB模式去解密,而这个"sSNnx1UKbYrA1+MOrdtDTA=="字符串进行base64解码后就是密文,因此就可以写脚本了

from Crypto.Cipher import AES
 import base64,binascii
 a = base64.b64decode('sSNnx1UKbYrA1+MOrdtDTA==')
 b = base64.b64decode('cGhyYWNrICBjdGYgMjAxNg==')
 x = AES.new(b,AES.MODE_ECB)
 print x.decrypt(a)
Flag:PCTF{Sm4liRiver}

爬楼梯

这道题比较特别,我们可以采用软件破解的方法来得到flag而不是算法分析。为什么这么说呢,首先我们运行一遍apk,如下所示

第二个按钮时没办法点击的,而每点一次爬一层楼,已爬楼层就会增加1。其实这里不用我们输入数据,说明flag就已经保存在软件中,那么当第二个按钮能被激活的时候,我们就可以得到flag。所以我们的思路是将第二个按钮的标志设置为可点击即可。下面是破解的流程

  1. 使用APKToolBOX反编译apk
  2. 进入文件夹,将unknow删除,这个和签名有关,我们要是修改的话,签名将不一致
  3. 到以下路径CFF_100\smali\com\ctf\test\ctf_100用记事本打开MainActivity.smali,找到setClickable函数,这里我们找到两个,刚好对应两个按钮

这个函数第一个参数我们可以猜测出是选择button,第二个参数就是设定标志位。第一个setClickable我们看到是0x1,说明可被点击,我们便将第二个setClickable也改为0x1

保存,然后使用APKToolBox回编译apk,任意命名一个apk,然后就会生成一个新的apk,运行新生成的apk的签名apk(如CFF_100(1)_Signed.apk),点击第二个按钮得到flag

原文发布于微信公众号 - 安恒网络空间安全讲武堂(gh_fa1e45032807)

原文发表时间:2018-02-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Linux驱动

14.QT-QFile文件,QBuffer缓冲区,QDir目录,QFileSystemWatcher文件系统监视

2683
来自专栏斑斓

AKKA中的事件流

在《企业应用集成模式》一书中,定义了许多与消息处理有关的模式,其中运用最为广泛的模式为Publisher-Subscriber模式,尤其是在异步处理场景下。 基...

3864
来自专栏谦谦君子修罗刀

程序员面试闪充--iOS密码学

但凡一个有点追求的iOS开发,总得会点加密技术,要不然用户信息就有可能被其他人获取用来做一些对我们不利的事情。 视频地址: 密码学 一、base64 base6...

36810
来自专栏一个番茄说

函数响应式编程框架RxSwift 学习——Subject

简单的比喻下,Observable像是一个水管,会源源不断的有水冒出来。Subject就像一个水龙头,它可以套在水管上,接受Observable上面的事件。但是...

922
来自专栏好好学java的技术栈

解密微信小程序Java登录流程(ssm实现具体功能)

signature,//签名、encryptedData,//用户敏感信息、iv//解密算法的向量:

4252
来自专栏一直在跳坑然后爬坑

RxJava2操作符之“Distinct”

我们创建了一个会发送1, 2, 1, 1, 2, 3, 4, 6, 4 这些item的被观察者 其中1,2,4都有重复的数字 然后用操作符distinct去...

922
来自专栏闻道于事

BCryptPasswordEncoder加密及判断密码是否相同

项目中用到了BCryptPasswordEncoder对密码进行二次加密,需要注意的是,加密后的字符串比较长,数据库的长度至少为60位。 通过BCryptPas...

1.5K5
来自专栏刘晓杰

retrofit的使用

36014
来自专栏好好学java的技术栈

微信小程序Java登录流程(ssm实现具体功能和加解密隐私信息问题解决方案)

signature,//签名、encryptedData,//用户敏感信息、iv//解密算法的向量:

5957
来自专栏青蛙要fly的专栏

Android技能树 — Rxjava取消订阅小结(2):RxLifeCycle

现在很多项目都在使用Rxjava了,对于RxJava的使用,估计都很熟悉了,但是很多人在使用RxJava的时候容易产生内存泄漏问题,比如我们在用RxJava配合...

4073

扫码关注云+社区

领取腾讯云代金券