文章目录

  • 1、Java Bytecode Reversing and Patching Exercise
  • 方法一:修改if_icmplt
  • 方法二:修改if_icmplt
  • 方法三:修改return
  • 2、201807 test.class Time4.class
  • 第一小题:test.class
  • 方法一:直接用二进制编辑器修改.class文件
  • 方法二:用Bytecode字节码查看器和JClassLib包的代码修改.class文件
  • 第二小题:Time4.class
  • 方法一:直接将当前日期的初始化格式“yyyy-MM”改为“2018-07”:
  • 方法二:将p = "070097105108101100033"(对应字符串Failed!)改为p=”087101108099111109101”(对应字符串Welcome):
  • 第三种方法是,将时间变量var1 = “2018-07”,修改为当前时间“2019-03”:
  • 3. MTC3 Brute-Force-Attack on Triple-DES with Reduced Key Space
  • 4. 阅读


1、Java Bytecode Reversing and Patching Exercise

Description of the Exercise:

Imagine that you have just implemented a Java version of a console
application called “Password Vault” that helps computer users create
and manage their passwords in a secure and convenient way. Before
releasing a limited trial version of the application on your company’s
Web site, you would like to understand how difficult it would be for a
reverse engineer to circumvent a limitation in the trial version that
exists to encourage purchases of the full version; the trial version
of the application limits the number of password records a user may
create to five. The Java version of the Password Vault application was
developed to provide a non-trivial application for reversing exercises
without the myriad of legal concerns involved with reverse engineering
software owned by others. The Java version of the Password Vault
application employs 128-bit AES encryption, using Sun’s Java
Cryptography Extensions (JCE), to securely store passwords for
multiple users—each in separate, encrypted XML files.

Software for the Exercise:

Password Vault Java Windows installer FrontEnd Plus (java bytecode
decompiler) Jad (java bytecode decompiler) (if you prefer to work at
the command-line)

首先把文件解压,安装密码库,把jar包解压缩,打开.class文件分析:

java逆向proto java逆向教程_DES算法

整个代码库最重要的部分就是这里的验证密码记录的条数是否大于5,而我们的目的是使密码记录的条数没有限制。

先用javap -verbose PasswordVault命令查看PasswordVault.class文件的字节码内容,找到对应函数的相应位置:

java逆向proto java逆向教程_.class_02

所以我们目前的主要思路有3种:
**1、**修改if_icmplt(比较栈顶两个数size和5,小于则跳转)为ifge(比较栈顶的一个数size,大于等于0跳转)
**2、**修改if_icmplt(比较栈顶两个数size和5,小于则跳转)为goto(无需栈顶的数,无条件跳转)。
**3、**修改return为nop让函数不结束返回。

方法一:修改if_icmplt

修改if_icmplt小于跳转为ifge(小于等于跳转)或goto无条件跳转

用编辑器以16进制打开PasswordVault.class,用搜索功能找到if_icmplt对应的16进制代码A1的位置:

java逆向proto java逆向教程_java逆向proto_03

用编辑器把if_icmplt对应的16进制代码A1改为goto对应的16进制代码A7:

java逆向proto java逆向教程_逆向工程_04

将所有的.class文件打包成jar包,并配置主清单属性修改MANIFEST.MF。之后运行jar包:

java逆向proto java逆向教程_java逆向proto_05

但是程序在修改的goto这报了错误:当前帧的堆栈大小与stackmap不匹配。打开源字节码:

java逆向proto java逆向教程_java逆向proto_06

发现这是因为原字节码中if_icmplt把两个数size和5压入了栈顶进行比较,而goto不需要比较数,直接跳转,所以我们还要修改栈顶让它不要压入数。

我们先尝试将if_icmplt(比较栈顶两个数size和5,小于则跳转)修改为ifge(比较栈顶的一个数size,大于等于0则跳转)、并将iconst_5改为nop:

java逆向proto java逆向教程_.class_07

将所有的.class文件打包成jar包,并配置主清单属性修改MANIFEST.MF。之后运行jar包:

java逆向proto java逆向教程_java逆向_08

可以看到添加成功:

java逆向proto java逆向教程_java逆向_09

方法二:修改if_icmplt

修改if_icmplt(比较栈顶两个数size和5,小于则跳转)为goto(无需栈顶的数,无条件跳转),并把size函数删掉。

先查看字节码,修改前:

java逆向proto java逆向教程_java逆向proto_10

所以我们要把if_icmplt修改为goto,将从aload_0到iconst_5的字节码都改为nop。

修改后:

java逆向proto java逆向教程_逆向工程_11

将所有的.class文件打包成jar包,并配置主清单属性修改MANIFEST.MF。之后运行jar包:

java逆向proto java逆向教程_java逆向proto_12

添加密码记录成功。

方法三:修改return

修改return让函数不返回结束

用编辑器以16进制打开PasswordVault.class,找到刚刚代码的位置,用编辑器把return对应的16进制代码B1改为nop对应的16进制代码00:

java逆向proto java逆向教程_逆向工程_13

将所有的.class文件打包成jar包,并配置主清单属性修改MANIFEST.MF。之后运行jar包:

java逆向proto java逆向教程_逆向工程_14

一直添加密码记录:

java逆向proto java逆向教程_DES算法_15

结果成功添加7条密码记录,没有限制退出,证明我们已经成功突破限制。

2、201807 test.class Time4.class

第一小题:test.class

首先,明确一下题目的意思和我们目的:

题目其实输出了两个部分,第一个就是由“201807”对应的“Welcome”:

java逆向proto java逆向教程_java逆向proto_16

第二个是判断当前时间是否在“2018-7-1 00:00:00”和“2018-7-31 23:59:59”之间,如果是则输出当前时间并等待输入,如果不是则输出当前时间并退出。

java逆向proto java逆向教程_.class_17

方法有很多种,这里就没有用第一题的解法了。尝试了另外两种不同的解法。

方法一:直接用二进制编辑器修改.class文件

先用编译器打开test.class查看源代码:

java逆向proto java逆向教程_java逆向proto_18

查看源代码,可以发现两个日期格式的参数是String类型,且本身没有类型检查,而我们的目的是使代码输出“Welcome”,而源代码只有在当前日期为“201807”时输出“Welcome”,所以我们把“yyyyMM”字符串直接改为“201807”字符串即可输出“Welcome”。

用编辑器以16进制打开test.class,找到“yyyyMM”字符串所在的那一行:

java逆向proto java逆向教程_DES算法_19

把“yyyyMM”字符串直接改为“201807”字符串(16进制是修改对应的ASCII码):

java逆向proto java逆向教程_逆向工程_20

保存test.class,运行:

java逆向proto java逆向教程_.class_21

这里我们只是解决了第一个问题输出Welcome,还有第二个问题使当前时间符合规范不退出。我们没有跟上面一样把“yyyy-MM-dd HH:mm:ss”字符串直接改为“2018-07-24 12:12:12”字符串,因为源代码中使用的SimpleDateFormat类的parse()函数需要正则表达式来分割时间,我们把“yyyy-MM-dd HH:mm:ss”字符串直接改为“2018-07-24 12:12:12”字符串会报错。

所以我们只能修改其他的地方了。既然是比较当前时间与“2018-7-1 00:00:00”和“2018-7-31 23:59:59”的大小,我们没法把时间改在这里面,但我们可以把时间改成无限大,即把“2018-7-1 00:00:00”改为“0000-0-0 00:00:00”、把“2018-7-31 23:59:59”和“9999-12-31 23:59:59”。

java逆向proto java逆向教程_java逆向_22

修改:这里因为内存已经限定了7的位数只有一位,所以我们只能把它改成最大的9了,最多将把“2018-7-1 00:00:00”改为“0000-0-0 00:00:00”、把“2018-7-31 23:59:59”和“9999-9-31 23:59:59”。但其实还可以把31、23、59也改成99,日期类即使超过了一般的限制也没什么关系,但反正已经改成9999年之后了,差几个月也没什么关系了:

java逆向proto java逆向教程_java逆向proto_23

保存test.class,运行:

java逆向proto java逆向教程_.class_24

成功输出“Welcome”且没有退出,进入了程序。

方法二:用Bytecode字节码查看器和JClassLib包的代码修改.class文件

用编译器打开.class文件,查看.class源码:

java逆向proto java逆向教程_逆向工程_25

发现代码的逻辑是当输入201807时,先将201807转换成大整数,然后201807乘以445再加上43597487644106,再将这个大整数转换成字符串就“是Welcome”了。而我们要使所有的时间都可以输入“Welcome”,所以关键就是修改445和43597487644106。
我的想法就是将445改为0,然后将43597487644106改为“Welcome”对应的大整数24599839172947301。
求Welcome对应大整数的源代码:

import java.math.BigInteger;

/**
 * 文件注释:
 *
 * @Auther: Legends
 * @Date: /22 18:44
 * @Description:   求Welecome对应的大整数
 */
public class New {
    public static void main(String[] args) {
        String test = "Welcome";
        //字符串转16进制字符串
        String s4 = str2HexStr(test);
        //再转大整数
        BigInteger biginteger = HexStr2int(s4);
        System.out.println("Welcome对应的大整数为:"+biginteger);
    }

    public static BigInteger HexStr2int(String s)
    {
        BigInteger biginteger = new BigInteger(s, 16);
        return biginteger;
    }

    public static String str2HexStr(String s)
    {
        char ac[] = "0123456789ABCDEF".toCharArray();
        StringBuilder stringbuilder = new StringBuilder("");
        byte abyte0[] = s.getBytes();
        for(int j = 0; j < abyte0.length; j++)
        {
            int i = (abyte0[j] & 0xf0) >> 4;
            stringbuilder.append(ac[i]);
            i = abyte0[j] & 0xf;
            stringbuilder.append(ac[i]);
        }
        return stringbuilder.toString().trim();
    }
}

代码结果截图:

java逆向proto java逆向教程_java逆向_26

这样不管输入的日期时什么因为是乘以0,所以消除了日期的影响,直接将大整数“24599839172947301”转换成对应的字符串“Welcome”即可。这里我们之所以没有像方法一一样直接用16进制编辑器修改,是因为在16进制编辑器中的“43597487644106”是占14位,而“24599839172947301”是占17位,无法直接通过编辑器直接修改16进制代码扩容。所以这就是我们用代码来修改的原因。

先用java字节码查看软件Bytecode查看test.class对应的字节码:

java逆向proto java逆向教程_逆向工程_27

我们要修改的常量字符串有:“2018-7-1 00:00:00”、“2018-7-31 23:59:59”、“445”、“43597487644106”(也可以修改“yyyyMM”,但修改这个已经在方法一中实现了,就不重复修改了),找到对应的常量字节码在常量池中的位置:

“2018-7-1 00:00:00”:

java逆向proto java逆向教程_.class_28


“2018-7-31 23:59:59”:

java逆向proto java逆向教程_java逆向_29

“445”:

java逆向proto java逆向教程_java逆向_30

“43597487644106”:

java逆向proto java逆向教程_DES算法_31

78、79、89、92、CONSTANT_Utf8_info这些都是字符串常量的标识,我们待会就通过这些标识找到它们,再修改。

源代码:

/**
 * 文件注释:
 *
 * @Auther: Legends
 * @Date: /22 21:48
 * @Description:  修改.class的方法
 */
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.gjt.jclasslib.io.ClassFileWriter;
import org.gjt.jclasslib.structures.CPInfo;
import org.gjt.jclasslib.structures.ClassFile;
import org.gjt.jclasslib.structures.InvalidByteCodeException;
import org.gjt.jclasslib.structures.constants.ConstantUtf8Info;

public class ChangeClass
{
    public static void main(String[] args){
        //用文件输入流将.class文件的内容读入
        String filePath = "D:\\test.class";
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filePath);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //数据输入流
        DataInput di = new DataInputStream(fis);
        ClassFile cf = new ClassFile();
        try {
            //即将数据流读入ClassFile类的对象
            cf.read(di);
        } catch (InvalidByteCodeException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        //获取常量池数组
        CPInfo[] infos = cf.getConstantPool();
        //遍历常量池
        int count = infos.length;
        for (int i = 0; i < count; i++) {
                //找到第78个常量字符串,修改“2018-7-1 00:00:00”为“0000-0-0 00:00:00”
                if(i == 78){
                    ConstantUtf8Info uInfo = (ConstantUtf8Info)infos[i];
                    uInfo.setBytes("0000-0-0 00:00:00".getBytes());
                    infos[i]=uInfo;
                }
                //找到第79个常量字符串,修改“2018-7-31 23:59:59”为“9999-9-31 23:59:59”
                if(i == 79){
                    ConstantUtf8Info uInfo = (ConstantUtf8Info)infos[i];
                    uInfo.setBytes("9999-9-31 23:59:59".getBytes());
                    infos[i]=uInfo;
                }
                //找到第79个常量字符串,修改“445”为“000”
                if(i == 89){
                    ConstantUtf8Info uInfo = (ConstantUtf8Info)infos[i];
                    uInfo.setBytes("000".getBytes());
                    infos[i]=uInfo;
                }
                //找到第79个常量字符串,修改“43597487644106”为“24599839172947301”
                if(i == 92){
                    ConstantUtf8Info uInfo = (ConstantUtf8Info)infos[i];
                    uInfo.setBytes("24599839172947301".getBytes());
                    infos[i]=uInfo;
                }
                //逐个输出常量池中的常量和类型
                if (infos[i] != null) {
                    System.out.print(i);
                    System.out.print(" = ");
                    try {
                        System.out.print(infos[i].getVerbose());
                    } catch (InvalidByteCodeException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.print(" = ");
                    System.out.println(infos[i].getTagVerbose());
                    if(i == 92){
                        break;
                    }
                }
        }
        cf.setConstantPool(infos);
        try {
            fis.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        File f = new File(filePath);
        try {
            ClassFileWriter.writeToFile(f, cf);
        } catch (InvalidByteCodeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

实验结果截图:

java逆向proto java逆向教程_DES算法_32

修改后的.class文件:

java逆向proto java逆向教程_java逆向_33

运行test.class文件:

java逆向proto java逆向教程_DES算法_34

成功输出结果Welecome,且没有退出!!!

第二小题:Time4.class

先用编译器反编译查看.class源代码:

java逆向proto java逆向教程_java逆向_35

题目是比较了当前日期与“2018-07”是否相等,又比较了当前的年份、“_”、当前月份连接而成的字符串与“2018-07”是否相等,我们在使用本机时间进入程序的时候是走的第三个分支:JOptionPane.showMessageDialog(var9, var3 + var10.m(var3) + var10.u(var8));所以关键就在于m函数和u函数。m函数是将p = "070097105108101100033"转成字符串“Failed!”。而u函数是一个字符自身求与的函数,返回结果为6个空格!!!
题目比较简单,所以我们也没有用第一题和第二题第一问的复杂方法了。

方法一:直接将当前日期的初始化格式“yyyy-MM”改为“2018-07”:

这样无论当前时间为多少,程序都会认为我们的时间是“2018-07”。用编辑器以16进制打开Time4.class,找到“yyyy-MM”:

java逆向proto java逆向教程_DES算法_36

修改为“2018-07”:

java逆向proto java逆向教程_java逆向_37

保存,运行Time4.classs:

java逆向proto java逆向教程_逆向工程_38

果然所有的当前时间都被初始化为“2018-07”了,并成功输出了Welcome。

方法二:将p = “070097105108101100033”(对应字符串Failed!)改为p=”087101108099111109101”(对应字符串Welcome):

java逆向proto java逆向教程_java逆向_39

这里刚好因为Failed!后面加了个!才与Welcome长度相等,否则长度不相等就得用第一问的方法修改常量池了。直接修改后:

java逆向proto java逆向教程_逆向工程_40

保存,运行Time4.class:

java逆向proto java逆向教程_逆向工程_41

成功在当前时间输出Welcome。

第三种方法是,将时间变量var1 = “2018-07”,修改为当前时间“2019-03”:

使得程序判定进入s函数,再将d = “%5;+B=.”(对应Failed!)修改为“[KEU>CR”(对应Welcome),但这并不是个一劳永逸的办法,每个月都得修改一次当前时间,就不使用了。

3. MTC3 Brute-Force-Attack on Triple-DES with Reduced Key Space

https://www.mysterytwisterc3.org/en/challenges/level-ii/brute-force-attack-on-triple-des-with-reduced-key-space 题目当中:“对于这个挑战,密钥不是随机生成的,而是选择了一种可以轻松重建的方式:作者只是使用了在2006年建造的被描述机器的名称,并添加了六个有意义的数字。然后,他将这个包含字符和数字的字符串转换为十六进制表示,并使用结果作为键。对于这个挑战,你可以发现一个密文文本在文文件中(mtc3-shoellhammer-01-3des.txt),使用此密钥使用2-Key Triple-DES (CBC)加密。明文的第一行就是这个挑战的解决方案。”
首先可以根据题意“作者使用了在2006年建造的被描述机器的名称”,所以我们先去找这个机器的名字叫什么,在百度百科词条1998年建造的机器EFF-DES破解机中,我们找到了:
java逆向proto java逆向教程_逆向工程_42

得出密钥的前十位为“COPACOBANA”,然后题目中告诉我们剩下的6位是数字,所以我们可以采用暴力破解的方式,从0-999999进行循环。

但是有个问题:用999999个密钥去破解整个密文文本,然后一个一个去观察明文确定哪一个是正确的密钥这显然不现实。所以我们只破解第一行的密文,之后用代码自动判断哪一个明文是999999个明文中正确的密钥解密得到的明文:

java逆向proto java逆向教程_java逆向_43

我们可以看到错误的密钥,对明文的解密得到的依旧是16进制字符串,所以我们可以认为正确的明文不是16进制字符串,当出现一个明文不含有“\x”的16进制字符串的标志时,我们就认为它是正确的密文。

但是这样竟然没有找到一个正确答案。。。所以我们换了个思路,找到一个出现“\x”的16进制字符串的标志次数最少的明文,我们就认为它是正确的密文:

java逆向proto java逆向教程_java逆向_44

所以我们得到密钥为COPACOBANA008880,得出密钥所用的代码:

# py -3
# coding:utf-8
import re
from Crypto.Cipher import DES3
import binascii

if __name__ == '__main__':
    # data = 'FC3455BF7BC0C27D7A88A7349B807CB541380887336B0A084C11128529D0F4C1'
    # 第一行密文字节
    ciphertext = b'\xFC\x34\x55\xBF\x7B\xC0\xC2\x7D\x7A\x88\xA7\x34\x9B\x80\x7C\xB5\x41\x38\x08\x87\x33\x6B\x0A\x08\x4C\x11\x12\x85\x29\xD0\xF4\xC1'

    # min记录\x最少的值
    min = 100
    for k in range(0, 999999 + 1):
        # 不足6位数的地方填充0
        tmp = str(k).zfill(6)
        # 密钥循环,key='COPACOBANA008880';
        key = 'COPACOBANA' + tmp
        # 转化为密钥字节
        key1 = bytes(key, 'utf-8')
        # 初始向量,需为8的倍数,做过PA2应该理解它的含义  DES3.MODE_CBC
        cipher = DES3.new(key1, DES3.MODE_CBC, b"00000000")
        plaintext = cipher.decrypt(ciphertext)

        # 输出当前密钥
        # print("密钥:", key)
        # 输出当前密钥解密出的明文
        # print("明文:", plaintext)

        # 把16进制明文转成字符串
        plaintext = str(plaintext)
        # 当前明文中的'\x'个数
        num = len(re.findall('\\\\x', plaintext))
        # 找到\x最少的明文
        if num < min:
            min = num
            # 正确的明文
            truePlaintext = plaintext
            # 正确的密钥
            trueKey = key
    print("key:", trueKey)
    print(truePlaintext[2:-1])

之后我们再用密钥COPACOBANA008880,解密完整的密文得到完整的明文:
源代码:

import re
import binascii
from Crypto.Cipher import DES3
if __name__ == '__main__':
   with open( 'mtc3-shoellhammer-01-3des.txt','r') as fp:
       # 密文字符串,并且去掉空格和回车
      ciphertext = "".join(fp.read().split())

   # 字符串转字节
   ciphertext = ciphertext.encode('utf-8')

   # 字节转16进制字节
   ciphertext = binascii.a2b_hex(ciphertext)
   
   key='COPACOBANA008880'
   #密钥字符串转字节
   keyBytes=bytes(key,'utf-8')
  
   # 初始向量,需为8的倍数,做过PA2应该理解他的含义  DES3.MODE_CBC
   cipher = DES3.new(keyBytes,DES3.MODE_CBC,b"00000000")
   plaintext = cipher.decrypt(ciphertext)
  # 字节转换成字符
   plaintext = str(plaintext)[2:-1]
   print(key)
   print(plaintext)

运行结果:

java逆向proto java逆向教程_java逆向_45

成功解密,只是其中的\r\n被解释成了普通字符串,没有回车换行。

在Linux系统终端中输出的明文字符串是可以自动换行的:

java逆向proto java逆向教程_java逆向proto_46

最终解密出来的明文:

x_G\x10D_\x10rreak DES for 8,980 Euro

S. Kumar, C. Paar, J. Pelzl, G. Pfeiffer, A. Rupp, M. Schimmler, “How
to Break DES for Euro 8,980”. 2nd Workshop on Special-purpose
Hardware for Attacking Cryptographic Systems � SHARCS 2006, Cologne,
Germany, April 3�4, 2006.

Abstract:

Cryptanalysis of symmetric and asymmetric ciphers is computationally
extremely demanding. Since the security parameters of almost all
practical crypto algorithms are chosen such that attacks with
conventional computers are computationally infeasible, the only
promising way to tackle existing ciphers (in the absence of
mathematical breakthroughs) is to build special-purpose hardware.
Dedicating those machines to the task of cryptanalysis holds the
promise of a dramatically improved cost-performance ratio so that
breaking of commercial ciphers comes within reach.

This contribution describes the design and realization of the
reprogrammable machine COPACOBANA (Cost-Optimized Parallel Code
Breaker), which is optimized for running cryptanalytical algorithms.
The primary design goal was to produce a re-programmable low-cost
design for less than BC 10,000 which is applicable for attacking the
Data Encryption Standard (DES) in less than nine days.

It will be shown that the architecture outperforms conventional
computers by several orders of magnitude. Fully configured, COPACOBANA
hosts 120 low-cost FPGAs and is able to perform an exhaustive key
search of DES at a rate of more than 235 keys per second, yielding an
average search time of less than nine days. For this, we used the
high-speed DES engine design of the Universite Catholique de Louvain’s
Crypto Group.

We provide a real-world example by giving an estimate of an attack
with COPACOBANA against a formerly popular encryption tool (Norton
Diskreet). Due to a cryptographical weak key derivation function it
can be broken in very little time by applying a smart key search. As a
further application, COPACOBANA can also be used to attack machine
readable travel documents (ePass).

COPACOBANA is suitable for computational problems which are
parallelizable and have low communication requirements. The hardware
can be used, e.g., to attack elliptic curve cryptosystems and to
factor numbers. COPACOBANA is intended to, but not necessarily
restricted to solving problems related to cryptanalysis.

是一篇2006年的论文《How to Break DES for Euro 8,980》:

java逆向proto java逆向教程_.class_47

根据题目中“明文的第一行就是这个挑战的解决方案”,所以答案应该就是:How to Break DES for 8,980 Euro。

4. 阅读

android逆向学习

在android逆向分析过程中:

  1. 我们可以使用ApkTool(本质上是BakSmali反汇编引擎)对apk文件进行反汇编,得到各个类、方法、资源、布局文件…的smali代码,我们可以直接通过阅读smali代码来分析程序的代码流,进行关键点的修改或者代码注入。
  2. 我们可以从apk中提取.dex文件,使用dex2jar工具对dex进行反汇编,得到jar包(java虚拟指令),然后使用jd-gui等工具再次反编译,得到java源代码,从源码级的高度来审计代码,更快的找到关键点函数或者判断,然后再回到smali层面,对代码进行修改。这种方法更倾向于辅助性的,最终的步骤我们都要回到smali层面来修改代码。
  3. 使用IDA Pro直接分析APK包中的.dex文件,找到关键点代码的位置,记下文件偏移量,然后直接对.dex文件进行修改。修改完之后把.dex文件重新导入apk中。这个时候要注意修改dex文件头中DexHeader中的checksum字段。将这个值修复后,重新导入apk中,并删除apk中的META-INF文件夹,重新签名即可完成破解。