第一步: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@[email]webview@webview.apk[/email]@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[i2].endsWith("_hmc")) {
MfLogUtils.e(Constant.METHOD_DEBUG, "找到hmc文件");
String str = list[i2];
String replace = list[i2].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[jSONArray.length()];
for (int i3 = 0; i3 < jSONArray.length(); i3++) {
iArr2[i3] = ((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[i] >= 0) {
bArr[i] = (byte) iArr[bArr[i]];
}
}
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[1024];
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的加固,估计还会有更多的加固方式吧,毕竟不用考虑性能,仅当做新人的学习。 |