前言
需要將特定的smali代碼插入到dex文件中起到特殊作用,但是對於smali語法一知半解,這次來總結一下,並介紹如何使用工具來講smali代碼插入dex文件中。
Smali語法
可以使用IDEA或者Android Studio的Java2Smali
插件來查看smali代碼。
源碼:
package cn.soulapp;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.souldemo.R;
public class MainAcitivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
smali代碼:
其中#
后面為注釋
# 類名 .class
.class public Lcn/soulapp/MainAcitivity;
# 該類的父類
.super Landroidx/appcompat/app/AppCompatActivity;
# 文件名
.source "MainAcitivity.java"
# direct methods
.method public constructor <init>()V
# 表示需要寄存器數量,這里表示需要1個寄存器
.registers 1
# 表示邏輯代碼的開始處
.prologue
# 表示Java源文件名的行數
.line 10
# invoke-direct 用於調用非 static 直接方法(也就是說,本質上不可覆蓋的實例方法,即 private 實例方法或構造函數)。
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
# 表示該方法無返回值
return-void
# 表示方法執行結束
.end method
# 后面的可以參考給出的Dalvik 字節碼鏈接中的信息,很多都是從這里獲得的。
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.registers 3
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.annotation build Landroidx/annotation/Nullable;
.end annotation
.end param
.prologue
.line 13
invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
.line 14
const v0, 0x7f0a001c
invoke-virtual {p0, v0}, Lcn/soulapp/MainAcitivity;->setContentView(I)V
.line 15
return-void
.end method
Java類型對應的類型描述符
Java 類型 | 類型描述符 |
---|---|
char | C |
byte | B |
short | S |
int | I |
long | J |
float | F |
double | D |
boolean | Z |
void | V |
對象 | L |
數組 | [ |
基本類型的表示很簡單,int 用 I
表示即可。對象的表示,頂級父類 Object 的表示方法 Ljava/lang/Object;
,再比如 String 類型,就用 Ljava/lang/String
表示。
對於數組,DalviK 有特殊的表示方法 [
后面跟上數組元素的類型。int[]
的表示方式就是 [I
, String[]
的表示方法是 [Ljava/lang/String;
。二維數組用 [[
表示,[[Ljava/lang/String
就是指 String[][]
,以此類推。
org.jf.dexlib2工具
github地址:https://github.com/JesusFreke/smali/tree/master/dexlib2/
下載地址:https://bitbucket.org/JesusFreke/smali/downloads/
dexlib2工具可以修改dex文件內容,你可以插入想要的代碼或者重寫一個dex文件。當然一些反編譯apk的工具也引用了該工具,比如apktool。
步驟
1.創建DexBackedDexFile
我們可以通過 DexFileFactory 類來創建一個 DexBackedDexFile實例
val dexBackedDexFile = DexFileFactory.loadDexFile(File("classes.dex"), Opcodes.getDefault())
一個 DexBackedDexFile 實例代表一個dex文件,通過 DexBackedDexFile 我們可以訪問到所有Class
//所有classes
val classes = dexBackedDexFile.classes
println(classes.size)
2.ClassDef
ClassDef 代表一個類,我們可以通過 DexBackedDexFile 實例來獲取一個類,也可以自行創建一個類.
//從DexBackedDexFile獲取ClassDef
val zzz = dexBackedDexFile.classes.find {
//type 代表class name
it.type == "Lcom.xxx.yyy.zzz;"
}
//父類
println(zzz?.superclass)
復制代碼
val type = "Lcom.xxx.yyy.zzz;"
val defClass = ImmutableClassDef(
//類名
type,
AccessFlags.PUBLIC.value,
//父類
"Ljava/lang/Object;",
//繼承的接口
null,
//源文件
null,
//注解
null,
//靜態成員變量
listOf(
ImmutableField(
//所屬類
type,
//成員名稱
"field1",
//成員類型
"I",
AccessFlags.PRIVATE.value,
//初始化值
ImmutableIntEncodedValue(666),
null,
null
)
),
//成員變量
null,
//直系方法(自定義)
null,
//非直系(繼承重載等)
null
)
3.修改某類的方法返回值
fun main() {
val dexBackedDexFile = DexFileFactory.loadDexFile(File("classes.dex"), Opcodes.getDefault())
val type = javaClassoType("com.uzmap.pkg.EntranceActivity")
//定義一個DexRewriter
val reWriter = DexRewriter(object : RewriterModule() {
//修改方法
override fun getMethodRewriter(rewriters: Rewriters): Rewriter<Method> = Rewriter {
//判斷類名和方法名
if (it.definingClass == type && it.name == "isFromNativeSDK") {
//返回修改后的方法
return@Rewriter MethodWrapper(it).apply {
//修改方法的實現
methodImplementation = ImmutableMethodImplementation(
//寄存器個數
1, listOf(
ImmutableInstruction11n(
//指令
Opcode.CONST_4,
//寄存器
0,
//值
1),
//return p0
ImmutableInstruction11x(
Opcode.RETURN,
0
)
), null, null
)
}.build()
}
it
}
})
val newDexFile = reWriter.dexFileRewriter.rewrite(dexBackedDexFile)
DexFileFactory.writeDexFile("new.dex", newDexFile)
}
fun javaClassToType(clz: String): String = "L${clz.replace(".", "/")};"
上面我們修改了 isFromNativeSDK 方法使其返回true,這里有關於寄存器的知識:
當一個方法被調用的時候,方法的參數被置於最后N個寄存器中。如果一個方法有2個參數,5個寄存器(v0-v4),那么參數將置於最后2個寄存器——v3和v4。 非靜態方法中的第一個參數總是調用該方法的對象。
這里指定了一個寄存器,就是第一個參數this——代表EntranceActivity實例。
修改后的smali代碼:
# virtual methods
.method protected final isFromNativeSDK()Z
.locals 0
const/4 p0, 0x1
return p0
.end method
寄存器都是32位的,能夠支持任何類型。64位類型(Long和Double型)用2個寄存器表示。 有兩種方式指定一個方法中有多少寄存器是可用的。.registers指令指定了方法中寄存器的總數。.locals指令表明了方法中非參寄存器的數量。
MethodWrapper 類的定義:
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.immutable.ImmutableMethod
class MethodWrapper(method: Method) {
var definingClass = method.definingClass
var name = method.name
var parameters = method.parameters
var returnType = method.returnType
var accessFlags = method.accessFlags
var annotations = method.annotations
var hiddenApiRestrictions = method.hiddenApiRestrictions
var methodImplementation = method.implementation
fun build(): ImmutableMethod = ImmutableMethod(
definingClass,
name,
parameters,
returnType,
accessFlags,
annotations,
hiddenApiRestrictions,
methodImplementation
)
}
總結
smali就相當於匯編一樣,是比較底層的語言。研究它無非是對於安卓逆向或者事安卓加固有些許幫助,並可以通過修改dex文件來獲得一些特殊功能。另外,研究它也可以增加自己反編譯的知識,畢竟翻譯這個還是很需要時間的,一些巨人已經將自己的成果發出來供大家使用,我們只是站在巨人的肩膀上。天下沒有絕對的安全。