本文将主要分析 smali 语法,已经熟悉的朋友可以直接略过就好. 可以直接查看第二篇: partB-analyze-solib
1.Ubuntu 16.04 x64 Host + Windows 10 x64 VMware + IDA Pro 6.8 + Android Phone 4.2
2.分析样本 simple.apk:下载.
cat simple/AndroidManifest.xml | grep -E "<activity" <activity android:label="@string/app_name" android:name="easyre.sjl.gossip.easyre.EasyRe" >可以看到主启动activity为: easyre.sjl.gossip.easyre.EasyRe,因此我们首先考虑 easyre/sjl/gossip/easyre下的EasyRe.smali文件.
.class public Leasyre/sjl/gossip/easyre/EasyRe; .super Landroid/support/v7/app/ActionBarActivity; .source "EasyRe.java" # interfaces .implements Landroid/view/View$OnClickListener;这几句很好理解了,就是public class EasyRe extends ActionBarActivity implements OnClickListener
# instance fields .field bt1:Landroid/widget/Button; .field et1:Landroid/widget/EditText; .field iv1:Landroid/widget/ImageView;类字段定义,就是Button bt1;EditText et1;ImageView iv1;
# direct methods .method public constructor方法调用,从constructor可以看出,这是类的构造方法,使用.locals指明有两个(v0和v1)局部变量, 函数包括一个隐含参数p0(非静态方法中当仅有一个参数时该参数为this),然后调用了其父类中的初始化函数,我们不需要关注:()V .locals 2 move-object v0, p0 # move-object vx,vy是将对vy的引用移入到vx中去 .local v0, "this":Leasyre/sjl/gossip/easyre/EasyRe; move-object v1, v0 invoke-direct {v1}, Landroid/support/v7/app/ActionBarActivity;-> ()V return-void .end method
public Easy(){return 0;}
我们知道android的activity的生命周期大致如下:
public class Activity extends ApplicationContext { protected void onCreate(Bundle savedInstanceState); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onPause(); protected void onStop(); protected void onDestroy(); }即onCreate永远都是第一个执行的(相当于C语言中的main()函数,因此在具体查看其他函数之前我们需要先查看onCreate函数, 遇到其他函数再具体深入查看理解,onCreate函数的代码如下()(删除了.prologue以及.line等对于我们无用的指令):
#void protected onCreate(Bundle p1) .method protected onCreate(Landroid/os/Bundle;)V #5个局部变量 .locals 5 #p0在非静态方法中为this,这里将指向EasyRe类的this指针存放入v0中 move-object v0, p0 .local v0, "this":Leasyre/sjl/gossip/easyre/EasyRe; #p1函数的参数,保存函数参数,存入v1中 move-object v1, p1 .local v1, "savedInstanceState":Landroid/os/Bundle; #声明参数”antidebug”,存入v2中 const-string v2, "antidebug" #库函数调用,参数放入花括号{}中:System->loadLibrary("antidebug") invoke-static {v2}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V #调用EasyRe类中的init函数(仅有一个隐式参数this):init() move-object v2, v0 invoke-virtual {v2}, Leasyre/sjl/gossip/easyre/EasyRe;->init()V #将v0放入v2,v1放入v3,然后v2,v3作为参数调用EasyRe父类中的onCreate()函数 #v0此时是this指针,v1是EasyRe中onCreate的参数 #也就是说 {v2,v3} v2 表示 this,而 v3 则表示函数的参数p1. #this.super.onCreate(p1) move-object v2, v0 move-object v3, v1 invoke-super {v2, v3}, Landroid/support/v7/app/ActionBarActivity;->onCreate(Landroid/os/Bundle;)V #v0仍然是this指针,后序不再赘述了,移入v3的是一个资源ID,我们可以在反编译的根目录下通过执行如下命令: #grep -Eir "0x7f030017" #输出:simple/res/values/public.xml: <public type="layout" name="activity_easy_re" id="0x7f030017" /> #可知:ID为 0x7f030017的资源实际上是一个布局文件(layout),其名字为 activity_easy_re #对应的文件位置为:res/layout/activity_easy_re.xml # v2.setContentView(v3) == this.setContentView(R.layout.activity_easy_re) move-object v2, v0 const v3, 0x7f030017 invoke-virtual {v2, v3}, Leasyre/sjl/gossip/easyre/EasyRe;->setContentView(I)V #v4作为参数传入 findViewById函数,v4是值0x7f09003f,使用grep来查找: grep -Eir “0x7f09003f” #可知名称为button,因此为findViewById(R.id.button) #返回值先存放在v3中,然后进行类型转换(转换为Button类型) #接下来将转换后的对象v3放入v2的引用实例(即this),this引用的实例为input-object指令的最后一个参数(这里即为Button bt1) # v3 = v3->findViewById(v4) # v3 = (Button) v3 # v2->bt1 = v3 # 总体来说就是: this.bt1=(Button)findViewById(R.id.button) move-object v2, v0 move-object v3, v0 const v4, 0x7f09003f invoke-virtual {v3, v4}, Leasyre/sjl/gossip/easyre/EasyRe;->findViewById(I)Landroid/view/View; move-result-object v3 check-cast v3, Landroid/widget/Button; iput-object v3, v2, Leasyre/sjl/gossip/easyre/EasyRe;->bt1:Landroid/widget/Button; #与上面分析类似 #this.iv1=(ImageView)findViewById(R.id.imageView) move-object v2, v0 move-object v3, v0 const v4, 0x7f090040 invoke-virtual {v3, v4}, Leasyre/sjl/gossip/easyre/EasyRe;->findViewById(I)Landroid/view/View; move-result-object v3 check-cast v3, Landroid/widget/ImageView; iput-object v3, v2, Leasyre/sjl/gossip/easyre/EasyRe;->iv1:Landroid/widget/ImageView; # this.et1=(EditText)findViedById(R.id.editText) move-object v2, v0 move-object v3, v0 const v4, 0x7f090041 invoke-virtual {v3, v4}, Leasyre/sjl/gossip/easyre/EasyRe;->findViewById(I)Landroid/view/View; move-result-object v3 check-cast v3, Landroid/widget/EditText; iput-object v3, v2, Leasyre/sjl/gossip/easyre/EasyRe;->et1:Landroid/widget/EditText; #取得this.bt1的引用 move-object v2, v0 iget-object v2, v2, Leasyre/sjl/gossip/easyre/EasyRe;->bt1:Landroid/widget/Button; #我们知道非静态函数参数列表的第一个参数为this,也就是调用对象,这里v2为this.bt1,即调用方法的调用对象为this.bt1 #实际向函数传递的参数为v3,我们可以看到v3实际上为this # this.bt1.setOnClickListener(this) move-object v3, v0 invoke-virtual {v2, v3}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V # return 0; return-void .end method因而实际上,onCreate的源码就出来了,如下:
void protected onCreate(Bundle p1) { System->loadLibrary("antidebug") init() super.onCreate(p1) setContentView(R.layout.activity_easy_re) this.bt1=(Button)findViewById(R.id.button) this.iv1=(ImageView)findViewById(R.id.imageView) this.et1=(EditText)findViedById(R.id.editText) this.bt1.setOnClickListener(this) return 0; }启动onCreate之后,将会加载库libantidebug.so,然后启动init()函数,初始化Activity界面并获取界面上的句柄,然后绑定按钮的监听器(onClick).
#public void init() # virtual methods .method public init()V .locals 9 #this指针初始化 move-object v0, p0 .local v0, "this":Leasyre/sjl/gossip/easyre/EasyRe; #常量值flag.txt的定义: # const string filename = "flag.txt" const-string v6, "flag.txt" move-object v1, v6 .local v1, "filename":Ljava/lang/String; # v6 = v6->getResources() # 即:v6=this->getResources() move-object v6, v0 :try_start_0 invoke-virtual {v6}, Leasyre/sjl/gossip/easyre/EasyRe;->getResources()Landroid/content/res/Resources; move-result-object v6 # InputStream fin=v6->openRawResource(R.id.flag),0x7f050000使用grep可以查看得知为raw目录下的flag.txt文件 const/high16 v7, 0x7f050000 invoke-virtual {v6, v7}, Landroid/content/res/Resources;->openRawResource(I)Ljava/io/InputStream; move-result-object v6 move-object v2, v6 .local v2, "fin":Ljava/io/InputStream; move-object v6, v2 #上面合起来就是:InputStream fin = this.getResources().openRawResource("flag.txt") #这里v6就是fin,因此下面的汇编码是用来检测可以向输入流读取的字节数,并将该值放入局部变量length中,然后创建字节数组 # int length=fin.available();Byte[] buffer=new Byte[length]; invoke-virtual {v6}, Ljava/io/InputStream;->available()I move-result v6 move v3, v6 .local v3, "length":I move v6, v3 #注意:new-array vx,vy,type_id 表示生成一个数组 type_id vx[vy]; new-array v6, v6, [B move-object v4, v6 .local v4, "buffer":[B #v2这时候为fin,v4为声明的数组引用buffer,接着调用read函数,其参数为数组buffer,因此读取数据到数组buffer中 # fin.read(buffer) move-object v6, v2 move-object v7, v4 invoke-virtual {v6, v7}, Ljava/io/InputStream;->read([B)I move-result v6 #这里v1为filename,v8为常量0,调用openFileOutputStream(String fname,int mode),返回对象赋予fout # FileOutputStream fout = openFileOutputStream(filename,0) ,filename实际上就是flag.txt move-object v6, v0 move-object v7, v1 const/4 v8, 0x0 invoke-virtual {v6, v7, v8}, Leasyre/sjl/gossip/easyre/EasyRe;->openFileOutput(Ljava/lang/String;I)Ljava/io/FileOutputStream; move-result-object v6 move-object v5, v6 .local v5, "fout":Ljava/io/FileOutputStream; #上面以v5引用输出流fout,v4这里是buffer # fout.write(buffer) move-object v6, v5 move-object v7, v4 invoke-virtual {v6, v7}, Ljava/io/FileOutputStream;->write([B)V #v2这里为fin, fin.close() move-object v6, v2 invoke-virtual {v6}, Ljava/io/InputStream;->close()V #v5这里为fout, fout.close() move-object v6, v5 invoke-virtual {v6}, Ljava/io/FileOutputStream;->close()V :try_end_0 #异常处理分支 .catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0 #局部变量范围结束边界 .end local v2 # "fin":Ljava/io/InputStream; .end local v3 # "length":I .end local v4 # "buffer":[B .end local v5 # "fout":Ljava/io/FileOutputStream; :goto_0 return-void #异常处理代码,printStackTrace() :catch_0 move-exception v6 move-object v2, v6 .local v2, "e":Ljava/io/IOException; move-object v6, v2 invoke-virtual {v6}, Ljava/io/IOException;->printStackTrace()V goto :goto_0 .end method所以以上代码反编译之后就是:
public void init() { const string filename = "flag.txt" try { InputStream fin = this.getResources().openRawResource("flag.txt") int length=fin.available();Byte[] buffer=new Byte[length]; fin.read(buffer) FileOutputStream fout = openFileOutputStream(filename,0) fout.write(buffer) fin.close() fout.close() } catch xxx { printStackTrace() } }可以看出这个init()函数打开flag.txt,然后读到缓冲区,然后又打开flag.txt,再将缓冲区中的内容写到flag.txt中(模式默认为0, 写入操作将会覆盖原文内容,而写入的内容正好为从原文件读入的内容).
#public void onClick(View p1) .method public onClick(Landroid/view/View;)V .locals 10 #初始化this move-object v0, p0 .local v0, "this":Leasyre/sjl/gossip/easyre/EasyRe; # View v = p1 move-object v1, p1 .local v1, "v":Landroid/view/View; # const String filaName="flag.txt" const-string v7, "flag.txt" move-object v2, v7 .local v2, "fileName":Ljava/lang/String; # const String flag="" const-string v7, "" move-object v3, v7 .local v3, "flag":Ljava/lang/String; # FileInputStream fin = this.openFileInput("flag.txt") move-object v7, v0 move-object v8, v2 :try_start_0 invoke-virtual {v7, v8}, Leasyre/sjl/gossip/easyre/EasyRe;->openFileInput(Ljava/lang/String;)Ljava/io/FileInputStream; move-result-object v7 move-object v4, v7 .local v4, "fin":Ljava/io/FileInputStream; # int length=fin.avaible() move-object v7, v4 invoke-virtual {v7}, Ljava/io/FileInputStream;->available()I move-result v7 move v5, v7 .local v5, "length":I # Byte[] buffer = new Byte[length] move v7, v5 new-array v7, v7, [B move-object v6, v7 .local v6, "buffer":[B #v4为fin,v6为buffer # fin.read(buffer) move-object v7, v4 move-object v8, v6 invoke-virtual {v7, v8}, Ljava/io/FileInputStream;->read([B)I move-result v7 #v6为buffer,注意静态方法调用时{}内的参数均为显式参数,这里调用了一个静态方法,没有this参数 # getString(buffer,"UTF-8") move-object v7, v6 const-string v8, "UTF-8" invoke-static {v7, v8}, Lorg/apache/http/util/EncodingUtils;->getString([BLjava/lang/String;)Ljava/lang/String; :try_end_0 #异常跳转语句 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 move-result-object v7 move-object v3, v7 .end local v4 # "fin":Ljava/io/FileInputStream; .end local v5 # "length":I .end local v6 # "buffer":[B :goto_0 #这里v3的值为getString的结果,也就是flag.txt中的内容,v0为this #通过this.et1.getText().toString()获取输入编辑框的字符串 move-object v7, v3 move-object v8, v0 iget-object v8, v8, Leasyre/sjl/gossip/easyre/EasyRe;->et1:Landroid/widget/EditText; invoke-virtual {v8}, Landroid/widget/EditText;->getText()Landroid/text/Editable; move-result-object v8 invoke-virtual {v8}, Ljava/lang/Object;->toString()Ljava/lang/String; move-result-object v8 #编辑框的字符串放在v8中,v7为flag.txt中的内容 #也就是将编辑输入框中的内容和flag.txt中的内容进行比较 # if(getString(buffer,"UTF-8").equals(et1.getText().toString)) invoke-virtual {v7, v8}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z move-result v7 #如果相等, Toast.makeText(this.getApplicationContext(),"That\'s the flag!",0).show(),如果不等,则跳转到cond_0 if-eqz v7, :cond_0 move-object v7, v0 invoke-virtual {v7}, Leasyre/sjl/gossip/easyre/EasyRe;->getApplicationContext()Landroid/content/Context; move-result-object v7 const-string v8, "That\'s the flag!" const/4 v9, 0x0 invoke-static {v7, v8, v9}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object v7 invoke-virtual {v7}, Landroid/widget/Toast;->show()V #结束 :goto_1 return-void #catch分支,打印异常回溯栈printStackTrace() :catch_0 move-exception v7 move-object v4, v7 .local v4, "e":Ljava/lang/Exception; move-object v7, v4 invoke-virtual {v7}, Ljava/lang/Exception;->printStackTrace()V goto :goto_0 .end local v4 # "e":Ljava/lang/Exception; #如果不相等 # Toast.makeText(this.getApplicationContext(),"0ops!That\'s wrong!",0).show() :cond_0 move-object v7, v0 invoke-virtual {v7}, Leasyre/sjl/gossip/easyre/EasyRe;->getApplicationContext()Landroid/content/Context; move-result-object v7 const-string v8, "0ops!That\'s wrong!" const/4 v9, 0x0 invoke-static {v7, v8, v9}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object v7 invoke-virtual {v7}, Landroid/widget/Toast;->show()V goto :goto_1 .end method因此上述代码源码即为:
public void onClick(View p1) { View v = p1 const String filaName="flag.txt" const String flag="" try { FileInputStream fin = this.openFileInput("flag.txt") int length=fin.avaible() Byte[] buffer = new Byte[length] fin.read(buffer) if(getString(buffer,"UTF-8").equals(et1.getText().toString)) { Toast.makeText(this.getApplicationContext(),"That\'s the flag!",0).show() } else { Toast.makeText(this.getApplicationContext(),"0ops!That\'s wrong!",0).show() } }catch { printStackTrace() } }这个便是点击button按钮后所触发的代码,代码就是简单的比较了flag.txt中的内容和文本编辑框中的输入内容,如果正确则提示That's the flag!否则将会提示That's wrong!
void protected onCreate(Bundle p1) { System->loadLibrary("antidebug") init() super.onCreate(p1) setContentView(R.layout.activity_easy_re) this.bt1=(Button)findViewById(R.id.button) this.iv1=(ImageView)findViewById(R.id.imageView) this.et1=(EditText)findViedById(R.id.editText) this.bt1.setOnClickListener(this) return 0; } public void init() { const string filename = "flag.txt" try { InputStream fin = this.getResources().openRawResource("flag.txt") int length=fin.available();Byte[] buffer=new Byte[length]; fin.read(buffer) FileOutputStream fout = openFileOutputStream(filename,0) fout.write(buffer) fin.close() fout.close() }catch { printStackTrace() } } public void onClick(View p1) { View v = p1 const String filaName="flag.txt" const String flag="" try { FileInputStream fin = this.openFileInput("flag.txt") int length=fin.avaible() Byte[] buffer = new Byte[length] fin.read(buffer) if(getString(buffer,"UTF-8").equals(et1.getText().toString)) { Toast.makeText(this.getApplicationContext(),"That\'s the flag!",0).show() } else { Toast.makeText(this.getApplicationContext(),"0ops!That\'s wrong!",0).show() } }catch { printStackTrace() } }