7

Smali the Parseltongue Language

 2 years ago
source link: https://blog.quarkslab.com/smali-the-parseltongue-language.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Write directly in Smali

When you want to modify the Smali code of an APK, you should follow certain rules to avoid getting incomprehensible issues that will make you tear your hair out.

When you compile a Java program, you give the compiler the architecture of your code:

java/
└── exploit
    └── intent
        └── exploit.java

This architecture is kept in Java compiled code and is given to the DEX compiler:

class/
└── exploit
    └── intent
        └── exploit.class

Smali keeps the same architecture as Java with class files:

smali/
└── exploit
    └── intent
        └── exploit.smali

But when you decompile a more recent APK, you will find multiples folders such as: smali, smali\_classes2, ..., smali\_classesXX. These folders have the same architecture as the Java class folder, but they seem to be incomplete. In fact, DEX files cannot contain a number of methods exceeding 64K (as in Kilo), in other words, no more than 65,536 methods. Since Android 5.0, it is possible to configure your APK to have more than one DEX file with the library multidex, and if your Android SDK level is greater than 21, multidex is natively integrated.

Another limitation that can come up when playing with DEX files is the size of the classes: the size of classesN-1.dex should always be larger than classesN.dex. Otherwise, the application may not respond. In fact, you can launch the application, but in some cases it will crash and in some others it will not respond and no interaction will be possible.

However, if your DEX file has more than 64K methods, you could move the excess number of methods to another DEX. Note that all the classes and nested classes which contain those excess methods should be moved too.

If you want more informations about [MULTIDEX]

Let's take an example

First of all, if you want to reproduce what follows, verify that you use the appropriate version of Java.

apktool b [redacted]/ -o output.apk
I: Using Apktool 2.5.0-dirty
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
    at org.jf.dexlib2.writer.DexWriter.writeAnnotationDirectories(DexWriter.java:919)
    at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:344)
    at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:300)
    at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:61)
    at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:36)
    at brut.androlib.Androlib.buildSourcesSmali(Androlib.java:420)
    at brut.androlib.Androlib.buildSources(Androlib.java:351)
    at brut.androlib.Androlib.build(Androlib.java:303)
    at brut.androlib.Androlib.build(Androlib.java:270)
    at brut.apktool.Main.cmdBuild(Main.java:259)
    at brut.apktool.Main.main(Main.java:85)

If you have this error, then you probably do not have the correct version of Java for your Apktool. In the following example, I use openjdk-17.

First, I decompile the application with Apktool:

apktool d ~/git/asthook/misc/[redacted].apk
I: Using Apktool 2.5.0-dirty on [redacted].apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/madsquirrel/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes3.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

Then, I get a folder [redacted] with 3 subfolders which contain Smali code smali, smali\_classes2, smali\_classes3.

If I rebuild it directly, there is no error:

apktool b -f -o output.apk [redacted]/
I: Using Apktool 2.5.0-dirty
I: Smaling smali folder into classes.dex...
I: Smaling smali_classes3 folder into classes3.dex...
I: Smaling smali_classes2 folder into classes2.dex...
I: Copying raw resources...
I: Copying libs... (/lib)
I: Copying libs... (/kotlin)
I: Copying libs... (/META-INF/services)
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...

At this point, if I modify a Smali file in the first directory [redacted]/smali/com/[redacted]/[redacted]/appUpgrade/AppUpgrade.smali and add a simple method toto:

.method public static toto()V
    locals 0
    return-void
.end method

And try to recompile:

apktool b -f -o output.apk [redacted]/
I: Using Apktool 2.5.0-dirty
I: Smaling smali folder into classes.dex...
Exception in thread "main" org.jf.util.ExceptionWithContext: Exception occurred while writing code_item for method Landroidx/collection/LongSparseArray;->clone()Landroidx/collection/LongSparseArray;
    at org.jf.dexlib2.writer.DexWriter.writeDebugAndCodeItems(DexWriter.java:1046)
    at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:345)
    at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:300)
    at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:61)
    at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:36)
    at brut.androlib.Androlib.buildSourcesSmali(Androlib.java:420)
    at brut.androlib.Androlib.buildSources(Androlib.java:351)
    at brut.androlib.Androlib.build(Androlib.java:303)
    at brut.androlib.Androlib.build(Androlib.java:270)
    at brut.apktool.Main.cmdBuild(Main.java:259)
    at brut.apktool.Main.main(Main.java:85)
Caused by: org.jf.util.ExceptionWithContext: Error while writing instruction at code offset 0x12
    at org.jf.dexlib2.writer.DexWriter.writeCodeItem(DexWriter.java:1319)
    at org.jf.dexlib2.writer.DexWriter.writeDebugAndCodeItems(DexWriter.java:1042)
    ... 10 more
Caused by: org.jf.util.ExceptionWithContext: Unsigned short value out of range: 65536
    at org.jf.dexlib2.writer.DexDataWriter.writeUshort(DexDataWriter.java:116)
    at org.jf.dexlib2.writer.InstructionWriter.write(InstructionWriter.java:356)
    at org.jf.dexlib2.writer.DexWriter.writeCodeItem(DexWriter.java:1279)
    ... 11 more

The error occurs randomly in the method Landroidx/collection/LongSparseArray;->clone()Landroidx/collection/LongSparseArray;, but we can clearly understand what happens when we see this error "Caused by: org.jf.util.ExceptionWithContext: Unsigned short value out of range: 65536". Indeed, 65536 is the limit for the allowed number of methods.

Warning: if during the recompilation step you did not get the above-mentioned error, this probably means that the number of methods did not go over the authorized limit.

Repackage the APK

We still need to sign our APK to finalize the repackaging. We can use a quick'n'dirty script to do this, as follows:

#!/bin/bash

folder="$1"
app="$2"

# genkey
keytool -genkey -keyalg RSA -keysize 2048 -validity 700 -noprompt -alias apkpatcheralias1 -dname "CN=apk.patcher.com, OU=ID, O=APK, L=Patcher, S=Patch, C=BR" -keystore apkpatcherkeystore -storepass password -keypass password 2> /dev/null

# repackage apk
apktool b -f -o "$app" "$folder"

# sign apk
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore apkpatcherkeystore -storepass password "$app" apkpatcheralias1 >/dev/null 2>&1

# zipalign
zipalign -c 4 "$app"

Now, you have all the clues to easily repackage an APK. However, be careful if you repackage an APK: I invite you to remove the build/ folder after each rebuild since some modifications may not be correctly set, thus, at runtime, the application may not open the DEX, as illustrated in the following example:

10-27 05:04:36.359 11241 11241 E AndroidRuntime: FATAL EXCEPTION: main
10-27 05:04:36.359 11241 11241 E AndroidRuntime: Process: com.example.myapplication, PID: 11241
10-27 05:04:36.359 11241 11241 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.ClassNotFoundException: Didn't find class "com.example.myapplication.MainActivity" on path: DexPathList[[zip file "/data/app/com.example.myapplication-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-1/lib/x86_64, /system/lib64, /vendor/lib64]]
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2567)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread.-wrap12(ActivityThread.java)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.os.Handler.dispatchMessage(Handler.java:102)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.os.Looper.loop(Looper.java:154)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread.main(ActivityThread.java:6119)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at java.lang.reflect.Method.invoke(Native Method)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
10-27 05:04:36.359 11241 11241 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.myapplication.MainActivity" on path: DexPathList[[zip file "/data/app/com.example.myapplication-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-1/lib/x86_64, /system/lib64, /vendor/lib64]]
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.Instrumentation.newActivity(Instrumentation.java:1078)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2557)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      ... 9 more
10-27 05:04:36.359 11241 11241 E AndroidRuntime:      Suppressed: java.io.IOException: Failed to open dex files from /data/app/com.example.myapplication-1/base.apk because: Failed to open dex file '/data/app/com.example.myapplication-1/base.apk' from memory: Unrecognized magic number in /data/app/com.example.myapplication-1/base.apk:
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexFile.openDexFileNative(Native Method)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexFile.openDexFile(DexFile.java:367)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexFile.<init>(DexFile.java:112)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexFile.<init>(DexFile.java:77)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexPathList.loadDexFile(DexPathList.java:359)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexPathList.makeElements(DexPathList.java:323)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexPathList.makeDexElements(DexPathList.java:263)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.DexPathList.<init>(DexPathList.java:126)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at com.android.internal.os.PathClassLoaderFactory.createClassLoader(PathClassLoaderFactory.java:43)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:58)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:520)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.LoadedApk.getClassLoader(LoadedApk.java:553)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ActivityThread.getTopLevelResources(ActivityThread.java:1866)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.LoadedApk.getResources(LoadedApk.java:766)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ContextImpl.<init>(ContextImpl.java:2038)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ContextImpl.createAppContext(ContextImpl.java:1983)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5294)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ActivityThread.-wrap2(ActivityThread.java)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
10-27 05:04:36.359 11241 11241 E AndroidRuntime:              ... 6 more

Let me introduce you to Smali programming

This tutorial is not exhaustive, but will help you with where to begin.

To start, Smali files follow a nomenclature.

Naming nomenclature of file

A file is named with Class Name, as in Java, but the code of the Class is not always located in a single file.

For each nested Class (or other artefacts such as Thread, Anonymous, etc.), the code of these artefacts is on another file with the nomenclature <class_name>$<nested_class>.smali. For all Thread objects, they are called like this: <class_name>$N.smali where N is an integer number (greater than 0).

Each Smali file begins with the definition of the Class, as follow:

.class <public|private|synthetic> <static?> L<path>/<class_name>;
# class name could be: "Test" for the file Test.smali
# If Test have a nested class "nestedTest" the name could be "Test$nestedTest"

.super L<parent_class>;
# As in Java, the mother of all classes is java/lang/Object;

Then, to introduce what follows, we talk about types as defined in Smali.

Types

Native types are the following:

  • V void, which can only be used for return value types
  • Z boolean
  • B byte
  • S short
  • C char
  • J long (64 bit)
  • F float
  • D double (64 bit)

For reference types (classes and arrays), we have:

  • L<object> which is an Object type used as follow Lpackage/ObjectName; is equivalent to package.ObjectName; in Java;
  • \[<type> which is an simple Array for an integer one-dimensional array we should have \[I equivalent to Java int\[\];
  • more complexe case where type can be concatenate as \[\[I which is equivalent to int\[\]\[\] in Java;

So now if you want to define some fields in the class:

.field <public|private> <field_name>:<type>

Examples:

# define a public field named defaultLeftPad, which is an Integer
.field public defaultLeftPad:I
# define a public field named defaultLongOptPrefix, which is an array of String
.field public defaultLongOptPrefix:Ljava/lang/String;

Now to define a method:

# definition of a method:
# the method can be private / protected / public
# Can be call static or need to be instanciate, if the method is static the class should be static
.method <public|private> <static?> <function_name>(<type>)<return_type>
# .locals will define the number of registers use
# .locals 3 allow you to use v0, v1 and v2
.locals <X>

# In function of return type the method could be end:
# If the return type is V (void)
return-void

# If the return type is natives types:
return <register>
# Where register contain the correct type of return

# If the return type is an object
return-object <register>

# Finally a method always finish with:
.end method

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK