Estudio en profundidad de la gramática smali

El autor se centra en el campo de la seguridad de Android. Bienvenido a prestar atención a mi cuenta pública personal de WeChat " Ingeniería de seguridad de Android " (haga clic para escanear el código a seguir). La cuenta pública personal de WeChat se enfoca principalmente en la protección de seguridad y el análisis inverso de las aplicaciones de Android, compartiendo varios métodos de ataque y defensa de seguridad, tecnología Hook, compilación ARM y otros conocimientos relacionados con Android. Siga mi WeChat personal y sea testigo del ascenso de los gigantes de la seguridad de Android~

prefacio

Al inyectar código en un archivo apk, a menudo nos enfrentamos con el código smali descompilado en lugar del archivo de código fuente de Java directo, por lo que es necesario comprender los conceptos básicos de la sintaxis de smali. En primer lugar, presente la máquina virtual Dalvik: Dalvik es una máquina virtual especialmente diseñada por Google para la plataforma Android. Aunque los programas de Android se pueden desarrollar utilizando el lenguaje Java, Dalvik VM y Java VM son dos máquinas virtuales diferentes. La máquina virtual Dalvik se basa en registros, mientras que la máquina virtual Java se basa en pilas. Dalvik VM tiene un formato especial de ejecución de archivos dex (Dalvik Executable), mientras que Java VM ejecuta Java bytecode. DVM es más rápido y ocupa menos espacio que JVM.

estructura de archivos pequeños

El siguiente código smali se toma de una demostración de prueba (obtenida al descompilar el archivo .apk con apktool, aquí hay una introducción al formato de sintaxis smali), el propósito es tener una comprensión general de la estructura de contenido del archivo smali, que es beneficioso para el posterior Hay una comprensión general al explicar los detalles de la gramática.

.class public abstract Lcom/happy/learnsmali/BaseActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "BaseActivity.kt"

# interfaces
.implements Lcom/happy/learnsmali/action/ActivityAction;
.implements Lcom/happy/learnsmali/action/ClickAction;
.implements Lcom/happy/learnsmali/action/HandlerAction;
.implements Lcom/happy/learnsmali/action/BundleAction;
.implements Lcom/happy/learnsmali/action/KeyboardAction;


# annotations
.annotation system Ldalvik/annotation/MemberClasses;
    value = {
        Lcom/happy/learnsmali/BaseActivity$Companion;,
        Lcom/happy/learnsmali/BaseActivity$OnActivityCallback;
    }
.end annotation

.annotation system Ldalvik/annotation/SourceDebugExtension;
    value = "SMAP\nBaseActivity.kt\nKotlin\n*S Kotlin\n*F\n+ 1 BaseActivity.kt\ncom/happy/learnsmali/BaseActivity\n+ 2 fake.kt\nkotlin/jvm/internal/FakeKt\n*L\n1#1,179:1\n1#2:180\n*E\n"
.end annotation

# static fields
.field public static final Companion:Lcom/happy/learnsmali/BaseActivity$Companion;

.field public static final RESULT_ERROR:I = -0x2


# instance fields
.field private final activityCallbacks$delegate:Lkotlin/Lazy;


# direct methods
.method public static synthetic $r8$lambda$mAxgPA6JBXhjuhBfNvUeqmKUmlk(Lcom/happy/learnsmali/BaseActivity;Landroid/view/View;)V
    .locals 0

    invoke-static {p0, p1}, Lcom/happy/learnsmali/BaseActivity;->initSoftKeyboard$lambda-0(Lcom/happy/learnsmali/BaseActivity;Landroid/view/View;)V

    return-void
.end method

.method static constructor <clinit>()V
    .locals 2

    new-instance v0, Lcom/happy/learnsmali/BaseActivity$Companion;

    const/4 v1, 0x0

    invoke-direct {v0, v1}, Lcom/happy/learnsmali/BaseActivity$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V

    sput-object v0, Lcom/happy/learnsmali/BaseActivity;->Companion:Lcom/happy/learnsmali/BaseActivity$Companion;

    return-void
.end method

.method public constructor <init>()V
    // ...
.end method

En el código anterior, si eres nuevo en el código smali, es normal que estés confundido. Lo analizaré a continuación. Comprender el significado de estos símbolos nos ayudará a inyectar código cuando descompilemos el tiempo apk para lograr el doble de resultado. con la mitad del esfuerzo.

Herencia, interfaz, información del paquete en smali

Primero, veamos las primeras líneas:

.class public abstract Lcom/happy/learnsmali/BaseActivity; // .class 表示类路径 包名+类名
.super Landroidx/appcompat/app/AppCompatActivity;		   // .super 表示父类的路径
.source "BaseActivity.kt"								   // 表示源码文件名

# interfaces
.implements Lcom/happy/learnsmali/action/ActivityAction;
.implements Lcom/happy/learnsmali/action/ClickAction;
.implements Lcom/happy/learnsmali/action/HandlerAction;
.implements Lcom/happy/learnsmali/action/BundleAction;
.implements Lcom/happy/learnsmali/action/KeyboardAction;


# annotations
.annotation system Ldalvik/annotation/MemberClasses;
    value = {
        Lcom/happy/learnsmali/BaseActivity$Companion;,
        Lcom/happy/learnsmali/BaseActivity$OnActivityCallback;
    }
.end annotation

Las líneas 1-3 definen información básica : indica el archivo smali (tercera línea) obtenido al descompilar el archivo fuente BaseActivity.kt, la ruta del archivo se encuentra en com/happy/learnsmali/ (segunda línea), heredado de androidx/appcompat/app / AppCompatActivity (tercera línea).

Las líneas 5 a 9 definen la información de la interfaz : Indica que las clases de interfaz implementadas por la clase BaseActivity son:

  • com/feliz/aprendersmali/acción/ActividadActividad
  • com/happy/learnsmali/action/ClickAction
  • com/happy/learnsmali/action/HandlerAcción
  • com/happy/learnsmali/action/BundleAction
  • com/happy/learnsmali/action/KeyboardAction

Las líneas 11 a 16 definen clases internas : indica que la clase BaseActivity tiene dos clases internas: Companion y OnActivityCallback.

Después de analizar la información del archivo al comienzo de smali, podemos construir código java basado en esto:

class BaseActivity extends AppCompatActivity 
    implements ActivityAction, ClickAction, HandlerAction, BundleAction, KeyboardAction {
    
    
    
    class Companion {
    
    
        // ...
    }
    
    class OnActivityCallback {
    
    
        // ...
    }
}

Otros metodos

# virtual methods   //Representation is a virtual method
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 10
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 11
    const/high16 v0, 0x7f050000

    invoke-virtual {p0, v0}, Lcom/justart/samlidemo/MainActivity;->setContentView(I)V

    .line 12
    return-void
.end method
  • El método .methodcomienza y .end methodtermina con ;
  • La última V en la primera línea indica que el tipo de devolución es nulo;
  • El parámetro del método Landroid/os/Bundle indica que el parámetro del método onCreate() es de tipo Bundle;
  • .param indica que el nombre del parámetro del método es SavedInstanceState;
  • Finalmente return-void indica que el tipo de valor devuelto es void;

tipo de datos

  • byte:B
  • carácter:C
  • doble:D
  • flotador: F
  • int: yo
  • largo:J
  • bermudas
  • vacío:V
  • booleano: Z
  • matriz:[XXX
  • Objeto:Lxxx/yyy

Creo que con una base JNI, los tipos de datos anteriores serán fáciles de entender. Aquí están los dos últimos elementos anteriores:

matriz:[XXX

Agregue antes del tipo básico [para indicar el tipo de matriz, por ejemplo, matriz int y matriz de bytes son [I, [B.

Objeto:Lxxx/yyy

Los tipos que comienzan Lcon se representan como objetos, como los objetos String se representan como Ljava/lang/String;(el tipo de objeto debe ir seguido de un punto y coma), donde java/lang representa el paquete java.lang y String representa un objeto en la ruta del paquete.

Aquí puede haber dudas sobre los zapatos para niños.Si la clase está representada por Ljava/lang/String;, ¿cómo se debe definir la clase interna en smali? El símbolo pasó por la mente de los zapatos de los niños que pueden haber usado el reflejo de Java $. Sí, también se usa en la sintaxis smali Ljava/lang/String$xxx;para indicar que xxx es una clase interna de la clase String.

registro

Una de las mayores diferencias entre Dalvik VM y JVM es que Dalvik VM se basa en registros. ¿Qué significa basado en registros? La comprensión personal es un poco similar al lenguaje ensamblador, que almacena y transfiere datos a través de registros. En smali, los registros locales se representan con letras que comienzan con v + números, como v0, v1, v2, ..., mientras que los registros de parámetros se representan con p + números iniciales, como p1, p2, p3, .... En particular, el registro del parámetro p0 no representa necesariamente el primer parámetro. En las funciones no estáticas, p0 representa this, p1 representa el primer parámetro y p2 representa el segundo parámetro de la función. En la función estática, p0 corresponde al primer parámetro ( porque el método estático de Java no tiene concepto de objeto ). No hay límite para los registros locales y, en teoría, pueden usarse arbitrariamente.

Variables miembro

Continuemos introduciendo el contenido sobre las variables miembro:

# static field
.field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = "installationId"
//...

# instance field
.field private _activityPackageName:Ljava/lang/String;

Tanto el campo estático como el campo de instancia definidos anteriormente son variables miembro y el formato es:

.field pubilc/private [estático] [final] varName:<类型>

Aunque tanto el campo estático como el campo de instancia son variables miembro, siguen siendo diferentes. Por supuesto, la diferencia más obvia es si está relacionado con objetos: el campo estático es un concepto de nivel de clase, mientras que el campo de instancia es un concepto de nivel de objeto.

La aparición de variables miembro significa que hay asignaciones y valores de variables. En la sintaxis pequeña, la instrucción de valor incluye: iget, sget, iget-boolean, sget-boolean, iget-object, sget-object, etc., y la instrucción de asignación incluye: iput, sput, iput-boolean, sput-boolean, iput-objeto, sput-objeto, etc.

iget / iput representan el valor y la asignación de las variables miembro del campo de instancia, respectivamente;

sget / sput representan el valor y la asignación de variables miembro de campo estático respectivamente;

Ya sea que se trate de un campo de instancia o de una instrucción de obtención y asignación de un miembro de campo estático, se puede juzgar de acuerdo con el prefijo de la instrucción . Con -objectel sufijo, significa que la variable miembro es un tipo de objeto, y sin el sufijo, significa que se opera el tipo de datos básico. En particular, el tipo de datos primitivo booleano usa el -booleansufijo.

Aquí hay un ejemplo:

const/4 v0, 0x0  
iput-boolean v0, p0, Lcom/disney/xx/XxActivity;->isRunning:Z

En el ejemplo anterior, se usa el registro local v0 y se pasa 0x0 al registro local v0, y luego la segunda oración usa la iput-booleaninstrucción para transferir el valor en el registro v0 a com.disney.xx.XxActivityla variable miembro de isRunning. Es decir, es equivalente a: this.isRunning = false;(Como se mencionó anteriormente, p0 se representa como una instancia de objeto en una función no estática this, pero aquí se representa como com.disney.xx.XxActivityuna instancia de objeto de).

variable miembro de campo estático

sget-object v0, Lcom/disney/xx/XxActivity;->PREFS_INSTALLATION_ID:Ljava/lang/String;

Las instrucciones de operación sget-objectse utilizan para obtener variables miembro estáticas y guardarlas en la lista de parámetros locales inmediata. Aquí, el valor del com.disney.xx.XxActivitymiembro estático ubicado en la clase PREFS_INSTALLATION_IDse pasa al registro local v0.

variable miembro de campo de instancia

iget-object v0, p0, Lcom/disney/xx/XxActivity;->_view:Lcom/disney/common/WMWView;

Las instrucciones de operación iget-objecttambién se utilizan para obtener variables de miembros de clase y almacenarlas en la lista de parámetros locales inmediatos. Aquí, com.disney.xx.XxActivitylos miembros del objeto en la clase _viewse asignan a los registros locales v0.

Al observar las variables miembro estáticas de campo estático anteriores y las variables miembro de clase de campo de instancia , se puede resumir el siguiente formato:

** <registro local>, [<registro de parámetros>], <variable de clase a la que pertenece la variable> ->varName:<tipo de variable> **

El formato del comando put es similar al del comando get mencionado anteriormente, aquí puede ver directamente el siguiente ejemplo:

const/4 v3, 0x0  
sput-object v3, p0, Lcom/disney/xx/XxActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;

Representación del código Java: this.globalIapHandler = null; (null = 0x0)

.local v0, wait:Landroid/os/Message;  
const/4 v1, 0x2  
iput v1, v0, Landroid/os/Message;->what:I

Representación del código Java: wait.what = 0x2; (wait es una instancia de Message)

Llamada de función

El formato de la definición de la función:

función (tipo1tipo2tipo3…)RetValor

Cabe señalar que el tipo de parámetro de la función debe definirse como el tipo en la sintaxis smali, y no debe haber otros separadores entre los parámetros. Los ejemplos son los siguientes:

holaSmali ()V
significavoid helloSmali()


pantalla helloSmall ([BI)Zboolean helloSmali(byte[], int)

holaSmali (ZLjava/lang/String;[I[I)V
pantallavoid helloSmali(boolean, String, int[], int[])

En smali, las funciones y las variables miembro también se dividen en dos tipos, pero a diferencia de las variables miembro estáticas de campo estático y las variables miembro de clase de campo de instancia en las variables miembro, las funciones son método directo y método virtual . Entonces, ¿cuál es la diferencia entre el método directo y el método virtual de la función? En términos simples, el método directo es una función privada y el método virtual es una función pública y de protección.

Entonces, al llamar a una función, hay varias instrucciones diferentes, como , invoke-directy invoke-virtual. Al mismo tiempo, también hay una instrucción, que es una instrucción que se llama cuando el número de parámetros pasados ​​es mayor que 4.invoke-staticinvoke-superinvoke-interfaceinvoke-XXX/range

invocar-estática

invoke-static {}, Lcom/disney/xx/UnlockHelper;->unlockCrankypack()Z

invocar-estática significa llamar a una función estática de clase. El código Java se expresa como: UnlockHelper.unlockCrankypack(), observe aquí que invocar-estática {}es seguida inmediatamente por la lista de instancias + parámetros que llama al método . Dado que este método no requiere parámetros y también es un método estático de clase, está {}vacío. otro ejemplo:

const-string v0, "fmodex"  
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

Lo que se llama aquí es static void System.loadLibrary(String)cargar la biblioteca so, y v0 significa pasar parámetros fmodex.

invocar-super

Indica la instrucción utilizada para llamar al método de la clase principal, que se puede ver en el método sobrecargado.

invocar-directo

Indica el método de llamar a una función privada, como:

invoke-direct {p0}, Lcom/disney/xx/XxActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

El GlobalPurchaseHandler getGlobalIapHandler() aquí significa que getGlobalIapHandler() es un método definido en la clase XxActivity con permisos privados.

invocar-virtual

Indica que se llama a una función pública o protegida.

sget-object v0, Lcom/disney/xx/XxActivity;->shareHandler:Landroid/os/Handler;  
invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V

Aquí, v0 se puede expresar como shareHandler:Landroid/os/Handler, y v3 se expresa como el parámetro de tipo Ljava/lang/Object; del método removeCallbacksAndMessages.

invocar-xxxxx/rango

Indica que cuando el parámetro del método >= 5, debe agregarse más tarde /range.

Algunos zapatos para niños pueden notar que los ejemplos anteriores están todos en 调用函数esta operación, ¿parece que no hay ninguna operación para obtener el valor de retorno de la función? En el código smali, si la función llamada devuelve un valor no nulo, también debe usar move-result(devolver tipo de datos básicos) y move-result-object(devolver objeto):

const/4 v2, 0x0  
invoke-virtual {p0, v2}, Lcom/disney/xx/XxActivity;->getPreferences(I)Landroid/content/SharedPreferences;  
move-result-object v1

v1 representa el objeto de tipo SharedPreferences devuelto al llamar al método this.getPreferences(0).

invoke-virtual {v2}, Ljava/lang/String;->length()I  
move-result v2

v2 representa el tipo primitivo int devuelto por String.length().

análisis de ejemplo

Lo anterior analiza preliminarmente las variables de función, las definiciones de métodos y las llamadas. Lo siguiente usa ejemplos para analizar más a fondo la sintaxis smali:

.method protected onDestroy()V
    .locals 0

    .line 79
    invoke-super {p0}, Landroidx/appcompat/app/AppCompatActivity;->onDestroy()V

    .line 80
    invoke-virtual {p0}, Lcom/happy/learnsmali/BaseActivity;->removeCallbacks()V

    .line 81
    return-void
.end method

Esta es la función onDestroy() con la que estamos familiarizados. En primer lugar, vemos la primera frase de la función: .locals 0, que indica el número de registros locales utilizados en esta función Aquí, el número de registros locales es 0 porque el método llamado no utiliza registros locales locales. Si agrego: this.isExited = true en ese método, entonces el método anterior debe modificarse para:

.method protected onDestroy()V
    .locals 1

    .line 79
    invoke-super {p0}, Landroidx/appcompat/app/AppCompatActivity;->onDestroy()V

    .line 80
    invoke-virtual {p0}, Lcom/happy/learnsmali/BaseActivity;->removeCallbacks()V
    
    .line 81
    const/4 v0, 0x1
    iput-boolean v0, p0, Lcom/happy/learnsmali/BaseActivity;->exited:Z

    .line 82
    return-void
.end method

Debido a que la función onDestroy() modificada usa un registro local v0, se .locals 0cambia a .locals 1. Además, también puede notar el identificador .line, que indica el número de línea de la línea de código correspondiente a smali en Java. Por lo general, cuando depuramos el programa en Android Studio y se bloquea, el número de línea del código donde se produce el bloqueo en logcat también es el valor. Por supuesto, este identificador no es obligatorio, pero se recomienda conservarlo para facilitar la depuración.

compartir datos

Al final del artículo, el autor comparte los materiales escritos y organizados en el proceso de aprendizaje de la gramática smali con amigos necesitados:

imagen-20220207205417745

imagen-20220207205710461

imagen-20220207205734440

Contiene algunos operadores detallados más básicos de smali (se pueden usar como una consulta manual), cómo revertir los pasos de una aplicación, etc.

Método de obtención: busque en WeChat, siga la cuenta pública Ingeniería de seguridad de Android y luego responda a la palabra clave smali para obtener.

Supongo que te gusta

Origin blog.csdn.net/HongHua_bai/article/details/122815018
Recomendado
Clasificación