KoZ 发表于 2024-11-22 15:34

某黑产app的so文件无法找到的分析

第一步:jadx分析,看起来是dex加壳的,直接frida脱掉,这些和本文的主题无关就不再详述

第二步:解压apk,发现lib里面只有一个so文件,很奇怪,ida不能分析,通过file指令查看,是一个data文件

第三步:继续frida hook dlopen,查看so的加载,发现加载了很多自带的so,但是apk里面没有,所以怀疑是自定义的so加载方式
android_dlopen_ext probe /data/user/0/com.hptfludyjx.syjqeafkll/files/HJfOTtKmmn/libflutter.so
android_dlopen_ext probe /vendor/lib/hw/gralloc.sdm845.so
android_dlopen_ext probe /data/user/0/com.hptfludyjx.syjqeafkll/files/HJfOTtKmmn/libkiwi.so
android_dlopen_ext probe /vendor/lib/hw/android.hardware.graphics.mapper@2.0-impl-qti-display.so
android_dlopen_ext probe /data/dalvik-cache/arm/system@product@app@webview@webview.apk@classes.dex
android_dlopen_ext probe libwebviewchromium.so
android_dlopen_ext probe /system/product/app/webview/webview.apk!/lib/armeabi-v7a/libwebviewchromium.so
android_dlopen_ext probe /system/lib/libwebviewchromium_plat_support.so


第四步:继续分析代码,发现了端倪,有一段可疑代码会对asset的文件进行解密解压,得出so文件
if (list.endsWith("_hmc")) {

                  MfLogUtils.e(Constant.METHOD_DEBUG, "找到hmc文件");

                  String str = list;

                  String replace = list.replace("_hmc", "");

                  File filesDir = context.getFilesDir();

                  String str2 = filesDir.getAbsolutePath() + PsuedoNames.PSEUDONAME_ROOT + replace;

                  if (!new File(str2 + PsuedoNames.PSEUDONAME_ROOT + str).exists()) {

                        MfLogUtils.e(Constant.METHOD_DEBUG, "filename_assetsso不存在");

                        copyAssetsFileToDataDir(context, str, str2 + PsuedoNames.PSEUDONAME_ROOT + str);

                        MfLogUtils.e(Constant.METHOD_DEBUG, "将assets压缩文件复制到data目录");

                        if (new File(str2 + PsuedoNames.PSEUDONAME_ROOT + str).exists()) {

                            MfLogUtils.e(Constant.METHOD_DEBUG, "data文件存在:" + str2 + PsuedoNames.PSEUDONAME_ROOT + str);

                        }

                        int[] iArr = null;

                        Enumeration<? extends ZipEntry> entries = new ZipFile(str2 + PsuedoNames.PSEUDONAME_ROOT + str).entries();

                        while (true) {

                            if (!entries.hasMoreElements()) {

                              break;

                            }

                            ZipEntry nextElement = entries.nextElement();

                            MfLogUtils.e(Constant.METHOD_DEBUG, "遍历到文件:" + nextElement.getName());

                            String comment = nextElement.getComment();

                            if (comment != null && !comment.equals("")) {

                              MfLogUtils.e(Constant.METHOD_DEBUG, "查询到密钥:" + comment);

                              JSONArray jSONArray = new JSONArray(comment);

                              int[] iArr2 = new int;

                              for (int i3 = 0; i3 < jSONArray.length(); i3++) {

                                    iArr2 = ((Integer) jSONArray.get(i3)).intValue();

                              }

                              iArr = iArr2;

                            }

                        }

                        MfLogUtils.e(Constant.METHOD_DEBUG, "已经成功获取密钥了:" + iArr.toString());

                        ZipFileUtils.unZip(str2 + PsuedoNames.PSEUDONAME_ROOT + str, str2);

                        MfLogUtils.e(Constant.METHOD_DEBUG, "解压文件");

                        for (File file : new File(str2).listFiles()) {

                            MfLogUtils.e(Constant.METHOD_DEBUG, "解压生成文件:" + file.getAbsolutePath());

                            if (file.getName().contains(".so")) {

                              desfile(file.getAbsolutePath(), iArr);

                              MfLogUtils.e(Constant.METHOD_DEBUG, "解密单个文件:" + file.getAbsolutePath());

                            }

                        }

                        MfLogUtils.e(Constant.METHOD_DEBUG, "解密文件完成");

                  }

第五步:分析代码可知,这个就是将hmc后缀的zip文件解压,然后获取so文件的comment内容,作为key,比较简单所以直接在java中实现key的获取和so解密
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.*;

public class solution {

    public static byte[] encDesSimple(byte[] bArr, int[] iArr) {
      int length = bArr.length;
      for (int i = 0; i < length; i++) {
            if (bArr >= 0) {
                bArr = (byte) iArr];
            }
      }
      return bArr;
    }


    public static void writeFileBytes(File file, byte[] bArr) {
      try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(bArr);
            fileOutputStream.flush();
            fileOutputStream.close();
      } catch (Exception e) {
            e.printStackTrace();
      }
    }

   public static byte[] readFileBytes(File file) throws IOException {
      byte[] bArr = new byte;
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      FileInputStream fileInputStream = new FileInputStream(file);
      while (true) {
            int read = fileInputStream.read(bArr);
            if (read != -1) {
                byteArrayOutputStream.write(bArr, 0, read);
            } else {
                fileInputStream.close();
                return byteArrayOutputStream.toByteArray();
            }
      }
    }

    public static void main(String args[])
    {
      try {
            // Create a Zip File
            Enumeration<? extends ZipEntry> entries
                = new ZipFile("test").entries();
                int i = 0;
                String soname = "libkiwi_enc.so";
                while (true) {
                  if (!entries.hasMoreElements()) {
                        break;
                  }
                  ZipEntry nextElement = entries.nextElement();
                  String comment = nextElement.getComment();

                  if (comment != null && !comment.equals("")) {

                  System.out.println("comment = "
                  + comment);
                  i++;
                  }
                  System.out.println("count = "
                  + i);
                }
            // Display the comment
            // of the zip file
            // using getComment() function

            int [] iArr = {73,76,74,98,77,115,124,103,68,99,75,104,85,126,105,83,117,93,112,121,96,102,111,114,66,94,81,125,127,113,90,123,84,88,65,107,87,116,91,120,118,106,101,110,69,78,122,82,89,70,100,72,67,64,80,97,119,109,92,108,79,95,86,71,53,34,24,52,8,44,49,63,51,0,2,10,1,4,45,60,54,26,47,15,32,12,62,36,33,48,30,38,58,17,25,61,20,55,3,9,50,42,21,7,11,14,41,35,59,57,43,22,18,29,23,5,37,16,40,56,39,19,46,31,6,27,13,28};
            writeFileBytes(new File(soname.replace("_enc", "")), encDesSimple(readFileBytes(new File(soname)), iArr));

      }
      catch (Exception e) {
            System.out.println(e.getMessage());
      }
    }
}


第六步:将解密后的示例so文件拖入ida中,可以正常解析,至此第一关算是结束,通过进一步查看代码发现他们还会动态的更新so文件做一些事情,但是更新的域名链接无法访问。
小结:黑产的app一般都是不考虑性能的,所以这种解压的方式在正经的app上应该是不存在的,另外这个app经过向反zha举报了很久,还是依旧活得很好,只能呵呵。后续会继续看so的加固,估计还会有更多的加固方式吧,毕竟不用考虑性能,仅当做新人的学习。
页: [1]
查看完整版本: 某黑产app的so文件无法找到的分析