CTF逆向--安卓篇

时间:2022-04-28
本文章向大家介绍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 NormalDDCTF-Normallibarm64-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_100smalicomctftestctf_100用记事本打开MainActivity.smali,找到setClickable函数,这里我们找到两个,刚好对应两个按钮

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

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