Reverse Engineering a Campus Android App

Sunday, June 14, 2015 🌐中文

Preparation

  • Phone: Google Nexus 5

Tools:

Target app: Neusoft Smart Campus Platform (East China Jiaotong University edition)

1

Let’s take a quick look at the app.

3

4

Log in with the following account:

username:20142110070202
password:011211

5

Open Package Capture to sniff the traffic.

6

You can see that during login, the client sends a GET request to the server:

portal.ecjtu.edu.cn/dcp/service?action=0B38EC05B0815AFB59A1B6171DD8378702E08185784ADC47B213462CC176EBC626865CAE40CF998812E25558152958267F28B8BE59532A45283F2A274FA363CB685347955B330164CDEFA8228F011F4BB73C36BFF072DB9C

The server returns a JSON response:

{"message":{"map":{"UNIT_NAME":"软件学院","USER_ID":"20142110070202","UUID":"201412019176","ID_TYPE":"1","UNIT_ID":"1021","USER_PASSWORD":"?h?g?g+t.[=dR);:","USER_ACCOUNT":"20142110070202","USER_SEX":"1","IS_ACTIVE":"1","BEGIN_TIME":"20141219","USER_FIRST_LOGIN":"0","USER_DESCRIPTION":"史泰龙","USER_ACCOUNT_LOCKED":"1","IS_MAIN_IDENTITY":"1","END_TIME":"20600606"}},"success":true}

So the traffic between the mobile client and the server is encrypted.

Open the app in APK IDE.

7

Smali is hard to read, so click Open Java Source.

8

Search for the keyword "/dcp/service?action=".

Key code:

public final a a(String paramString1, String paramString2, String paramString3)
{
    String str = paramString3;
    if (paramString3 == null) {
        str = "";
    }
    paramString1 = "method=checkUserLoginIFS&idNumber=" + paramString1 + "&UserPwd=" + paramString2 + "&logonIP=" + str;
    try
    {
        paramString2 = com.neusoft.edu.v6.ydszxy.hdjiaoda.appcenter.c.b.a.a(paramString1);
        paramString1 = paramString2;
    }
    catch (Exception paramString2)
    {
        for (;;)
        {
            paramString2.printStackTrace();
        }
    }
    paramString1 = "/dcp/service?action=" + paramString1;
    return (a)a(this.a.a(paramString1), new a());
}

From the source we can see that paramString1 originally looks like:

method=checkUserLoginIFS&idNumber=xxx&UserPwd=xxx&logonIP=xxx

But after being encrypted by:

com.neusoft.edu.v6.ydszxy.hdjiaoda.appcenter.c.b.a.a

it becomes the ciphertext after action= in the GET request. Follow the call to appcenter.c.b.a.a:

9

Key code:

public static String a(String paramString)
{
    Object localObject = new DESedeKeySpec("neusofteducationplatform".getBytes());
    localObject = SecretKeyFactory.getInstance("desede").generateSecret((KeySpec)localObject);
    Cipher localCipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
    localCipher.init(1, (Key)localObject, new IvParameterSpec("01234567".getBytes()));
    return a(localCipher.doFinal(paramString.getBytes("utf-8")));
    }

    private static String a(byte[] paramArrayOfByte)
    {
    String str1 = "";
    int i = 0;
    for (;;)
    {
        if (i >= paramArrayOfByte.length) {
        return str1;
    }
    String str3 = Integer.toHexString(paramArrayOfByte[i] & 0xFF);
    String str2 = str3;
    if (str3.length() == 1) {
        str2 = '0' + str3;
    }
    str1 = str1 + str2.toUpperCase();
    i += 1;
    }
}

From this we can determine:

  • The algorithm is DESede (TripleDES) (applies DES encryption three times to each block).
  • The mode is CBC (each plaintext block is XORed with the previous ciphertext block before encryption).
  • Padding is PKCS5Padding (“The number of bytes to be padded equals to 8 - numberOfBytes(clearText) mod 8. So 1 to 8 bytes will be padded…”).
  • The initialization vector (IV) is "01234567"—in this case it effectively acts like a salt.

The private static String a method appears to convert the byte array to an uppercase hex string.

Based on this, we can write a corresponding decryption routine:

public static String desDecode(String encryptText, String pk)//encryptText为密文,pk为私钥,通过跟踪到的代码可知为"neusofteducationplatform"
throws Exception
{
    java.security.Key deskey = null;
    DESedeKeySpec spec = new DESedeKeySpec(pk.getBytes());
    SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
    deskey = keyfactory.generateSecret(spec);
    Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
    IvParameterSpec ips = new IvParameterSpec("01234567".getBytes());
    cipher.init(2, deskey, ips);
    System.out.println(encryptText);
    byte decryptData[] = cipher.doFinal(hexStringToBytes(encryptText));
    return new String(decryptData, "utf-8");
}

public static String Bytes2HexString(byte b[])
{
    String ret = "";
    for(int i = 0; i < b.length; i++)
    {
    String hex = Integer.toHexString(b[i] & 0xff);
    if(hex.length() == 1)
        hex = (new StringBuilder(String.valueOf('0'))).append(hex).toString();
        ret = (new StringBuilder(String.valueOf(ret))).append(hex.toUpperCase()).toString();
    }
    return ret;
}

Now take the ciphertext after action= from the earlier request:

0B38EC05B0815AFB59A1B6171DD8378702E08185784ADC47B213462CC176EBC626865CAE40CF998812E25558152958267F28B8BE59532A45283F2A274FA363CB685347955B330164CDEFA8228F011F4BB73C36BFF072DB9C

Decrypt it and you get:

method=checkUserLoginIFS&idNumber=20142110070202&UserPwd=011211&logonIP=10.0.8.1

At this point, you might ask: why is UserPwd=011211 different from USER_PASSWORD=?h?g?g+t.[=dR);: in the JSON above? The latter is the result after an additional string-encoding/encryption step.

Here is the corresponding decode code:

public static String Decode(String varCode)
    {
        String des = new String();
        String strKey = new String();
        if(varCode == null || varCode.length() == 0)
            return "";
        strKey = "zxcvbnm,./asdfghjkl;'qwertyuiop[]\1234567890-=` ZXCVBNM<>?:LKJHGFDSAQWERTYUIOP{}|+_)(*&^%$#@!~";
        if(varCode.length() % 2 == 1)
            varCode = (new StringBuilder(String.valueOf(varCode))).append("?").toString();
        des = "";
        int n;
        for(n = 0; n <= varCode.length() / 2 - 1; n++) { char b = varCode.charAt(n * 2); int a = strKey.indexOf(varCode.charAt(n * 2 + 1)); des = (new StringBuilder(String.valueOf(des))).append((char)(b ^ a)).toString(); System.out.println(des); } n = des.indexOf('01'); if(n > 0)
            return des.substring(0, n);
        else
            return des;
}

References

Java Cipher class

Java encryption (2) — DES data encryption algorithm (with IV)

Block cipher modes of operation

What Is PKCS5Padding?

Reverse EngineeringNeusoftAppDigital Campus
Table of Contents

A Simple Crack for CKFinder 3 for PHP

Notes on Some Easily Confused Units