查阅 Dalvik虚拟机操作码 来阅读 smail 代码和改写
# instance fields 实例字段
# instance (标识字段)
# private (访问权限修饰符,表示私有的)
# :(冒号是字段名 与 字段类型的分隔符)
# Ljava/lang/String 表示字段类型
注意点:
- invoke-direct 调用 直接方法
- 这种就相当于 net/bluelotus/tomorrow/easyandroid/Crackme 类 调用 GetFlag 方法
- Ljava/lang/String; 参数 ,形参类型-String 类型的参数
- {p0, v0} 这个 相当于实参,有俩个参数,但是形参只有一个参数,这种情况 第一个参数相当于this
invoke-direct {p0, v0}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->GetFlag(Ljava/lang/String;)Ljava/lang/String;
- invoke-virtual 调用虚拟方法
- 执行虚拟方法 ,就是 参数调用后面的方法getBytes
- v2.getBytes()
invoke-virtual {v2}, Ljava/lang/String;->getBytes()[B
Smail Code:
# .class 表示当前类 public 当前的类的修饰符
# 类的表示方式= L + 包名 + 类名
# L net/bluelotus/tomorrow/easyandroid/ Crackme
.class public Lnet/bluelotus/tomorrow/easyandroid/Crackme;
.super Ljava/lang/Object; # .super 父类
.source "Crackme.java" # .source 表示源文件 .java文件
# instance fields 实例字段
# instance (标识字段)
# private (访问权限修饰符,表示私有的)
# str2(字段名)
# :(冒号是字段名 与 字段类型的分隔符)
# Ljava/lang/String 表示字段类型
# instance fields
.field private str2:Ljava/lang/String;
# direct methods
.method public constructor <init>()V
.locals 1 # 局部使用 寄存器个数是 1,当前方法的v寄存器 v局部寄存器,p参数寄存器
.prologue # 方法代码的逻辑开始的位置
.line 22 # .java 文件的行号‘
# 调用直接方法,构造方法 默认的 p0.<>init
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
.line 21 # .java 文件的行号‘
# 定义字符串,base64 编码
const-string v0, "cGhyYWNrICBjdGYgMjAxNg=="
# i 表示 操作实例字段 put 表示 set,设置字段的值
# v0 p0表示 this指向 当前对象
# p0.str2 = v0
iput-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;
.line 23 # 行号
# 定义字符串,base64 编码
const-string v0, "sSNnx1UKbYrA1+MOrdtDTA=="
# invoke-direct 表示调用 直接方法
# {p0 , v0 } 需要传入的 参数
# p0.GetFlag(v0)
invoke-direct {p0, v0}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->GetFlag(Ljava/lang/String;)Ljava/lang/String;
.line 24
return-void
.end method
.method private GetFlag(Ljava/lang/String;)Ljava/lang/String;
.locals 4 # 表示 当前方法 使用到的 局部变量 寄存器个数 是 4
.param p1, "str" # Ljava/lang/String; 方法参数 p 参数寄存器 v 局部寄存器
.prologue # 代码开始的位置
const/4 v3, 0x0 # v3 = 0
.line 27
# invoke-virtual 调用虚拟方法
# byte[] v2 = p1.getBytes();
# {p1} 是 参数列表
invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B
move-result-object v2 # move-result-object 移动上一次方法调用的对象引用返回值到vx。
invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B
move-result-object v0 # move-result-object 移动上一次方法调用的对象引用返回值到vx。
.line 29
.local v0, "content":[B # 给 v0 一个 别名
new-instance v1, Ljava/lang/String; # 实例化对象 v1 = String()
# i 表示 操作实例字段 get,表示获取字段的值
# v0 p0表示 this指向 当前对象
# v2 = p0.str2
iget-object v2, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;
# {v2} 这里的 v2 是参数,因为String是类对象,所以实例化调用getBytes --> new String(v2).getBytes();
# virtual 执行虚拟方法 v2.getBytes() 虚拟方法是接参数后面
invoke-virtual {v2}, Ljava/lang/String;->getBytes()[B
move-result-object v2 # move-result-object 移动上一次方法调用的对象引用返回值到 v2(接受上一个方法的返回值)
# static 执行静态方法 Base64.decode() 直接方法是接类后面,参数是直接方法的参数
# Base64.decode(字节列表,整型)
invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B
move-result-object v2 # move-result-object 移动上一次方法调用的对象引用返回值到 v2 (接受上一个方法的返回值)
# 直接看方法参数个数,如果不一致,第一过就是this或者某个类的对象
# v1 是上面的String 实例化对象,这里init是初始化 new String(v2)
invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V
.line 30 # java代码的 行号
.local v1, "kk":Ljava/lang/String; # 给 v1寄存器 设置 别名
# 根据字段ID读取静态对象引用字段到vx。 v2 = System.out()
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
# direct 执行直接方法 Crackme.decrypt() 直接方法是接类后面,参数是直接方法的参数
invoke-direct {p0, v0, v1}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->decrypt([BLjava/lang/String;)Ljava/lang/String;
move-result-object v3 # move-result-object 移动上一次方法调用的对象引用返回值到 v3。
# 虚拟方法就是第一个参数调用 后面的方法,其他的参数都是 方法的参数
# virtual 执行 虚拟方法 v3 是 参数
# v2.println(v3)
invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 31 # java的代码 行数
const/4 v2, 0x0 # 存入4位常量到vx。
return-object v2 # 返回 0x0 常量 0
.end method
.method private decrypt([BLjava/lang/String;)Ljava/lang/String;
.locals 8 # 表示 方法内 用到了 8个 寄存器
.param p1, "content" # [B 字节列表
.param p2, "password" # Ljava/lang/String; # 字符串
.prologue
.line 35
const/4 v4, 0x0 #存入4位常量到v4。
.line 37
.local v4, "m":Ljava/lang/String; # 设置 v4 寄存器的 别名
:try_start_0 # try 异常机制 try_start 异常机制 开始的地方,后面会有 end 结束的地方
# 虚拟方法就是第一个参数调用 后面的方法,其他的参数都是 方法的参数
# virtual 执行 虚拟方法
# p2.getBytes()
invoke-virtual {p2}, Ljava/lang/String;->getBytes()[B
# v3 来接受 v3 = p2.getBytes()
move-result-object v3 # # move-result-object 移动上一次方法调用的对象引用返回值到 v3(接受上一个方法的返回值)
.line 38
.local v3, "keyStr":[B # 设置 v3 寄存器的 别名 keyStr
# instance 实例化对象 SecretKeySpec 类
new-instance v2, Ljavax/crypto/spec/SecretKeySpec;
const-string v7, "AES" # 设置 常量 v7 为 AES
# 直接方法 就是 类->方法 {方法的参数}
# direct 执行 直接方法
# v2 是 前面 SecretKeySpec 的实例化。v2.init() 初始化
# v3 是 字节列表([B) v7 是 字符串(Ljava/lang/String) 方法的返回是 V(代表没有返回值 )
# v2 = SecretKeySpec(v3,v7)
invoke-direct {v2, v3, v7}, Ljavax/crypto/spec/SecretKeySpec;-><init>([BLjava/lang/String;)V
.line 39
.local v2, "key":Ljavax/crypto/spec/SecretKeySpec; # 给 v2 寄存器一个 别名key:后面是寄存器的类型
const-string v7, "AES/ECB/NoPadding" # 定义一个 常量 v7 为 "AES/ECB/NoPadding" 字符串
# invoke-static 执行静态方法
# Cipher.getInstance(v7) # 返回一个 Cipher 类型
invoke-static {v7}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
# # move-result-object 移动上一次方法调用的对象引用返回值到 v0 (接受上一个方法的返回值)
# V0 = Cipher.getInstance(v7)
move-result-object v0
.line 40
.local v0, "cipher":Ljavax/crypto/Cipher; # V0 寄存器 设置变量 cipher = Cipher.getInstance(v7)
const/4 v7, 0x2 # 存入4位常量到v7。
# 虚拟方法就是第一个参数调用 后面的方法,其他的参数都是 方法的参数
# virtual 执行 虚拟方法
# v0 就是 cipher v7 是 个 0x2 的整型int ,v2 是 Ljava/security/Key 类型 V 方法没有返回值
# cipher.init(v7,v2) ##### --->>>> 注意 :初始化 是 <init> ,这里 init 是方法
invoke-virtual {v0, v7, v2}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;)V
.line 41
# 虚拟方法就是第一个参数调用 后面的方法,其他的参数都是 方法的参数
# virtual 执行 虚拟方法
# v0 就是 cipher p1 是最开始 decrypt方法 擦传入的 字节列表 content
# cipher.doFinal(p1)
invoke-virtual {v0, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B
# # move-result-object 移动上一次方法调用的对象引用返回值到 v6 (接受上一个方法的返回值)
# v6 = cipher.doFinal(p1)
move-result-object v6
.line 42
.local v6, "result":[B # 寄存器 v6 别名为 result ,类型是 [B 字节列表
# 实例化的对象 ,v5 为 String实例对象
new-instance v5, Ljava/lang/String;
# 直接方法 就是 类->方法 {方法的参数}
# init 执行 直接方法,初始化
# v5 = new String(v6)
invoke-direct {v5, v6}, Ljava/lang/String;-><init>([B)V
:try_end_0 # # try 异常机制 try_start 异常机制 结束的地方,于前面的start开始地方对应
.catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_1
.catch Ljavax/crypto/NoSuchPaddingException; {:try_start_0 .. :try_end_0} :catch_0
.catch Ljava/security/InvalidKeyException; {:try_start_0 .. :try_end_0} :catch_4
.catch Ljavax/crypto/IllegalBlockSizeException; {:try_start_0 .. :try_end_0} :catch_2
.catch Ljavax/crypto/BadPaddingException; {:try_start_0 .. :try_end_0} :catch_3
.end local v4 # "m":Ljava/lang/String; # 起别名 结束
.local v5, "m":Ljava/lang/String; # v5 别名 m
move-object v4, v5 # 移动v5寄存器中的对象引用到v5。
.line 46
.end local v0 # "cipher":Ljavax/crypto/Cipher;
.end local v2 # "key":Ljavax/crypto/spec/SecretKeySpec;
.end local v3 # "keyStr":[B
.end local v5 # "m":Ljava/lang/String;
.end local v6 # "result":[B # 起别名 结束
.restart local v4 # "m":Ljava/lang/String;
# 下面 都是 catch 的 异常出来跳转
:goto_0
return-object v4
.line 43
:catch_0
move-exception v1
.line 44
.local v1, "e":Ljava/security/GeneralSecurityException;
:goto_1
invoke-virtual {v1}, Ljava/security/GeneralSecurityException;->printStackTrace()V
goto :goto_0
.line 43
.end local v1 # "e":Ljava/security/GeneralSecurityException;
:catch_1
move-exception v1
goto :goto_1
:catch_2
move-exception v1
goto :goto_1
:catch_3
move-exception v1
goto :goto_1
:catch_4
move-exception v1
goto :goto_1
.end method
Java Code :
package com.smail_;
import java.util.Base64;
//import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class Crackme01 {
// 私有成员变量
private String str2;
public Crackme01(){
str2 = "cGhyYWNrICBjdGYgMjAxNg==";
GetFlag("sSNnx1UKbYrA1+MOrdtDTA==");
}
private String GetFlag(String str){
byte[] v2 = str.getBytes();
byte[] content = Base64.getDecoder().decode(v2);
byte[] bytes2 = this.str2.getBytes();
String kk = new String(Base64.getDecoder().decode(bytes2));
String flag = decrypt(content,kk);
System.out.println(flag);
return "";
}
private String decrypt(byte[] content,String password){
try{
byte[] keyStr = password.getBytes();
SecretKeySpec key = new SecretKeySpec(keyStr,"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(2,key);
byte[] result = cipher.doFinal(content);
return new String(result);
}catch (Exception e){
e.printStackTrace();
}
return "";
}
// 测试 程序入口
public static void main(String[] args) {
new Crackme01();
}
}