bugku-逆向-9、easy-100(LCTF)

一、反编译代码具体分析

下载easy-100(LCTF).apk,最好用JEB打开反编译,其他的软件反编译出来的代码会有一些不同,影响代码的可读性。
(1)、首先是MainActivity.class的代码:

package com.example.ring.myapplication;

import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.support.v7.a.q;
import java.io.InputStream;

public class MainActivity extends q {
    
    
    private String v;

    public MainActivity() {
    
    
        super();
    }
/*
*返回当前MainActivity的成员变量字符串v
*/
    static String a(MainActivity arg1) {
    
    
        return arg1.v;
    }

    static boolean a(MainActivity arg1, String arg2, String arg3) {
    
    
        return arg1.a(arg2, arg3);
    }
/*
*arg4:当前MainActivity的成员变量字符串v
*arg5:输入的密码字符串
*/
    private boolean a(String arg4, String arg5) {
    
    
        return new c().a(arg4, arg5).equals(new String(new byte[]{
    
    21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}));
    }

    protected void onCreate(Bundle arg3) {
    
    
        super.onCreate(arg3);
        this.setContentView(2130968602);
        ApplicationInfo v0 = this.getApplicationInfo();
        v0.flags &= 2;
        this.p();
//创建了一个按钮监听事件在classs d
        this.findViewById(2131427413).setOnClickListener(new d(this));
    }
/**
*读取url.png的二进制数据,取出这张图片byte[144:144+16]的数据保存在v字符串中.
*/
    private void p() {
    
    
        try {
    
    
//以输入流读取assets目录下的资源url.png
            InputStream v0_1 = this.getResources().getAssets().open("url.png");
 	//获取文件大小,
            int v1 = v0_1.available();
	//进而用来定义缓冲数组的长度。
            byte[] v2 = new byte[v1];
//从v0_1流中每次读取v1个字节放在v2第0个位置开始的地方,这里就是一次直接把v1读入v2
            v0_1.read(v2, 0, v1);
            byte[] v0_2 = new byte[16];
	//从v2的144位置之后复制16个字节给 v1的第 0个位置开始
            System.arraycopy(v2, 144, v0_2, 0, 16);
	//变量v就等于这张图片byte[144:144+16]的数据
            this.v = new String(v0_2, "utf-8");
        }
        catch(Exception v0) {
    
    
            v0.printStackTrace();
        }
    }
}

MainActivity主要是调用p函数:读取url.png的二进制数据,取出这张图片byte[144:144+16]的数据保存在v字符串中。之后创建了一个按钮监听事件在classs d。

(2)d.class的代码:

package com.example.ring.myapplication;

import android.view.View$OnClickListener;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

class d implements View$OnClickListener {
    
    
    d(MainActivity arg1) {
    
    
        this.a = arg1;
        super();
    }

public void onClick(View arg5) {
    
    
//this.a.findViewById(2131427414).getText().toString())获得id为2131427414=0x7F0B0056对应的passCode中输入的密码
//调用MainActivity的第2个有三个参数的a函数,返回值为真则输出flag。第一个参数为句柄this.a,第二个参数调用了第1个只有一个参数的a函数返回一个字符串,第三个参数是我们输入的字符串。
        if(MainActivity.a(this.a, MainActivity.a(this.a), this.a.findViewById(2131427414).getText().toString())) {
    
    
            View v0 = this.a.findViewById(2131427412);
            Toast.makeText(this.a.getApplicationContext(), "Congratulations!", 1).show();
            ((TextView)v0).setText(2131099682);
        }
        else {
    
    
            Toast.makeText(this.a.getApplicationContext(), "Oh no.", 1).show();
        }
    }
}

d类主要是调用MainActivity的2个a函数,通过a函数来判断输入的值是否正确,正确的输出Congratulations!下面是MainActivity的3个a函数:

/*
*返回当前MainActivity的成员变量字符串v
*/
    static String a(MainActivity arg1) {
    
    
        return arg1.v;
    }

    static boolean a(MainActivity arg1, String arg2, String arg3) {
    
    
        return arg1.a(arg2, arg3);
    }
/*
*arg4:当前MainActivity的成员变量字符串v
*arg5:输入的密码字符串
*/
    private boolean a(String arg4, String arg5) {
    
    
        return new c().a(arg4, arg5).equals(new String(new byte[]{
    
    21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}));
    }

最后调用的a函数是通过调用c类的a函数new c().a(arg4, arg5),得到的返回值与new String(new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59})比较是否相等。

(3)、c.class的代码:

package com.example.ring.myapplication;

import java.io.UnsupportedEncodingException;

public class c {
    
    
    public c() {
    
    
        super();
}
/*
*arg5:前MainActivity的成员变量字符串v
*arg6:输入的密码字符串
*/
public String a(String arg5, String arg6) {
    
    
//v0=当前对象调用下面那个只有一个参数的a函数的返回值
//返回互换位置后的图片字符串
        String v0 = this.a(arg5);
        String v1 = "";
//a类的变量v2
        a v2 = new a();
//v2调用a类的a函数
        v2.a(v0.getBytes());
        try {
    
    
	//v2调用a类的b函数
            v0 = new String(v2.b(arg6.getBytes()), "utf-8");
        }
        catch(Exception v0_1) {
    
    
            v0_1.printStackTrace();
            v0 = v1;
        }

        return v0;
    }
/*
*arg4的实参是:图片byte[144:144+16]的数据
*函数作用:前后两个字符一组,互换位置,返回之后的字符串
*/
    private String a(String arg4) {
    
    
        String v0_2;
        try {
    
    
            arg4.getBytes("utf-8");
            StringBuilder v1 = new StringBuilder();
            int v0_1;
	//前后两个字符一组,互换位置
            for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) {
    
    
                v1.append(arg4.charAt(v0_1 + 1));
                v1.append(arg4.charAt(v0_1));
            }
            v0_2 = v1.toString();
        }
        catch(UnsupportedEncodingException v0) {
    
    
            v0.printStackTrace();
            v0_2 = null;
        }
        return v0_2;
    }
}

C类主要是转换图片字符串中字符的先后顺序,然后作为a类a函数的实参字符串,之后把输入的密码字符串作为a类b函数的实参字符串。

(4)、a.class的代码:

package com.example.ring.myapplication;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class a {
    
    
    private SecretKeySpec a;
    private Cipher b;

    public a() {
    
    
        super();
    }

    protected void a(byte[] arg4) {
    
    
        if(arg4 != null) {
    
    
            goto label_15;
        }

        try {
    
    
            this.a = new SecretKeySpec(MessageDigest.getInstance("MD5").digest("".getBytes("utf-8")), "AES");
            this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
            return;
        label_15:
	//根据字节数组生成AES密钥
            this.a = new SecretKeySpec(arg4, "AES");
	//根据指定算法AES生成成密码器
            this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
        }
        catch(UnsupportedEncodingException v0) {
    
    
            v0.printStackTrace();
        }
        catch(NoSuchAlgorithmException v0_1) {
    
    
            v0_1.printStackTrace();
        }
        catch(NoSuchPaddingException v0_2) {
    
    
            v0_2.printStackTrace();
        }
    }

protected byte[] b(byte[] arg4) {
    
    
        //初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
        this.b.init(1, this.a);
        //根据密码器的初始化方式--加密:将数据加密 
        return this.b.doFinal(arg4);
    }
}

a类主要是调用AES算法的库,其中a函数主要用转换位置后的图片字符串来初始化AES密钥和密码器,b函数是根据密钥和密码器把密码字符串加密。将加密的密文返回给c类第一个调用加密算法的a函数,然后c类的a函数再返回给MainActivity的第三个a函数:new c().a(arg4, arg5).equals(new String(new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}))比较是否相等,将结果返回d函数决定是否输出flag。

扫描二维码关注公众号,回复: 11956940 查看本文章

二、程序流程图

程序的具体逻辑分析就是上面这样了,为了让条理更清楚,画了个流程图,如图所示:
在这里插入图片描述

三、逆向代码分析

所以我们要得到输入的密码字符串,实际上就是要用AES算法解密,分别得到密文、密钥,再按照原来加密的密码器AES/ECB/PKCS5Padding解密即可得到原来的密码明文。
(1)首先,复现p函数:从图片url.png中读取出byte[144:144+16]的数据保存在v字符串中。

/**
 *读取url.png的二进制数据,取出这张图片byte[144:144+16]的数据保存在v字符串中.
 */
private void p() {
    
    
    try {
    
    
        //以输入流读取assets目录下的资源url.png
        //InputStream v0_1 = this.getResources().getAssets().open("url.png");
        File imageFile = new File("E:/学习资料/IDEA源代码 workspace/平时没事写的一些代码/src/easy100/url.png");
        FileInputStream fis = new FileInputStream(imageFile);
        InputStream v0_1 = fis;
        //获取文件大小,
        int v1 = v0_1.available();
        //进而用来定义缓冲数组的长度。
        byte[] v2 = new byte[v1];
        //从v0_1流中每次读取v1个字节放在v2第0个位置开始的地方,这里就是一次直接把v1读入v2
        v0_1.read(v2, 0, v1);
        byte[] v0_2 = new byte[16];
        //从v2的144位置之后复制16个字节给 v1的第 0个位置开始
        System.arraycopy(v2, 144, v0_2, 0, 16);
        //变量v就等于这张图片byte[144:144+16]的数据
        v = new String(v0_2, "utf-8");
        //图片[144:144+16]字符串:this_is_the_key.
        System.out.println("图片[144:144+16]字符串:"+v);
    }
    catch(Exception v0) {
    
    
        v0.printStackTrace();
    }
}

(2)之后复现c类的a1函数,把v字符串前后两个字符一组,互换位置,之后返回字符串:

/**
 *arg4的实参是:图片byte[144:144+16]的数据
 *函数作用:前后两个字符一组,互换位置,返回之后的字符串
 */
String a(String arg4) {
    
    
    String v0_2;
    try {
    
    
        arg4.getBytes("utf-8");
        StringBuilder v1 = new StringBuilder();
        int v0_1;
        //前后两个字符一组,互换位置
        for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) {
    
    
            v1.append(arg4.charAt(v0_1 + 1));
            v1.append(arg4.charAt(v0_1));
        }
        v0_2 = v1.toString();
    }
    catch(UnsupportedEncodingException v0) {
    
    
        v0.printStackTrace();
        v0_2 = null;
    }
    return v0_2;
}

(3)a类的a函数:将转换位置后的字符串转换为字节数组来初始化AES密钥和密码器。

/**
 * a函数主要用转换后的图片字符串来初始化AES密钥和密码器
 */
protected void a(byte[] arg4) {
    
    
    try {
    
    
        if(arg4 != null) {
    
    
            //根据字节数组生成AES密钥
            this.a = new SecretKeySpec(arg4, "AES");
            //System.out.println("根据字节数组生成AES密钥:" + this.a);
            //根据指定算法AES生成成密码器
            this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //System.out.println("根据指定算法AES生成成密码器:" + this.b);
        }else {
    
    
            this.a = new SecretKeySpec(MessageDigest.getInstance("MD5").digest("".getBytes("utf-8")), "AES");
            this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
            return;
        }
    }
    catch(UnsupportedEncodingException v0) {
    
    
        v0.printStackTrace();
    }
    catch(NoSuchAlgorithmException v0_1) {
    
    
        v0_1.printStackTrace();
    }
    catch(NoSuchPaddingException v0_2) {
    
    
        v0_2.printStackTrace();
    }
}

(4)最后和代码的AES加密不同,我们要逆向使用AES解密,加密模式为AES/ECB/PKCS5Padding,根据a类的b函数写的解密函数b_decrypt():

/**
 *AES解密
 * @throws InvalidKeyException
 * @throws BadPaddingException
 * @throws IllegalBlockSizeException
 * @throws UnsupportedEncodingException
 */
protected void b_decrypt() throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
    
    
    //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
    this.b.init(Cipher.DECRYPT_MODE, this.a);
    //8.将加密并编码后的内容解码成字节数组
    byte [] byte_content = new byte[]{
    
    21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59};
    // 解密
    byte [] byte_decode=this.b.doFinal(byte_content);
    String AES_decode=new String(byte_decode,"utf-8");
    //解密后的输入的字符串:LCTF{1t's_rea1ly_an_ea3y_ap4}
    System.out.println("解密后的输入的字符串:" + AES_decode);
}

四、完整的Java代码

MainActivity.java

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;

public class MainActivity {
    
    
    static String v = "";

    public static void main(String[] args) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UnsupportedEncodingException {
    
    
        MainActivity mainActivity = new MainActivity();
        //图片字符串v
        mainActivity.p();
        //返回互换位置后的图片字符串
        String  str2 = new c().a(v);
        System.out.println("返回互换位置后的图片字符串:" + str2);
        //用互换位置后的图片字符串生成AES密钥和密码器
        a flag = new a();
        flag.a(str2.getBytes());
        //用生成的AES密钥和密码器解密
        flag.b_decrypt();
    }
    /**
     *读取url.png的二进制数据,取出这张图片byte[144:144+16]的数据保存在v字符串中.
     */
    private void p() {
    
    
        try {
    
    
            //以输入流读取assets目录下的资源url.png
            //InputStream v0_1 = this.getResources().getAssets().open("url.png");
            File imageFile = new File("E:/学习资料/IDEA源代码 workspace/平时没事写的一些代码/src/easy100/url.png");
            FileInputStream fis = new FileInputStream(imageFile);
            InputStream v0_1 = fis;
            //获取文件大小,
            int v1 = v0_1.available();
            //进而用来定义缓冲数组的长度。
            byte[] v2 = new byte[v1];
            //从v0_1流中每次读取v1个字节放在v2第0个位置开始的地方,这里就是一次直接把v1读入v2
            v0_1.read(v2, 0, v1);
            byte[] v0_2 = new byte[16];
            //从v2的144位置之后复制16个字节给 v1的第 0个位置开始
            System.arraycopy(v2, 144, v0_2, 0, 16);
            //变量v就等于这张图片byte[144:144+16]的数据
            v = new String(v0_2, "utf-8");
            //图片[144:144+16]字符串:this_is_the_key.
            System.out.println("图片[144:144+16]字符串:"+v);
        }
        catch(Exception v0) {
    
    
            v0.printStackTrace();
        }
    }

    /**
     *arg4:当前MainActivity的成员变量字符串v
     *arg5:输入的密码字符串
     */
    private boolean a(String arg4, String arg5) {
    
    
        return new c().a(arg4, arg5).equals(new String(new byte[]{
    
    21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}));
    }
}

c.java

import java.io.UnsupportedEncodingException;

public class c {
    
    
    public c() {
    
    
        super();
    }
    /**
     *arg5:前MainActivity的成员变量字符串v
     *arg6:输入的密码字符串
     */
    public String a(String arg5, String arg6) {
    
    
         //v0=当前对象调用下面那个只有一个参数的a函数的返回值
        //返回互换位置后的图片字符串
        String v0 = this.a(arg5);
        String v1 = "";
        //a类的变量v2
        a v2 = new a();
        //v2调用a类的a函数
        v2.a(v0.getBytes());
        try {
    
    
            //v2调用a类的b函数
            v0 = new String(v2.b(arg6.getBytes()), "utf-8");
        }
        catch(Exception v0_1) {
    
    
            v0_1.printStackTrace();
            v0 = v1;
        }

        return v0;
    }
    /**
     *arg4的实参是:图片byte[144:144+16]的数据
     *函数作用:前后两个字符一组,互换位置,返回之后的字符串
     */
    String a(String arg4) {
    
    
        String v0_2;
        try {
    
    
            arg4.getBytes("utf-8");
            StringBuilder v1 = new StringBuilder();
            int v0_1;
            //前后两个字符一组,互换位置
            for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) {
    
    
                v1.append(arg4.charAt(v0_1 + 1));
                v1.append(arg4.charAt(v0_1));
            }
            v0_2 = v1.toString();
        }
        catch(UnsupportedEncodingException v0) {
    
    
            v0.printStackTrace();
            v0_2 = null;
        }
        return v0_2;
    }
}

a.java

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class a {
    
    
    //就是封装好被加密了的KEY
    private SecretKeySpec a;
    private Cipher b;

    public a() {
    
    
        super();
    }

    /**
     * a函数主要用转换后的图片字符串来初始化AES密钥和密码器
     */
    protected void a(byte[] arg4) {
    
    
        try {
    
    
            if(arg4 != null) {
    
    
                //根据字节数组生成AES密钥
                this.a = new SecretKeySpec(arg4, "AES");
                //System.out.println("根据字节数组生成AES密钥:" + this.a);
                //根据指定算法AES生成成密码器
                this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
                //System.out.println("根据指定算法AES生成成密码器:" + this.b);
            }else {
    
    
                this.a = new SecretKeySpec(MessageDigest.getInstance("MD5").digest("".getBytes("utf-8")), "AES");
                this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
                return;
            }
        }
        catch(UnsupportedEncodingException v0) {
    
    
            v0.printStackTrace();
        }
        catch(NoSuchAlgorithmException v0_1) {
    
    
            v0_1.printStackTrace();
        }
        catch(NoSuchPaddingException v0_2) {
    
    
            v0_2.printStackTrace();
        }
    }

    /**
     *AES解密
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws UnsupportedEncodingException
     */
    protected void b_decrypt() throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
    
    
        //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
        this.b.init(Cipher.DECRYPT_MODE, this.a);
        //8.将加密并编码后的内容解码成字节数组
        byte [] byte_content = new byte[]{
    
    21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59};
        // 解密
        byte [] byte_decode=this.b.doFinal(byte_content);
        String AES_decode=new String(byte_decode,"utf-8");
        //解密后的输入的字符串:LCTF{1t's_rea1ly_an_ea3y_ap4}
        System.out.println("解密后的输入的字符串:" + AES_decode);
    }

    /**
     * b函数是根据密钥和密码器把密码字符串加密
     * @param arg4
     * @return
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeyException
     */
    protected byte[] b(byte[] arg4) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
    
    
        //初始化密码器,第一个参数为加密(Encrypt_mode,1)或者解密解密(Decrypt_mode,2)操作,第二个参数为使用的KEY
        this.b.init(1, this.a);
        //根据密码器的初始化方式--加密:将数据加密
        return this.b.doFinal(arg4);
    }
}

五、Java代码运行结果

在这里插入图片描述

得到的flag是LCTF{1t’s_rea1ly_an_ea3y_ap4}。
用Java来解的好处就是比较方便,可以直接借用.class反编译的代码,修改有问题的部分就可以了,而且这样也可以按照原来的类和方法,让条理更清楚更容易理解。当你对这个程序的逻辑比较了解的时候,也可以直接用其他语言直接逆向实现程序即可。下面用的是Python代码:

六、完整的Python代码

from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex

#equals比较的字节数组
byteArray =[21, -93, -68, -94, 86, 117, -19, -68, -92, 33,
            50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18,
            81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59]
#读取图片
with open('url.png','rb') as f:
    p =f.read()
str1 =p[144:144+16]
# 字节数组转字符串
byte2str = str1.decode()
print("图片[144:144+16]字符串:"+ byte2str)

# 前后两个字符一组,互换位置,密钥字符串
str2 =""
for i in range(0,len(str1),2):
    str2 += chr(str1[i+1])
    str2 += chr(str1[i])
print("返回互换位置后的图片字符串:" + str2)

# 把密文字节数组转换为16进制字符串
str3 =""
for i in byteArray:
    s=str(hex((i+256)%256))
    if len(s) <4:
        s = s[0:2]+'0'+s[2:]
    str3+=s[2:]
print(str3)

# AES算法解密,ECB的工作模式,PKCS5Padding 的填充
# 去掉 PKCS5Padding 的填充
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
# 通过 密钥,工作模式ECB 进行初始化密码器
cipher = AES.new(str2.encode(), AES.MODE_ECB)
# a2b_hex()16进制转字符串,之后密码器解密字符串,再用unpad表达式去掉 PKCS5Padding 的填充,最后指定utf8的编码格式将bytes解码成字符串str
data_response = unpad(cipher.decrypt(a2b_hex(str3))).decode('utf8')
print("解密得到的明文密码flag:"+data_response)

七、Python代码运行结果

在这里插入图片描述
得到的flag也是LCTF{1t’s_rea1ly_an_ea3y_ap4}。

猜你喜欢

转载自blog.csdn.net/Onlyone_1314/article/details/108836559