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.