Generating keys to beat McDonalds!

a rather interesting adventure to generate x-plexure-api-key’s

A tradition of mine is to make PoC workarounds for the McDonalds app & loyalty program. This time it is no different except a rather interesting new implementation that I discovered; the x-plexure-api-key! A header containing a cipher with some secret data (unknown at this time) and then an expiry

Reverse engineered

So by digging around in the APK file this piece of code turned up

co.vmob.sdk.util.c.f();
      localObject2 = new StringBuilder();
      ((StringBuilder)localObject2).append(ConfigurationUtils.c());
      ((StringBuilder)localObject2).append("|");
      ((StringBuilder)localObject2).append(co.vmob.sdk.util.c.f());
      localObject2 = b.a(((StringBuilder)localObject2).toString(), i.b());

A string constructor, not so interesting .. until I discovered the functionality of co.vmob.sdk.util.c.f() that is appended lastly in the string after the pipe (|).

   public static String f() {
      SimpleDateFormat var0 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'", Locale.US);
      var0.setTimeZone(TimeZone.getTimeZone("UTC"));
      long var1 = g.b(a.s, 0L);
      return var0.format(new Date(Long.valueOf((new Date()).getTime() + Long.valueOf(var1))));
   }

A date! + some value(expiry in future, I guessed). Already knowing that the x-plexure-api-key had some sort of expiry to it, this looked like the right spot in the code. So moving on to ConfigurationUtils.c() that is a function which gets the value “d4eee068-272a-4aec-9681-5e16dcef6fbd” from /data/data/com.mcdonalds.mobileapp/shared_prefs/VMOD_PREFERENCES.xml in runtime. So know we know what localObject2 would look like; time to find the encryption method. Looking for the functions that we call after constructing localObject2 (b.a) we find it.

public class b {
    public static String a(String str, String str2) {
        try {
            byte[] bArr = new byte[8];
            new SecureRandom().nextBytes(bArr);
            byte[] encoded = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(str2.toCharArray(), bArr, 100, 384)).getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(encoded, 0, 32), "AES");
            Cipher instance = Cipher.getInstance("AES/CBC/PKCS7Padding");
            instance.init(1, secretKeySpec, new IvParameterSpec(Arrays.copyOfRange(encoded, 32, 48)));
            byte[] doFinal = instance.doFinal(str.getBytes("UTF-8"));
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byteArrayOutputStream.write(doFinal);
            byteArrayOutputStream.write(bArr);
            return Base64.encodeToString(byteArrayOutputStream.toByteArray(), 0).replace("\n", "").replace(" ", "");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

An encryption method that takes in (String str, String str2) str = localObject2 str2 = i.b(): (x-vmob-uid) - which is a device uuid4; judging by Plexure official API documentation.

 byte[] encoded = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(str2.toCharArray(), bArr, 100, 384)).getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(encoded, 0, 32), "AES");

The encryption code tells us that str2 is the salt of the cipher we’re about to generate and str is the string we encode.

Let’s recreate it!

Generation

(unencrypted cipher, x-mob-uid) -> encryption method -> x-api-plexure-key

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.util.Arrays;
import java.lang.StringBuilder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Calendar;
import java.util.*;


public class b {
    public static String a(String str, String str2) {
        try {
            byte[] bArr = new byte[8];
            new SecureRandom().nextBytes(bArr);
            byte[] encoded = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new
PBEKeySpec(str2.toCharArray(), bArr, 100, 384)).getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(encoded, 0, 32), "AES");
            Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
            instance.init(1, secretKeySpec, new IvParameterSpec(Arrays.copyOfRange(encoded, 32, 48)));
            byte[] doFinal = instance.doFinal(str.getBytes("UTF-8"));
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byteArrayOutputStream.write(doFinal);
            byteArrayOutputStream.write(bArr);
	    return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args){
         String auth_key = "d4eee068-272a-4aec-9681-5e16dcef6fbd";
         SimpleDateFormat var0 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'", Locale.US);
         var0.setTimeZone(TimeZone.getTimeZone("UTC"));
         String payload = auth_key+"|"+var0.format(new Date(Long.valueOf((new Date()).getTime()-1000*10*60))); //auth key from xml file & 10min expiry
         String outputVal = a(payload, args[0]); //x-vmob-uid
         System.out.println(outputVal.replace("\n", "").replace(" ", ""));
}
}

Bravo. We can now communicate with the McDonalds / Plexure API endpoints using our brand new x-plexure-api-key’s.

~ an adventure by Emil Kudahl

Written on May 11, 2019