回到目录
内容总结
- 这个Lab与Lab 3“相反”:Lab 3是根据提供的或自己写的Java代码改写为Smali代码;而本Lab是根据Smali代码重写为Java代码。总的来说,这个Lab更符合期末考试的真实情境。
- 如课件的Reverse Smali中展示的一样,课程内容大致如下:
控制流图(CFG, Control Flow Graph),能够清晰地展现出一个函数中的控制流的转换与流程。其中需要注意如循环结构、分支结构、以及错误处理(try-catch)结构的处理。期末考试虽然并不要求画出正确的CFG,但按照个人经历来说,由于不少循环和分支的存在,画出一个大概的CFG确实能给解题带来很大的帮助。
伪代码生成(Pseudo-code Generation),在本Lab和课程的实践中,需要按照Smali代码以及对应的CFG整理和构建一段正确的代码。其中需要注意几点:
一个是变量的命名及处理,尤其对于那些临时变量。由于Smali代码自身的特点之一即允许寄存器复用,因此在使用和引用每个寄存器的时候,最重要的就是确定是什么类型。此外,中间变量也建议进行处理,譬如传参时的处理,以避免生成过于冗长的代码。
goto结构的处理,尤其当使用到循环和分支结构时。虽然Java本身并无法使用goto语句,但出于控制流的转换,Smali语法中有goto语句以及对应的标签。借由这个Lab的实践,可以掌握循环和分支在Smali中的大概的结构,也可以通过绘画CFG验证是否是循环或分支。
Java语句结构。对于循环,Java内可不止有while结构,还有for循环……
Lab简介与参考
- 这个Lab一共分成两个Task,分别是Checker.java和Encoder.java。由于大部分都是Smali和CFG的直译,因此仅记录了成果。
- Task1: Checker.java
public class Checker {
//instance-field
private byte[] bytes;
//constructor
public Checker() {
bytes=new byte[]{0x70, 0x64, 0x64, 0x44, 0x1f, 0x5, 0x72, 0x78};
}
//private method: charToByteAscii
private static byte charToByteAscii(char c) {
return (byte)c;
}
//private method: checkStr1
private boolean checkStr1(String s) {
int i=0, j;
while (i<s.length()) {
int bi=charToByteAscii(s.charAt(i));
j=i*11;
bi=bi^j;
if (bytes[i] == bi) {
++i;
}
else {
return false;
}
}
return true;
}
//private method: checkStr2
private boolean checkStr2(String s) {
try {
Integer i=Integer.valueOf(Integer.parseInt(s));
int ii=i.intValue();
if (ii>=1000) {
if (ii%16==0 || ii%27==0 || ii%10==8) {
return true;
}
else {
return false;
}
}
else {
return false;
}
} catch (NumberFormatException e) {
return false;
}
}
//virtual methods: check
public boolean check(String s) {
if (s.length()==12) {
String s1=s.substring(0,8);
String s2=s.substring(8,12);
if (checkStr1(s1)&&checkStr2(s2)) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
}
- Task2: Encoder.java
import java.util.Random;
public class Encoder {
//Constructor
public Encoder() {
//Only call Object's constructor.
//There is no need to call it manually.
}
//private method: convertHexToString
private String convertHexToString(String s) {
StringBuilder sb = new StringBuilder();
int i=0;
while (i<s.length()-1) {
String ss=s.substring(i,i+2);
int j=Integer.parseInt(ss,0x10);
j=j^0xff;
sb.append((char)j);
i+=2;
}
return sb.toString();
}
//private method: convertStringToHex
private String convertStringToHex(String s) {
char[] chars=s.toCharArray();
StringBuffer sb=new StringBuffer();
int i=0;
while (i<chars.length) {
char c=chars[i];
int j=((int)c)^0xff;
sb.append(Integer.toHexString(j));
++i;
}
return sb.toString();
}
//private method: getSalt
private byte[] getSalt() {
byte[] bytes=new byte[]{0,0,0,0,0,0};
Random random=new Random();
int i=0;
while (i<bytes.length) {
bytes[i]=(byte)random.nextInt(0xf);
++i;
}
return bytes;
}
//virtual method: decode
public String decode(String s) {
if (s.length()==0) {
return "";
}
StringBuffer stringBuffer = new StringBuffer();
int i=0;
while (i<s.length()) {
String s1=s.substring(i,i+1);
int j=Integer.parseInt(s1,0x10);
j=j%4;
j=4-j;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(s.substring(i+j+1,i+5));
stringBuffer.append(stringBuilder.append(s.substring(i+1,i+j+1)).toString());
i+=5;
}
String ss=convertHexToString(stringBuffer.toString());
return ss.substring(0,11);
}
//virtual method: encode
public String encode(String s) {
if (s.length()==11) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(s);
stringBuilder.append("a");
String ss=stringBuilder.toString();
byte[] bytes=getSalt();
String sc=convertStringToHex(ss);
StringBuffer stringBuffer = new StringBuffer();
int i=0;
while (i<sc.length()) {
int j=i/4;
j=bytes[j];
int k=j%4;
stringBuffer.append(Integer.toHexString(j));
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append(sc.substring(i+k,i+4));
stringBuilder1.append(sc.substring(i,i+k));
stringBuffer.append(stringBuilder1.toString());
i+=4;
}
return stringBuffer.toString();
}
else {
System.out.println("input error!");
return "";
}
}
}
其中其实还有不少的细节可以修改,例如中间变量的命名与简化并不太清晰。如果嫌这些操作不必要或麻烦的,也可以如同我室友一样,遇到一个新的寄存器使用就定义一个新的,最后都命名成例如v1、v11、v111……也算是别树一帜的书写风格了。