Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行

跟着龙哥学 SO逆向入门实战教程一:OASIS

前言

  1. 功力不及龙哥百分之一文笔也是实力也是, 仅作为个人记录学习过程。
  2. 龙哥博客传送门

1. 需解密对象

  1. “sign” 字段
  2. 跟着龙哥学的,忘了查壳用jadx打开之后发现文件有点少查壳之后发现是x60加固。掉以轻心了…

Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行_第1张图片

2. 脱壳

  1. 查壳

Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行_第2张图片

  1. 脱壳 直接上dump_dex.js

3. 定位到 java 关键函数

  • 重新压缩成zip用jadx 再次打开 搜索 “sign”
  • 去掉一些加载sdk的包名是个剩下框框之内,心急的朋友可能开始一个一个去点,考虑到开发者习惯不可能一个一个设置变量所以大概率一般都是map,所以基本就是那个混淆了的包名每个点进去看一下

Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行_第3张图片

  • 找到了关键函数 来hook一下(因为这里是看到龙哥教程做的所以这里肯定是sign的java成接口,但是还是要假装hook一下确定)

Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行_第4张图片

  • 全部 hook代码直接看龙哥博客,懒得截图csdn传图太蠢了
/* 
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    frida -UF -l Hook.js -o hook.log
    call_java()
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
*/
function hook_java() {
    Java.perform(function () {
        var NativeApi = Java.use('com.weibo.xvideo.NativeApi');
        // 使用系统工具类将byte数组转成hex、utf8.
        var ByteString = Java.use("com.android.okhttp.okio.ByteString");
        NativeApi.s.implementation = function (str1, str2) {
            /*  这样也可以 
                var StringCls = Java.use("java.lang.String");
                var stringVal = StringCls.$new(str1, "utf-8");
                console.log("NativeApi s param:", stringVal, str2); 
            */
            var result = this.s(str1, str2);
            console.log("NativeApi str:" + ByteString.of(str1).utf8())
            // console.log("hex:" + ByteString.of(str1).hex())
            console.log("NativeApi result:" + result);
            return result;
        }
    });
}

function call_java() {
    var securityUtil = null;
    Java.perform(function () {
        Java.choose("com.weibo.xvideo.NativeApi", {
            //枚举时调用
            onMatch: function (instance) {
                //打印实例
                securityUtil = instance;
                console.log("find instance")
            },
            //枚举完成后调用
            onComplete: function () {
                console.log("end")
            }
        });
        var javaString = Java.use('java.lang.String');
        var str = "aid=01A5bEg1VpHUmdB92RsAS7tsf-wTU6Eg41MEUWUYgsMDG6rq8.&cfrom=28B5295010&cuid=7529403746&filter_quick_replay=0&filter_rainbow=0&noncestr=0x3pp5rIbBIH8R1U7h67o675rh7522&platform=ANDROID×tamp=1628603374728&ua=Xiaomi-Mi8__oasis__3.5.8__Android__Android8.1.0&version=3.5.8&vid=2004301948574&wm=20004_90024";
        var input1 = javaString.$new(str).getBytes();
        var result = securityUtil.s(input1, false);
        console.log(result);
    })
}
  • 打印结果 主动调用一次发现结果不会变动 剩下的看
--------------------
com.sina.oasis 绿洲
start...
end...
--------------------
NativeApi str:aid=01A5bEg1VpHUmdB92RsAS7tsf-wTU6Eg41MEUWUYgsMDG6rq8.&cfrom=28B5295010&cuid=7529403746&filter_quick_replay=0&filter_rainbow=0&noncestr=0x3pp5rIbBIH8R1U7h67o675rh7522&platform=ANDROID&timestamp=1628603374728&ua=Xiaomi-Mi8__oasis__3.5.8__Android__Android8.1.0&version=3.5.8&vid=2004301948574&wm=20004_90024
NativeApi result:fe93d8397edb90c4d84f2248370cb82a
--------------------
find instance
end
fe93d8397edb90c4d84f2248370cb82a
--------------------
  • Frida Hook_RegisterNatives 找到动态函数注册地址
[RegisterNatives] javaClass: com.weibo.xvideo.NativeApi name: s sig: ([BZ)Ljava/lang/String; fnPtr: 0x7d3d0696cc soName: liboasiscore.so soBase: 0x7d3d058000 funcOffset: 0x116cc
[RegisterNatives] javaClass: com.weibo.xvideo.NativeApi name: e sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x7d3d069af4 soName: liboasiscore.so soBase: 0x7d3d058000 funcOffset: 0x11af4
[RegisterNatives] javaClass: com.weibo.xvideo.NativeApi name: d sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x7d3d069cd8 soName: liboasiscore.so soBase: 0x7d3d058000 funcOffset: 0x11cd8
[RegisterNatives] javaClass: com.weibo.xvideo.NativeApi name: dg sig: (Ljava/lang/String;Z)Ljava/lang/String; fnPtr: 0x7d3d06a20c soName: liboasiscore.so soBase: 0x7d3d058000 funcOffset: 0x1220c
  • 这里发现offset 是 0x116cc 龙哥的例子是0xc365 想到应该是我的设备是armv8的 用IDA分别打开32位和64位跳到地址发现果然都是JNI_OnLoad
  • 因为有很多只支持v7的ida脚本或者其他工具在v8上没做适配,比如
findHash (v0.1) plugin has been loaded.
这个脚本只考虑了32位SO的反编译代码,64位未适配。
  • 我想在v8的cpu上跑v7的so , 那怎么解决这个问题呢 直接问龙哥!龙哥了一个 “搜adb install 指定arm32”,有时候就差这临门一脚龙哥yyds
adb install -r --abi armeabi-v7a E:\Download\1.apk
  • 最后贴一下unidbg armv8跑的结果 和上面的结果一模一样的
RegisterNative(com/weibo/xvideo/NativeApi, s([BZ)Ljava/lang/String;, RX@0x400116cc[liboasiscore.so]0x116cc)
RegisterNative(com/weibo/xvideo/NativeApi, e(Ljava/lang/String;)Ljava/lang/String;, RX@0x40011af4[liboasiscore.so]0x11af4)
RegisterNative(com/weibo/xvideo/NativeApi, d(Ljava/lang/String;)Ljava/lang/String;, RX@0x40011cd8[liboasiscore.so]0x11cd8)
RegisterNative(com/weibo/xvideo/NativeApi, dg(Ljava/lang/String;Z)Ljava/lang/String;, RX@0x4001220c[liboasiscore.so]0x1220c)
JNIEnv->GetArrayLength([B@b97c004 => 305) was called from RX@0x4001174c[liboasiscore.so]0x1174c
JNIEnv->NewStringUTF("fe93d8397edb90c4d84f2248370cb82a") was called from RX@0x4001191c[liboasiscore.so]0x1191c
fe93d8397edb90c4d84f2248370cb82a

4. unidbg模拟执行

  • 上面既然得到了地址是 0x116cc , 我们直接callFunction地址就可以拿到结果,因为是入门demo所以也不用补环境,很适合新手入门 !!!
package com.sina.oasis;

// 导入通用且标准的类库

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

// 继承AbstractJni类
public class oasis64 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    oasis64() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.sina.oasis").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/com.sina.oasis/com.sina.oasis.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/com.sina.oasis/arm64-v8a/liboasiscore.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志

        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    }


    public static void main(String[] args) {
        oasis64 test = new oasis64();
        System.out.println(test.getS());
    }

    public String getS() {
        // args list
        List<Object> list = new ArrayList<>(10);
        // arg1 env
        list.add(vm.getJNIEnv());
        // arg2 jobject/jclazz 一般用不到,直接填0
        list.add(0);
        // arg3 bytes
        // result:fe93d8397edb90c4d84f2248370cb82a
        String input = "aid=01A5bEg1VpHUmdB92RsAS7tsf-wTU6Eg41MEUWUYgsMDG6rq8." +
                "&cfrom=28B5295010" +
                "&cuid=7529403746" +
                "&filter_quick_replay=0" +
                "&filter_rainbow=0" +
                "&noncestr=0x3pp5rIbBIH8R1U7h67o675rh7522" +
                "&platform=ANDROID" +
                "×tamp=1628603374728" +
                "&ua=Xiaomi-Mi8__oasis__3.5.8__Android__Android8.1.0" +
                "&version=3.5.8" +
                "&vid=2004301948574" +
                "&wm=20004_90024";
      /*  String input = "aid=01A-khBWIm48A079Pz_DMW6PyZR8" +
                "uyTumcCNm4e8awxyC2ANU.&cfrom=28B529501" +
                "0&cuid=5999578300&noncestr=46274W9279Hr1" +
                "X49A5X058z7ZVz024&platform=ANDROID×tamp" +
                "=1621437643609&ua=Xiaomi-MIX2S__oasis__3.5.8_" +
                "_Android__Android10&version=3.5.8&vid=10190135" +
                "94003&wm=20004_90024";*/
        byte[] inputByte = input.getBytes(StandardCharsets.UTF_8);
        ByteArray inputByteArray = new ByteArray(vm, inputByte);
        list.add(vm.addLocalObject(inputByteArray));
        // arg4 ,boolean false 填入0
        list.add(0);
        // 参数准备完成
        // call function
        Number number = module.callFunction(emulator, 0x116cc, list.toArray())[0];
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }
}


5. 算法分析

  • 因为这是个简单的样本,输出又是32位,很容易就让人联想到哈希算法,掏出FindHash跑一下。
The initial autoanalysis has been finished.
findHash (v0.1) plugin has been loaded.
***************************在二进制文件中检索hash算法常量************************************
0x2a322:padding used in hashing algorithms (0x80 0 ... 0)
0x6131:函数sub_6130疑似哈希函数,包含初始化魔数的代码。
0x8975:函数sub_8974疑似哈希函数,包含初始化魔数的代码。
0x1225d:函数sub_1225C疑似哈希函数运算部分。
0x1935d:函数sub_1935C疑似哈希函数运算部分。
0x1f465:函数sub_1F464疑似哈希函数运算部分。
0x22091:函数sub_22090疑似哈希函数运算部分。
***************************存在以下可疑的字符串************************************
0x295e7:/Volumes/Android/buildbot/src/android/ndk-release-r17/external/libcxx/../../external/libcxxabi/src/abort_message.cpp
0x29b6c:/Volumes/Android/buildbot/src/android/ndk-release-r17/external/libcxx/../../external/libunwind_llvm/src/Unwind-EHABI.cpp
0x29cc8:/Volumes/Android/buildbot/src/android/ndk-release-r17/external/libcxx/../../external/libunwind_llvm/src/UnwindCursor.hpp
0x29d4d:/Volumes/Android/buildbot/src/android/ndk-release-r17/external/libcxx/../../external/libunwind_llvm/src/Registers.hpp
生成对应的hook脚本如下:
frida -UF -l C:\Users\zhoumi\Desktop\demo1\liboasiscore_findhash_1628655879.js
***********************************************************************************
花费 120.12736535072327 秒,因为会对全部函数反编译,所以比较耗时间哈
  • 运行FindHash提示的脚本 主动调用call_java() 拿到结果
函数sub_8974疑似哈希函数,包含初始化魔数的代码。
0xc4e9a461 liboasiscore.so!0xc461
  • 搜 sub_8974 定位到 sub_8AB2
function hook_md5_update() {
    var soAddr = Module.findBaseAddress("liboasiscore.so");
    //thumb
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    var relativePtr = 0x8AB2 + 1;
    var funcPtr = soAddr.add(relativePtr);
    Interceptor.attach(funcPtr, {
        onEnter: function (args) {
            // console.log("args onEnter args[0]: ", ptr(args[0])); // 内存 地址
            console.log("args onEnter args[1]: \n", ptr(args[1]).readCString()); // 内存 地址
            console.log("args onEnter args[1]: \n", hexdump(args[1], { length: args[2].toInt32() }));    // 16 进制 ASCII码;
            console.log("args onEnter args[2]: \n" + args[2].toInt32());
            this.args0 = args[0];
        },
        onLeave: function (retval) {
            // console.log("retval onLeave retval: ", (retval)); // 内存 地址
            console.log("retval onLeave this.args0: \n", hexdump(this.args0, { length: 20 }));
        }
    });
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
}
  • 再次主动调用拿到结果
(fridaMi10) D:\Project\AndroidSecurity\FridaHook>frida -UF -l Hook.js -o hook.log
     ____
    / _  |   Frida 14.2.18 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
--------------------
com.sina.oasis 绿洲
start...
end...
--------------------
[Mi 8::绿洲]-> call_java();
find instance
end
args onEnter args[1]:
YP1Vty&$Xm*kJkoR,Opk&aid=01A5bEg1VpHUmdB92RsAS7tsf-wTU6Eg41MEUWUYgsMDG6rq8.&cfrom=28B5295010&cuid=7529403746&filter_quick_replay=0&filter_rainbow=0&noncestr=0x3pp5rIbBIH8R1U7h67o675rh7522&platform=ANDROID&timestamp=1628603374728&ua=Xiaomi-Mi8__oasis__3.5.8__Android__Android8.1.0&version=3.5.8&vid=2004301948574&wm=20004_90024
args onEnter args[1]:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
ddba4a00  59 50 31 56 74 79 26 24 58 6d 2a 6b 4a 6b 6f 52  YP1Vty&$Xm*kJkoR
ddba4a10  2c 4f 70 6b 26 61 69 64 3d 30 31 41 35 62 45 67  ,Opk&aid=01A5bEg
ddba4a20  31 56 70 48 55 6d 64 42 39 32 52 73 41 53 37 74  1VpHUmdB92RsAS7t
ddba4a30  73 66 2d 77 54 55 36 45 67 34 31 4d 45 55 57 55  sf-wTU6Eg41MEUWU
ddba4a40  59 67 73 4d 44 47 36 72 71 38 2e 26 63 66 72 6f  YgsMDG6rq8.&cfro
ddba4a50  6d 3d 32 38 42 35 32 39 35 30 31 30 26 63 75 69  m=28B5295010&cui
ddba4a60  64 3d 37 35 32 39 34 30 33 37 34 36 26 66 69 6c  d=7529403746&fil
ddba4a70  74 65 72 5f 71 75 69 63 6b 5f 72 65 70 6c 61 79  ter_quick_replay
ddba4a80  3d 30 26 66 69 6c 74 65 72 5f 72 61 69 6e 62 6f  =0&filter_rainbo
ddba4a90  77 3d 30 26 6e 6f 6e 63 65 73 74 72 3d 30 78 33  w=0&noncestr=0x3
ddba4aa0  70 70 35 72 49 62 42 49 48 38 52 31 55 37 68 36  pp5rIbBIH8R1U7h6
ddba4ab0  37 6f 36 37 35 72 68 37 35 32 32 26 70 6c 61 74  7o675rh7522&plat
ddba4ac0  66 6f 72 6d 3d 41 4e 44 52 4f 49 44 26 74 69 6d  form=ANDROID&tim
ddba4ad0  65 73 74 61 6d 70 3d 31 36 32 38 36 30 33 33 37  estamp=162860337
ddba4ae0  34 37 32 38 26 75 61 3d 58 69 61 6f 6d 69 2d 4d  4728&ua=Xiaomi-M
ddba4af0  69 38 5f 5f 6f 61 73 69 73 5f 5f 33 2e 35 2e 38  i8__oasis__3.5.8
ddba4b00  5f 5f 41 6e 64 72 6f 69 64 5f 5f 41 6e 64 72 6f  __Android__Andro
ddba4b10  69 64 38 2e 31 2e 30 26 76 65 72 73 69 6f 6e 3d  id8.1.0&version=
ddba4b20  33 2e 35 2e 38 26 76 69 64 3d 32 30 30 34 33 30  3.5.8&vid=200430
ddba4b30  31 39 34 38 35 37 34 26 77 6d 3d 32 30 30 30 34  1948574&wm=20004
ddba4b40  5f 39 30 30 32 34                                _90024
args onEnter args[2]:
326
retval onLeave this.args0:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
bff0297c  00 00 00 00 9c 94 4e 9d db 99 fc d2 0c 71 30 68  ......N......q0h
bff0298c  19 e1 d2 03                                      ....
args onEnter args[1]:
 �
args onEnter args[1]:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
c4eb8322  80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
c4eb8332  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
c4eb8342  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
c4eb8352  00 00                                            ..
args onEnter args[2]:
50
retval onLeave this.args0:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
bff0297c  00 00 00 00 9c 94 4e 9d db 99 fc d2 0c 71 30 68  ......N......q0h
bff0298c  19 e1 d2 03                                      ....
args onEnter args[1]:
 0

args onEnter args[1]:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
bff028f8  30 0a 00 00 00 00 00 00                          0.......
args onEnter args[2]:
8
retval onLeave this.args0:
            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
bff0297c  00 00 00 00 fe 93 d8 39 7e db 90 c4 d8 4f 22 48  .......9~....O"H
bff0298c  37 0c b8 2a                                      7..*
result fe93d8397edb90c4d84f2248370cb82a
  • MD5 Update一共被调用了三次,需要注意的是,MD5的Update的后两次调用,都是数据的填充,属于算法内部细节,所以我们只用关注第一次的输出。

  • 我们的明文是从aid开始的,前面多了一块,这一块每次运行都不变,所以猜测它是盐,使用逆向之友Cyberchef 测试一下:

Android逆向 某州 解密sign字段 so层 算法分析 Unidbg模拟执行_第5张图片

  • 大功告成!

6. 尾声

  • 假装是自己分析的,除了开头的脱壳和找sign的java层hook点其他的直接看龙哥的代码就行了,文章只能算笔记跟着大佬学unidbg并不能算做教程,教程贴直接看龙哥帖!

你可能感兴趣的:(安卓逆向)