SMALI
במסמך זה אתאר את העקרונות של .smali שפת
מבנה קובץ smali:
- instance fields – משתנים שמוגדרים עבור כל מופע של המחלקה
- static fields – משתנים המוגדרים עבור כל המופעים של אותה מחלקה
- direct methods – מתודות מסוג static\private\constructor
- virtual methods – כל שאר הסוגים של המתודות
Registers:
ב-dex, נעשה שימוש ברג'יסטרים וירטואלים (בדומה
לאסמבלי) בכדי לאחסן ערכים ולהעביר אותם לפקודות \ מתודות. הם באורך 32 ביט,
ויכולים להכיל כל סוג של ערך.
בכל תחילת מתודה מוגדרת כמות הרג'יסטרים שהיא תכיל.
ישנם שני סוגים רג'יסטרים:
- רג'יסטרים מקומיים (locals):
רג'יסטרים שהמתודה עושה בהם שימוש, ומוגדרים בתוכה. דומה למשתנים מקומיים המוגדרים בתוך פונקציה. באמצעותם ייעשו חישובים פנימיים של הפונקציה והם תקפים רק בתחומה.
הם יהיו הרג'יסטרים הראשונים מבחינת הסדר. בפונקציות שאינן סטטיות, הפרמטר הראשון (p0) יכיל את המופע של המחלקה.
- רג'יסטרים המכילים
פרמטרים (arguments):
כאשר מעבירים לפונקציה פרמטרים, הם יתקבלו וישמרו בתור רג'יסטרים. הם יהיו הרג'יסטרים האחרונים, אחרי הרג'יסטרים המקומיים.
לדוגמא, נתונה פונקציה לא סטטית המקבלת שני פרמטרים, int ו-string, ועושה שימוש פנימי ב3
רג'יסטרים.
ב-smali, הפונקציה תכיל 6 רג'יסטרים (v0-v5)
שימוש
פנימי של המתודה
|
V0
|
שימוש
פנימי של המתודה
|
V1
|
שימוש
פנימי של המתודה
|
V2
|
V3
|
|
יכיל
את פרמטר ה-int
|
V4
|
יכיל
את פרמטר ה-string
|
V5
|
בכל תחילת מתודה, יוגדר כמה רג'יסטרים יש בה. יש 2 דרכים לעשות זאת
- המילה השמורה '.registers'צורה זו מגדירה כמה רג'יסטרים יהיו אבסולוטית במתודה (מקומיים + פרמטרים)
- המילה השמורה '.locals'
צורה זו מגדירה כמה רג'יסטרים לוקאליים יהיו במתודה (אליהם יתווספו הרג'יסטרים של הפרמטרים)
כמו-כן, לרג'יסטרים המכילים פרמטרים ניתן לפנות גם באמצעות האות p ואחריה מספר הפרמטר. לדוגמא, במתודה מהדוגמא הקודמת יהיה ניתן
להתייחס לרג'יסטרים גם באופן הבא:
השימוש זה עדיף מן הסתם, מכיוון שניתן להוסיף רג'יסטרים מקומיים נוספים, ועדיין לשמור על תקינות הפונקציה.
דגשים נוספים:
- כל הפקודות ב-dex תומכות ב16 רג'יסטרים (v0-v15), אך חלק גדול מהן אינו תומך ביותר מ16. לכן, כשנרצה לעשות patch לקוד קיים, נעדיף לא להגדיל את מספר הרג'יסטרים ליותר מ16, אלא להשתמש בכאלה קיימים.
טיפוסים:
ישנם שני סוגי טיפוסים ב-Smali:
- Reference types: אובייקטים ומערכים
- Primitive types: כל השאר
Primitives types (רשימה לא מלאה)
|
|
V
|
void - can only be used for return
types
|
Z
|
boolean
|
B
|
byte
|
S
|
short
|
C
|
char
|
I
|
int
|
J
|
long (64 bits)
|
F
|
float
|
D
|
double (64 bits)
|
- ההתייחסות ל-Boolean מבחינת פקודות dex זהה להתייחסות ל-int, כש0 מייצג false ו-1 מייצג true.
אובייקטים:
אובייקטים מיוצגים באופן הבא:
Lpackage/name/ObjectName;
האות L מייצגת תחילת אובייקט, אחריה יגיע המיקום שלו בפרוייקט, ובסוף Semicolon
מערכים:
מערכים מיוצגים בפורמט הבא: קודם כל התו ']' כפול מספר הממדים של המערכך, ואחריו הטיפוס של המערך.דוגמאות:
[[I – מערך דו ממדי של Int (int[][])
[Ljava/lang/String; - מערך חד ממדי של
מחרוזות (String[])
מספרים:
מספרים מיוצגים בייצוג הקסדצימלי, ומשתמשים ב-IEEE754 standards.כדאי להשתמש ב-converter ייעודי על מנת לחשב אותם.
מתודות:
תיאור מתודות:
מתודות מתוארות בצורה מפורטת, בפורמט:מתודה זו שייכת למחלקה ObjectName, מקבלת 3 טיפוסים מסוג int ומחזירה טיפוס בוליאני.
הגדרת
מתודה:
הגדרת
מתודה תעשה בפורמט הבא –
.method + defintions (public/final/static/etc..) + specification
לאחר
מכן, יוגדרו מספר הרג'יסטרים במתודה (בפורמט .locals או .registers)לאחר מכן, הפרמטרים שהמתודה מקבלת (שורה עבור כל פרמטר, בגרסאות ישנות
'.parameter' ובגראות חדשות '.param' + שם הפרמטר (אם קיים)
ובסיום המתודה, '.end method'
הגדרות Debugging:
ישנן הגדרות נוספות בתוך מתודה שנועדו לצורכי Debugging, כמו .line או .prologue
דוגמא:
הגדרת מתודת Constructor המקבלת שני פרמטרים – טיפוס int וטיפוס בוליאני.
במתודה זו, יש שימוש ב2 רג'יסטרים מקומיים(v0,v1) , וב2 רג'יסטרים המכילים פרמטרים (v2,v3 או p0,p1)
יש לשים ♥ - בגלל שמדובר במתודה סטטית, הרג'יסטר הראשון (v0) משמש בתור רג'יסטר מקומי רגיל, ואינו מכיל את המופע של אותה מחלקה.
שדות:
פניה
לשדות של אובייקט תעשה בפורמט הבא :
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
פנייה לאובייקט בשם FieldName מסוג String, של המחלקה ObjectName.
הזחות:
בקוד smali, ההזחה נעשית באמצעות 4 רווחים. בין כל שורה לשורה יהיה רווח של
שורה ריקה (\r\n\r\n לאחר כל פקודה). השפה אינה מתירנית, ועל כן
יש להקפיד על חוקי ההזחות.
יש 2 צורות שבהן ניתן להעביר משתנים לפקודות:
פקודות:
פקודות ב-smali תואמות ל-dalvik opcodes והן למעשה הלב של השפה. הן מזכירות קצת Assembly ומבצעות את הלוגיקה של הקוד. מעבירים להן רג'יסטרים והן מבצעות עליהם פעולות שונות.יש 2 צורות שבהן ניתן להעביר משתנים לפקודות:
- בתור פרמטרים:
בדרך כלל בקריאה לפקודה שמבצעת invoke לפונקציה
ייעשה בפורמט {{v0, v1, ..
- לא בתור פרמטרים:
בשאר הפקודות, ללא סוגריים.
יעשה בפרומט v0, v1
דוגמא
|
תיאור
|
פקודה
|
invoke-static
{},
Ljava/util/concurrent/Executors;->newSingleThreadExecutor()Ljava/util/concurrent/ExecutorService;
קריאה לפונקציה newSingleThreadExecutor,
של המחלקה Executors, המחזירה אובייקט מסוג ExecutorService.
|
invoke-static
{parameters}, methodtocall
|
|
invoke-static
{p0, v2, v3}, Lkeva/katzinActivity;->moreKeva(ILjava/lang/String)Z
קריאה לפונקציה moreKeva של אובייקט מסוג katzinActivity,
המחזירה משתנה בולייאני. P0 יכיל את מופע המחלקה katzinActivity
שהגיע בתור פרמטר למתודה הקוראת, v2 יכיל טיפוס INT (I) ו-v3 יכיל אובייקט מחרוזת
|
מבצעת קריאה למתודה וירטואלית
|
|
השמה של הפניה לאובייקט שהחזיר ה-invocation
הקודם, לתוך הרג'יסטר vx
|
move-result-object
vx
|
|
השמה של הערך שהחזיר ה-invocation
הקודם, לתוך רג'יסטר vx
|
move-result
vx
|
|
* משמש גם לבדיקה של
ביטויים בוליאניים. שקול ל
if (flag)
|
אם הערך של vx שווה ל-0, קפוץ ל-target.
ה-target
הוא ה-label של המיקום שאליו נרצה לקפוץ. ב-dex bytecode ה-label יתורגם ל-offset הרצוי
|
If-eqz
vx, target
|
שקול ל
if (!flag)
|
אם הערך של vx לא שווה ל-0, קפוץ ל-target.
|
If-nez
vx,target
|
const/4
v1, 0x2
השמה של הספרה 2 בתוך הרג'יסטר v1
|
השמה של 4 ביטים בתוך vx
|
const/4
vx,lit4
|
const-string
v1, "ACCESS_TOKEN"
|
השמה של מחרוזת בתוך רג'יסטר vx. ברמת ה-smali
נראה ממש מחרוזת. בפועל יש שם הפנייה למספר מחרוזת ב-string table
בקובץ ה-dex
|
const-string
vx,string
|