[Smali 코드배우기 ] 작성 : ttamna@i2sec 테스트를위해안드로이드앱을리패키징할일이있는데, 이때 smali에대한지식이필요하다. 그래서웹상에있는정보들을모아정리했다. Understanding the dalvik bytecode with the dedexer tool. : Dalvik 구조와, 바이트코드등이설명되어있고, Smali to Java를연습해볼수있도록구성되어있음 :https://www.slideshare.net/paller/understanding-the-dalvik-bytecode-with-thededexer-tool Dalvik은가상레지스터기반으로동작한다. 최대 64k 만큼있을수있고, 대부분의명령들은앞의 256개의레지스터만사용할수있다. 하나의레지스터는하나의값을가지고있을수있다 (char부터 float까지 ) Double과 long 값은두개의연속된레지스터가필요하다. 핵심 - Dalvik 레지스터들은지역변수처럼동작한다. - 메소드들은지역변수처럼각자의레지스터를갖는다. - 호출된메소드는호출하는메소드의레지스터에영향을주지않는다. 기본타입 I int, J long, Z Boolean, D double, F float, S short,
C char, V - void, [x - array (x:datatype) 메소드종류 Static : "this" 인수가암묵적으로첫번째인수로전달되지않으면, Static메소드. Direct : overridde될수없으면 Direct메소드. vtable 개입없이, 직접적으로 invoke 한다. 프라이빗메소드, 생성자 Virtual : 자식클래스들에의해 overridde될수있으면 Virtual메소드. 클래스와관련된 vtable을사용해 invoke 한다 인스트럭션패밀리 # 레지스터간 move : move, move/from16, move-wide, move-wide/from16, move-object, move-object/from16 # 결과값을얻거나세팅 : move-result, move-result-wide, move-result-object, return-void, return, return-wide, return-object # 예외처리 : throw, move-exception # 레지스터에상수대입 : const/4, const/16, const, const/high16, const-wide/1, const-wide/32, const-wide, const-wide/high16, const-string, const-class # 동기화 : monitor-enter, monitor-exit
# 타입체크 : check-cast, instance-of # 배열조작 : new-array, array-length, filled-new-array, filled-new-array/range, fill-array-data # 인스턴스생성 : new-instance # 실행조작 : goto, goto/16, packed-switch, sparse-switch, if-eq, if-ne, if-lt, if-ge, if-gt, if-le, if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez # 비교 : cmpl-float, cmpg-float, cmpl-double, cmpl-double, cmpg-double, cmp-long # 멤버필드에읽기 / 쓰기 : iget, iget-wide, iget-object, iget-boolean, iget-byte, iget-char, iget-short, iput, iput-wide, iput-object, iput-boolean, iput-byte, iput-char, iput-short # 배열요소에읽기 / 쓰기 : aget, aget-wide, aget-object, aget-boolean, aget-byte, aget-char, aget-short, aput, aput-wide, aput-object, aput-boolean, aput-byte, aput-char, aput-short # 메소드호출 : invoke-virtual, invoke-super, invoke-direct, invoke-static, invoke-interface, invoke-virtual/range, invoke-super/range, invoke-direct/range, invoke-static/range, invoke-interface/range # int, long, float, double 연산명령 : add, sub, mul, div, rem, and, or, xor, shl, shr, ushr, neg-(int, long, float, double), not-(int, long)
# ODEX 명령 : execute-inline, invoke-direct-empty, iget-quick, iget-wide-quick, iget-object-quick, iput-quick, iput-wide-quick, iput-object-quick, invoke-virtual-quick, invoke-virtual-quick/range, invoke-super-quick, invoke-super-quick/range Smali to Java 연습문제 Exercise 1.method private swap([ii)v.registers 5 # this: v2 (Ltest10;) # parameter[0] : v3 ([I) # parameter[1] : v4 (I) aget v0, v3, v4 ; v0 = v3[v4] add-int/lit8 v1, v4, 1 ; v1 = v4+1 aget v1, v3, v1 ; v1 = v3[v1] aput v1, v3, v4 ; v3[v4] = v1 add-int/lit8 v1, v4, 1 ; v1 = v4+1 aput v0, v3, v1 ; v3[v1] = v0 return-void.end method
Solution 1 private void swap( int array[], int i ){ int temp = array[i]; array[i] = array[i+1]; array[i+1] = temp; } Exercise 2.method private sort([i)v # this: v6 (Ltest10;) # parameter[0] : v7 ([I) const/4 v5, 1 # v5 = 1 const/4 v4, 0 # v4 = 0 l2c4: move v0, v4 # v0 = v4 move v1, v4 # v1 = v4 l2c8 array-length v2, v7 # v2 = v7.length sub-int/2addr v2, v5 # v2 = v2-v5 if-ge v0, v2, l2ee # if (v0 >= v2) -> l2ee aget v2, v7, v0 # v2 = v7[v0] add-int/lit8 v3, v0, 1 # v3 = v0+1 aget v3, v7, v3 # v3 = v7[v3] if-le v2, v3, l2e8 # if (v2 <= v3) -> l2e8 invoke-direct {v6, v7, v0}, Test10/swap;swap([II)V move v1, v5 # v1 = v5 l2e8 add-int/lit8 v0, v0, 1 # v0 = v0+1 goto l2c8 # -> l2c8 l2ee if-nez v1, l2c4 # if (v1!= 0) -> l2c4 return-void
Solution 2 private void sort(int arr[]) { boolean v1; do { v1 = false; for (int i = 0; i < arr.length; i++) { if (arr[i] > arr[i + 1]) { swap(arr, i); v1 = true; } } } while (v1!= false); } Exercise 3 const/16 v1, 8 new-array v1, v1, [I fill-array-data v1, l288 invoke-direct {v0, v1}, Test10/sort ; sort([i)v... l288: data-array 0x04, 0x00, 0x00, 0x00 0x07, 0x00, 0x00, 0x00 0x01, 0x00, 0x00, 0x00 0x08, 0x00, 0x00, 0x00 0x0A, 0x00, 0x00, 0x00 0x02, 0x00, 0x00, 0x00 0x01, 0x00, 0x00, 0x00 0x05, 0x00, 0x00, 0x00 end data-array
Solution 3 int array[] = {4, 7, 1, 8, 10, 2, 1, 5}; this.sort(array); Exercise 4.method private read(ljava/io/inputstream;)i.registers 3 # this: v1 (Ltest10;) # parameter[0] : v2 (Ljava/io/InputStream;).catch java/io/ioexception from l300 to l306 using l30a l300: invoke-virtual {v2}, java/io/inputstream/read; read()i l306: move-result v0 l308: return v0 l30a: move-exception v0 const/4 v0, 15 goto l308.end method
Solution 4 private int read(java.io.inputstream input_stream){ int result = 0; try{ result = input_stream.read(); } catch (Exception e){ result = 15; } return result; }
example.smali : Smali로된클래스를코멘트로설명하는데, 전반적인내용들이모두포함되어있어서 smali를익히는데큰도움이된다. : http://androidcracking.blogspot.kr/2010/09/examplesmali.html # 클래스이름, 덤프시파일경로결정.class public Lcom/packageName/example; # Object 에서상속 (Activity, View 등일수있다 ) # 클래스구조는다음과같다 : L<class path="">;.super Ljava/lang/Object; # 원래의자바파일이름.source "example.java" # 이것들은클래스인스턴스변수들이다..field private somestring:ljava/lang/string; # final 변수들은실제적으로직접사용되지않는다. # 왜냐하면그것들에대한참조가값자체로대체되기때문이다 # primitive cheat sheet: # V - void, B - byte, S - short, C - char, I - int # J - long ( 두개의레지스터를사용 ), F - float, D - double.field public final someint:i # the :I integer 를의미한다.field public final somebool:z # the :Z boolean 을의미한다 # 배열을만드는코드 [x (x 는자료형 ).field public final somechararray:[c.field private somestringarray:[ljava/lang/string; # 이것은생성자의 <init> 이다. # 매개변수목록은다음과같다 : ZLjava/lang/String;I # Z - boolean # Ljava/lang/String; - 자바의문자열오브젝트 # (primitive 타입이아닌것의뒤에는세미콜론이붙는다 ) # I - integer # 그리고이생성자는 V 를리턴하는데, V 는 void 를의미한다.method public constructor <init>(zljava/lang/string;i)v # 변수공간이얼만큼필요한지정의한다. # 우리는 v0, v1, v2, v3, v4 그리고 v5 를변수로갖는다 # smali/baksmali 는기본으로.registers 를사용한다 # 그러나이것은 --use-locals 옵션을통해바꿀수있다 # apktool 은 --use-locals 과 --sequential-labels 를사용한다
.locals 6 # 자바코드에서의 Z, Ljava/lang/String, I 의원래이름을알려준다 # 이것들은항상존재하지는않으며, 보통최적화 / 난독화에의해제거된다.parameter "somebool".parameter "someint".parameter "examplestring" #.prologue 와.line 지시어는대부분무시할수있다 # line number 는가끔디버깅에러분석에유용하다.prologue.line 10 # p0 은 0 번째파라메터를의미한다. 이경우 p0 은 Java 클래스의 this 를의미한다 # 부모클래스의생성자를호출한다 # ( 상단에서볼수있듯이 ) 이경우엔 ( 부모 class 가 ) Ljava/lang/Object; 다 invoke-direct {p0}, Ljava/lang/Object;-><init>()V # v0 에문자열을저장한다 const-string v0, "i will not fear. fear is the mind-killer." # v0 에 0xf 를저장한다 (0xf 는 10 진수로 15 다 ) # 16 진수는모든기계언어에서사용한다 ( 사람은보통 10 진수를사용한다 ) # 이전에 v0 에있던값은사라진다 # 변수는그냥레지스터고, 따로타입은존재하지않는다. # 어떤타입의값이던저장할수있다 const/4 v0, 0xF # v1 에 StringBuilder 인스턴스를만든다 new-instance v1, Ljava/lang/StringBuilder; # StringBuilder 를 v2 로초기화한다 (V 를반환한다, V 는 void) const-string v2, "the spice must flow" invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V # v1 에 p1 을 append 한다 (append 메소드호출 ) # p1 은첫번째파라메터다 (boolean 타입의 ), 따라서 append(z) 라는코드가된다 # append 가 StringBuilder 를반환하는코드를주목하자 invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder; # move-result-object 를사용해서위의결과를 v1 에저장한다 move-result-object v1 # object 가아닌것들은 move-result 또는 move-result-wide 를사용한다 # 우리 StringBuilder 에 v2 를 append 한다 # append 가 boolean 타입이아닌 String 타입을취할때어떤형태를갖는지주목하자 const-string v2, "some random string" invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1 # 우리 StringBuilder 의 tostring 메소드를호출한다 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v1 # 우리의새문자열을 log 로출력한다 # 이는 debug 에사용할수있고, ddms 나 logcat 등을통해확인할수있다 # 안드로이드개발자문서를검색해 d() 함수가뭔지살펴보자 const-string v0, "Tag" invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I move-result v0 # 현재시간을밀리초단위로구한다 # J 는 long (wide value, 2 개레지스터사용 ) invoke-static {}, Ljava/lang/System;->currentTimeMillis()J move-result-wide v2 # 여기부터 v2 는 wide value 를갖는것을기억하자, 따라서 v2 과 v3 를차지한다 # 따라서우리는 v4 를다음변수로사용할수있다 # 가능하면변수를재사용하자 const-wide/16 v4, 0x300 # 이로인해 v4 와 v5 를차지한다 div-long/2addr v2, v4 # v2 를 v4 로나눈다 long-to-int v2, v2 # v2 를 int 형으로바꾼다 # Java 소스코드의 line number 와관련된다 ( 정확하지않은경우있음 ).line 12 # p0( 현재인스턴스 ) 의 somebool 변수에 p1 을저장한다 # 자바코드에선 this.somebool = p1; 와같은형태의코드일것이다 iput-boolean p1, p0, Lcom/packageName/example;->someBool:Z.line 14 # this.someint = p3 iput p3, p0, Lcom/packageName/example;->someInt:I # v0 = this.someint iget v0, p0, Lcom/packageName/example;->someInt:I # now we will invoke a static method. # 이제정적메소드를호출할것이다 # {} 는파라메터가없는것을의미한다. # Lfull-package-name;->method-name()return-value-type # ( 풀패키지이름 ;-> 메소드이름 () 리턴값타입 ) 모든내용이있어야한다 invoke-static {}, Lcom/packageName/example;->someMethod()Ljava/lang/String; # invoke-virtual 과 invoke-direct 는클래스인스턴스변수를첫번째파라메터로취한다
.line 16 return-void # meditate on the void..end method # 아래메소드를 java 소스코드로변경해보자.method public static somemethod()ljava/lang/string; # 더적은변수를사용해도될까?.locals 4 new-instance v0, Ljava/lang/Long; invoke-static {}, Ljava/lang/System;->currentTimeMillis()J move-result-wide v1 invoke-direct {v0, v1, v2}, Ljava/lang/Long;-><init>(J)V invoke-static {v0}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 # 그냥 return 이아니라 return-object 명령을사용하는것을주목하자 return-object v1.end method</class>
유용한 Smali snippet # 디버거를기다리도록만드는코드 invoke-static {}, Landroid/os/Debug;->waitForDebugger()V # 로그출력하도록만드는코드 # Log.v( SADIEYU, ==12306 ); # # 레지스터를오염시키지않도록주의 # 필요에따라레지스터를더사용할수있게수정해도된다 const-string v0, "SADIEYU" const-string v1, "==12306" invoke-static {v0, v1}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I 위코드조각들을익히고응용해서소스코드에삽입하면디버깅에큰도움이된다이외에 smali 코드가필요할경우아래에서소개하는 java2smali 플러그인을사용하자. 유용한안드로이드스튜디오플러그인들 : 아래링크들에접속하면 zip 파일을받을수있는데, 안드로이드스튜디오의 [File settings] (plugins) 에서 Install from disk 메뉴를이용해서설치할수있다 java2smali : Java 코드를 Smali 코드로바꿀수있다 : https://plugins.jetbrains.com/plugin/7385-java2smali smalidea : 안드로이드스튜디오에서 Smali 코드를 Syntax highlighting 해준다 : https://github.com/jesusfreke/smali/wiki/smalidea