2328 字
12 分钟
某手app逆向分析

抓包&定位#

首先,这个样本是没有加固的,不用查了,直接抓包吧,进去就要我登录,直接抓登录的包了吧
image.png 可以发现,参数还是挺多的,有 sig, __NS_sig3 这样的参数,当然 __NS_sig3 它是重点需要分析的
打开 jadx-mcp-server 进行分析定位,定位是哪一个 native 函数生成的这两个参数,ai 同志已经直接找出来了 sig 的调用链如下 image.png 最后会落在 getClock 这个 native 方法上面,跳转过去 image.png 复制一下 frida 的 hook 代码,这里用了一下小风魔改的 jadx,看起来还不错啊,省去了一些时间

// Smali signature: getClock(Landroid/content/Context;[BI)Ljava/lang/String;
function hook_mointor_getClock(){
Java.perform(function () {
let com_yxcorp_gifshow_util_CPU = Java.use("com.yxcorp.gifshow.util.CPU");
com_yxcorp_gifshow_util_CPU["getClock"].implementation = function (context, bArr, i4) {
console.log(`[->] com_yxcorp_gifshow_util_CPU.getClock is called! args are as follows:\n ->context= ${context}\n ->bArr= ${bArr}\n ->i4= ${i4}`);
showByteArray(bArr, "bArr");
var retval = this["getClock"](context, bArr, i4);
// showJavaStacks();
console.log(`[<-] com_yxcorp_gifshow_util_CPU.getClock ended! \n retval= ${retval}`);
return retval;
};
});
// 辅助函数1: 打印调用栈
function showJavaStacks() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
};
// 辅助函数2: 打印字节数组
function showByteArray(byteArray, name) {
if (byteArray == null) return;
var result = Java.use('java.lang.String').$new(Java.array('byte', byteArray)).toString();
console.log(name + ": " + result);
}
console.warn(`[*] hook_mointor_getClock is injected!`);
};
hook_mointor_getClock();

hook 结果如下:

Terminal window
[->] com_yxcorp_gifshow_util_CPU.getClock is called! args are as follows:
->context= com.yxcorp.gifshow.App@b388367
->bArr= 97,98,105,61,97,114,109,54,52,97,110,100,114,111,105,100,65,112,105,76,101,118,101,108,61,51,51,97,110,100,114,111,105,100,95,111,115,61,48,97,112,112,61,48,97,112,112,118,101,114,61,49,49,46,52,46,50,48,46,51,48,57,56,52,98,111,97,114,100,80,108,97,116,102,111,114,109,61,103,115,49,48,49,98,111,116,116,111,109,95,110,97,118,105,103,97,116,105,111,110,61,102,97,108,115,101,98,114,111,119,115,101,84,121,112,101,61,52,99,61,86,73,86,79,99,100,105,100,95,116,97,103,61,50,99,108,105,101,110,116,95,107,101,121,61,51,99,50,99,100,51,102,51,99,111,108,100,95,108,97,117,110,99,104,95,116,105,109,101,95,109,115,61,49,55,55,54,55,55,48,49,54,53,53,50,55,99,111,117,110,116,114,121,95,99,111,100,101,61,99,110,99,115,61,102,97,108,115,101,100,97,114,107,77,111,100,101,61,102,97,108,115,101,100,100,112,105,61,52,50,48,100,101,118,105,99,101,66,105,116,61,48,100,101,118,105,99,101,95,97,98,105,61,97,114,109,54,52,100,105,100,61,65,78,68,82,79,73,68,95,53,100,97,53,56,100,55,50,57,54,56,49,102,49,97,99,100,105,100,95,103,116,61,49,55,55,54,54,48,54,55,51,56,56,57,50,100,105,100,95,116,97,103,61,48,101,97,114,112,104,111,110,101,77,111,100,101,61,49,101,103,105,100,61,68,70,80,54,68,55,49,66,69,57,70,69,50,50,51,70,56,50,66,50,57,54,54,55,70,67,69,70,54,65,52,50,70,54,57,70,53,50,49,51,66,53,52,54,54,68,53,53,48,49,65,52,48,67,67,70,68,69,57,51,65,52,55,68,102,116,116,61,103,114,97,110,116,95,98,114,111,119,115,101,95,116,121,112,101,61,65,85,84,72,79,82,73,90,69,68,104,111,116,102,105,120,95,118,101,114,61,105,115,95,98,97,99,107,103,114,111,117,110,100,61,48,105,115,112,61,105,117,105,100,61,107,99,118,61,49,54,49,56,107,101,121,99,111,110,102,105,103,95,115,116,97,116,101,61,50,107,112,102,61,65,78,68,82,79,73,68,95,80,72,79,78,69,107,112,110,61,75,85,65,73,83,72,79,85,108,97,110,103,117,97,103,101,61,122,104,45,99,110,109,97,120,95,109,101,109,111,114,121,61,50,53,54,109,111,98,105,108,101,61,51,115,67,116,51,105,65,65,77,122,65,121,77,106,99,122,78,122,89,121,65,77,56,72,65,79,55,74,116,107,56,105,117,50,102,53,122,104,65,65,65,65,66,48,81,49,76,81,51,117,68,87,67,73,79,89,49,85,71,55,79,90,82,112,109,111,98,105,108,101,67,111,117,110,116,114,121,67,111,100,101,61,43,56,54,109,111,100,61,71,111,111,103,108,101,40,80,105,120,101,108,32,54,41,110,98,104,61,54,51,110,101,116,61,87,73,70,73,110,101,119,79,99,61,86,73,86,79,111,68,105,100,61,65,78,68,82,79,73,68,95,55,50,57,102,56,97,55,100,53,97,49,101,101,51,52,57,111,99,61,86,73,86,79,111,115,61,97,110,100,114,111,105,100,112,85,105,100,61,114,81,65,74,51,97,48,68,57,100,122,122,72,67,51,115,76,122,112,97,73,99,77,109,81,98,102,83,54,95,52,101,51,99,52,55,51,102,53,55,49,102,52,101,97,54,98,112,97,115,115,119,111,114,100,61,98,97,51,50,53,51,56,55,54,97,101,100,54,98,99,50,50,100,52,97,54,102,102,53,51,100,56,52,48,54,99,54,97,100,56,54,52,49,57,53,101,100,49,52,52,97,98,53,99,56,55,54,50,49,98,54,99,50,51,51,98,53,52,56,98,97,101,97,101,54,57,53,54,100,102,51,52,54,101,99,56,99,49,55,102,53,101,97,49,48,102,51,53,101,101,51,99,98,99,53,49,52,55,57,55,101,100,55,100,100,100,51,49,52,53,52,54,52,101,50,97,48,98,97,98,52,49,51,114,97,119,61,49,55,55,54,55,55,48,51,53,52,53,53,54,114,100,105,100,61,65,78,68,82,79,73,68,95,50,52,97,100,97,51,55,53,49,102,52,51,55,102,50,97,115,98,104,61,49,50,56,115,101,99,114,101,116,61,101,90,65,102,120,115,111,113,113,107,119,53,114,79,82,101,112,55,48,99,56,72,116,81,52,66,73,111,82,50,56,120,69,121,72,53,83,86,120,78,98,114,119,77,81,121,116,119,80,72,116,82,119,68,78,106,48,47,55,47,111,51,117,76,110,98,112,107,105,116,43,86,67,121,72,111,107,77,80,103,43,99,82,88,105,66,55,97,102,104,109,88,66,113,55,75,102,70,89,110,116,106,88,50,71,72,74,47,75,106,86,75,103,86,49,102,49,105,67,119,120,121,87,108,71,120,118,72,90,72,88,51,49,86,121,51,75,76,105,70,83,85,117,114,102,54,43,77,100,108,51,74,51,80,98,82,114,70,57,68,114,118,90,54,52,57,107,110,122,100,101,53,57,53,119,78,87,81,75,111,65,77,116,73,72,57,112,110,52,80,117,105,73,119,68,86,55,72,113,103,102,55,120,84,98,80,69,90,88,75,74,66,47,76,100,84,86,108,77,84,117,85,113,109,66,81,57,111,99,83,65,106,47,114,108,66,114,107,50,85,74,56,57,74,106,87,113,70,77,101,78,47,87,108,89,76,67,81,109,78,55,88,57,114,116,103,54,50,118,47,81,87,102,107,43,69,84,99,121,105,105,57,87,119,112,48,122,52,104,119,82,106,106,119,105,107,90,89,84,114,119,52,78,56,105,86,71,53,80,108,120,113,98,56,74,70,99,101,77,55,88,66,72,98,78,97,103,116,80,71,112,84,49,47,81,70,52,121,82,118,118,81,61,61,115,104,61,50,52,48,48,115,108,104,61,48,115,111,99,78,97,109,101,61,85,110,107,110,111,119,110,115,119,61,49,48,56,48,115,121,115,61,65,78,68,82,79,73,68,95,49,51,116,104,101,114,109,97,108,61,49,48,48,48,48,116,111,116,97,108,77,101,109,111,114,121,61,55,54,49,52,117,81,97,84,97,103,61,117,100,61,48,117,115,101,114,82,101,99,111,66,105,116,61,48,118,101,114,61,49,49,46,52,118,105,100,101,111,77,111,100,101,108,67,114,111,119,100,84,97,103,61
->i4= 33
bArr: abi=arm64androidApiLevel=33android_os=0app=0appver=11.4.20.30984boardPlatform=gs101bottom_navigation=falsebrowseType=4c=VIVOcdid_tag=2client_key=3c2cd3f3cold_launch_time_ms=1776770165527country_code=cncs=falsedarkMode=falseddpi=420deviceBit=0device_abi=arm64did=ANDROID_5da58d729681f1acdid_gt=1776606738892did_tag=0earphoneMode=1egid=DFP6D71BE9FE223F82B29667FCEF6A42F69F5213B5466D5501A40CCFDE93A47Dftt=grant_browse_type=AUTHORIZEDhotfix_ver=is_background=0isp=iuid=kcv=1618keyconfig_state=2kpf=ANDROID_PHONEkpn=KUAISHOUlanguage=zh-cnmax_memory=256mobile=3sCt3iAAMzAyMjczNzYyAM8HAO7Jtk8iu2f5zhAAAAB0Q1LQ3uDWCIOY1UG7OZRpmobileCountryCode=+86mod=Google(Pixel 6)nbh=63net=WIFInewOc=VIVOoDid=ANDROID_729f8a7d5a1ee349oc=VIVOos=androidpUid=rQAJ3a0D9dzzHC3sLzpaIcMmQbfS6_4e3c473f571f4ea6bpassword=ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413raw=1776770354556rdid=ANDROID_24ada3751f437f2asbh=128secret=eZAfxsoqqkw5rORep70c8HtQ4BIoR28xEyH5SVxNbrwMQytwPHtRwDNj0/7/o3uLnbpkit+VCyHokMPg+cRXiB7afhmXBq7KfFYntjX2GHJ/KjVKgV1f1iCwxyWlGxvHZHX31Vy3KLiFSUurf6+Mdl3J3PbRrF9DrvZ649knzde595wNWQKoAMtIH9pn4PuiIwDV7Hqgf7xTbPEZXKJB/LdTVlMTuUqmBQ9ocSAj/rlBrk2UJ89JjWqFMeN/WlYLCQmN7X9rtg62v/QWfk+ETcyii9Wwp0z4hwRjjwikZYTrw4N8iVG5Plxqb8JFceM7XBHbNagtPGpT1/QF4yRvvQ==sh=2400slh=0socName=Unknownsw=1080sys=ANDROID_13thermal=10000totalMemory=7614uQaTag=ud=0userRecoBit=0ver=11.4videoModelCrowdTag=
[<-] com_yxcorp_gifshow_util_CPU.getClock ended!
retval= a96759a30a15b808508489a6b9bfe107

第一个参数是 context,可以进行构造,第二个参数是一个字节数组,可以用字符串转,第三个参数是 Android sdk 的版本号,33

接着来看__NS_sig3 这个参数
image.png image.png 直接 hook 一下 doCommandNative 这个方法就行了

// Smali signature: doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;
function hook_mointor_doCommandNative(){
Java.perform(function () {
let com_kuaishou_android_security_internal_dispatch_JNICLibrary = Java.use("com.kuaishou.android.security.internal.dispatch.JNICLibrary");
com_kuaishou_android_security_internal_dispatch_JNICLibrary["doCommandNative"].implementation = function (i4, objArr) {
console.log(`[->] com_kuaishou_android_security_internal_dispatch_JNICLibrary.doCommandNative is called! args are as follows:\n ->i4= ${i4}\n ->objArr= ${objArr}`);
showObjectArray(objArr, "objArr");
var retval = this["doCommandNative"](i4, objArr);
// showJavaStacks();
console.log(`[<-] com_kuaishou_android_security_internal_dispatch_JNICLibrary.doCommandNative ended! \n retval= ${retval}`);
return retval;
};
});
// 辅助函数1: 打印调用栈
function showJavaStacks() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
};
// 辅助函数2: 打印对象数组
function showObjectArray(objArr, name) {
if (objArr == null) return;
var length = objArr.length;
console.log(name + ' length: ' + length);
for (let i = 0; i < length; i++) {
var item = objArr[i];
if (item != null && item.getClass().getName() === "[B") {
var str = Java.use('java.lang.String').$new(Java.array('byte', item)).toString();
console.log(' [' + i + '] = (byte[]) ' + str);
} else {
console.log(' [' + i + '] = ' + (item != null ? item.toString() : 'null'));
}
}
}
console.warn(`[*] hook_mointor_doCommandNative is injected!`);
};
hook_mointor_doCommandNative();

hook 到的数据如下:

Terminal window
[->] com_kuaishou_android_security_internal_dispatch_JNICLibrary.doCommandNative is called! args are as follows:
->i4= 10418
->objArr= [Ljava.lang.String;@d4e8801,d7b7d042-d4f2-4012-be60-d97ff2429c17,-1,false,com.yxcorp.gifshow.App@2e1247,,false,
objArr length: 8
[0] = [Ljava.lang.String;@d4e8801
[1] = d7b7d042-d4f2-4012-be60-d97ff2429c17
[2] = -1
[3] = false
[4] = com.yxcorp.gifshow.App@2e1247
[5] = null
[6] = false
[7] =
[<-] com_kuaishou_android_security_internal_dispatch_JNICLibrary.doCommandNative ended!
retval= 8796e6c586ab978bcfcfcccd8f2f0793fc8833bcd2ded0c6

unidbg 模拟参数#

sig#

害,直接模拟参数的生成的吧,首先来 sig

package com.kuaishou;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.arm.context.RegisterContext;
import java.io.File;
import java.nio.charset.StandardCharsets;
public class Sig extends AbstractJni implements IOResolver {
// ========== 每次新项目只改这里 ==========
// 模拟器位数:so在armeabi-v7a目录下填false,arm64-v8a目录下填true
private static final boolean IS_64BIT = true;
// app包名(随意,一般填真实包名)
private static final String PROCESS_NAME = "com.smile.gifmaker";
// apk路径,不需要apk时填null(填null时LOAD_BY_NAME必须为false)
private static final String APK_PATH = "apks/kuaishou/ks_11.4.apk";
// so加载方式:
// false = 传文件路径(用SO_PATH),不依赖apk,最常用
// true = 传库名(用SO_NAME),unidbg自动去apk内查找,必须提供APK_PATH
private static final boolean LOAD_BY_NAME = true;
private static final String SO_PATH = ""; // LOAD_BY_NAME=false时生效
private static final String SO_NAME = "core"; // LOAD_BY_NAME=true时生效,不带lib前缀和.so后缀
// 是否打印JNI调用细节,调试时改true
private static final boolean VERBOSE = true;
// ========================================
public static AndroidEmulator emulator;
public static Memory memory;
public static VM vm;
public static Module module;
// 1. 构造方法 —— 初始化模拟器
public Sig() {
// 1. 创建模拟器(32/64位由IS_64BIT控制)
emulator = IS_64BIT
? AndroidEmulatorBuilder.for64Bit().setProcessName(PROCESS_NAME).build()
: AndroidEmulatorBuilder.for32Bit().setProcessName(PROCESS_NAME).build();
// 2. 获取内存对象
memory = emulator.getMemory();
// 3. 设置安卓SDK版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));
// 4. 创建虚拟机
vm = APK_PATH != null
? emulator.createDalvikVM(new File(APK_PATH))
: emulator.createDalvikVM();
vm.setJni(this);
vm.setVerbose(VERBOSE);
// 5. 加载so文件(两种方式由LOAD_BY_NAME控制)
DalvikModule dm = LOAD_BY_NAME
? vm.loadLibrary(SO_NAME, false)
: vm.loadLibrary(new File(SO_PATH), false);
// 6. 动态注册才需要执行JNI_OnLoad
dm.callJNI_OnLoad(emulator);
// 7. 获取module对象(后续拿基址、偏移等)
module = dm.getModule();
}
public void get_sig(){
DvmClass CPU = vm.resolveClass("com/yxcorp/gifshow/util/CPU");
DvmObject<?> context = vm.resolveClass(
"com/yxcorp/gifshow/App",vm.resolveClass("android/app/Application",vm.resolveClass("android/content/ContextWrapper",vm.resolveClass("android/content/Context"))
)
).newObject(null);
byte[] data = "abi=arm64androidApiLevel=33android_os=0app=0appver=11.4.20.30984boardPlatform=gs101bottom_navigation=falsebrowseType=4c=VIVOcdid_tag=2client_key=3c2cd3f3cold_launch_time_ms=1776770165527country_code=cncs=falsedarkMode=falseddpi=420deviceBit=0device_abi=arm64did=ANDROID_5da58d729681f1acdid_gt=1776606738892did_tag=0earphoneMode=1egid=DFP6D71BE9FE223F82B29667FCEF6A42F69F5213B5466D5501A40CCFDE93A47Dftt=grant_browse_type=AUTHORIZEDhotfix_ver=is_background=0isp=iuid=kcv=1618keyconfig_state=2kpf=ANDROID_PHONEkpn=KUAISHOUlanguage=zh-cnmax_memory=256mobile=3sCt3iAAMzAyMjczNzYyAM8HAO7Jtk8iu2f5zhAAAAB0Q1LQ3uDWCIOY1UG7OZRpmobileCountryCode=+86mod=Google(Pixel 6)nbh=63net=WIFInewOc=VIVOoDid=ANDROID_729f8a7d5a1ee349oc=VIVOos=androidpUid=rQAJ3a0D9dzzHC3sLzpaIcMmQbfS6_4e3c473f571f4ea6bpassword=ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413raw=1776770354556rdid=ANDROID_24ada3751f437f2asbh=128secret=eZAfxsoqqkw5rORep70c8HtQ4BIoR28xEyH5SVxNbrwMQytwPHtRwDNj0/7/o3uLnbpkit+VCyHokMPg+cRXiB7afhmXBq7KfFYntjX2GHJ/KjVKgV1f1iCwxyWlGxvHZHX31Vy3KLiFSUurf6+Mdl3J3PbRrF9DrvZ649knzde595wNWQKoAMtIH9pn4PuiIwDV7Hqgf7xTbPEZXKJB/LdTVlMTuUqmBQ9ocSAj/rlBrk2UJ89JjWqFMeN/WlYLCQmN7X9rtg62v/QWfk+ETcyii9Wwp0z4hwRjjwikZYTrw4N8iVG5Plxqb8JFceM7XBHbNagtPGpT1/QF4yRvvQ==sh=2400slh=0socName=Unknownsw=1080sys=ANDROID_13thermal=10000totalMemory=7614uQaTag=ud=0userRecoBit=0ver=11.4videoModelCrowdTag="
.getBytes(StandardCharsets.UTF_8);
String ret = CPU.callStaticJniMethodObject(
emulator,
"getClock(Landroid/content/Context;[BI)Ljava/lang/String;",
context,
new ByteArray(vm, data),
33
).getValue().toString();
System.out.println(ret);
}
// 5. main方法 —— 右键直接运行
public static void main(String[] args) {
Sig sig = new Sig();
// sig.get_sig();
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("pathname:" + pathname);
return null;
}
}

随便补一下就出值了

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "com/yxcorp/gifshow/App->getPackageName()Ljava/lang/String;":{
String packageName = vm.getPackageName();
if (packageName != null) {
return new StringObject(vm, packageName);
}
}
case "com/yxcorp/gifshow/App->getPackageManager()Landroid/content/pm/PackageManager;":{
return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

出结果了,能够对的上
image.png

__NS_sig3#

这两个参数的 so 文件都不一样,就另起一个文件开始写了,先搭梯子吧

package com.kuaishou;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.wrapper.DvmBoolean;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class KS extends AbstractJni implements IOResolver {
// ========== 每次新项目只改这里 ==========
// 模拟器位数:so在armeabi-v7a目录下填false,arm64-v8a目录下填true
private static final boolean IS_64BIT = true;
// app包名(随意,一般填真实包名)
private static final String PROCESS_NAME = "com.smile.gifmaker";
// apk路径,不需要apk时填null(填null时LOAD_BY_NAME必须为false)
private static final String APK_PATH = "apks/kuaishou/ks_11.4.apk";
// so加载方式:
// false = 传文件路径(用SO_PATH),不依赖apk,最常用
// true = 传库名(用SO_NAME),unidbg自动去apk内查找,必须提供APK_PATH
private static final boolean LOAD_BY_NAME = true;
private static final String SO_PATH = ""; // LOAD_BY_NAME=false时生效
private static final String SO_NAME = "kwsgmain"; // LOAD_BY_NAME=true时生效,不带lib前缀和.so后缀
// 是否打印JNI调用细节,调试时改true
private static final boolean VERBOSE = true;
// ========================================
public static AndroidEmulator emulator;
public static Memory memory;
public static VM vm;
public static Module module;
public static DvmClass JNICLibrary;
// 1. 构造方法 —— 初始化模拟器
public KS() {
// 1. 创建模拟器(32/64位由IS_64BIT控制)
emulator = IS_64BIT
? AndroidEmulatorBuilder.for64Bit().setProcessName(PROCESS_NAME).build()
: AndroidEmulatorBuilder.for32Bit().setProcessName(PROCESS_NAME).build();
// 2. 获取内存对象
memory = emulator.getMemory();
// 3. 设置安卓SDK版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));
// 4. 创建虚拟机
vm = APK_PATH != null
? emulator.createDalvikVM(new File(APK_PATH))
: emulator.createDalvikVM();
vm.setJni(this);
vm.setVerbose(VERBOSE);
// 5. 加载so文件(两种方式由LOAD_BY_NAME控制)
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = LOAD_BY_NAME
? vm.loadLibrary(SO_NAME, false)
: vm.loadLibrary(new File(SO_PATH), false);
// 6. 动态注册才需要执行JNI_OnLoad
dm.callJNI_OnLoad(emulator);
// 7. 获取module对象(后续拿基址、偏移等)
module = dm.getModule();
JNICLibrary = vm.resolveClass("com.kuaishou.android.security.internal.dispatch.JNICLibrary");
}
public void call_doCommandNative(String text) {
StringObject textObj = new StringObject(vm, text);
StringObject arg1 = new StringObject(vm, "d7b7d042-d4f2-4012-be60-d97ff2429c17");
DvmInteger arg2 = DvmInteger.valueOf(vm, -1);
DvmBoolean arg3 = DvmBoolean.valueOf(vm, false);
DvmObject<?> context = vm.resolveClass("com/yxcorp/gifshow/App").newObject(null); // context
StringObject arg4 = new StringObject(vm, "");
ArrayObject arrObj = new ArrayObject(new ArrayObject(textObj), arg1, arg2, arg3, context, null, arg3, arg4);
String retulst = JNICLibrary.callStaticJniMethodObject(emulator,"doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;",10418,arrObj).getValue().toString();
System.out.println(retulst);
}
public void traceCode() {
String traceFile = "unidbg-android/src/test/java/com/kuaishou/traceCode.log";
PrintStream traceStream = null; // 打印流
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
// traceCode 对代码进行监控
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
}
// 5. main方法 —— 右键直接运行
public static void main(String[] args) {
KS ks = new KS();
// kuaiShou.traceCode();
ks.call_doCommandNative("0xnonce");
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("pathname:" + pathname);
return null;
}
}

但是,直接运行的话出现问题了
image.png 这个一般是还有初始化方法未调用的问题,开始以为会调用其他 native 函数,结果 hook 无果之后,发现每次在调用 10418 这个传参之前都会调用 10412 这个传参
image.png

直接调用

public void call_init() {
StringObject appkey = new StringObject(vm, "d7b7d042-d4f2-4012-be60-d97ff2429c17");
DvmObject<?> context = vm.resolveClass("com/yxcorp/gifshow/App").newObject(null); // context
ArrayObject arg2 = new ArrayObject(null, appkey, null, null, context, null, null);
JNICLibrary.callStaticJniMethodObject(emulator,"doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;",10412,arg2);
}

发现缺环境了
image.png 这个是在获取 apk 安装的路径,直接 adb 获取即可

C:\Users\p1913>adb shell pm path com.smile.gifmaker
package:/data/app/~~tFzou1DIESBRZ1LFDyaPvw==/com.smile.gifmaker-6HJLIKArWgIclnWEZ_zuzA==/base.apk

直接补上

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "com/yxcorp/gifshow/App->getPackageCodePath()Ljava/lang/String;":{
return new StringObject(vm,"/data/app/~~tFzou1DIESBRZ1LFDyaPvw==/com.smile.gifmaker-6HJLIKArWgIclnWEZ_zuzA==/base.apk");
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

获取包名
image.png 直接补

case "com/yxcorp/gifshow/App->getPackageName()Ljava/lang/String;": {
return new StringObject(vm, "com.smile.gifmaker");
}

获取进程名
image.png

我在一开始就已经补了进程名的,直接复制过来

@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "com/kuaishou/android/security/internal/common/ExceptionProxy->getProcessName(Landroid/content/Context;)Ljava/lang/String;": {
return new StringObject(vm, PROCESS_NAME);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

继续报错

Terminal window
java.lang.UnsupportedOperationException: com/yxcorp/gifshow/App->getAssets()Landroid/content/res/AssetManager;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
at com.kuaishou.KS.callObjectMethodV(KS.java:143)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

直接补空对象

case "com/yxcorp/gifshow/App->getAssets()Landroid/content/res/AssetManager;":{
return vm.resolveClass("android/content/res/AssetManager").newObject(null);
}

继续报错

Terminal window
java.lang.UnsupportedOperationException: com/kuaishou/android/security/internal/common/ExceptionProxy->nativeReport(ILjava/lang/String;)V
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticVoidMethodV(AbstractJni.java:708)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticVoidMethodV(AbstractJni.java:703)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticVoidMethodV(DvmMethod.java:204)
at com.github.unidbg.linux.android.dvm.DalvikVM64$139.hook(DalvikVM64.java:2280)

这是一个异常上报方法,直接忽略

@Override
public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "com/kuaishou/android/security/internal/common/ExceptionProxy->nativeReport(ILjava/lang/String;)V":{
int code = vaList.getIntArg(0);
String message = vaList.getObjectArg(1).getValue().toString();
System.out.println("code=" + code + " message=" + message);
return;
}
}
super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
}

继续

Terminal window
java.lang.UnsupportedOperationException: java/lang/Boolean->booleanValue()Z
at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)
at com.github.unidbg.linux.android.dvm.DalvikVM64$35.handle(DalvikVM64.java:637)

直接补即可

@Override
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "java/lang/Boolean->booleanValue()Z":{
Boolean value = (Boolean)dvmObject.getValue();
return value != null && value;
}
}
return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
}

然后就出结果了

image.png 好像是之前分析某音的时候将时间戳给固定了,那这儿就不用操心了,结果值也是固定的了,下面来分析算法

算法分析#

sig#

先跳转到 0x34dc,然后让 ai 扫了一下这块,发现就是一个加了盐的 md5 算法
image.png 但是,如果我遇到这种标准的 md5,我每次都需要去到 ida 中进行分析吗?md5 算法的特征都是固定的,那能不能提取出来,写一个 unidbg 插件,在每次运行的时候,就直接识别到这些特征,将明文,有加盐的话盐值,结果值都给打印出来呢?这样的话分析起来更自动化一些
image.png 有了 ai 之后,马上就实现了一个,可以看到,它的盐值是 382700b563f4,去 cyberchef 中进行验证一下
image.png 也确实跟识别到的效果一样了

__NS_sig3#

某手app逆向分析
https://819229.xyz/posts/某手app逆向分析/
作者
0xnonce
发布于
2026-04-23
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...
0/2000

    最后更新于 ,距今已过 129

    部分内容可能已过时