綁定帳號登入

Android 台灣中文網

打印 上一主題 下一主題

[教程] android逆向分析之smali語法(一)

[複製連結] 查看: 1759|回覆: 0|好評: 0
跳轉到指定樓層
樓主
fam1001 | 收聽TA | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
發表於 2016-3-22 13:31

馬上加入Android 台灣中文網,立即免費下載應用遊戲。

您需要 登錄 才可以下載或查看,沒有帳號?註冊

x

一 、smali資料類型

1.Dalvik位元組碼

Davlik位元組碼中,寄存器都是32位的,能夠支援任何類型,64位類型(Long/Double)用2個連續的寄存器表示;

Dalvik位元組碼有兩種類型:原始類型;引用類型(包括對像和數組)


原始類型:
         v   void  只能用於返回值類型
          Z   boolean
          B   byte
          S   short
          C   char
          I    int
          J    long(64位)
          F   float
          D   double(64位)
對像類型:
Lpackage/name/ObjectName;       相當於java中的package.name.ObjectName;
     L:表示這是一個對像類型
     package/name:該對像所在的包
    ;:表示對像名稱的結束

2.數組的表示形式:
[I  :表示一個整形的一維數組,相當於java的int[];對於多維數組,只要增加[ 就行了,[[I = int[][];註:每一維最多255個;
對像數組的表示形式:[Ljava/lang/String表示一個String的對象數組;

3.方法的表示形式:
  Lpackage/name/ObjectName;->methodName(III)Z
          Lpackage/name/ObjectName  表示類型
          methodName   表示方法名
          III   表示參數(這裡表示為3個整型參數)說明:方法的參數是一個接一個的,中間沒有隔開

4.字段的表示形式:
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;即表示:包名,字段名和字段類型

5.寄存器指定
有兩種方式指定一個方法中有多少寄存器是可用的:
.registers  指令指定了方法中寄存器的總數
.locals    指令表明了方法中非參寄存器的總數,出現在方法中的第一行

6.方法的表示
方法有直接方法和虛方法兩種,直接方法的聲明格式如下:
.method<存取權限>[修飾關鍵字]<方法原型>
<.locals>
[.parameter]
[.prologue]
[.line]
<代碼體>
.end method

存取權限有public、private等,修飾關鍵字有static、constructor等。方法原型描述了方法的名稱、參數與返回值。
.registers指定了方法中寄存器的總數
.locals指定了方法中非參寄存器的總數(局部變量的個數);
.parameter指定了方法的參數;
.prologue指定了代碼的開始處;
.line指定了該處指令在源代碼中的位置。

注意:構造函數的返回類型為V,名字為<init>。

7.方法的傳參:
當一個方法被調用的時候,方法的參數被置於最後N個寄存器中;
例如:一個方法有2個參數,5個寄存器(v0~v4),那麼,參數將置於最後2個寄存器(v3和v4)。非靜態方法中的第一個參數總是調用該方法的對象。
說明:對於靜態方法除了沒有隱含的this參數外,其他都一樣

8.寄存器的命名方式:V命名、P命名
第一個寄存器就是方法中的第一個參數寄存器。比較:使用P命名是為了防止以後如果在方法中增加寄存器,需要對參數寄存器重新進行編號的缺點。特別說明一下:Long和Double類型是64位的,需要2個連續的寄存器
例如:對於非靜態方法LMyObject->myMethod(IJZ)V,有4個參數:  LMyObject(隱含的),int,long,boolean
需要5個寄存器(因為long佔有2個連續的寄存器)來存儲參數:
     P0    this(非靜態方法中這個參數是隱含的)
     P1    I (int)
     P2,P3  J (long)
     P4    Z(bool)
如果是靜態的就是三個參數
     P0   I (int)
     P1,P2 J (long)
     P3    Z(bool)


二、成員變量

# static fields             定義靜態變量的標記
# instance fields        定義實例變量的標記
# direct methods       定義靜態方法的標記
# virtual methods      定義非靜態方法的標記


三、控制條件
"if-eq vA, vB, :cond_**"   如果vA等於vB則跳轉到:cond_**
"if-ne vA, vB, :cond_**"   如果vA不等於vB則跳轉到:cond_**
"if-lt vA, vB, :cond_**"    如果vA小於vB則跳轉到:cond_**
"if-ge vA, vB, :cond_**"   如果vA大於等於vB則跳轉到:cond_**
"if-gt vA, vB, :cond_**"   如果vA大於vB則跳轉到:cond_**
"if-le vA, vB, :cond_**"    如果vA小於等於vB則跳轉到:cond_**
"if-eqz vA, :cond_**"   如果vA等於0則跳轉到:cond_**
"if-nez vA, :cond_**"   如果vA不等於0則跳轉到:cond_**
"if-ltz vA, :cond_**"    如果vA小於0則跳轉到:cond_**
"if-gez vA, :cond_**"   如果vA大於等於0則跳轉到:cond_**
"if-gtz vA, :cond_**"   如果vA大於0則跳轉到:cond_**
"if-lez vA, :cond_**"    如果vA小於等於0則跳轉到:cond_**

z 既可以表示zero(0) 也可以是null、或者false;具體看邏輯。


四、switch分支語句


  1. .method private packedSwitch(I)Ljava/lang/String;  
  2.     .locals 1  
  3.     .parameter "i"  
  4.     .prologue  
  5.     .line 21  
  6.     const/4 v0, 0x0  
  7.     .line 22  
  8.     .local v0, str:Ljava/lang/String;  #v0為字符串,0表示null  
  9.     packed-switch p1, :pswitch_data_0  #packed-switch分支,pswitch_data_0指定case區域  
  10.     .line 36  
  11.     const-string v0, "she is a person"  #default分支  
  12.     .line 39  
  13.     :goto_0      #所有case的出口  
  14.     return-object v0 #返回字符串v0  
  15.     .line 24  
  16.     :pswitch_0    #case 0  
  17.     const-string v0, "she is a baby"  
  18.     .line 25  
  19.     goto :goto_0  #跳轉到goto_0標號處  
  20.     .line 27  
  21.     :pswitch_1    #case 1  
  22.     const-string v0, "she is a girl"  
  23.     .line 28  
  24.     goto :goto_0  #跳轉到goto_0標號處  
  25.     .line 30  
  26.     :pswitch_2    #case 2  
  27.     const-string v0, "she is a woman"  
  28.     .line 31  
  29.     goto :goto_0  #跳轉到goto_0標號處  
  30.     .line 33  
  31.     :pswitch_3    #case 3  
  32.     const-string v0, "she is an obasan"  
  33.     .line 34  
  34.     goto :goto_0  #跳轉到goto_0標號處  
  35.     .line 22  
  36.     nop  
  37.     :pswitch_data_0  
  38.     .packed-switch 0x0    #case  區域,從0開始,依次遞增  
  39.         :pswitch_0  #case 0  
  40.         :pswitch_1  #case 1  
  41.         :pswitch_2  #case 2  
  42.         :pswitch_3  #case 3  
  43.     .end packed-switch  
  44. .end method
複製代碼

packed-switch 指令。p1為傳遞進來的 int 類型的數值,pswitch_data_0 為case 區域,在 case 區域中,第一條指令「.packed-switch」指定了比較的初始值為0 ,pswitch_0~ pswitch_3分別是比較結果為「case 0 」到「case 3 」時要跳轉到的地址。可以發現,標號的命名採用 pswitch_ 開關,後面的數值為 case 分支需要判斷的值,並且它的值依次遞增。再來看看這些標號處的代碼,每個標號處都使用v0 寄存器初始化一個字符串,然後跳轉到了goto_0 標號處,可見goto_0 是所有的 case 分支的出口。另外,「.packed-switch」區域指定的case 分支共有4 條,對於沒有被判斷的 default 分支,會在代碼的 packed-switch指令下面給出。
至此,有規律遞增的 switch 分支就算是搞明白了。最後,將這段 smali 代碼整理為Java代碼如下。
  1. private String packedSwitch(int i) {  
  2.     String str = null;  
  3.     switch (i) {  
  4.         case 0:  
  5.             str = "she is a baby";  
  6.             break;  
  7.         case 1:  
  8.             str = "she is a girl";  
  9.             break;  
  10.         case 2:  
  11.             str = "she is a woman";  
  12.             break;  
  13.         case 3:  
  14.             str = "she is an obasan";  
  15.             break;  
  16.         default:  
  17.             str = "she is a person";  
  18.             break;  
  19.     }  
  20.     return str;  
  21. }
複製代碼

現在我們來看看無規律的case 分支語句代碼會有什麼不同
  1. .method private sparseSwitch(I)Ljava/lang/String;  
  2.     .locals 1  
  3.     .parameter "age"  
  4.     .prologue  
  5.     .line 43  
  6.     const/4 v0, 0x0  
  7.     .line 44  
  8.     .local v0, str:Ljava/lang/String;  
  9.     sparse-switch p1, :sswitch_data_0  # sparse-switch分支,sswitch_data_0指定case區域  
  10.     .line 58  
  11.     const-string v0, "he is a person"  #case default  
  12.     .line 61  
  13.     :goto_0    #case 出口  
  14.     return-object v0  #返回字符串  
  15.     .line 46  
  16.     :sswitch_0    #case 5  
  17.     const-string v0, "he is a baby"  
  18.     .line 47  
  19.     goto :goto_0 #跳轉到goto_0標號處  
  20.     .line 49  
  21.     :sswitch_1    #case 15  
  22.     const-string v0, "he is a student"  
  23.     .line 50  
  24.     goto :goto_0 #跳轉到goto_0標號處  
  25.     .line 52  
  26.     :sswitch_2    #case 35  
  27.     const-string v0, "he is a father"  
  28.     .line 53  
  29.     goto :goto_0 #跳轉到goto_0標號處  
  30.     .line 55  
  31.     :sswitch_3    #case 65  
  32.     const-string v0, "he is a grandpa"  
  33.     .line 56  
  34.     goto :goto_0 #跳轉到goto_0標號處  
  35.     .line 44  
  36.     nop  
  37.     :sswitch_data_0  
  38.     .sparse-switch            #case 區域  
  39.         0x5 -> :sswitch_0     #case 5(0x5)  
  40.         0xf -> :sswitch_1     #case 15(0xf)  
  41.         0x23 -> :sswitch_2    #case 35(0x23)  
  42.         0x41 -> :sswitch_3    #case 65(0x41)  
  43.     .end sparse-switch  
  44. .end method
複製代碼

按照分析packed-switch 的方法,我們直接查看 sswitch_data_0 標號處的內容。可以看到「.sparse-switch 」指令沒有給出初始case 的值,所有的case 值都使用「case 值 -> case 標號」的形式給出。此處共有4 個case ,它們的內容都是構造一個字符串,然後跳轉到goto_0 標號處,代碼架構上與packed-switch 方式的 switch 分支一樣。
最後,將這段smali 代碼整理為Java 代碼如下。
  1. private String sparseSwitch(int age) {  
  2.     String str = null;  
  3.     switch (age) {  
  4.         case 5:  
  5.             str = "he is a baby";  
  6.             break;  
  7.         case 15:  
  8.             str = "he is a student";  
  9.             break;  
  10.         case 35:  
  11.             str = "he is a father";  
  12.             break;  
  13.         case 65:  
  14.             str = "he is a grandpa";  
  15.             break;  
  16.         default:  
  17.             str = "he is a person";  
  18.             break;  
  19.     }  
  20.     return str;  
  21. }
複製代碼
五、
try/catch 语句

  1. .method private tryCatch(ILjava/lang/String;)V  
  2.     .locals 10  
  3.     .parameter "drumsticks"  
  4.     .parameter "peple"  
  5.     .prologue  
  6.     const/4 v9, 0x0  
  7.                                                                                                                                                               .line 19  
  8.     try_start_0                                                                      # 第1個try開始  
  9.     invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I           #將第2個參數轉換為int 型  
  10.     :try_end_0                                                                       # 第1個try結束  
  11.     .catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} : catch_1  # catch_1  
  12.      move-result v1                                      #如果出現異常這裡不會執行,會跳轉到catch_1標號處  
  13.                                                                                                                                                                .line 21  
  14.     .local v1, i:I                                      #.local聲明的變量作用域在.local聲明與.end local 之間  
  15.    :try_start_1                                     #第2個try 開始  
  16.     div-int v2, p1, v1                                      # 第1個參數除以第2個參數  
  17.     .line 22  
  18.     .local v2, m:I      
  19.     mul-int v5, v2, v1                                                   #m * i  
  20.     sub-int v3, p1, v5                                                              #v3  = p1 - v5  
  21.     .line 23  
  22.     .local v3, n:I  
  23.     const-string v5, "u5171u6709%du53eau9e21u817fuff0c%d  
  24.         u4e2au4ebau5e73u5206uff0cu6bcfu4ebau53efu5206u5f97%d  
  25.         u53eauff0cu8fd8u5269u4e0b%du53ea"   # 格式化字符串  
  26.     const/4 v6, 0x4  
  27.     new-array v6, v6, [Ljava/lang/Object;  
  28.     const/4 v7, 0x0  
  29.     .line 24  
  30.     invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;  
  31.     move-result-object v8  
  32.     aput-object v8, v6, v7  
  33.     const/4 v7, 0x1  
  34.     invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;  
  35.     move-result-object v8  
  36.     aput-object v8, v6, v7  
  37.     const/4 v7, 0x2  
  38.     invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;  
  39.     move-result-object v8  
  40.     aput-object v8, v6, v7  
  41.     const/4 v7, 0x3  
  42.     invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;  
  43.     move-result-object v8  
  44.    
  45.     aput-object v8, v6, v7  
  46.     .line 23  
  47.     invoke-static {v5, v6}, Ljava/lang/String;  
  48.         ->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;  
  49.     move-result-object v4  
  50.     .line 25  
  51.     .local v4, str:Ljava/lang/String;  
  52.     const/4 v5, 0x0  
  53.     invoke-static {p0, v4, v5}, Landroid/widget/Toast;  
  54.         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)  
  55.         Landroid/widget/Toast;  
  56.     move-result-object v5  
  57.     invoke-virtual {v5}, Landroid/widget/Toast;->show()V                 # 使用Toast 顯示格式化後的結果  
  58.    :try_end_1                                           #第2個try 結束  
  59.     .catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} : catch_0       # catch_0  
  60.     .catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_1} : catch_1     # catch_1  
  61.     .line 33  
  62.     .end local v1           #i:I  
  63.     .end local v2           #m:I  
  64.     .end local v3           #n:I  
  65.     .end local v4           #str:Ljava/lang/String;  
  66.     :goto_0   
  67.     return-void                                                                     # 方法返回  
  68.     .line 26   
  69.     .restart local v1       #i:I  
  70.     :catch_0     
  71.     move-exception v0  
  72.     .line 27  
  73.     .local v0, e:Ljava/lang/ArithmeticException;  
  74.                                                                                                                                                               :try_start_2                                     #第3個try 開始  
  75.     const-string v5, "u4ebau6570u4e0du80fdu4e3a0"                                  #「人數不能為0」  
  76.     const/4 v6, 0x0  
  77.     invoke-static {p0, v5, v6}, Landroid/widget/Toast;  
  78.         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)  
  79.         Landroid/widget/Toast;  
  80.     move-result-object v5  
  81.     invoke-virtual {v5}, Landroid/widget/Toast;->show()V         #使用Toast 顯示異常原因  
  82.     :try_end_2                                                   #第3個try 結束  
  83.     .catch Ljava/lang/NumberFormatException; {:try_start_2 .. :try_end_2} :catch_1  
  84.      goto :goto_0 #返回  
  85.     .line 29  
  86.     .end local v0           #e:Ljava/lang/ArithmeticException;  
  87.     .end local v1           #i:I  
  88.     :catch_1   
  89.     move-exception v0  
  90.     .line 30  
  91.     .local v0, e:Ljava/lang/NumberFormatException;  
  92.     const-string v5, "u65e0u6548u7684u6570u503cu5b57u7b26u4e32"   
  93.     #「無效的數值字符串」  
  94.     invoke-static {p0, v5, v9}, Landroid/widget/Toast;  
  95.         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)  
  96.         Landroid/widget/Toast;  
  97.     move-result-object v5  
  98.     invoke-virtual {v5}, Landroid/widget/Toast;->show()V  # 使用Toast 顯示異 常原因  
  99.     goto :goto_0                                         #返回  
  100. .end method
複製代碼

代碼中的try語句塊使用try_start_開頭的標號註明,以try_end_開頭的標號結束。第一個try語句的開頭標號為try_start_0,結束標號為 try_end_0。使用多個try語句塊時標號名稱後面的數值依次遞增,本實例代碼中最多使用到了try_end_2。
在try_end_0 標號下面使用「.catch」指令指定處理到的異常類型與catch的標號,格式如下。

.catch < 異常類型> {<try起始標號> .. <try 結束標號>} <catch標號>

查看catch_1標號處的代碼發現,當轉換 String 到int 時發生異常會彈出「無效的數值字符串」的提示。對於代碼中的漢字,baksmali 在反編譯時將其使用Unicode進行編碼,因此,在閱讀前需要使用相關的編碼轉換工具進行轉換。

仔細閱讀代碼會發現在try_end_1標號下面使用「.catch」指令定義了 catch_0與catch_1兩個catch。catch_0標號的代碼開頭又有一個標號為try_start_2的try 語句塊,其實這個try語句塊是虛構的,假如下面的代碼。
  1. private void a() {  
  2.     try {  
  3.         ……  
  4.         try {  
  5.             ……  
  6.         } catch (XXX) {  
  7.             ……  
  8.         }  
  9.     } catch (YYY) {  
  10.         ……  
  11.     }        
  12. }
複製代碼

當執行內部的try語句時發生了異常,如果異常類型為XXX,則內部catch就會捕捉到並執行相應的處理代碼,如果異常類型不是 XXX,那麼就會到外層的 catch中去查找異常處理代碼,這也就是為什麼實例的try_end_1標號下面會有兩個catch的原因,另外,如果在執行XXX異常的處理代碼時又發生了異常,這個時候該怎麼辦?此時這個異常就會擴散到外層的catch中去,由於XXX異常的外層只有一個YYY的異常處理,這時會判斷發生的異常是否為YYY類型,如果是就會進行處理,不是則拋給應用程式。回到本實例中來,如果在執行內部的ArithmeticException異常處理時再次發生別的異常,就會調用外層的 catch進行異常捕捉,因此在try_end_2標號下面有一個 catch_1就很好理解了。
最後,將這段 smali 代碼整理為Java 代碼如下。
  1. private void tryCatch(int drumsticks, String peple) {  
  2.         try {  
  3.             int i = Integer.parseInt(peple);  
  4.             try {  
  5.                 int m = drumsticks / i;  
  6.                 int n = drumsticks - m * i;  
  7.                 String str = String.format("共有%d隻雞腿,%d個人平分,每人可分得%d只,還剩下%d只",drumsticks, i, m, n);  
  8.                 Toast.makeText(MainActivity.this, str,Toast.LENGTH_SHORT).show();  
  9.             } catch (ArithmeticException e) {  
  10.                 Toast.makeText(MainActivity.this, " 人數不能為0",Toast.LENGTH_SHORT).show();  
  11.             }  
  12.         } catch (NumberFormatException e) {  
  13.             Toast.makeText(MainActivity.this, " 無效的數值字符串",Toast.LENGTH_SHORT).show();  
  14.         }         
  15. }
複製代碼

finally語句塊
  1. try {  
  2.          ServerSocket serverSocket= new ServerSocket(10000);  
  3.          Socket socket=serverSocket.accept();  
  4.      } catch (IOException e) {  
  5.          e.printStackTrace();  
  6.      }finally{  
  7.         int abc=5;  
  8.         Toast.makeText(this, "sssss ", Toast.LENGTH_SHORT).show();  
  9. }
複製代碼

finally 語句塊作用:執行一些必要代碼。即不管出現異常與否,在finally中的代碼都會被執行
執行時機:針對所有catch語句之後,退出方法之前將被執行(即先執行catch裡面的代碼,但在throw之前將轉向finally)。finally中返回的結果將可以覆蓋catch中返回的結果
對應的smail代碼如下:
:try_start_0
    new-instance v2, Ljava/net/ServerSocket;                                   #ServerSocket v2 = null;   
    const/16 v3, 0x2710                                                                   # v3 = 10000;
    invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V       # v2 = new ServerSocket(v3);
    .line 21
    .local v2, serverSocket:Ljava/net/ServerSocket;
    invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket;   # v2.accept( );
    :try_end_0
    .catchall {:try_start_0 .. :try_end_0} :catchall_0  //處理start_0對應的異常塊是catchall_0   也就是finally
    .catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0//處理start_0對應的異常塊是catch_0,catch_0異常塊先執行,之後再執行catchall_0  


想對應的smali代碼為:
  
    :try_start_0
    new-instance v2, Ljava/net/ServerSocket;
    const/16 v3, 0x2710
    invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V
    .line 21
    .local v2, serverSocket:Ljava/net/ServerSocket;
    invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket;
    :try_end_0

    .catchall {:try_start_0 .. :try_end_0} :catchall_0
    .catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
    .line 27
    const/4 v0, 0x5                                                                             #正常流程 即未發生異常
    .line 28
    .local v0, abc:I
    const-string v3, "sssss "
    invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    move-result-object v3
    invoke-virtual {v3}, Landroid/widget/Toast;->show()V
    .line 32
    .end local v2           #serverSocket:Ljava/net/ServerSocket;
    :goto_0
    return-void
    .line 22
    .end local v0           #abc:I
  
   :catch_0                                                                                        #當發生異常時執行
    move-exception v1
    .line 24
    .local v1, e:Ljava/io/IOException;
   
    :try_start_1
      invoke-virtual {v1}, Ljava/io/IOException;->printStackTrace()V
    :try_end_1
    .catchall {:try_start_1 .. :try_end_1} :catchall_0                        #異常部分執行完畢,轉而執行finally
  
    .line 27
    const/4 v0, 0x5
    .line 28
    .restart local v0       #abc:I
    const-string v3, "sssss "
    invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    move-result-object v3
    invoke-virtual {v3}, Landroid/widget/Toast;->show()V
    goto :goto_0
    .line 25
    .end local v0           #abc:I
    .end local v1           #e:Ljava/io/IOException;
   
  #finally代碼定義部分
    :catchall_0
    move-exception v3
    .line 27
    const/4 v0, 0x5
    .line 28
    .restart local v0       #abc:I
    const-string v4, "sssss "
    invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    move-result-object v4
    invoke-virtual {v4}, Landroid/widget/Toast;->show()V
    .line 30
    throw v3
「用Android 就來APK.TW」,快來加入粉絲吧!
Android 台灣中文網(APK.TW)

評分

參與人數 2碎鑽 +1 幫助 +2 收起 理由
球-球 + 1 好內容,老衲來為這篇文章開開光.
lulala123 + 1 + 1 非常讃

查看全部評分

收藏收藏1 分享分享 分享專題
用Android 就來Android 台灣中文網(https://apk.tw)
回覆

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 註冊

本版積分規則