Copyright belongs to the author. If reprinted, please indicate the source of the article: https://cyrus-studio.github.io/blog/
Introduction to Dex2C
Dex2C is a tool that converts DEX bytecode (Java layer code) in Android applications into semantically equivalent C code.
After processing with Dex2C, Java methods become native methods, thus achieving source code obfuscation protection.
Java code:
object AESUtils {
// Convert a regular string to IvParameterSpec
private fun stringToIV(iv: String): IvParameterSpec {
// Convert the string to a byte array using UTF-8 encoding, ensuring its length is 16 bytes
val ivBytes = iv.toByteArray(Charsets.UTF_8)
val ivArray = ByteArray(16)
System.arraycopy(ivBytes, 0, ivArray, 0, Math.min(ivBytes.size, 16))
return IvParameterSpec(ivArray)
}
}
Converted C/C++ code:
#include "Dex2C.h"
/* LAESUtils;->stringToIV(Ljava/lang/String;)Ljavax/crypto/spec/IvParameterSpec; */
extern "C" JNIEXPORT jobject
JNICALL Java_AESUtils_stringToIV__Ljava_lang_String_2(JNIEnv *env, jobject thiz, jstring p4) {
jobject v0 = NULL;
jobject v1 = NULL;
jobject v2 = NULL;
jobject v3 = NULL;
jint v4;
jobject v5 = NULL;
jint v6;
jint v7;
jclass cls0 = NULL, cls1 = NULL, cls2 = NULL, cls3 = NULL, cls4 = NULL, cls5 = NULL, cls6 = NULL;
jfieldID fld0 = NULL;
jmethodID mth0 = NULL, mth1 = NULL, mth2 = NULL, mth3 = NULL, mth4 = NULL;
v0 = (jobject)
env->NewLocalRef(thiz);
v1 = (jobject)
env->NewLocalRef(p4);
L0:
LOGD("0:sget-object \x76\x30\x2c\x20\x4c\x6b\x6f\x74\x6c\x69\x6e\x2f\x74\x65\x78\x74\x2f\x43\x68\x61\x72\x73\x65\x74\x73\x3b\x2d\x3e\x55\x54\x46\x5f\x38\x20\x4c\x6a\x61\x76\x61\x2f\x6e\x69\x6f\x2f\x63\x68\x61\x72\x73\x65\x74\x2f\x43\x68\x61\x72\x73\x65\x74\x3b");
{
#define EX_HANDLE EX_UnwindBlock
if (v2) {
LOGD("env->DeleteLocalRef(%p):v2", v2);
env->DeleteLocalRef(v2);
}
jclass &clz = cls0;
jfieldID &fld = fld0;
D2C_RESOLVE_STATIC_FIELD(clz, fld, "kotlin/text/Charsets", "UTF_8", "Ljava/nio/charset/Charset;");
v2 = (jobject)
env->GetStaticObjectField(clz, fld);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("4:invoke-virtual \x76\x34\x2c\x20\x76\x30\x2c\x20\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x2d\x3e\x67\x65\x74\x42\x79\x74\x65\x73\x28\x4c\x6a\x61\x76\x61\x2f\x6e\x69\x6f\x2f\x63\x68\x61\x72\x73\x65\x74\x2f\x43\x68\x61\x72\x73\x65\x74\x3b\x29\x5b\x42");
{
#define EX_HANDLE EX_UnwindBlock
D2C_NOT_NULL(v1);
jclass &clz = cls1;
jmethodID &mid = mth0;
D2C_RESOLVE_METHOD(clz, mid, "java/lang/String", "getBytes", "(Ljava/nio/charset/Charset;)[B");
jvalue args[] = {{.l = v2}};
v3 = (jarray) env->CallObjectMethodA(v1, mid, args);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("a:move-result-object \x76\x34");
if (v1) {
LOGD("env->DeleteLocalRef(%p):v1", v1);
env->DeleteLocalRef(v1);
}
v1 = (jobject)
v3;
LOGD("c:const-string \x76\x30\x2c\x20\x27\x67\x65\x74\x42\x79\x74\x65\x73\x28\x2e\x2e\x2e\x29\x27");
if (v2) {
LOGD("env->DeleteLocalRef(%p):v2", v2);
env->DeleteLocalRef(v2);
}
v2 = (jstring) env->NewStringUTF("\x67\x65\x74\x42\x79\x74\x65\x73\x28\x2e\x2e\x2e\x29");
LOGD("10:invoke-static \x76\x34\x2c\x20\x76\x30\x2c\x20\x4c\x6b\x6f\x74\x6c\x69\x6e\x2f\x6a\x76\x6d\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x49\x6e\x74\x72\x69\x6e\x73\x69\x63\x73\x3b\x2d\x3e\x63\x68\x65\x63\x6b\x4e\x6f\x74\x4e\x75\x6c\x6c\x45\x78\x70\x72\x65\x73\x73\x69\x6f\x6e\x56\x61\x6c\x75\x65\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x20\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x56");
{
#define EX_HANDLE EX_UnwindBlock
jclass &clz = cls2;
jmethodID &mid = mth1;
D2C_RESOLVE_STATIC_METHOD(clz, mid, "kotlin/jvm/internal/Intrinsics", "checkNotNullExpressionValue", "(Ljava/lang/Object;Ljava/lang/String;)V");
jvalue args[] = {{.l = v1},
{.l = v2}};
env->CallStaticVoidMethodA(clz, mid, args);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
v4 = 16;
v4 = 16;
LOGD("1a:new-array \x76\x31\x2c\x20\x76\x30\x2c\x20\x5b\x42");
{
#define EX_HANDLE EX_UnwindBlock
if (v4 < 0) {
d2c_throw_exception(env, "java/lang/NegativeArraySizeException", "negative array size");
goto EX_HANDLE;
}
if (v5) {
LOGD("env->DeleteLocalRef(%p):v5", v5);
env->DeleteLocalRef(v5);
}
v5 = (jarray) env->NewByteArray((jint) v4);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("1e:array-length \x76\x32\x2c\x20\x76\x34");
{
#define EX_HANDLE EX_UnwindBlock
D2C_NOT_NULL(v1);
v6 = env->GetArrayLength((jarray) v1);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("20:invoke-static \x76\x32\x2c\x20\x76\x30\x2c\x20\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4d\x61\x74\x68\x3b\x2d\x3e\x6d\x69\x6e\x28\x49\x20\x49\x29\x49");
{
#define EX_HANDLE EX_UnwindBlock
jclass &clz = cls4;
jmethodID &mid = mth2;
D2C_RESOLVE_STATIC_METHOD(clz, mid, "java/lang/Math", "min", "(II)I");
jvalue args[] = {{.i = v6},
{.i = v4}};
v7 = (jint) env->CallStaticIntMethodA(clz, mid, args);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("26:move-result \x76\x30");
v4 = (jint) v7;
v6 = 0;
LOGD("2a:invoke-static \x76\x34\x2c\x20\x76\x32\x2c\x20\x76\x31\x2c\x20\x76\x32\x2c\x20\x76\x30\x2c\x20\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x79\x73\x74\x65\x6d\x3b\x2d\x3e\x61\x72\x72\x61\x79\x63\x6f\x70\x79\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x20\x49\x20\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x3b\x20\x49\x20\x49\x29\x56");
{
#define EX_HANDLE EX_UnwindBlock
jclass &clz = cls5;
jmethodID &mid = mth3;
D2C_RESOLVE_STATIC_METHOD(clz, mid, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V");
jvalue args[] = {{.l = v1},
{.i = v6},
{.l = v5},
{.i = v6},
{.i = v4}};
env->CallStaticVoidMethodA(clz, mid, args);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("30:new-instance \x76\x34\x2c\x20\x4c\x6a\x61\x76\x61\x78\x2f\x63\x72\x79\x70\x74\x6f\x2f\x73\x70\x65\x63\x2f\x49\x76\x50\x61\x72\x61\x6d\x65\x74\x65\x72\x53\x70\x65\x63\x3b");
{
#define EX_HANDLE EX_UnwindBlock
if (v1) {
LOGD("env->DeleteLocalRef(%p):v1", v1);
env->DeleteLocalRef(v1);
}
jclass &clz = cls6;
D2C_RESOLVE_CLASS(clz, "javax/crypto/spec/IvParameterSpec");
v1 = (jobject)
env->AllocObject(clz);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
LOGD("34:invoke-direct \x76\x34\x2c\x20\x76\x31\x2c\x20\x4c\x6a\x61\x76\x61\x78\x2f\x63\x72\x79\x70\x74\x6f\x2f\x73\x70\x65\x63\x2f\x49\x76\x50\x61\x72\x61\x6d\x65\x74\x65\x72\x53\x70\x65\x63\x3b\x2d\x3e\x3c\x69\x6e\x69\x74\x3e\x28\x5b\x42\x29\x56");
{
#define EX_HANDLE EX_UnwindBlock
D2C_NOT_NULL(v1);
jclass &clz = cls6;
jmethodID &mid = mth4;
D2C_RESOLVE_METHOD(clz, mid, "javax/crypto/spec/IvParameterSpec", "<init>", "([B)V");
jvalue args[] = {{.l = v5}};
env->CallVoidMethodA(v1, mid, args);
D2C_CHECK_PENDING_EX;
#undef EX_HANDLE
}
return (jobject)
v1;
EX_UnwindBlock:
return NULL;
}
DCC (Dex-to-C Compiler)
DCC is an open-source compiler that transforms smali instruction streams into semantically equivalent C/C++ code.
Project address: https://github.com/amimo/dcc.git
1. Install Dependencies
- • Install JRE or JDK and add Java to the PATH.
- • Install Python 3, refer to: Miniconda Guide: Elegantly Manage Your Python Environment
- • Install project dependencies
cd dcc
pip3 install -r requirements.txt
- • Download apktool, rename it to apktool.jar and place it in the dcc/tools directory
- • Install NDK (r17+), modify ndk_dir in dcc.cfg to the NDK installation directory
2. Load Native Libraries
First, add the code to load the .so library in the appropriate place in the app code, such as the static code block of the Application or onCreate, and regenerate the APK
package com.cyrus.example
import android.app.Application
class CyrusStudioApplication : Application() {
companion object {
init {
try {
System.loadLibrary("nc")
} catch (e: Throwable) {
// Load failed, do nothing
}
}
}
}
3. Specify Methods to Compile
3.1 Using Whitelists and Blacklists
DCC supports filtering functions to compile or prohibit compilation using whitelists and blacklists. Modify filter.txt and configure the functions to be processed using regular expressions.
By default, it compiles Activity.onCreate and all functions in the test demo.
# Do not compile constructors (<init>) and class initialization blocks (<clinit>). (! indicates exclusion)
!<clinit|init>
# Do not compile methods named bigGoto
!bigGoto
# Compile classes/methods containing TestCompiler
.*TestCompiler.*
# Compile any method signature containing onCreate(Landroid/os/Bundle;
.*;onCreate\(Landroid/os/Bundle;.*
# Compile all methods
#.*
3.2 Using Annotations
Add a Dex2C annotation class in any package
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Dex2C
Then use Dex2C to mark the classes that need to be compiled
import com.cyrus.example.dex2c.Dex2C
@Dex2C
object AESUtils {
...
}
Or methods
package com.cyrus.example.md5
import com.cyrus.example.dex2c.Dex2C
import java.security.MessageDigest
object MD5Tools {
@Dex2C
fun javaMD5(input: String): String {
val md = MessageDigest.getInstance("MD5")
val digest = md.digest(input.toByteArray())
return digest.joinToString("") { "%02x".format(it) }
}
}
However, it has been tested that annotations on methods do not take effect.
4. Harden the APP
Use the following command to harden app.apk
python dcc.py app.apk -o out.apk
- • This command will generate two files out.apk and project-source.zip.
- • Among them, out.apk is the hardened app that has been signed and can be installed directly.
- • project-source.zip is a JNI project that contains the compiled C code, which can be directly used for NDK compilation after extraction.
Resolving Signature Failure Issues
Use the command below to harden the APK
python dcc.py app-debug.apk -o out.apk
It reports the following error during the signing step:
I: Built apk into: C:\Users\cyrus\AppData\Local\Temp\tmp6qg2cdco-unsigned.apk
[INFO ] dcc: signing C:\Users\cyrus\AppData\Local\Temp\tmp6qg2cdco-unsigned.apk -> out.apk
Exception in thread "main" java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder
at com.android.signapk.SignApk.addDigestsToManifest(SignApk.java:184)
at com.android.signapk.SignApk.main(SignApk.java:504)
Caused by: java.lang.ClassNotFoundException: sun.misc.BASE64Encoder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/jdk.internal.loader.ClassLoader.loadClass(ClassLoader.java:526)
... 2 more
[ERROR ] dcc: Compile app-debug.apk failed!
Traceback (most recent call last):
File "D:\Projects\dcc\dcc.py", line 541, in <module>
dcc_main(infile, filtercfg, outapk, do_compile, project_dir, source_archive, dynamic_register)
File "D:\Projects\dcc\dcc.py", line 495, in dcc_main
sign(unsigned_apk, outapk)
File "D:\Projects\dcc\dcc.py", line 83, in sign
subprocess.check_call(['java', '-jar', SIGNJAR, pem, pk8, unsigned_apk, signed_apk])
File "D:\App\Miniconda3\envs\anti-app\lib\subprocess.py", line 373, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['java', '-jar', 'tools/signapk.jar', 'tests/testkey/testkey.x509.pem', 'tests/testkey/testkey.pk8', 'C:\Users\cyrus\AppData\Local\Temp\tmp6qg2cdco-unsigned.apk', 'out.apk']' returned non-zero exit status 1.
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\dcc-project-hrosdmqy
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\tmpzhdviz75-dcc
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\dcc-apktool-2jznztrx
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\tmp6qg2cdco-unsigned.apk
The reason for this exception is:
- • DCC internally uses an old signing tool signapk.jar
- • signapk.jar depends on sun.misc.BASE64Encoder, but you are using Java 9 or above
- • Starting from Java 9, sun.misc.BASE64Encoder has been removed
In simple terms: the signing tool that comes with DCC is too old and incompatible with your current version of Java.
Switching to the apksigner tool provided by the Android SDK for signing will work.
Add the apksigner configuration in dcc.cfg
{
"apktool": "tools/apktool.jar",
"ndk_dir": "D:\App\android\sdk\ndk\27.1.12297006",
"apksigner": "D:\App\android\sdk\build-tools\35.0.0\apksigner.bat"
}
Parse the apksigner configuration in the main method of dcc.py
if 'apksigner' in dcc_cfg and os.path.exists(dcc_cfg['apksigner']):
apksigner = dcc_cfg['apksigner']
Modify the sign method in dcc.py
def sign(unsigned_apk, signed_apk):
pem = os.path.join('tests/testkey/testkey.x509.pem')
pk8 = os.path.join('tests/testkey/testkey.pk8')
logger.info("signing %s -> %s" % (unsigned_apk, signed_apk))
subprocess.check_call(['java', '-jar', SIGNJAR, pem, pk8, unsigned_apk, signed_apk])
Change the sign method to use apksigner for signing
def sign(unsigned_apk, signed_apk):
keystore = 'cyrus.jks'
alias = 'cyrus_studio'
storepass = 'cyrus_studio'
keypass = 'cyrus_studio'
logger.info("signing %s -> %s" % (unsigned_apk, signed_apk))
# The sign command of apksigner directly operates on the output file (--out), so I first copy unsigned to signed
import shutil
shutil.copy(unsigned_apk, signed_apk)
# Call apksigner to sign
subprocess.check_call([
apksigner, 'sign',
'--ks', keystore,
'--ks-key-alias', alias,
'--ks-pass', f'pass:{storepass}',
'--key-pass', f'pass:{keypass}',
'--out', signed_apk,
signed_apk
])
Why not use jarsigner?
Because jarsigner can only generate the old v1 signature, while apksigner supports v1/v2/v3/v4 new signatures, ensuring that the APK can be installed normally on Android 7.0 and above.
Resolving Installation Error Messages
Installation error message:
adb: failed to install .\out.apk: Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]
The APK has been marked with android:testOnly=”true”, and the system does not allow direct installation.
Using ApkToolPlus to view the AndroidManifest.xml information of the APK indeed shows android:testOnly

Tool address: https://github.com/CYRUS-STUDIO/ApkToolPlus
Usually, this is because you packaged the APK in debug mode, and the system prohibits users from manually installing such APKs for security reasons.
Add the -t parameter to force installation, allowing adb to ignore the testOnly flag
adb install -t .\out.apk
Or: Because the APK packaged in debug mode is unsigned, repackaging and signing will solve the issue.
Debugging DCC
Create a new run/debug configuration in PyCharm, select dcc.py as the script file, and add Script parameters

Set breakpoints and run debugging

Reverse Analysis of Hardened APK
Conversion completed
[WARNING ] androguard.core.api_specific_resources: Requested API level 34 is larger than maximum we have, returning API level 28 instead.
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
Android NDK: android-19 is unsupported. Using minimum supported version android-21.
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= DynamicRegister.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_encrypt_00024lambda_000240__B.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_decrypt__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_encrypt__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_getTransformation__Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_stringToIV__Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Java_AESUtils_stringToSecretKey__Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] SharedLibrary : libnc.so
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Compile++ : nc <= DynamicRegister.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[armeabi-v7a] Compile++ thumb: nc <= Dex2C.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Compile++ : nc <= Java_AESUtils_decrypt__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Compile++ : nc <= Java_AESUtils_encrypt_00024lambda_000240__B.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Compile++ : nc <= Java_AESUtils_encrypt__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Compile++ : nc <= well_known_classes.cpp
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] SharedLibrary : libnc.so
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Install : libnc.so => libs/armeabi-v7a/libnc.so
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] SharedLibrary : libnc.so
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
make: Entering directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
[arm64-v8a] Install : libnc.so => libs/arm64-v8a/libnc.so
make: Leaving directory 'C:/Users/cyrus/AppData/Local/Temp/dcc-project-nz2i_v6g'
I: Using Apktool 2.11.1 on app.apk with 8 threads
I: Baksmaling classes.dex...
I: Copying raw resources...
I: Baksmaling classes2.dex...
I: Copying raw manifest...
I: Copying original files...
I: Copying assets...
I: Copying lib...
I: Copying unknown files...
I: Using Apktool 2.11.1 on tmpe27zhohx-unsigned.apk with 8 threads
I: Checking whether resources have changed...
I: Checking whether sources have changed...
I: Checking whether sources have changed...
I: Smaling smali folder into classes.dex...
I: Copying raw resources...
I: Smaling smali_classes2 folder into classes2.dex...
I: Building apk file...
I: Importing assets...
I: Importing lib...
I: Importing unknown files...
I: Built apk into: C:\Users\cyrus\AppData\Local\Temp\tmpe27zhohx-unsigned.apk
[INFO ] dcc: signing C:\Users\cyrus\AppData\Local\Temp\tmpe27zhohx-unsigned.apk -> out.apk
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\dcc-project-nz2i_v6g
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\tmpgzmhlbm5-dcc
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\dcc-apktool-h7yloeiq
[INFO ] dcc: removing C:\Users\cyrus\AppData\Local\Temp\tmpe27zhohx-unsigned.apk
Using GDA to open out.apk, you can see that all Java methods in the AESUtils class that have been annotated have been converted into native functions

The Java methods in the AESUtils class have been converted to the following C++ code


Using IDA to open libnc.so, you can see that the protected functions are all statically registered through JNI

Complete Source Code
Open source addresses:
References
[1] Miniconda Guide: Elegantly Manage Your Python Environment: https://cyrus-studio.github.io/blog/posts/miniconda-%E5%85%A8%E6%94%BB%E7%95%A5%E4%BC%98%E9%9B%85%E7%AE%A1%E7%90%86%E4%BD%A0%E7%9A%84-python-%E7%8E%AF%E5%A2%83/[2] apktool: https://apktool.org/[3] NDK(r17+): https://developer.android.com/ndk/guides?hl=zh-cn