APNA Tunnel Lite is an Android application that provides free VPN utility. It has been created along with Apna Tunnel and Apna Tunnel Plus. From the Telegram channel, it appears that it was initially named AM Tunnel. The design is very minimalist, we are greeted with a screen to choose the VPN server we would like to connect to, and a big “START” button. Pretty straightforward. There is a LOT of server available to chose from, not all of them seems to be working for us, but most do.
There isn’t a lot of information online regarding the application except from store pages, but found the website
https://apnatunnelvpn[.]com/ lost deep in the fourth or fifth page of our search engine’s results.
Android Sample
We’ve analyzed the following version of the app com.apnatunnel.lite:
This application requests the following permissions:
High risk
android.permission.POST_NOTIFICATIONS: Allows an app to post notifications.
android.permission.READ_EXTERNAL_STORAGE: Allows an application to read from external storage.
android.permission.USE_CREDENTIALS: Allows an application to request authentication tokens.
Medium risk
android.permission.ACCESS_ADSERVICES_AD_ID: This ID is a unique, user-resettable identifier provided by Google’s advertising services, allowing apps to track user behavior for advertising purposes while maintaining user privacy.
android.permission.ACCESS_ADSERVICES_ATTRIBUTION: This enables the app to retrieve information related to advertising attribution, which can be used for targeted advertising purposes. App can gather data about how users interact with ads, such as clicks or impressions, to measure the effectiveness of advertising campaigns.
android.permission.ACCESS_ADSERVICES_TOPICS: This enables the app to retrieve information related to advertising topics or interests, which can be used for targeted advertising purposes.
android.permission.RECEIVE_BOOT_COMPLETED: Allows an application to start itself as soon as the system has finished booting. This can make it take longer to start the phone and allow the application to slow down the overall phone by always running.
Low risk
android.permission.ACCESS_NETWORK_STATE: Allows an application to view the status of all networks.
android.permission.FOREGROUND_SERVICE: Allows a regular application to use Service.startForeground.
android.permission.FOREGROUND_SERVICE_SPECIAL_USE: Allows a regular application to use Service.startForeground with the type “specialUse”.
android.permission.INTERNET: Allows an application to create network sockets.
android.permission.WAKE_LOCK: Allows an application to prevent the phone from going to sleep.
com.google.android.c2dm.permission.RECEIVE: Allows an application to receive push notifications from cloud.
com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE: A custom permission defined by Google.
com.google.android.gms.permission.AD_ID: This app uses a Google advertising ID and can possibly serve advertisements.
Anti-VM and anti-debug checks can be found in the application, as reported by mobSF.
We also detected root detection, which was automatically bypassed by the pirogue-intercept-gated script, as shown here:
08:04:06 INFO ℹ Inject dynamic hooks
INFO ℹ Bypass test-keys check
INFO ℹ Bypass return value for binary: Superuser.apk
INFO ℹ Bypass return value for binary: su
Obfuscation
The application is obfuscated with a recent version of LSparanoid
We tried to deobfuscate using paranoid-deobfuscator, but the version used is too recent and not supported by this tool. Unfortunately our attempts to de-obfuscate the code through static analysis lead to out-of-range errors becaus of the algorithm for obfuscation. As it would taken a little too long to debug, we settled for a dynamic analysis option using a Frida script to dump the strings when they get used. This approach was a lot simpler to implement even though we could not get all the strings since it would require to explore all branches.
This port is opened by the prince.open.vpn.service.InjectorService class, as show here:
@Override// java.lang.Runnablepublicfinalvoidrun(){WAwa;try{j(String.format("Listening to local port %s",Integer.toString(1724)));j("Listening for incoming connection");this.d=newServerSocket(1724);inti=this.t;if(i==3||i==5){R7r7=this.r;if(r7!=null){ServerSocketserverSocket=r7.a;if(serverSocket!=null){serverSocket.close();}Socketsocket=r7.b;if(socket!=null){socket.close();}r7.c=false;r7.interrupt();}??thread=newThread();thread.c=true;this.r=thread;thread.start();}this.b.sendEmptyMessage(2);while(u){Socketaccept=this.d.accept();this.e=accept;if(accept!=null&&!accept.isClosed()&&g()){this.e.setKeepAlive(true);SSLSocketsSLSocket=this.s;if(sSLSocket!=null&&sSLSocket.isConnected()){this.s.setKeepAlive(true);this.f.setKeepAlive(true);i(this.s);Socketsocket2=this.e;SSLSocketsSLSocket2=this.s;Stringstring="16384";Stringstring2="32768";newWA(socket2,sSLSocket2,true,string,string2).start();wa=newWA(sSLSocket2,socket2,false,string,string2);}else{Socketsocket3=this.f;if(socket3!=null&&socket3.isConnected()){this.f.setKeepAlive(true);i(this.f);Socketsocket4=this.e;Socketsocket5=this.f;Stringstring3="16384";Stringstring4="32768";newWA(socket4,socket5,true,string3,string4).start();wa=newWA(socket5,socket4,false,string3,string4);}}wa.start();}}}catch(Exceptione){j(String.format("%s: %s","Injector Exception",e.getMessage()));}}
This piece of code seems to be doing some kind of port forwarding, redirecting the traffic to another socket. Correlating the port number and the application’s functionalities, it seems this open port is used for the HTTP injector, aiming to bypass restrictions on the network.
Considering this, the mainly concerning point is that this port should probably not be listening on all interfaces, and could probably be set to localhost or vpn only.
External communications
The domain pullmerge[.]org is a part of the application’s ecosystem. When the app is started it makes 2 similar request to https://pullmerge[.]org/updater/1df3d3112f9077493387694d84b2de, which is answered by a big base64 encoded data.
This data when decoded does not appear to be something intelligible, it’s entropy is close to 8, with a score of Shannon entropy: 7.999176221331905. It is probably compressed or encrypted.
This request is the exact same of the one that is made when clicking the “Update online” button in the “Config Updater” of the application.
When connecting to the various VPN servers, we encounter various behaviors. Some connections are initiated with a websocket to a random subdomain of frogflyer[.]xyz, some other don’t. Many of the connections we tried were not working. Some other connections instantly sent continuous data over the VPN but we couldn’t figure out what it was. Another initiated a connection to econet.zigssh[.]com.
Conclusion
The analysis of APNA Tunnel Lite v27 reveals a simple application that provides free VPN services, however it seems very hack-ish in the way its servers are managed. It allows users to bypass internet restrictions and access the internet freely, and is particularly targeted for users in regions where internet access is limited or censored.
The permissions requested seems acceptable for such an application, except maybe for android.permission.USE_CREDENTIALS which may raise some questions. This permission is the older version of the android.permission.ACCOUNT_MANAGER, but it has been removed from Android since version 6.0. On these versions, it would allow access to the Account Manager class.
Apart from this permission and the weird server management and connection, the application seems to be doing its job of providing free VPN access to users in countries with restricted internet.
Annex
You will find below the python script we used to update the decompiled Java with the deobfuscated strings from the Frida script.
Usage:
Start by decompiling the apk files with jadx, or using APKlab in vscode, then run the app with frida, and finally use the python script to update the decompiled java source code.
# Get decompiled java files using jadx$ jadx -r -q -ds output_directory path_to_apk.apk
# Run the application with Frida and our script (see section Obfuscation)$ frida -U -f com.apnatunnel.lite -l frida-deobfuscate-lsparanoid.js | grep ':'# grep to remove frida's output, there is probably a cleaner way to do that though# update decompiled java files$ python update_java.py output_directory/java_src