Smali 概述我們都知道港庄,Dalvik 虛擬機(jī)(Dalvik VM)是 Google 專門為 Android 平臺(tái)設(shè)計(jì)的一套虛擬機(jī)。區(qū)別于標(biāo)準(zhǔn) Java 虛擬機(jī) JVM 的 class 文件格式恕曲,Dalvik VM 擁有專屬的 DEX 可執(zhí)行文件格式和指令集代碼鹏氧。smali 和 baksmali 則是針對(duì) DEX 執(zhí)行文件格式的匯編器和反匯編器,反匯編后 DEX 文件會(huì)產(chǎn)生.smali 后綴的代碼文件佩谣,smali 代碼擁有特定的格式與語法把还,smali 語言是對(duì) Dalvik 虛擬機(jī)字節(jié)碼的一種解釋。Smali 語言起初是由一個(gè)名叫 JesusFreke 的 hacker 對(duì) Dalvik 字節(jié)碼的翻譯茸俭,并非一種官方標(biāo)準(zhǔn)語言吊履,因?yàn)?Dalvik 虛擬機(jī)名字來源于冰島一個(gè)小漁村的名字,JesusFreke 便把 smali和 baksmali 取自了冰島語中的“匯編器”和“反編器”调鬓。目前 Smali 是在 Google Code 上的一個(gè)開源項(xiàng)目率翅。雖然主流的 DEX 可執(zhí)行文件反匯編工具不少,如 Dedexer袖迎、IDA Pro 和 dex2jar+jd-gui冕臭,但 Smali 提供反匯編功能的同時(shí),也提供了打包反匯編代碼重新生成 dex 的功能燕锥,因此 Smali被廣泛地用于 APP 廣告注入辜贵、漢化和破解,ROM 定制等方面归形。
Smali 語法規(guī)范與格式Smali 是對(duì) Dalvik 虛擬機(jī)字節(jié)碼的一種解釋托慨,雖然不是官方標(biāo)準(zhǔn)語言,但所有語句都遵循一套語法規(guī)范暇榴。要了解 smali 語法規(guī)范厚棵,可以先從了解 Dalvik 虛擬機(jī)字節(jié)碼的指令格式開始。3.1 Dalvik 虛擬機(jī)字節(jié)碼指令格式在 Android 4.0 源碼 Dalvik/docs 目錄下提供了一份文檔 instruction-formats.html蔼紧,里面詳細(xì)列舉了 Dalvik 虛擬機(jī)字節(jié)碼指令的所有格式.
Dalvik 虛擬機(jī)字節(jié)碼的類型婆硬、方法和字段的表示方法3.2.1 類型Dalvik 字節(jié)碼有兩種類型,基本類型和引用類型奸例。對(duì)象和數(shù)組是引用類型彬犯,其它都是基本類型。
Dalvik 字節(jié)碼類型描述符
描述符 類型
V void查吊,只能用于返回值類型
Z boolean
B byte
S short
C charI intJ long(64 位)
F floatD double(64 位)
L Java 類類型
-
[ 數(shù)組類型
每個(gè) Dalvik 寄存器都是 32 位大小谐区,對(duì)于小于或者等于 32 位長(zhǎng)度的類型來說,一個(gè)寄存器就可以存放該類型的值逻卖,而像 J宋列、D 等 64 位的類型,它們的值是使用相鄰兩個(gè)寄存器來存儲(chǔ)的评也,如 v0 與 v1炼杖、v3 與 v4 等戈鲁。 Java 中的對(duì)象在 smali 中以 Lpackage/name/ObjectName;的形式表示。前面的 L 表示這是一個(gè)對(duì)象類型嘹叫,package/name/表示該對(duì)象所在的包婆殿,ObjectName 是對(duì)象的名字,“;”表示對(duì)象名稱的結(jié)束罩扇。相當(dāng)于 java 中的 package.name.ObjectName婆芦。例 如:Ljava/lang/String;相當(dāng)于 java.lang.String。
<ignore_js_op style="word-wrap: break-word; color: rgb(68, 68, 68); font-family: "Segoe UI", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", Simsun; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
“[”類型可以表示所有基本類型的數(shù)組喂饥。[I 表示一個(gè)整型一維數(shù)組消约,相當(dāng)于 java 中的int[]。對(duì)于多維數(shù)組员帮,只要增加[就行了或粮,[[I 相當(dāng)于 int[][],[[[I 相當(dāng)于 int[][][]捞高。注意每一維的最多 255 個(gè)氯材。對(duì)象數(shù)組的表示:[Ljava/lang/String;表示一個(gè) String 對(duì)象數(shù)組。
方法
方法調(diào)用的表示格式:Lpackage/name/ObjectName;->MethodName(III)Z硝岗。Lpackage/name/ObjectName;表示類型氢哮,MethodName 是方法名,III 為參數(shù)(在此是 3 個(gè)整型參數(shù))型檀,Z 是返回類型(bool 型)冗尤。函數(shù)的參數(shù)是一個(gè)接一個(gè)的,中間沒有隔開胀溺。一個(gè)更復(fù)雜的例子:method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;在 java 中則為:String method(int, int[][], int, String, Object[]).
字段
字段裂七,即 java 中類的成員變量,表示格式:Lpackage/name/ObjectName;->FieldName:Ljava/lang/String; 即包名仓坞,字段名和字段類型背零,字段名與字段類型是以冒號(hào)“:”分隔。
兩種不同的寄存器表示法
在 Dalvik 虛擬機(jī)字節(jié)碼中寄存器的命名法中主要有 2 種:v 命名法和 p 命名法扯躺。假設(shè)一個(gè)函數(shù)使用到 M 個(gè)寄存器捉兴,并且該函數(shù)有 N 個(gè)入?yún)ⅲ鶕?jù) Dalvik 虛擬機(jī)參數(shù)傳遞方式中的規(guī)定:入?yún)⑹褂米詈蟮?N 個(gè)寄存器中录语,局部變量使用從 v0 開始的前 M-N 個(gè)寄存器。比如禾乘,某函數(shù) A 使用了 5 個(gè)寄存器澎埠,2 個(gè)顯式的整形參數(shù),如果函數(shù) A 是非靜態(tài)方法始藕,函數(shù)被調(diào)用時(shí)會(huì)傳入一個(gè)隱式的對(duì)象引用蒲稳,因此實(shí)際傳入的參數(shù)個(gè)數(shù)是 3 個(gè)氮趋。根據(jù)傳參規(guī)則,局部變量將使用前 2 個(gè)寄存器江耀,參數(shù)會(huì)使用后 3 個(gè)寄存器剩胁。v 命名法采用小寫字母“v”開頭的方式表示函數(shù)中用到的局部變量與參數(shù),所有的寄存器命名從 v0 開始祥国,依次遞增昵观。對(duì)于上文的函數(shù) A,v 命名法會(huì)用到 v0舌稀、v1啊犬、v2、v3壁查、v4等 5 個(gè)寄存器觉至,v0 與 v1 表示函數(shù) A 的局部變量,v2 表示傳入的隱式對(duì)象引用睡腿,v3 與 v4 表示實(shí)際傳入的 2 個(gè)整形參數(shù)语御。P 命名法對(duì)函數(shù)的局部變量寄存器命名沒有影響,它的命名規(guī)則是:函數(shù)的入?yún)?p0開始命名席怪,依次遞增沃暗。對(duì)于上文的函數(shù) A,p 命名法會(huì)用到 v0何恶、v1孽锥、p0、p1细层、p2 等 5 個(gè)寄存器惜辑,v0 與 v1 表示函數(shù) A 的局部變量,p0 表示傳入的隱式對(duì)象引用疫赎,p1 與 p2 表示實(shí)際傳入的 2 個(gè)整形參數(shù)盛撑。此時(shí),p0捧搞、p1抵卫、p2 實(shí)際上分別表示 v2、v3胎撇、v4介粘,只是命名不一樣而已。在實(shí)際的 Smali 文件中晚树,幾乎都是使用了 p 命名法姻采,主要原因是使用 p 命名法能夠通過寄存器的名字前綴就能很容易判斷寄存器到底是局部變量還是函數(shù)的入?yún)ⅰ3醮螌W(xué)習(xí) smali語法時(shí)容易對(duì)寄存器 p0 表示的意義出現(xiàn)混亂爵憎,這主要體現(xiàn)在靜態(tài)方法和非靜態(tài)方法中慨亲。其實(shí)只要理解 p 命名法的定義后就可以很清楚的理解婚瓜。在 smali 語法中,在調(diào)用非靜態(tài)方法時(shí)需要傳入該方法所在對(duì)象的引用刑棵,因此此時(shí) p0 表示的是傳入的隱式對(duì)象引用巴刻,從 p1 開始才是實(shí)際傳入的入?yún)ⅰ5窃谡{(diào)用靜態(tài)方法時(shí)蛉签,由于靜態(tài)方法不需要構(gòu)建對(duì)象的引用胡陪,因而也就不需要傳入該方法所在對(duì)象的引用,因此此時(shí)從 p0 開始就是實(shí)際傳入的入?yún)⒄堋T?Dalvik 指令中使用“v 加數(shù)字”的方法來索引寄存器督弓,如:v0、v1乒验、v15愚隧、v255,但每條指令使用的寄存器索引范圍都有限制(因?yàn)?Dalvik 指令字節(jié)碼必須字節(jié)對(duì)齊)锻全,這里我們使用一個(gè)大寫字母來表示 4 位數(shù)據(jù)寬度的取值范圍狂塘,如:指令 move vA, vB,目的寄存器 vA可使用 v0 ~ v15 的寄存器鳄厌,源寄存器 vB 可以使用 v0 ~ v15 寄存器荞胡。指令 move/from16 vAA,vBBBBB,目的寄存器 vAA 可使用 v0 ~ v255 的寄存器了嚎,源寄存器 vB 可以使用 v0 ~ v65535 寄存器泪漂。簡(jiǎn)而言之,當(dāng)目的寄存器和源寄存器中有一個(gè)寄存器的編號(hào)大于 15 時(shí)歪泳,即需要加上/from16 指令才能得到正確運(yùn)行萝勤。初次學(xué)習(xí) Smali 語法時(shí)也容易對(duì)這一點(diǎn)不能理解,不注意就會(huì)導(dǎo)致 Smali 文件匯編為 dex 文件的時(shí)候出現(xiàn)編譯錯(cuò)誤呐伞。比如敌卓,按照前面總結(jié)的 p 命名法,當(dāng) p0 實(shí)際表示的寄存器編號(hào)大于 15 時(shí)伶氢,此時(shí) Smali 語句 move v0趟径,p0 就會(huì)編譯出錯(cuò)。
</ignore_js_op>
在以上指令中癣防,在部分指令助記符后添加了 jumbo 后綴蜗巧,這是在 Android 4.0 開始的擴(kuò)展指令,增加了寄存器和常量的取值范圍劣砍。需要引起注意的是惧蛹,以上指令表中形如 VA 表示寄存器范圍為 v0-v15,形如 VAA 表示寄存器范圍為 v0-v255刑枝,這一點(diǎn)在理解指令時(shí)容易被忽略而導(dǎo)致修改 smali 代碼時(shí)編譯出錯(cuò)香嗓。比如方法調(diào)用指令 invoke 未添加/range 時(shí)傳入方法的參數(shù)列表的寄存器需要在 v0-v15 范圍內(nèi),如果不在范圍內(nèi)需要將不合格寄存器賦值給合格寄存器装畅,然后再調(diào)用方法靠娱。
Smali 格式結(jié)構(gòu)
文件格式
無論是普通類、抽象類掠兄、接口類或者內(nèi)部類像云,在反編譯出的代碼中,它們都以單獨(dú)的Smali 文件來存放蚂夕。每個(gè) smali 文件頭 3 行描述了當(dāng)前類的一些信息迅诬,格式如下。
.class <訪問權(quán)限> [修飾關(guān)鍵字] <類名>.super <父類名>.source <源文件名>
打開 HelloWorld.smali 文件婿牍,頭 3 行代碼如下侈贷。
.class public LHelloWorld;
.super Landroid/app/Activity;
.source "HelloWorld.java"
第 1 行“.class”指令指定了當(dāng)前類的類名。在本例中等脂,類的訪問權(quán)限為 public俏蛮,類名為“LHelloWorld;”,類名開頭的 L 是遵循 Dalvik 字節(jié)碼的相關(guān)約定上遥,表示后面跟隨的字符串為一個(gè)類搏屑。
第 2 行的“.super ”指令指定了當(dāng)前類的父類。本例中的“LHelloWorld;”的父類為“Landroid/app/Activity;”粉楚。
第 3 行的“.source”指令指定了當(dāng)前類的源文件名辣恋。經(jīng)過混淆的 dex 文件,反編譯出來的 smali 代碼可能沒有源文件信息模软,因此“.source”行的代碼可能為空伟骨。
前 3 行代碼過后就是類的主體部分了,一個(gè)類可以由多個(gè)字段或方法組成撵摆。smali 文件中字段的聲明使用“.field”指令底靠。字段有靜態(tài)字段與實(shí)例字段兩種。靜態(tài)字段的聲明格式如下特铝。
static fields
.field <訪問權(quán)限> static [修飾關(guān)鍵字] <字段名>:<字段類型>
baksmali 在生成 Smali 文件時(shí)暑中,會(huì)在靜態(tài)字段聲明的起始處添加“static fields”注釋,Smali 文件中的注釋與 Dalvik 語法一樣鲫剿,也是以井號(hào)“#”開頭鳄逾。“.field”指令后面跟著的是訪問權(quán)限灵莲,可以是 public雕凹、private、protected 之一。修飾關(guān)鍵字描述了字段的其它屬性枚抵,如synthetic线欲。指令的最后是字段名與字段類型,使用冒號(hào)“:”分隔汽摹,語法上與 Dalvik 也是一樣的李丰。實(shí)例字段的聲明與靜態(tài)字段類似,只是少了 static 關(guān)鍵字逼泣,它的格式如下趴泌。
#instance fields
.field <訪問權(quán)限> [修飾關(guān)鍵字] <字段名>:<字段類型>
比如以下的實(shí)例字段聲明。
#instance fields
.field private btn:Landroid/widget/Button;
第 1 行的“instance fields”是 baksmali 生成的注釋拉庶,第 2 行表示一個(gè)私有字段 btn嗜憔,它的類型為“Landroid/widget/Button;”。如果一個(gè)類中含有方法氏仗,那么類中必然會(huì)有相關(guān)方法的反匯編代碼吉捶,Smali 文件中方法的聲明使用“.method”指令。方法有直接方法與虛方法兩種廓鞠。直接方法的聲明格式如下帚稠。
direct methods
.method <訪問權(quán)限> [修飾關(guān)鍵字] <方法原型>
<.locals>
[.parameter]
[.prologue]
[.line]
<代碼體>
.end method
“direct methods”是 baksmali 添加的注釋,訪問權(quán)限和修飾關(guān)鍵字與字段的描述相同床佳,方法原型描述了方法的名稱滋早、參數(shù)與返回值∑雒牵“.locals ”指定了使用的局部變量的個(gè)數(shù)杆麸。“.parameter”指定了方法的參數(shù)浪感,與 Dalvik 語法中使用“.parameters”指定參數(shù)個(gè)數(shù)不同昔头,每個(gè)“.parameter”指令表明使用一個(gè)參數(shù),比如方法中有使用到 3 個(gè)參數(shù)影兽,那么就會(huì)出現(xiàn)3 條“.parameter”指令揭斧。“.prologue”指定了代碼的開始處峻堰,混淆過的代碼可能去掉了該指令讹开。“.line”指定了該處指令在源代碼中的行號(hào)捐名,同樣的旦万,混淆過的代碼可能去除了行號(hào)信息。
虛方法的聲明與直接方法相同镶蹋,只是起始處的注釋為“virtual methods”成艘。如果一個(gè)類實(shí)現(xiàn)了接口赏半,會(huì)在 smali 文件中使用“.implements”指令指出。相應(yīng)的格式聲明如下淆两。
interfaces
.implements <接口名>
“#interfaces”是 baksmali 添加的接口注釋断箫,“.implements”是接口關(guān)鍵字,后面的接口名是 DexClassDef 結(jié)構(gòu)中 interfacesOff 字段指定的內(nèi)容琼腔。如果一個(gè)類使用了注解瑰枫,會(huì)在 smali 文件中使用“.annotation”指令指出踱葛。注解的格式聲明如下丹莲。
annotations
.annotation [注解屬性] <注解類名>
[注解字段=值]
.endannotation
注解的作用范圍可以是類、方法或字段尸诽。如果注解的作用范圍是類甥材,“.annotation”指令會(huì)直接定義在 smali 文件中,如果是方法或字段性含,“.annotation”指令則會(huì)包含在方法或字段定義中洲赵。例如下面的代碼。
instance fields
.field public sayWhat:Ljava/lang/String;
.annotation runtime LMyAnnoField;
info="Hellomyfriend"
.end annotation
.end field
實(shí)例字段 sayWhat 為 String 類型商蕴,它使用了 MyAnnoField 注解叠萍,注解字段 info 值為“Hellomyfriend”。將其轉(zhuǎn)換為 Java 代碼為:
@MyAnnoField(info="Hellomyfriend")
public String sayWhat;
如何分析和修改 Smali 代碼
一個(gè)完整的的 Android 程序反編譯后的代碼量可能非常龐大绪商,并且反編譯后的 Smali 源碼相比 java 的可讀性差太多了苛谷,我們應(yīng)該如何定位關(guān)鍵代碼,分析并修改它們格郁。
定位分析的方法
關(guān)鍵信息查找法
程序運(yùn)行時(shí)會(huì)呈現(xiàn)給我們很多信息腹殿,如提示的文字內(nèi)容、Log 輸出的信息和 ActivityTaskRecord 等信息例书,那么可以從這些信息入手來定位關(guān)鍵的代碼锣尉。
比如,我們想查找程序顯示 Toast 時(shí)上下文代碼决采,Toast 提示的文字內(nèi)容則會(huì)存放到strings.xml 文件或硬編碼到程序代碼中自沧,在資源文件中的字符串會(huì)有一個(gè) id 索引,只需在反編譯的代碼中全文檢索這個(gè) id 即可找到顯示該 Toast 的代碼树瞭;如果是后者拇厢,則在反編譯代碼中查找這個(gè)字符串本身即可。
如在Log中分析到程序發(fā)出了一個(gè)廣播移迫,根據(jù)廣播的Action字符串查找所有smali代碼旺嬉,也可定位到所有發(fā)出和接收該廣播的多處代碼位置,再逐個(gè)分析代碼上下文不難定位時(shí)何處發(fā)出的廣播厨埋。
對(duì)于涉及到程序 UI 邏輯的分析邪媳,通常借助 android-sdk 中的工具 hierarchyviewer 來快速分析定位是程序哪個(gè) Activity 甚至是哪個(gè) View 的相關(guān)代碼。
代碼動(dòng)態(tài)調(diào)試法
Smali 代碼相對(duì)復(fù)雜冗長(zhǎng),對(duì)于需要實(shí)現(xiàn)的功能雨效,直接寫 Smali 代碼既費(fèi)時(shí)間又容易出錯(cuò)迅涮,因此通常采用另一種做法,就是先把功能用 Java 源碼的方式實(shí)現(xiàn)徽龟,然后反編譯得到 Smali代碼叮姑,再把 Smali 代碼合并到目標(biāo)代碼中。
逆向之Smali入門學(xué)習(xí)
https://www.52pojie.cn/thread-687375-1-1.html
(出處: 吾愛破解論壇)