Closed AndroidMaster24 closed 1 year ago
Hi,
I knew about this library and some protections behind it, but I don't know much who's behind. More samples at Koodous (after log in): https://koodous.com/rules/L3nkVGvq2X14jXDY/general
Would you like to open a pull-request?
Best
No need, I just like to report some info about it 😀
If you notice then many of the core strings in the dex file have been replaced with strings which are uninitialized and would be NULL by default. Thanks to research done by CmP, It seems that the libpairipcore.so is involved in initializing those strings. Now have to investigate more.
If you notice then many of the core strings in the dex file have been replaced with strings which are uninitialized and would be NULL by default. Thanks to research done by CmP, It seems that the libpairipcore.so is involved in initializing those strings. Now have to investigate more.
Yes, I'm aware of it
I have another info that pairipcore could possibly be related to Google Play. When pairipcore is enabled, APKCombo, the APK downloader that retrieves APK via Google Play API, would never be able to retrieve single APK and only give us option to download split APK (XAPK file). This is always the case for most apps with pairipcore... except Google Camera. Not sure why single APK is still available for Google Camera, it could be it's a system app for Google phones so split APK won't be supported for it. I once encountered single APK went available again on APKcombo after the removal of pairipcore.
It might sound non-sense but it is the fact in my experience
Change the "could be related to Google Play" to "is related to Google Play". :)
There are some Google documentations on it which further prove it out that this protection is from Google.
Also if you would like to discuss more about it then perhaps you can send me a message on discord or telegram as I am interested in reverse engineering it.
There are some Google documentations on it which further prove it out that this protection is from Google.
Can you link to the Google documentation here?
Change the "could be related to Google Play" to "is related to Google Play". :)
There is no reason to change, all I need is collect info and get rednaga or APKiD contributers to check and confirm it to be proposed as new detection
It looks like the latest JEB Pro can recover those missing strings.
In the blog at https://www.pnfsoftware.com/blog/recovering-jni-registered-natives/ they showcase a plugin that does that. It doesn't seem specific to libpairipcore.so, but it can work on it.
Thanks for the info guys! Feel free to discuss more topics in this thread.
Did anyone actually bypassed it other than analyzing?
It's not completely impossible. It will just take time to RE.
I see
Did anyone actually bypassed it other than analyzing?
Modders already start released mod with pairipcore
Did anyone actually bypassed it other than analyzing?
Modders already start released mod with pairipcore
@Quicker666 could you provide modded samples? And also, could you elaborate and tell us why this sample was modded? I could write a rule for it.
Found one:
> md5 northgard-2.0.3-mod.apk
MD5 (northgard-2.0.3-mod.apk) = 8a7aa55725d8f2a0b7a470c0ec9e60b2
> apkid northgard-2.0.3-mod.apk
[+] APKiD 2.1.4 :: from RedNaga :: rednaga.io
[*] northgard-2.0.3-mod.apk!classes.dex
|-> anti_vm : Build.FINGERPRINT check, Build.MANUFACTURER check, Build.TAGS check, possible VM check
|-> compiler : dexlib 2.x
[*] northgard-2.0.3-mod.apk!classes2.dex
|-> anti_debug : Debug.isDebuggerConnected() check
|-> anti_vm : Build.FINGERPRINT check, Build.HARDWARE check, Build.MANUFACTURER check, Build.MODEL check, Build.PRODUCT check, Build.TAGS check, SIM operator check, network operator name check, possible VM check
|-> compiler : dexlib 2.x
[*] northgard-2.0.3-mod.apk!classes3.dex
|-> anti_debug : Debug.isDebuggerConnected() check
|-> anti_vm : Build.FINGERPRINT check
|-> compiler : dexlib 2.x
[*] northgard-2.0.3-mod.apk!classes4.dex
|-> anti_vm : Build.FINGERPRINT check, Build.MANUFACTURER check, Build.TAGS check, possible VM check
|-> compiler : dexlib 2.x
[*] northgard-2.0.3-mod.apk!lib/arm64-v8a/libpairipcore.so
|-> protector : Google Play Integrity
[*] northgard-2.0.3-mod.apk!lib/arm64-v8a/libRMS.so
|-> packer : 5play.ru
[*] northgard-2.0.3-mod.apk!classes5.dex
|-> compiler : dexlib 2.x
hello, has anyone got a solution for bypass pairip?
The weird asset you guys see being loaded are bytecode which get loaded and executed by the VM.
Normally, but not mandatory the protection should be in the first bytecode file it gets loaded, which can be seen by enabling debugging in com.pairip.VMRunner
and adb logcat | grep VMRunner
.
You can just block the asset from loading, but be careful that they might also contain things needed by the app so just block the one you need to block.
Also as an example, idk if there's something else hidden in the app, or the app needed something from the blocked asset but it can't verify the play integrity anymore with their server:
Aaand frida attached to the pairip protected application as a bonus xD
The weird asset you guys see being loaded are bytecode which get loaded and executed by the VM.
Normally, but not mandatory the protection should be in the first bytecode file it gets loaded, which can be seen by enabling debugging in
com.pairip.VMRunner
andadb logcat | grep VMRunner
.You can just block the asset from loading, but be careful that they might also contain things needed by the app so just block the one you need to block.
Also as an example, idk if there's something else hidden in the app, or the app needed something from the blocked asset but it can't verify the play integrity anymore with their server:
Aaand frida attached to the pairip protected application as a bonus xD
Found what's the deal with it, it was actuality even explained on the play integrity docs.
There's also a server side integrity check, the play server also sends the app singing certificate to the app server, which is then evaluated and the app server chooses if it will fulfill the API request or not.
Can't be bypassed by just modifying the app, since not the app sends the signature to the play server, as far as I saw.
Played a little and faked the signature of the app read by com.android.vending but
{
"detail": {
"description": "Something went wrong.",
"param": "deviceRecognitionVerdict",
"type": "invalid_verdict_response"
}
}
The weird asset you guys see being loaded are bytecode which get loaded and executed by the VM. Normally, but not mandatory the protection should be in the first bytecode file it gets loaded, which can be seen by enabling debugging in
com.pairip.VMRunner
andadb logcat | grep VMRunner
. You can just block the asset from loading, but be careful that they might also contain things needed by the app so just block the one you need to block. Also as an example, idk if there's something else hidden in the app, or the app needed something from the blocked asset but it can't verify the play integrity anymore with their server: Aaand frida attached to the pairip protected application as a bonus xDFound what's the deal with it, it was actuality even explained on the play integrity docs.
There's also a server side integrity check, the play server also sends the app singing certificate to the app server, which is then evaluated and the app server chooses if it will fulfill the API request or not.
Can't be bypassed by just modifying the app, since not the app sends the signature to the play server, as far as I saw.
Played a little and faked the signature of the app read by com.android.vending but
{ "detail": { "description": "Something went wrong.", "param": "deviceRecognitionVerdict", "type": "invalid_verdict_response" } }
In older version of parip, on frida attach app will crash until you reboot your device (emulators, real devices no matter). Now, how I can see, they making less protection, but pairip still easily removable as it was before.
The weird asset you guys see being loaded are bytecode which get loaded and executed by the VM. Normally, but not mandatory the protection should be in the first bytecode file it gets loaded, which can be seen by enabling debugging in
com.pairip.VMRunner
andadb logcat | grep VMRunner
. You can just block the asset from loading, but be careful that they might also contain things needed by the app so just block the one you need to block. Also as an example, idk if there's something else hidden in the app, or the app needed something from the blocked asset but it can't verify the play integrity anymore with their server: Aaand frida attached to the pairip protected application as a bonus xDFound what's the deal with it, it was actuality even explained on the play integrity docs. There's also a server side integrity check, the play server also sends the app singing certificate to the app server, which is then evaluated and the app server chooses if it will fulfill the API request or not. Can't be bypassed by just modifying the app, since not the app sends the signature to the play server, as far as I saw. Played a little and faked the signature of the app read by com.android.vending but
{ "detail": { "description": "Something went wrong.", "param": "deviceRecognitionVerdict", "type": "invalid_verdict_response" } }
In older version of parip, on frida attach app will crash until you reboot your device (emulators, real devices no matter). Now, how I can see, they making less protection, but pairip still easily removable as it was before.
You're going yo have unity games/apps.
They use pairip in their libunity.so
ELF and calling it's ExecuteProgram
which I have no idea what it does.
~ $ ldd libunity.so | grep pairip libpairipcore.so => not found
Anyway, if you bypass pirip the way I explained above, the game crashes with SIGESIGV_MAPERR
08-05 09:47:04.921 30739 30739 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
08-05 09:47:04.921 30739 30739 F DEBUG : LineageOS Version: '20.0-20230715-UNOFFICIAL-miatoll'
08-05 09:47:04.921 30739 30739 F DEBUG : Build fingerprint: 'Redmi/joyeuse_global/joyeuse:12/RKQ1.211019.001/V13.0.1.0.SJZMIXM:user/release-keys'
08-05 09:47:04.921 30739 30739 F DEBUG : Revision: '0'
08-05 09:47:04.921 30739 30739 F DEBUG : ABI: 'arm64'
08-05 09:47:04.921 30739 30739 F DEBUG : Timestamp: 2023-08-05 09:47:04.821552126+0300
08-05 09:47:04.921 30739 30739 F DEBUG : Process uptime: 1s
08-05 09:47:04.921 30739 30739 F DEBUG : Cmdline: com.teenageengineering.pocketoperatorforpixel
08-05 09:47:04.921 30739 30739 F DEBUG : pid: 30710, tid: 30710, name: peratorforpixel >>> com.teenageengineering.pocketoperatorforpixel <<<
08-05 09:47:04.921 30739 30739 F DEBUG : uid: 10295
08-05 09:47:04.921 30739 30739 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000000000000
08-05 09:47:04.921 30739 30739 F DEBUG : Cause: null pointer dereference
08-05 09:47:04.921 30739 30739 F DEBUG : x0 0000007fe1318251 x1 0000006fe8f75040 x2 0000000000000010 x3 0000000000000000
08-05 09:47:04.921 30739 30739 F DEBUG : x4 0000006fe8f75050 x5 0000007fe1318261 x6 413738415a72396c x7 486678526a4a434b
08-05 09:47:04.921 30739 30739 F DEBUG : x8 0000000000000020 x9 0000007fe1318250 x10 0000000000000003 x11 0000000000000003
08-05 09:47:04.921 30739 30739 F DEBUG : x12 00000070e54e0f80 x13 000000705f625f40 x14 fffffffffc000000 x15 fffffffffffffff9
08-05 09:47:04.921 30739 30739 F DEBUG : x16 0000007040670070 x17 00000070e55427a0 x18 000000710492c000 x19 0000000000000001
08-05 09:47:04.921 30739 30739 F DEBUG : x20 0000006fe8f75000 x21 0000000000000000 x22 0000006fe8f75040 x23 0000000000000010
08-05 09:47:04.921 30739 30739 F DEBUG : x24 0000007fe1318251 x25 000000710525a000 x26 0000007103df6000 x27 0000007fe13193e0
08-05 09:47:04.921 30739 30739 F DEBUG : x28 000000710525b9d8 x29 0000007fe1318350
08-05 09:47:04.921 30739 30739 F DEBUG : lr 0000007040669c34 sp 0000007fe1318150 pc 0000007040669c38 pst 0000000020001000
08-05 09:47:04.921 30739 30739 F DEBUG : backtrace:
08-05 09:47:04.921 30739 30739 F DEBUG : #00 pc 0000000000050c38 /data/app/~~P0-wSvWFoFv990jstjMtCg==/com.teenageengineering.pocketoperatorforpixel-g8N9aBofc2NJIB-n1d2kVg==/lib/arm64/libpairipcore.so (ExecuteProgram+196)
@nitanmarcel Pairip has 3 levels of protection. Signature check and LicenseCheck make up the first level. It is easily removable and are found in smali. In the second level, Many strings from Dex files (generally around 1500 for most of the unity games) are removed by Google Play. Pairip restores them in runtime. In the third level, Data from library files is removed by Pairip. In runtime the library calls ExecuteProgram in the pairip library to restore that data.
You can check if a library invokes ExecuteProgram by searching for it in that library with some hex editor but not always the data gets removed as I have seen some exceptions.
Currently I am trying to analyze the VM and Bytecode of pairip. It's not very hard and with some persistence, You can remove it :)
Edit: I forgot to include the removed code from onReceived function of broadcast receivers. Pairip can replace the code there.
@nitanmarcel Pairip has 3 levels of protection. Signature check and LicenseCheck make up the first level. It is easily removable and are found in smali. In the second level, Many strings from Dex files (generally around 1500 for most of the unity games) are removed by Google Play. Pairip restores them in runtime. In the third level, Data from library files is removed by Pairip. In runtime the library calls ExecuteProgram in the pairip library to restore that data.
You can check if a library invokes ExecuteProgram by searching for it in that library with some hex editor but not always the data gets removed as I have seen some exceptions.
Currently I am trying to analyze the VM and Bytecode of pairip. It's not very hard and with some persistence, You can remove it :)
More like anti tempering protection than signature and license checks, since it includes multiple things such as frida detection and root most likely.
If you want to analyze the bytecode you can put a logcat on the VMRunner;->invoke(). You can see there that it reads and I think decrypts the asset file :)
About unity, I think ExecuteProgram is their main entry point for the game, unless they just put it there randomly to add a little but of protection, which sounds weird.
Anyway I won't be able to find what it does until I stop the library from calling that the specific function.
You can check if a library invokes ExecuteProgram by searching for it in that library with some hex editor but not always the data gets removed as I have seen some exceptions.
Yep, that's how I found libunity.so calls ExecuteProgram
Tho, I have a feeling that blocking the bytecode from the startup application also don't call ExecuteProgram, so stopping the library from calling it should work.
@nitanmarcel Yes it is a complete anti tampering solution. I mentioned them and not the anti debugging ones because I haven't paid much attention to them, yet.. :)
The asset file contains encrypted bytecode and they are passed over to the execVM function inside the library through JNI. The execVM function further calls the bytecode handling function in the library after the decryption of bytecode but due to the huge number of instructions and complex pseudocode...I haven't been able to understand it yet. I need to surely work up on my abilities.
The ExecuteProgram is not the main entry of the library. You are wrong there. The entry point is DT_INIT/.init_proc() function in the library which calls ExecuteProgram.
If you stop the execution of ExecuteProgram without restoring the library data then you will get a crash.
@nitanmarcel Yes it is a complete anti tampering solution. I mentioned them and not the anti debugging ones because I haven't paid much attention to them, yet.. :)
The asset file contains encrypted bytecode and they are passed over to the execVM function inside the library through JNI. The execVM function further calls the bytecode handling function in the library after the decryption of bytecode but due to the huge number of instructions and complex pseudocode...I haven't been able to understand it yet. I need to surely work up on my abilities.
The ExecuteProgram is not the main entry of the library. You are wrong there. The entry point is DT_INIT/.init_proc() function in the library which calls ExecuteProgram.
If you stop the execution of ExecuteProgram without restoring the library data then you will get a crash.
Ah, I didn't really took a long look at the lib.
DT_INIT is the library initialization (e.g: int main()..)
. So that's called when the library is loaded. But there's a function in libunity that's named ExecuteProgram 🤔 well it could mean something else
Removed libpairipcore.so link from libunity.so. and at least it seems that on it's own calls ExecuteProgram of pairip library
IDA calls it as .init_proc() but ghidra calls it DT_INIT. Same thing basically and this function further calls ExecuteProgram.
The ExecuteProgram inside the libUnity is a reference to the ExecuteProgram function in libpairipcore.so
IDA calls it as .init_proc() but ghidra calls it DT_INIT. Same thing basically and this function further calls ExecuteProgram.
The ExecuteProgram inside the libUnity is a reference to the ExecuteProgram function in libpairipcore.so
Yes. But question is, if ExecuteProgram is also called in the pairip initialization, why does libunity needs to call it?
Are you sure that it's called in Pairip initialisation?
Are you sure that it's called in Pairip initialisation?
You said that is called in DT_INIT/.init_proc()
with DT_INIT being the initialization function:
DT_INIT
--
Points to the address of an initialization function that must be called when the file is loaded.</span>
https://android.googlesource.com/platform/bionic/+/android-4.2_r1/linker/README.TXT
so if I don't understand wrong. when the library is loaded, it calls .init_proc() which calls ExecuteProgram as you mentioned. so when libunity loads pairip, it calls the initialization one more time which ends up calling ExecuteProgram
IDA calls it as .init_proc() but ghidra calls it DT_INIT. Same thing basically and this function further calls ExecuteProgram. The ExecuteProgram inside the libUnity is a reference to the ExecuteProgram function in libpairipcore.so
Yes. But question is, if ExecuteProgram is also called in the pairip initialization, why does libunity needs to call it?
ExecuteProgram called not only in libunity. libil2cpp has it too. Unity games easily bypassable too.
IDA calls it as .init_proc() but ghidra calls it DT_INIT. Same thing basically and this function further calls ExecuteProgram. The ExecuteProgram inside the libUnity is a reference to the ExecuteProgram function in libpairipcore.so
Yes. But question is, if ExecuteProgram is also called in the pairip initialization, why does libunity needs to call it?
ExecuteProgram called not only in libunity. libil2cpp has it too. Unity games easily bypassable too.
Yeah I just found out after I replaced the one in libunity with my own hook xD.
Now with both replaced, the SEGV_MAPERR
crash is gone, and replace with another error xD:
08-05 13:47:28.127 11080 11080 I IL2CPP : JNI_OnLoad
08-05 13:47:28.142 1453 2012 E DatabaseUtils: Writing exception to parcel
08-05 13:47:28.142 1453 2012 E DatabaseUtils: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.enforceSettingReadable(SettingsProvider.java:2135)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.getSystemSetting(SettingsProvider.java:1838)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.call(SettingsProvider.java:434)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProvider.call(ContentProvider.java:2511)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProvider$Transport.call(ContentProvider.java:525)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:295)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.os.Binder.execTransactInternal(Binder.java:1280)
08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.os.Binder.execTransact(Binder.java:1244)
08-05 13:47:28.142 11080 11080 D AndroidRuntime: Shutting down VM
08-05 13:47:28.143 11080 11080 E AndroidRuntime: FATAL EXCEPTION: main
08-05 13:47:28.143 11080 11080 E AndroidRuntime: Process: com.teenageengineering.pocketoperatorforpixel, PID: 11080
08-05 13:47:28.143 11080 11080 E AndroidRuntime: java.lang.RuntimeException: Unable to resume activity {com.teenageengineering.pocketoperatorforpixel/com.unity3d.player.UnityPlayerActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4773)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4806)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:57)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:179)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2306)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:201)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Looper.loop(Looper.java:288)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7918)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.createExceptionOrNull(Parcel.java:3021)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.createException(Parcel.java:2999)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:2982)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProviderProxy.call(ContentProviderNative.java:732)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$NameValueCache.getStringForUser(Settings.java:3161)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getStringForUser(Settings.java:3755)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getIntForUser(Settings.java:3860)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getInt(Settings.java:3854)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.OrientationLockListener.<init>(Unknown Source:22)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.UnityPlayer.resume(Unknown Source:59)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.UnityPlayerActivity.onResume(UnityPlayerActivity.java:116)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1570)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.Activity.performResume(Activity.java:8474)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4763)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: ... 13 more
08-05 13:47:28.143 11080 11080 E AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace:
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.enforceSettingReadable(SettingsProvider.java:2135)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.getSystemSetting(SettingsProvider.java:1838)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.call(SettingsProvider.java:434)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProvider.call(ContentProvider.java:2511)
08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProvider$Transport.call(ContentProvider.java:525)
08-05 13:47:28.143 11080 11080 E AndroidRuntime:
I still couldn't hook in it with frida, so I created an empty library and patch libunity and libil2cpp with patchelf
#include <cstdio>
extern "C" void ExecuteProgram(int arg1, int arg2) {
}
IDA calls it as .init_proc() but ghidra calls it DT_INIT. Same thing basically and this function further calls ExecuteProgram. The ExecuteProgram inside the libUnity is a reference to the ExecuteProgram function in libpairipcore.so
Yes. But question is, if ExecuteProgram is also called in the pairip initialization, why does libunity needs to call it?
ExecuteProgram called not only in libunity. libil2cpp has it too. Unity games easily bypassable too.
Yeah I just found out after I replaced the one in libunity with my own hook xD.
Now with both replaced, the
SEGV_MAPERR
crash is gone, and replace with another error xD:08-05 13:47:28.127 11080 11080 I IL2CPP : JNI_OnLoad 08-05 13:47:28.142 1453 2012 E DatabaseUtils: Writing exception to parcel 08-05 13:47:28.142 1453 2012 E DatabaseUtils: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.enforceSettingReadable(SettingsProvider.java:2135) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.getSystemSetting(SettingsProvider.java:1838) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at com.android.providers.settings.SettingsProvider.call(SettingsProvider.java:434) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProvider.call(ContentProvider.java:2511) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProvider$Transport.call(ContentProvider.java:525) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:295) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.os.Binder.execTransactInternal(Binder.java:1280) 08-05 13:47:28.142 1453 2012 E DatabaseUtils: at android.os.Binder.execTransact(Binder.java:1244) 08-05 13:47:28.142 11080 11080 D AndroidRuntime: Shutting down VM 08-05 13:47:28.143 11080 11080 E AndroidRuntime: FATAL EXCEPTION: main 08-05 13:47:28.143 11080 11080 E AndroidRuntime: Process: com.teenageengineering.pocketoperatorforpixel, PID: 11080 08-05 13:47:28.143 11080 11080 E AndroidRuntime: java.lang.RuntimeException: Unable to resume activity {com.teenageengineering.pocketoperatorforpixel/com.unity3d.player.UnityPlayerActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4773) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4806) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:57) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:179) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2306) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:201) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Looper.loop(Looper.java:288) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7918) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.createExceptionOrNull(Parcel.java:3021) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.createException(Parcel.java:2999) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:2982) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProviderProxy.call(ContentProviderNative.java:732) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$NameValueCache.getStringForUser(Settings.java:3161) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getStringForUser(Settings.java:3755) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getIntForUser(Settings.java:3860) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.provider.Settings$System.getInt(Settings.java:3854) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.OrientationLockListener.<init>(Unknown Source:22) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.UnityPlayer.resume(Unknown Source:59) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.unity3d.player.UnityPlayerActivity.onResume(UnityPlayerActivity.java:116) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1570) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.Activity.performResume(Activity.java:8474) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4763) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: ... 13 more 08-05 13:47:28.143 11080 11080 E AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace: 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.enforceSettingReadable(SettingsProvider.java:2135) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.getSystemSetting(SettingsProvider.java:1838) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at com.android.providers.settings.SettingsProvider.call(SettingsProvider.java:434) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProvider.call(ContentProvider.java:2511) 08-05 13:47:28.143 11080 11080 E AndroidRuntime: at android.content.ContentProvider$Transport.call(ContentProvider.java:525) 08-05 13:47:28.143 11080 11080 E AndroidRuntime:
Ok, these are unitialized strings:
OrientationLockListener(Context context) {
this.b = context;
this.a = new k(context);
ContentResolver contentResolver = this.b.getContentResolver();
String str = fJRqkwjZyCbJn.MCPNoFLU; // Here
nativeUpdateOrientationLockState(Settings.System.getInt(contentResolver, str, 0));
this.a.a(this, str);
}
Are you sure that it's called in Pairip initialisation?
You said that is called in
DT_INIT/.init_proc()
with DT_INIT being the initialization function:DT_INIT -- Points to the address of an initialization function that must be called when the file is loaded.</span>
https://android.googlesource.com/platform/bionic/+/android-4.2_r1/linker/README.TXT
To clear confusion, The .init_proc() function is found in libraries which will call ExecuteProgram. You can check libunity.so. The code inside init_proc will have a call to ExecuteProgram. During runtime, The reference inside libunity.so will be resolved by the linker and it will have the address of ExecuteProgram function which is inside libpairipcore.so
Sorry, I am bad at explaining.
@Daniel-1647 the ExecuteProgram command could exactly be the function pairip calls the bytecode.
For example given the asset l9rZA87AKCJjRxfH
:
Broke the smali and no idea where xD. Going to start again and hopefully I should be able to find where more exactly to load the assets. Maybe I'm onto something
@Daniel-1647 at least it doesn't crash anymore
@nitanmarcel Yes it is a complete anti tampering solution. I mentioned them and not the anti debugging ones because I haven't paid much attention to them, yet.. :)
The asset file contains encrypted bytecode and they are passed over to the execVM function inside the library through JNI. The execVM function further calls the bytecode handling function in the library after the decryption of bytecode but due to the huge number of instructions and complex pseudocode...I haven't been able to understand it yet. I need to surely work up on my abilities.
The ExecuteProgram is not the main entry of the library. You are wrong there. The entry point is DT_INIT/.init_proc() function in the library which calls ExecuteProgram.
If you stop the execution of ExecuteProgram without restoring the library data then you will get a crash.
Any with engendering ExecuteProgram? It has the first argument as the asset file, I'm not sure about the other two, and I'm not sure how it reads the asset file. It could just be there for doing some checks, and the arg2 could be the byte array, and third the length of the array
ExecuteProgram is not really hard to understand. Read the pseudocode and try to analyze the JNI calls
Want to know the address of this native function!
Can you guys also analyze 5play mod? They are the one who got it work on Unity games without using signature killers. Best game to check is Motor Depot https://5play.ru/8121-motor-depot.html
What's interesting is, they replaced libpairipcore.so
string to their own lib libRMS.so
in libunity.so and libil2cpp.so, so that ExecuteProgram calls to libRMS.so
instead
Unfortunately, I don't have much knowledge on the lib side so I have no idea what their own ExecuteProgram is doing. Here's the pseduocode. Also the lib is packed using a private program
void __fastcall ExecuteProgram(const char *a1)
{
__int64 v1; // x30
__int64 v3; // x0
__int64 v4; // x19
__int64 v5; // x21
__int64 v6; // x0
int v7; // w0
unsigned __int64 i; // x8
char *v9; // x20
unsigned int v10; // w14
__int64 v11; // x13
bool v12; // cc
__int64 v13; // x19
__int64 v14; // x1
unsigned int v15; // w8
void *v16; // x0
char *v17; // x9
unsigned __int64 v18; // x8
__int64 v19; // x10
char v20; // t1
void *ptr[2]; // [xsp+0h] [xbp-10h] BYREF
ptr[1] = *(void **)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v3 = sub_3B10(v1);
if ( a1 )
{
if ( qword_ED858 )
{
if ( qword_ED860 )
{
if ( qword_ED868 )
{
if ( qword_ED870 )
{
v4 = v3;
if ( v3 )
{
v5 = (unsigned int)crc32(0LL, 0LL, 0LL);
v6 = strlen(a1);
v7 = crc32(v5, a1, v6);
for ( i = 0LL; i < 0x5816D; i += v11 + 17 )
{
v9 = (char *)&byte_956BC[i];
v10 = byte_956BC[i];
if ( v10 > 1 )
break;
v11 = *(unsigned int *)(v9 + 13);
v12 = v10 != 1 || (unsigned int)(v11 - 1) > 0x5816B;
if ( !v12 && *(_DWORD *)(v9 + 1) == v7 && *(_DWORD *)(v9 + 5) && *(_DWORD *)(v9 + 9) && qword_ED878 )
{
v13 = sub_3CF4(v4);
mprotect((void *)(v13 + *(unsigned int *)(v9 + 5)), *(_DWORD *)(v9 + 9), 7);
v14 = *(unsigned int *)(v9 + 13);
ptr[0] = 0LL;
v15 = sub_3838(v9 + 17, v14, ptr);
v16 = ptr[0];
if ( v15 && v13 && ptr[0] && v15 >= 5 )
{
v17 = (char *)ptr[0] + 4;
v18 = v15 / 5uLL;
do
{
v19 = *((unsigned int *)v17 - 1);
--v18;
v20 = *v17;
v17 += 5;
*(_BYTE *)(v13 + v19) = v20;
}
while ( v18 );
}
free(v16);
return;
}
}
}
}
}
}
}
}
}
good !
Ребята, можете ли вы также проанализировать мод 5play? Именно они заставили его работать в играх Unity без использования сигнатурных убийц. Лучшая игра для проверки — Motor Depot https://5play.ru/8121-motor-depot.html Что интересно, они заменили
libpairipcore.so
строку на свою собственную библиотеку в libunity.so и libil2cpp.so, так что вместо этогоlibRMS.so
вызывается ExecuteProgramlibRMS.so
К сожалению, у меня мало знаний о библиотеках, поэтому я понятия не имею, что делает их собственная программа ExecuteProgram. Вот псевдокод. Также библиотека упакована с помощью приватной программы.
void __fastcall ExecuteProgram(const char *a1) { __int64 v1; // x30 __int64 v3; // x0 __int64 v4; // x19 __int64 v5; // x21 __int64 v6; // x0 int v7; // w0 unsigned __int64 i; // x8 char *v9; // x20 unsigned int v10; // w14 __int64 v11; // x13 bool v12; // cc __int64 v13; // x19 __int64 v14; // x1 unsigned int v15; // w8 void *v16; // x0 char *v17; // x9 unsigned __int64 v18; // x8 __int64 v19; // x10 char v20; // t1 void *ptr[2]; // [xsp+0h] [xbp-10h] BYREF ptr[1] = *(void **)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); v3 = sub_3B10(v1); if ( a1 ) { if ( qword_ED858 ) { if ( qword_ED860 ) { if ( qword_ED868 ) { if ( qword_ED870 ) { v4 = v3; if ( v3 ) { v5 = (unsigned int)crc32(0LL, 0LL, 0LL); v6 = strlen(a1); v7 = crc32(v5, a1, v6); for ( i = 0LL; i < 0x5816D; i += v11 + 17 ) { v9 = (char *)&byte_956BC[i]; v10 = byte_956BC[i]; if ( v10 > 1 ) break; v11 = *(unsigned int *)(v9 + 13); v12 = v10 != 1 || (unsigned int)(v11 - 1) > 0x5816B; if ( !v12 && *(_DWORD *)(v9 + 1) == v7 && *(_DWORD *)(v9 + 5) && *(_DWORD *)(v9 + 9) && qword_ED878 ) { v13 = sub_3CF4(v4); mprotect((void *)(v13 + *(unsigned int *)(v9 + 5)), *(_DWORD *)(v9 + 9), 7); v14 = *(unsigned int *)(v9 + 13); ptr[0] = 0LL; v15 = sub_3838(v9 + 17, v14, ptr); v16 = ptr[0]; if ( v15 && v13 && ptr[0] && v15 >= 5 ) { v17 = (char *)ptr[0] + 4; v18 = v15 / 5uLL; do { v19 = *((unsigned int *)v17 - 1); --v18; v20 = *v17; v17 += 5; *(_BYTE *)(v13 + v19) = v20; } while ( v18 ); } free(v16); return; } } } } } } } } }
they using "libRMS" for hooking il2cpp games.
they using "libRMS" for hooking il2cpp games.
Of course, it unpacks the lib to unknown region and hooks the game, but in pairip mods, they are using custom ExecuteProgram code in libRMS
Guys... Even if you read the crash logs when you try to fix the modified dependencies... You will get closer to the answer. This is a great hint itself
Hi, is there any way to bypass it? I decomplie the apk and change nothing. Then pack it and resign the apk. But it crash when open it.
Does this work ? https://platinmods.com/threads/how-to-bypass-pairip-protections-latest-too-easy.203105/ And it seems use xposed on the protected app will crash on startup too.
I tried last tutorial by platinmods but failed. I will try this new later. Thank you!
Provide the file BitLife - Life Simulator: https://apkcombo.com/bitlife/com.candywriter.bitlife/download/apk Ronin: The Last Samurai: https://apkcombo.com/ronin-the-last-samurai/com.dreamotion.ronin/download/apk 放肆武林: https://apkcombo.com/%E6%94%BE%E8%82%86%E6%AD%A6%E6%9E%97/com.wlgt.bd/download/apk Google Camera: https://apkcombo.com/google-camera/com.google.android.GoogleCamera/download/apk Epic Mecha Girls: Anime RPG: https://apkcombo.com/epic-mecha-girls-anime-rpg/com.vivastudios.mecha.robot.rpg.girls/download/apk Dead Ahead: Zombie Warfare: https://apkcombo.com/dead-ahead-zombie-warfare/com.mobirate.DeadAheadTactics/download/apk Fire Strike Online FPS: https://apkcombo.com/fire-strike-gun-shooter-fps/com.edkongames.aurora/download/apk
Describe the detection issue More and more apps comes with Pairip protection. There is almost no infomation about it, there are very few:
I suspect it has do to with Google Play automatically integrating pairipcore in APK file, maybe Google Play Integrity? The documentation says it is implemented for server communication but there must be something else within Google Play publishing system.
When APK is tampered, the app crashes immediately without error. In the logcat, the "Apk signature is invalid." exception is thrown. It also crashes but just throw lib exception without any details when trying to run in any emulators. I guess emulators are not supported to handle this protection
Doing some Google searching, I discovered Google Camera has the same protection, so I did some little reversing with Google Camera APK. Here is the result. There is signature check and VM runner
OnReciever methods are encrypted and replaced with VM invoke call, making it more difficult to disable the protection. This is similar to Jiagu360, encrypting all OnCreate methods. It would be super easy to disable protection if they didn't do this, right? XD
The VM runner reads encrypted files from assets
The libpairipcore.so lib. I checked it in IDA but it seems obfuscated with no useful strings exposed, so I didn't bother checking it further
Reversing BitLife APK, there is license check included
APKiD current results... Yes, it does give some hint that the app is protected
Google Camera
BitLife