吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1792|回复: 18
收起左侧

[Android CTF] DASCTF-2024暑期赛-Reverse-BabyAndroid-WP

  [复制链接]
sffool 发表于 2024-7-21 15:12
本帖最后由 sffool 于 2024-7-21 15:22 编辑

吾爱这个MD有点麻烦,不能直接导入
不想再贴图了
去隔壁看雪看图片吧


BabyAndroid题型:安卓-Hook-Native分析

DAS46-BabyAndroid.zip

题目不支持模拟器运行,只能真机
附件里面除了APK,还有一段抓包后的响应体
出题人说是加密后的数据
所以我们的目标就是解密这段数据了
看看数据形式

TwMkYUkg4bYsY0hL99ggYWnVjWyXQrWAdNmToB0eBXbS6wBzL6ktorjNWI9VOroTU4HgIUYyzGLpcHzd1zNGT+bFZZI7IoxJwpcgXfdwW1LSmiNSP+PuSUsqAzNclF1nJ07b4tYyLWg0zTypbzWsLhOIM+6uci3RFZLREUCALafi01M8mS+KMNxX1Pyn8mSP+KKKjQ5S5fasHRSn+L9qBFws0mWavpfI0QEiMgarxv0iGhYU8cfgonWyL70RvoXET5VUDP1vfYWIBLzzzaAqLC0OiMtUK3TTATSU7yijdgXm18OKMcGIke/NZIM6Sr5fL3t6psDOOkw2C/5uYrJVPn+D6U9KTL64bgREppDqMOvhvbhtuf/S3ASW/+rhtPMtoaD8FxDg0wWSLZA53fQfNA==

有两个==
我们可以推测里面可能至少会用到base64编码
嗯,基本不可能只考base64

但具体是什么加密,我们目前不得而知
故,用上 算法助手
算法助手作用域勾选+算法助手开关
同时勾选算法分析4个开关
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721476815090-d2f603f3-1403-4267-abf7-53d2af7835f3.png
打开目标软件
让我们设置图形解锁
无所谓,这个不是关键点

进入软件后就会发现软件功能就是做笔记的
我们尝试下做个笔记
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721477281844-f0aa050c-32b8-4572-a584-e8981c42ad96.png
然后打开算法助手
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721477346557-a6782825-5b3b-42b2-834a-254298410d64.png
非常nice
分析出了加密的算法AES/ECB/PKCS5Padding
而下面的SHA-256,不用管,没有人拿这个当加密算法的,解密不出来的
我们点进去看看
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721477528117-9d086f5a-499e-4a94-909c-f84c27da5fdf.png
加密密钥都已经被Hook出来了
DQ0NDQ0NDQ0NDQ0NDQ0NDQ==
而加密的内容从1变成49
这中间应该还有一些操作
不可能就这么梭出来的
我们拿密钥和密文去在线网站上解密
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721477743358-06b907aa-3d94-4a3a-96ba-91d56bc37ecc.png
解密出来的明显不是最后的flag
我们可以验证下
49.000000和密钥进行加密
看看结果是不是FXb5k9BUw5T1EkGuNStrRw==
嗯,结果确实是
那至少我们解出来了一层加密

接下来我们就要开始看dex了
不过我们不直接看
我们先看看算法助手里面的调用堆栈
看看是哪个函数调用了刚刚的加密
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721477528117-9d086f5a-499e-4a94-909c-f84c27da5fdf.png
还是刚刚的图片
我们看到其中有一行是

at site.qifen.note.ui.Encrypto.encrypt(Encrypto.java:37)

那我们先看看加密函数吧
MT查看Dex
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721478428726-b684bb7d-7f23-4353-9e23-10c5a22fc88b.png
我们发现了上面这个
但这明显和刚刚不一样啊,一个0一个o
又找不到带o的
这里面肯定是有蹊跷的
我们先点进去看看

package site.qifen.note.ui;

import android.util.Base64;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Encrypt0 {
    private static final String KEY = "DSACTF";
    private static final String TAG = "Encrypto";

    public static String encrypt(String data) throws Exception {
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        byte[] keyBytes = sha.digest(KEY.getBytes("UTF-8"));
        byte[] keyBytes16 = new byte[16];
        System.arraycopy(keyBytes, 0, keyBytes16, 0, 16);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes16, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, secretKeySpec);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes("UTF-8"));
        return Base64.encodeToString(encryptedBytes, 2);
    }
}

加密是这个加密类型
但明显key不对劲
这里显示的key是DASCTF

那这时候
我们就需要考虑是不是这东西从其他地方加载了dex(例如PYCC2024的challengemobile

其实
我们查看APK的时候可以发现
在assets目录下有个sex.jpg
但它又不是图片
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721481277212-4bd3b46f-9cf6-4dc8-abc7-a2fb5d355205.png
大概这个就存储了dex数据
我们用算法助手看看是不是这样
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721481584714-125b8f3d-df40-4b9f-8013-64324ae6757e.png
重启,保存一个笔记
然后发现确实是读入了sex.jpg
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721481559568-46a5b264-645d-462f-ba72-1d514c10e8e2.png
我们点进去看调用堆栈
发现这两个比较特殊
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721481692030-7724e244-d602-4fc9-86d0-afa94c893c1f.png
我们再用MT找到先相关的函数

    public byte[] loadData(String str) {
        try {
            InputStream open = getAssets().open(str);
            byte[] encryptedData = new byte[open.available()];
            open.read(encryptedData);
            open.close();
            byte[] key = "DASCTF".getBytes();
            return rc4Decrypt(key, encryptedData);
        } catch (IOException e) {
            Log.e("错误", "加载数据时发生错误", e);
            return null;
        }
    }

    private byte[] rc4Decrypt(byte[] key, byte[] data) {
        int[] S = new int[256];
        for (int i = 0; i < 256; i++) {
            S[i] = i;
        }
        int j = 0;
        for (int i2 = 0; i2 < 256; i2++) {
            j = ((S[i2] + j) + (key[i2 % key.length] & 255)) % 256;
            int temp = S[i2];
            S[i2] = S[j];
            S[j] = temp;
        }
        int i3 = data.length;
        byte[] result = new byte[i3];
        int i4 = 0;
        int j2 = 0;
        for (int k = 0; k < data.length; k++) {
            i4 = (i4 + 1) % 256;
            j2 = (S[i4] + j2) % 256;
            int temp2 = S[i4];
            S[i4] = S[j2];
            S[j2] = temp2;
            int t = (S[i4] + S[j2]) % 256;
            result[k] = (byte) (data[k] ^ S[t]);
        }
        return result;
    }

看看如上代码
loadData函数 getAssest 获取文件
然后调用rc4解密函数和一个key来解密
最终返回所解密的内容
那我们直接Hook loadData函数
来看看
但这里我们要换成SimpleHookR 或者用Frida
因为最新版算法助手自动把数据转换成编码字节集了
我们用SimpleHookR记录这个函数的参返
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721482471116-e79c75b9-aea9-407d-af2e-074f78f458e9.png
我们把返回值复制下来
用python脚本把其转换化成dex文件

def to_unsigned_bytes(byte_list):
    return bytes([(b + 256) % 256 for b in byte_list])

your_byte_list = [数据]
converted_bytes = to_unsigned_bytes(your_byte_list)

with open('dump.dex', 'wb') as file:
    file.write(converted_bytes)

print("Data written to dump.dex")

用MT打开试试
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721482622043-73fd5dc5-afc5-425e-9719-2d5b08ea71ff.png
发现了我们之前在找的Encrypto

package site.qifen.note.ui;

import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Encrypto {
    private static final String KEY = "DSACTF";
    private static final String TAG = "Encrypto";

    private static byte[] customHash(String input) {
        byte[] keyBytes = new byte[16];
        int[] temp = new int[16];
        for (int i = 0; i < input.length(); i++) {
            int charVal = input.charAt(i);
            for (int j = 0; j < 16; j++) {
                temp[j] = ((temp[j] * 31) + charVal) % 251;
            }
        }
        for (int i2 = 0; i2 < 16; i2++) {
            keyBytes[i2] = (byte) (temp[i2] % 256);
        }
        return keyBytes;
    }

    public static String encrypt(String data) throws Exception {
        byte[] keyBytes = customHash(KEY);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, secretKeySpec);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes("UTF-8"));
        return Base64.encodeToString(encryptedBytes, 2);
    }
}

现在我们去看看另一个红框里的东西

package site.qifen.note.ui;

import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import dalvik.system.InMemoryDexClassLoader;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import site.qifen.note.model.Note;
import site.qifen.note.model.sendRequest;
import site.qifen.note.util.NoteUtil;

class NoteActivity$EncryptAndSendTask extends AsyncTask<String, Void, String> {
    final NoteActivity this$0;

    private NoteActivity$EncryptAndSendTask(NoteActivity noteActivity) {
        this.this$0 = noteActivity;
    }

    @Override
    public String doInBackground(String... params) {
        String contentText = params[0];
        try {
            byte[] dexData = this.this$0.loadData("Sex.jpg");
            ByteBuffer dexBuffer = ByteBuffer.wrap(dexData);
            InMemoryDexClassLoader classLoader = null;
            if (Build.VERSION.SDK_INT >= 26) {
                classLoader = new InMemoryDexClassLoader(dexBuffer, this.this$0.getClassLoader());
            }
            Class<?> checkerClass = classLoader.loadClass("site.qifen.note.ui.Encrypto");
            Method checkMethod = checkerClass.getMethod("encrypt", String.class);
            this.this$0.contentText_back = contentText;
            String cipher = (String) checkMethod.invoke(checkerClass.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]), this.this$0.sendInit(contentText));
            String response = sendRequest.sendPost("http://yuanshen.com/", "data=" + cipher);
            Log.d("JNITest", "Server Response: " + response);
            return cipher;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void onPostExecute(String cipher) {
        if (cipher != null) {
            String titleText = this.this$0.noteWriteTitleEdit.getText().toString();
            String tagText = this.this$0.noteWriteTagEdit.getText().toString();
            String date = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
            if (NoteActivity.access$100(this.this$0) == null) {
                NoteActivity.access$200(this.this$0).insertNote(new Note(tagText, titleText, this.this$0.contentText_back, date, false));
                NoteUtil.toast("保存成功");
                this.this$0.finish();
                return;
            }
            NoteActivity.access$100(this.this$0).setTitle(titleText);
            NoteActivity.access$100(this.this$0).setContent(this.this$0.contentText_back);
            NoteActivity.access$100(this.this$0).setDate(date);
            NoteActivity.access$100(this.this$0).setTag(this.this$0.contentText_back);
            NoteActivity.access$200(this.this$0).updateNote(NoteActivity.access$100(this.this$0));
            NoteUtil.toast("修改成功");
            this.this$0.finish();
            return;
        }
        NoteUtil.toast("加密失败");
    }
}

我们可以发现
就是doInBackground函数调用了loadData
并且还使用了classLoader去加载我们刚刚得到得到的类
然后我们发现cipher就是加密后的数据
它是由我们输入的文本经sendInit处理,然后再被刚刚加载的类加密得来的
我们跳转去看看sendInit
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721483625155-5cf54301-9a65-4312-a927-7f68c86a218f.png
原来就在刚刚的NoteActivity里面
这里 native
说明我们要去分析/lib里面的so文件了
当然,我们可以先Hook一下,验证一下
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721483781125-596755fa-9b9d-4385-90a8-1d3858ef0504.png
参数1
返回49.000000
没问题,就是他

我们用ida64打开so
我们直接找带sendInit
https://cdn.nlark.com/yuque/0/2024/png/40436415/1721484039885-62a19d85-a6e1-4897-95c0-bc7c94d57d7d.png

v20 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
StringUTFChars = _JNIEnv::GetStringUTFChars(a1, a3, 0LL);
sub_15994(v19, StringUTFChars);
_JNIEnv::ReleaseStringUTFChars(a1, a3, StringUTFChars);
v8 = sub_15A40(v19);
v7 = sub_15AB4(v19);
std::vector<int>::vector<std::__wrap_iter<char *>>(v18, v8, v7);
encrypt(v18);
sub_15C34(v16);
v15 = sub_15C74(v17);
v14 = sub_15CB4(v17);
while ( (sub_15CF0(&v15, &v14) & 1) != 0 )
{
    v3 = (std::__ndk1 *)sub_15D38(&v15);
    std::to_string(v3, *(double *)v3);
    sub_15D50(v12, ",");
    sub_15D98(v16, v13);
    std::string::~string(v13);
    std::string::~string(v12);
    sub_15E34(&v15);
}
if ( (sub_15E5C(v16) & 1) == 0 )
    sub_15EA0(v16);
v6 = (char *)sub_15FD4(v16);
v5 = _JNIEnv::NewStringUTF(a1, v6);
std::string::~string(v16);
sub_15668(v17);
sub_15FF8(v18);
std::string::~string(v19);
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return v5;
}

我刚看到这个伪代码的时候确实是崩溃的
因为我伪代码分析能力太弱了
压根看不懂
丢给chatgpt都没救

但看到都有2个解了
就一个一个点进去看
encrypt(v18);
这个其实满显眼的函数时

  v12[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v10 = sub_15548(a1);
  v12[0] = 0LL;
  result = (double *)std::vector<double>::vector(a2, v10, v12);
  for ( i = 0; i < v10; ++i )
  {
    for ( j = 0; j < v10; ++j )
    {
      v7 = (double)*(int *)sub_15608(a1, j);
      v5 = cos(((double)j + 0.5) * ((double)i * 3.14159265) / (double)v10) * v7;
      v3 = (double *)sub_15638(a2, i);
      *v3 = *v3 + v5;
    }
    if ( i )
      v4 = sqrt(2.0 / (double)v10);
    else
      v4 = sqrt(1.0 / (double)v10);
    result = (double *)sub_15638(a2, i);
    *result = *result * v4;
  }
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

直觉告诉我,就是这个了
丢给chatgpt
分析是什么离散啥啥的,简称DCT
又问有没有逆向的
chatgpt给出了IDCT的python脚本

import numpy as np

def idct(dct_data):
    N = len(dct_data)
    result = np.zeros(N)

    for n in range(N):
        sum_value = 0.0
        for k in range(N):
            cos_term = np.cos((k * 3.14159265 * (n + 0.5)) / N)
            if k == 0:
                sum_value += dct_data[k] * np.sqrt(1.0 / N) * cos_term
            else:
                sum_value += dct_data[k] * np.sqrt(2.0 / N) * cos_term
        result[n] = sum_value

    return result

def decrypt_to_ascii(dct_data):
    # 执行IDCT解密
    decrypted_data = idct(dct_data)
    # 四舍五入并转化为整数
    int_data = np.rint(decrypted_data).astype(int)
    # 转换为ASCII字符
    char_data = [chr(num) for num in int_data]
    return ''.join(char_data)

# 使用 DCT 变换后的数据
encrypted_data = [458.853181,-18.325492,-18.251911,-2.097520,-21.198660,-22.304648,21.103162,-5.786284,-15.248906,15.329286,16.919499,-19.669045,30.928253,-37.588034,-16.593954,-5.505211,3.014744,6.553616,31.131491,16.472500,6.802400,-78.278577,15.280099,3.893073,56.493581,-34.576344,30.146729,4.445671,6.732204]

# 解密并转换为ASCII字符
decrypted_message = decrypt_to_ascii(encrypted_data)

# 打印解密后的消息
print("Decrypted message:", decrypted_message)

运行脚本就会得到flag

免费评分

参与人数 6威望 +1 吾爱币 +24 热心值 +6 收起 理由
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
d3c0lor + 1 + 1 我很赞同!
sw1f7 + 1 + 1 用心讨论,共获提升!
offerking + 1 + 1 谢谢@Thanks!
starhao + 1 我很赞同!
wuaixx + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

wantwill 发表于 2024-7-21 17:35
感谢分享
Lyscross 发表于 2024-7-21 20:33
starhao 发表于 2024-7-21 22:49
newAny 发表于 2024-7-21 23:19
感谢分享
Oc20 发表于 2024-7-22 08:22
感谢分享!!!!!!
abaichuan 发表于 2024-7-22 09:43
本帖最后由 abaichuan 于 2024-7-22 09:46 编辑

谢谢大佬分享
美想成真 发表于 2024-7-22 10:06
支持。学习。。。。
harmonyBupt 发表于 2024-7-22 10:20
膜拜大佬,感谢分享
zyzyzyzy 发表于 2024-7-22 12:03
学习一下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-9-8 09:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表