发布于 

攻防世界Mobile - Android2.0、APK逆向

第三题(Android2.0)

0x01 包体基本分析

拿到题目apk包,发现未加固,直接改后缀解压:


使用jadx-gui 分析一下java层代码,看到只有一个输入框输入密码,并调用 JNI.getResult() 方法进行密码校验,并提示正确或错误。
开始分析JNI类,发现为native调用getResult()函数。观察解压文件发现有lib目录,内部只有一个动态库文件:




针对此种情况,直接分析动态库的逻辑即可。

0x02 动态库分析

判断出动态库为32位版本,使用IDA反编译,一进来就看到导出了目标函数Java_com_example_test_ctf03_JNI_getResult,对其F5生成伪代码:


观察代码逻辑,发现密码为15位字符串,在init函数中按照3为模对输入串进行分组,并分组进行运算。判断出是一个典型的格栅密码:

进一步分析运算逻辑得知:

  1. 第一组(模3=0)和固定字符进行异或运算,并与硬编码密文进行匹配:
    该程序将a1中每个字符都乘以2,再与0x80异或,最后比较处理后的字符串是否与"LN^dl"相等,如果相等返回1,否则返回0。

执行流程如下:
1) 变量v1被初始化为0;
2) 将a1中的第0个字符乘以2,再与0x80异或,最后将结果存入a1;
3) 将v1加1;
4) 判断v1是否小于4,


将密文与上一组密文按位异或运算(只运算前4位)即可。

  1. 第二组(模3=1)和第一组密文进行异或运算,并与硬编码密文进行匹配:

将密文与上一组密文按位异或运算(只运算前4位)即可。

  1. 第三组(模3=2)和第二组密文进行异或运算,并与硬编码密文进行匹配:

同第二组,将密文与第二组密文按位异或运算(只运算前4位)即可。

0x03 编写脚本

简单编写C语言脚本,对上述密文进行逆运算

a = [0x4C,0x4E,0x5E,0x64,0x6C]  #字符串 LN^dl 的16进制
b = []
c = []
for i in range(5):
    b.append((a[i]^0x80)//2)
print(b)

for i in range(5):
    b[4] = 0x6c
    c.append(chr(b[i]))

print(c)

d = [0x20, 0x35, 0x2D, 0x16, 0x61]  #a5的字符串 的16进制
e = []

for i in range(5):
    e.append(a[i]^d[i])
    if i == 4:
        e[4] = 0x61
    e[i] = chr(e[i])
print(e)

f = [0x41,0x46,0x42,0x6f,0x7d]  # AFBo} 的16进制
g = []
for i in range(5):
    g.append(f[i]^d[i])
    if i == 4:
        g[4]=0x7d
    g[i] = chr(g[i])
print(g)

得到结果:
[102, 103, 111, 114, 118]
[‘f’, ‘g’, ‘o’, ‘r’, ‘l’]
[‘l’, ‘{’, ‘s’, ‘r’, ‘a’]
[‘a’, ‘s’, ‘o’, ‘y’, ‘}’]

另外一种方式:

r1 = [ord(i) for i in list("LN^dl")]
r2 = [0x20, 0x35, 0x2D, 0x16, 0x61]
r3 = [ord(i) for i in list("AFBo}")]
print(''.join(chr(i) for i in r2))
print(r2)
print(r3)
flag = ""
for i in range(5):
    if(i < 4):
        r3[i] ^= r2[i]
        r2[i] ^= r1[i]
        r1[i] = (r1[i] ^ 0x80)//2
    flag += chr(r1[i])
    flag += chr(r2[i])
    flag += chr(r3[i])

print(flag)

得到结果: 5-a
[32, 53, 45, 22, 97]
[65, 70, 66, 111, 125]
flag{sosorryla}

第四题(APK逆向)

0x01 静态分析

下载使用Jadx-gui打开查看java层源代码

查看代码,发现没有so文件,分析java代码得出checkSN这个方法里面是对字符串进行操作

MD5不可逆,我们需要调试源码进行测试,打开androidStudio编写类似源码进行测试:

编写代码测试

java

package com.example.ctf04;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {

    private EditText editUsr;
    private String UserName = "Tenshine";
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    private void init(){
        editUsr = findViewById(R.id.edit_sn);

        btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!MainActivity.this.checkSN(UserName, editUsr.getText().toString().trim())) {
                    Toast.makeText(getApplicationContext(), "失敗例", Toast.LENGTH_SHORT).show();
                    return;
                }
                Toast.makeText(getApplicationContext(),"SUCCESS", Toast.LENGTH_SHORT).show();
                btn.setEnabled(false);
                MainActivity.this.setTitle("This is My pro");
            }
        });

    }

    /* JADX INFO: Access modifiers changed from: private */
    public boolean checkSN(String userName, String sn) {
        if (userName != null) {
            try {
                if (userName.length() == 0 || sn == null || sn.length() != 22) {
                    return false;
                }
                MessageDigest digest = MessageDigest.getInstance("MD5");
                digest.reset();
                digest.update(userName.getBytes());
                byte[] bytes = digest.digest();
                String hexstr = toHexString(bytes, "");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < hexstr.length(); i += 2) {
                    sb.append(hexstr.charAt(i));
                }
                String userSN = sb.toString();
                return new StringBuilder().append("flag{").append(userSN).append("}").toString().equalsIgnoreCase(sn);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }

    private static String toHexString(byte[] bytes, String separator) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(b & 255);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex).append(separator);
        }
        return hexString.toString();
    }


}

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="请输入用户名信息:"
        android:id="@+id/edit_sn"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:text="确认信息"
        />
</LinearLayout>

0x02 运行测试,debug附加进程进行调试


输入flag{1*16} 保持长度等于22,输入后进入调试模式


可以看到解密后的字符串的flag为 bc72f242a6af3857 ,输入flag,提示正确!