2022-12-04
Refer, rinse, repeat: doing my laundry for free
The washers and dryers at my apartment complex cost an outrageous $1.85 per wash and another $1.85 per dry.
Almost $4 per laundry trip? In this economy?
Fortunately, this poster is hung up in the laundry room:

Let’s see if we can get free laundry out of this, shall we?
Investigating the referral system
Once we download the app and sign up with a new account, we can click the “Get Free Laundry” tab on the sidebar and get sent to this page:

So we can get free money by referring someone! Testing this manually shows that it works, but there’s some kind of detection for accounts created on the same device.
Let’s see how this works in the app’s code. Maybe we can find a way to create accounts automatically.
Decompiling the app
$ jadx --show-bad-code --deobf --deobf-use-sourcename \
--deobf-parse-kotlin-metadata cscpay_2.16.1.apk
INFO - loading ...
INFO - processing ...
ERROR - finished with errors, count: 9
With everything decompiled by jadx, we can open it up in Android Studio and poke around.
Conveniently, in ApiInterface.java, we can find an interface for all the API calls that
the app makes (thanks to them using retrofit2).
Signing up
Using an emulator and Burp Suite, we can see that the app makes a POST request
to api/auth/register_device_check upon signup.
@POST("api/auth/register_device_check")
Call<NormalResponse> registeCommrWithEmail(@Header("X-API-KEY") String str, @Body Map<String, String> map);
Let’s see where registeCommrWithEmail, the interface function that makes this request, is called in the code.
setBgAndText(this.mEmail, 2, this.llEmail, this.tipEmail, this.imageEmail, "Email");
setBgAndText(this.mPassword, 2, this.llPassword, this.tipPassword, this.imagePassword, "Password");
AnalyticsUtil.email(this, str);
AnalyticsUtil.regSubmit(this, str, str2, Constants.STANDARD, Constants.NewLogin);
HashMap hashMap = new HashMap();
hashMap.put("email", str);
hashMap.put("password", str2);
hashMap.put("confirm_password", str2);
hashMap.put("sitecode", AppConfig.SITE_CODE);
hashMap.put(Constants.LOCATION_CODE, AppConfig.LOCATION_CODE);
hashMap.put("app_type", "2");
hashMap.put("referring_uid", AppConfig.referring_uid);
hashMap.put("app_token", AppConfig.uniqueID);
LoadingDialog.show(this, "Creating Account");
WbApiModule.registeCommrWithEmail(new C10725(str, str2), hashMap);
What are the parameters put in the hashmap?
app_token might be the device ID. Investigating further, we see that AppConfig.uniqueID
is set to a UUID generated from Settings.Secure.ANDROID_ID. This is a unique constant
for each device (almost).
When we create our new accounts, let’s make sure to generate a new UUID for each one.
From looking at the network requests, sitecode and location_code are constants identifying
the apartment complex and the specific laundry room within it. We can set these to some dummy
values found by looking at the network requests when registering an account after choosing a random
apartment/laundry room.
referring_uid is the only thing left that we need. How can we get our user ID?
Logging in
The app makes a POST request to api/auth/login when we log in.
@Headers({"Content-Type: application/json", "Cache-Control: no-cache"})
@POST("api/auth/login")
Call<SigninResponse> signin(@Header("X-API-KEY") String str, @Body Map<String, String> map);
Following the same process as before, the interface function signIn
that makes the request is called in SigninFormActivity.java.
private void signinRequest(String str, String str2) {
AnalyticsUtil.loginAttempt(this.mContext, str, Constants.STANDARD);
HashMap hashMap = new HashMap();
hashMap.put(FirebaseAnalytics.Event.LOGIN, str);
hashMap.put("password", str2);
emailStr = str;
pwdStr = str2;
AppConfig.USER_EMAIL = "";
if (WbApiModule.signinRequest(this.signinCallback, hashMap)) {
return;
}
this.progressBar.setVisibility(8);
}
Tracing back, str is the email address and str2 is the password.
Referring a user
Finally, we can see that the app makes a POST request to api/referral/set_referred_mark when we
say we’ve been referred. You know the drill.
@FormUrlEncoded
@POST("api/referral/set_referred_mark")
Call<ResponseBody> setReferredMark(@Header("X-API-KEY") String str, @Field("token") String str2, @Field("user_id") String str3);
public static void setReferredMark(Callback<ResponseBody> callback) {
LogUtils.m101i("Retrofit", "setReferredMark...");
((ApiInterface) retrofit.create(ApiInterface.class)).setReferredMark(AppConfig.WASHBOARD_KEY, AppConfig.USER_TOKEN, AppConfig.USER_ID).enqueue(callback);
}
AppConfig.USER_TOKEN and AppConfig.USER_ID are set when we log in.
API details
What is the base URL?
Easy. It’s set to AppConfig.WASHBOARD_URL, which is set to https://digitalinsights.cscsw.com:443/.
What is X-API-KEY?
Each request requires a header X-API-KEY, which is set to AppConfig.WASHBOARD_KEY.
It’s set to the response of this function:
@Headers({"Cache-Control: no-cache"})
@GET("api/security/api_key")
Call<WashboardKeyResponse> getNewApiKey(@Header("Authorization") String str, @Query("vendor_id") String str2);
str is the authorization token, which is set to AppConfig.NEW_HEADER_AUTH2, which is… hardcoded to Basic YWRtaW46OTMxNQ==.
wtf? the username and password is just “admin” and “9314”?
str2 is the vendor ID, which is set to AppConfig.VENDOR_ID, which is always set to 20806??
Putting it all together
- Get our own user ID by sending a request to the login endpoint with our email and password.
- Create a new account with our own user ID as the
referring_uidparameter. - Mark the new account as referred by sending a request to the referral endpoint with the new user’s ID and token.
- ???
- Profit!
