Smali注入之打造属于自己的安卓crack利器(6月14日更新,待续。。。)
本帖最后由 落华无痕 于 2014-12-4 18:52 编辑关于Smali注入大家应该了解过,网上有不少教程。那些注入代码看上去简单,实际用起来得花不少功夫。
普通的注入就是在软件源代码中添加几行代码,用于改变软件的功能,或查看某个寄存器在运行中具体的值。
需要注意的地方是:添加的注入代码所使用的寄存器不影响其他代码的执行。当注入代码较多时,这个要求就变得很困难了。
在这里我的解决办法是:把注入代码写进自己专属的crack.smali,然后在要注入的地方调用crack.smali里的注入方法即可,只用一行注入代码,而且不影响其他寄存器。
举个例子说明下两种方法的区别,假设原软件中有以下代码:
.method public methodName()Ljava/lang/String;
.locals 4
.prologue
const-string v0, "test1"
const-string v3, "test2"
invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V
new-instance v3, Ljava/lang/StringBuilder;
invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
return-object v0
.end method
假如我要查看move-result-object v1和move-result-object v2,两处中v1、v2的值该如何注入?
普通的log.d注入方法如下(可以用Logcat查看日志):
.method public methodName()Ljava/lang/String;
.locals 5
.prologue
const-string v0, "test1"
const-string v3, "test2"
invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
const-string v4, "info"
invoke-static {v4, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
const-string v4, "info"
invoke-static {v4, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V
new-instance v3, Ljava/lang/StringBuilder;
invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
return-object v0
.end method
在上面的代码中,我先是修改了开头的“.locals 5”,表示使用的寄存器为v0-v4。然后我用v4作为log.d的第一个参数,寄存器v1、v2为第二个参数。
由于原代码中,v0、v1、v2、v3从头到尾都有使用,所以注入时使用这几个寄存器会影响软件的正常执行,所以我才修改“.locals”开辟新的寄存器v4。
明显这样注入很麻烦。而且刚好v1、v2都是字符串,符合log.d的要求。如果v1、v2为整数值,注入就更加复杂了。
下面看看创建了crack.smali的注入会如何。
假设我已经有了个crack.smali,代码如下:
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log(Ljava/lang/String;)V
.locals 1
.prologue
const-string v0, "info"
invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
把crack.smali放到反编译后的smali根目录,在源代码中注入:
.method public methodName()Ljava/lang/String;
.locals 4
.prologue
const-string v0, "test1"
const-string v3, "test2"
invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-static {v1}, Lcrack;->log(Ljava/lang/String;)V
invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
invoke-static {v2}, Lcrack;->log(Ljava/lang/String;)V
invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V
new-instance v3, Ljava/lang/StringBuilder;
invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
return-object v0
.end method
明显比前面的注入方式简单多了。crack.smali的方法可以不断丰富,需要用时信手拈来。
既然如此,就让我们打造属于自己的安卓crack利器吧!
最基本的crack.smali推荐加入log日志输出,代码如下:
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log(Ljava/lang/String;)V
.locals 1
.prologue
const-string v0, "info"
invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
把crack.smali放进smali目录,在要查看的,保存了字符串的寄存器vx的下面,添加代码:
invoke-static {vx}, Lcrack;->log(Ljava/lang/String;)V
保存并重新编译,在手机或模拟器上安装软件,连上电脑,打开cmd命令行:
cd到桌面,然后输入命令adb logcat>test.txt,然后在手机上运行软件,当软件执行了注入的代码(自行判断),按ctrl+c结束,然后回到桌面。
如无法识别adb命令,请先添加adb.exe所在位置的环境变量,如我安装了靠谱助手,我找到了它提供的adb.exe所在的路径,然后我照下面图设置:
打开test.txt,查找d/info,在找到的那一行的右边就是要查看的寄存器的值。
“info”是由“ const-string v0, "info" ”给出。可以自行修改。
上面的说的命令还可以添加过滤代码,有经验的人自行修改,使得test.txt的内容更简洁。
从上面test.txt的结果可以看出,如果需要注入的位置有多处,那么就很难分辨具体哪个结果对应哪个了。
解决办法是,给crack.smali的log方法加序号,并复制多份log方法,参照下面修改方式,如:
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log1(Ljava/lang/String;)V
.locals 1
.prologue
const-string v0, "info1"
invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
.method public static log2(Ljava/lang/String;)V
.locals 1
.prologue
const-string v0, "info2"
invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
第一处注入调用log1,第二处注入调用log2,依次类推,然后根据序号对应结果。
上面说的注入,前提都是vx是String,如果vx是int型怎么办?
可以给crack.smali添加下面方法:
.method public static I(I)V
.locals 2
.prologue
const-string v0, "info_int"
invoke-static {p0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
上面方法调用代码为:
invoke-static {vx}, Lcrack;->I(I)V
vx为要查看的寄存器。
当vx保存的是long型的话,就比较麻烦了,稍微用错就会导致程序停止运行,所以尽量避免查看long型vx。
给crack.smali添加以下代码:
.method public static J(J)V
.locals 2
.prologue
const-string v0, "info_long"
invoke-static {p0, p1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
上面方法调用代码为:
invoke-static {vx, vx+1}, Lcrack;->J(J)V
vx为要查看的寄存器,同时确保vx+1在上面的代码中没有被使用过,且在.locals声明的可使用的寄存器范围内。
至于[C、[B、[Ljava/lang/String的查看,有机会再补上。
软件大部分时间都在跟字符串、数据打交道。很多时候就因为某个寄存器的值不知,导致软件代码很难分析下去。
因此在没有调试器的前提下,通过注入查看寄存器的值就变得非常重要了。
总是通过logcat查看寄存器值,多少有点不方便,所以下面讲下如何将字符串输出到文本。
直接用smali写有点麻烦,所以先用java写,编译后再反编译为smali,参考java代码如下:
import java.io.*;
import android.util.Log;
public class crack
{
/*将字符串s输出到/sdcard/debug.txt*/
public static void puts(String s)
{
try
{
String path= "/sdcard/debug.txt";
FileOutputStream outStream = new FileOutputStream(path,true);
OutputStreamWriter writer = new OutputStreamWriter(outStream,"gb2312");
writer.write(s);
write.write("\r\n");
writer.flush();
writer.close();
outStream.close();
}
catch (Exception e)
{
Log.e("debug", "file write error");
}
}
}
反编译后smali代码如下:
.method public static puts(Ljava/lang/String;)V
.locals 7
.prologue
:try_start_0
const-string v3, "/sdcard/debug.txt"
new-instance v2, Ljava/io/FileOutputStream;
const/4 v5, 0x1
invoke-direct {v2, v3, v5}, Ljava/io/FileOutputStream;-><init>(Ljava/lang/String;Z)V
.line 19
new-instance v4, Ljava/io/OutputStreamWriter;
const-string v5, "gb2312"
invoke-direct {v4, v2, v5}, Ljava/io/OutputStreamWriter;-><init>(Ljava/io/OutputStream;Ljava/lang/String;)V
.line 21
invoke-virtual {v4, p0}, Ljava/io/OutputStreamWriter;->write(Ljava/lang/String;)V
const-string v5, "\r\n"
invoke-virtual {v4, v5}, Ljava/io/OutputStreamWriter;->write(Ljava/lang/String;)V
.line 23
invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->flush()V
.line 25
invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->close()V
.line 27
invoke-virtual {v2}, Ljava/io/FileOutputStream;->close()V
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.line 37
:cond_0
:goto_0
return-void
.line 30
:catch_0
move-exception v0
.line 34
const-string v5, "debug"
const-string v6, "file write error"
invoke-static {v5, v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
goto :goto_0
.end method
使用方法为,在要查看的寄存器vx的下方换行添加代码:
invoke-static {vx}, Lcrack;->puts(Ljava/lang/String;)V
至于输出整型变量到文本,请参考之前的例子,先将整型变量转为字符串,再输出,具体代码这里略过。。。
下面介绍通过注入,改变软件获得的imei号,让软件改为读取保存在 “/sdcard/deviceid/imei.txt” 里的串号。
参考java代码如下:
import java.io.*;
public class crack {
public static String getDeviceId()
{
String path = "/sdcard/deviceid/imei.txt";
String str = null;
File f = new File(path);
if (f != null && f.exists())
{
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
InputStreamReader inputStreamReader = null;
try {
inputStreamReader = new InputStreamReader(fis, "utf-8");
try {
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer("");
String line;
line = reader.readLine();
sb.append(line);
reader.close();
fis.close();
str = sb.toString();
}catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return str;
}
}
反编译后得到的smali代码如下:
.method public static getDeviceId()Ljava/lang/String;
.locals 13
.prologue
const-string v8, "/sdcard/deviceid/imei.txt"
const/4 v11, 0x0
new-instance v2, Ljava/io/File;
invoke-direct {v2, v8}, Ljava/io/File;-><init>(Ljava/lang/String;)V
if-eqz v2, :cond_0
invoke-virtual {v2}, Ljava/io/File;->exists()Z
move-result v12
if-eqz v12, :cond_0
const/4 v3, 0x0
:try_start_0
new-instance v4, Ljava/io/FileInputStream;
invoke-direct {v4, v2}, Ljava/io/FileInputStream;-><init>(Ljava/io/File;)V
:try_end_0
.catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
move-object v3, v4
:goto_0
const/4 v5, 0x0
:try_start_1
new-instance v6, Ljava/io/InputStreamReader;
const-string v12, "utf-8"
invoke-direct {v6, v3, v12}, Ljava/io/InputStreamReader;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V
:try_end_1
.catch Ljava/io/UnsupportedEncodingException; {:try_start_1 .. :try_end_1} :catch_3
:try_start_2
new-instance v9, Ljava/io/BufferedReader;
invoke-direct {v9, v6}, Ljava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
new-instance v10, Ljava/lang/StringBuffer;
const-string v12, ""
invoke-direct {v10, v12}, Ljava/lang/StringBuffer;-><init>(Ljava/lang/String;)V
invoke-virtual {v9}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String;
move-result-object v7
invoke-virtual {v10, v7}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;
invoke-virtual {v9}, Ljava/io/BufferedReader;->close()V
invoke-virtual {v3}, Ljava/io/FileInputStream;->close()V
invoke-virtual {v10}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;
:try_end_2
.catch Ljava/io/IOException; {:try_start_2 .. :try_end_2} :catch_1
.catch Ljava/io/UnsupportedEncodingException; {:try_start_2 .. :try_end_2} :catch_2
move-result-object v11
:cond_0
:goto_1
return-object v11
:catch_0
move-exception v1
invoke-virtual {v1}, Ljava/io/FileNotFoundException;->printStackTrace()V
goto :goto_0
:catch_1
move-exception v0
:try_start_3
invoke-virtual {v0}, Ljava/io/IOException;->printStackTrace()V
:try_end_3
.catch Ljava/io/UnsupportedEncodingException; {:try_start_3 .. :try_end_3} :catch_2
goto :goto_1
:catch_2
move-exception v0
move-object v5, v6
:goto_2
invoke-virtual {v0}, Ljava/io/UnsupportedEncodingException;->printStackTrace()V
goto :goto_1
:catch_3
move-exception v0
goto :goto_2
.end method
使用方法为:
查找
Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;
在下面换行添加
invoke-static {}, Lcrack;->getDeviceId()Ljava/lang/String;
心情原因,拖了这么久都没更新,有机会再续。。。
犀利 这样可以全称DIY了{:1_918:} h3616 发表于 2014-12-2 15:10
你好 ,将字符串输出到文本。用的你的代码,为什么每次文本都会覆盖写入呢,有没有可以追加写入的
已经修改帖子。
把相关smali代码的第12行,由const/4 v5, 0x0 改成 const/4 v5, 0x1
相关java代码的FileOutputStream outStream = new FileOutputStream(path,false)改成FileOutputStream outStream = new FileOutputStream(path,true)
红色为修改部分。感觉还是默认追加写入的好,所以原帖改成默认追加写入,感谢提醒。
牛B,学习了 小菜路过!!!! 不错学习了 多谢楼主分享 不错,学习了,长知识了 不会~定起来 不懂也帮顶 不错,学习了,长知识了 nice啊,省去了很大的麻烦