某手app逆向分析

抓包&定位

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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 结果如下:

1
2
3
4
5
6
7
[->] 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 这个方法就行了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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 到的数据如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[->] 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

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

随便补一下就出值了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@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 文件都不一样,就另起一个文件开始写了,先搭梯子吧

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

直接调用

1
2
3
4
5
6
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 获取即可

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

直接补上

1
2
3
4
5
6
7
8
9
@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
直接补

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

获取进程名
image.png

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

1
2
3
4
5
6
7
8
9
@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);
}

继续报错

1
2
3
4
5
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)  

直接补空对象

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

继续报错

1
2
3
4
5
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)

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@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);
}

继续

1
2
3
4
5
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)

直接补即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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