本文将主要分析 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()
}
}