Do not get me wrong, the title is not somewhat japenese light novel or manga but perfectly fit to describe what annoy me this current weeks : unsolicited online gambling ads!
Disclaimer: The site that Yukari want to investigate already reported. Unfortunately, there no real action. Which is there plenty time for us to toying with them. Here the Report.
You still can get the apk here: https://ad-dl-300209[.]idgamefun11[.]online/apk/300209/pp7_300209.apk (do not download it in android).
But if you want to try it with different APK that they serve, here the list https://ad-dl-3002090[.]idgamefun11[.]online/index.js (the another link served as JSON list inside that js file). They very resourceful.
The site is name (double P with 7) which is an online gambling that targeted Android user by the way the showing the invitation. They lure user to download the android APK and the victim will install it, poor users.
Despite the web is alive or blocked. Yukari assuming the APK connect to different domain that serve API Endpoint. Why?
So, here Yukari plan:
First, lets decompile APK. For reducing our need to install tools, Yukari will go with https://www.decompiler.com/, then upload there.
FYI: Android APK is simple ZIP archive, it can easily to open. But our main goal is to get the reversed source code which compiled into bytecode inside classes.dex and classes2.dex, so the website will get the job done (if you want do manually, install JADX).
Here the result when we done:

Now, let dive into sources/com, in here Yukari will determine where the interesting place:

Everything looks legit, except ppid7 and SSFun.
Let download the result as zip then visit the ppid7 via VSCode, and here we get the entrypoint ("MainActivity.java"):
package com.ppid7.prod2025;
import android.os.Bundle;
import com.ssfun.commonss.SSFunContext;
import com.ssfun.commonss.SSFun_PoolMainActivity;
public class MainActivity extends SSFun_PoolMainActivity {
/* access modifiers changed from: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
OnCoreCreate();
SSFunContext.SSFun_SetActivity(this);
SSFunContext.SSFun_SetContext(this);
Init();
}
}
From code above, what we got is they redirect to com.ssfun.commonss. It's common practice to make entry point small, but I got bad feeling about this.

The code above shown that it use Firebase, constant or string value likely written somewhere (in this case android resources file, my guts tell me because how the function invocation same as call string from resources file.) -> SSFunContext.GetMetaData(SSFunConst.K_META_CHANNEL_ID). They wrapper to accessing metadata and resource, read sources/com/ssfun/commonss/SSFunContext.java for the details.
Yukari opinion: for a gambling app they tooks design pracitce quite well also intentionally separating layer can be smart way to obfuscate they app flow. Yukari conclude SSFuns is somewhat template that will changing the MainActivity but the behaivour still same.
I'am stuck, just for you information that gotten to next paragraf of this article, roughly through ~ an hour or more about finding alternative way and mess around with the code.
In other word, tracing via entrypoint is could be difficult as the developer intented. So, Yukari switching to hunting interesting string to jump off from the developer hand.
To do so, lets looking for constant or any string that indicating URL.

Gotcha, we found interesting pattern (think like f(x)=blah.x.blah, that we need find the x, where the function is called and what inside it).
Go to sources/com/ssfun/commonss/SSFun_PoolHandler.java line 50 ~ 58 (focus at the two last line):
public void SSFun_CheckOneApi(List<SSFun_PoolListItem> list, int i, int i2) {
final int size = list.size();
Log.i(SSFun_Utils.TAG, String.format("CheckOneApi Start!! apiPoolSize=%d, index=%d, retry=%d", new Object[]{Integer.valueOf(size), Integer.valueOf(i), Integer.valueOf(i2)}));
if (size <= i) {
SSFun_CheckFallbackApi();
return;
}
final String SSFun_getDomain = list.get(i).SSFun_getDomain();
String str = "https://" + SSFun_getDomain + "/v1/site/domains?rand=" + System.currentTimeMillis();
Now our task is figuring out what SSFun_getDomain string is, but first let me explain bit to you:
list is collection of SSFUN_PoolListItem class, Nice we got data structure which base of lead to understand how appliction treat data.
SSFun_getDomain() likely getter/setter function that generated from Kotlin, which mean it actually function to access an attribute.Yukari found this way good indication, the SSFUN_PoolListItem is somehow is important to the app. It had been 65 times invoked and used in com.ssfuns.commonss.

The file definiton is at : sources/com/ssfun/commonss/SSFun_PoolListItem.java:

Found what Yukari wanted: domain. Now we need to find where this class serialize data or transforming certain format data into that class object. Lets investigate the search result of SSFUN_PoolListItem one by one, look for code that encode or decode into that class object.
After carefully looking (randomly, that to VSCode preview that save a lot of click), Yukari landed to sources/com/ssfun/commonss/SSFun_XmlPoolHelper.java:
package com.ssfun.commonss;
import com.alibaba.fastjson.JSON;
import java.util.List;
public class SSFun_XmlPoolHelper {
private static final String XML_KEY = "e12m83+IhY@2s#UB";
public static List<SSFun_PoolListItem> SSFun_getXmlList() {
return JSON.parseArray(SSFun_EncryptUtil.SSFun_decrypt(SSFunContext.SSFun_GetResString(SSFunConst.K_STRING_POOL_DATA), XML_KEY), SSFun_PoolListItem.class);
}
}
Okay we find the code, let Yukari explain:
SSFun_decrypt which sign we will dealing with crypthography (but that latter ok?).SSFunConst.K_STRING_POOL_DATA, which if Yukari diving it founded as (look at sources/com/ssfun/commonss/SSFunConst.java):public static final String K_STRING_POOL_DATA = "pool_data";
SSFunContext is basically is just Context from main application that abstracted into SSFunMainApplication, so GetRes_String() basically, context.GetResources().GetString('key') alternative to getString(R.string.key).pool_data resource value can be found at resources/res/values/strings.xml, with value:bO4PkHAM8CsULa5JuvXTWl....<trimmed>..egE5cbUGIMMq8=
Then how we figure what inside it?
Let take a look at SSFun_decrypt at sources/com/ssfun/commonss/SSFun_EncryptUtil.java.

It use Base64 encoding with AES, which the latter is quite. AES have different implementation especially in Java, we have the ciphertext and the key, but not IV (Initialization Vector). The Java make that simple by SecretKeySpec.
To handle that and Yukari not want to much bother, Yukari write Java code to handling decryption in JDoodle online compiler with custom main function to print the decrypted result in contained manner like this:

Mind you, Yukari change "AES/ECB/PKCS7Padding" into "AES/ECB/PKCS5Padding", this because it not supported by current JDK or else. Yukari figure out when surfing in stackoverflow and found out the commonly use PKCS5Padding instead PKCS7Padding.

TADA!!! We got the hidden domain address to API Endpoint.
[
{
"domain": "id-game-app-0000000001-client-ep.fungame123.online",
"domainType": 1
},
{
"domain": "id-game-app-0000000002-client-ep.fungame123.pro",
"domainType": 1
},
{
"domain": "id-game-app-0000000003-client-ep.gameid321.store",
"domainType": 1
},
{
"domain": "id-game-app-0000000004-client-ep.gameid456.store",
"domainType": 1
},
{
"domain": "id-game-app-0000000005-client-ep.gameid789.store",
"domainType": 1
},
{
"domain": "api-id-game-server-ep-0000000001a.fungame123.online",
"domainType": 2
},
{
"domain": "api-id-game-server-ep-0000000002a.fungame123.pro",
"domainType": 2
},
{
"domain": "api-id-game-server-ep-0000000003a.gameid321.store",
"domainType": 2
},
{
"domain": "api-id-game-server-ep-0000000004a.gameid456.store",
"domainType": 2
},
{
"domain": "api-id-game-server-ep-0000000005a.gameid789.store",
"domainType": 2
},
{
"domain": "id-game-api-server-ep-0000000006.idfun321.online",
"domainType": 2
},
{
"domain": "id-game-api-server-ep-0000000007.idfun321.pro",
"domainType": 2
},
{
"domain": "id-game-api-server-ep-0000000008.idfun321.store",
"domainType": 2
},
{
"domain": "client-id-game-app-ep-0000000006c.idfun321.online",
"domainType": 1
},
{
"domain": "client-id-game-app-ep-0000000007c.idfun321.pro",
"domainType": 1
},
{
"domain": "client-id-game-app-ep-0000000008c.idfun321.store",
"domainType": 1
},
{
"domain": "pp7-game-prod.oss-ap-southeast-5.aliyuncs.com",
"domainType": 2,
"isFallback": 1
}
]
The developer of the apps is pretty smart to obfuscate the APK domain URL to make difficult for outsider figuring out by scrambling it with AES encryption and extra abstraction layer in the apps itself. They took great care in the app code it self.
Also the result of APK analysis prove that Yukari hipotheses correct:
So, blocking online gambling is not enough just to block the domain and call it a day. We need to address underlying APK that store dozen of URL for effectively do the blocking.
Now let report it to aduankonten.id.