首页
社区
课程
招聘
[原创]某东东的jdgs算法分析--适合进阶学习
发表于: 2024-8-5 15:26 20417

[原创]某东东的jdgs算法分析--适合进阶学习

2024-8-5 15:26
20417

  这个贴主要还是对算法本身结构部分描述会多点,憋问,问就是过去太久了,很多逆向过程不一定能还原(主要是懒,不想原路再走一遍),所以可能有部分跳跃的内容,会给具体代码,但对应的偏移地址和具体信息没有,给大家一个锻炼自己的机会 ( •_•)

  继续观看前申明:本文涉及的内容是为给大家提供适合大家巩固基础及进阶更高的技术,别做不好的事情哦。

1、查找java调用jdgs算法位置,frida主动调用获取参数;
2、unidbg模拟算法so执行;
3、枯燥的边调边复现算法;

  这部分直接参考其他佬的,挺详细的[原创]某东 APP 逆向分析+ Unidbg 算法模拟

  jdgs算法的unidbg模拟执行上面链接里的结果出现以下情况问题解决:
执行报错

图片描述
图片描述
  看到java层和so层都对i2出现了不同参数对应不同功能的分支就要打起精神了,需要判断在走i2=101主体算法获取jdgs结果之前是否有走其他流程,明显是的,它执行了i2=103的init初始化部分,你在分析java层调用jdgs native算法的时候会看到init部分,so层分析时也能看到i2值会走不同的分支;
所以需要在unidbg里提前执行一步init:

  jdgs算法过程会调用具体的两个资源文件,位置在解压文件夹assets里,后缀是.jdg.jpg和.jdg.xbt,通过unidbg自带的HookZz框架将这两个本地文件先入内存再写入到寄存器里(这部分我不贴代码了,新手可以练练手);

  这个问题就是需要你手动去利用unidbg调试算法过程,去查看控制台报红日志代码位置点在哪,追溯为什么会走这个报红日志,去手动修改这些点,这里我就直接贴代码给大家:

  这些问题主要还是动手能力的体现,就算天赋异禀,也要老老实实的动手;
  最终会看到满意的结果:
图片描述

  首先看下jdgs的结果

  通过重复抓包和执行,确定固定值b1(.jdg.xbt文件名)、b2、b3、b7(时间戳,分析过程通过hook固定),
需要分析b4、b5、b6,其实实际走完算法,主要是考验你对标准算法的熟悉程度(ida脚本Findcrypt),因为并没有出现魔改算法,自定义算法也没混淆,难度不大,但详细写篇幅有点大了,适合新手进阶,所以我说下算法具体实现,就不参照ida和unidbg调试过程手摸手复现;

  经验之谈,分析算法过程,时间戳一般都是在算法中主要变动参数之一,为了减小分析影响,我们可以选择固定时间戳值:
  so直接搜索获取时间戳的常见函数名进行回溯找到时间戳生成位置:
图片描述
  然后通过unidbg的HookZz实现固定

首先传参拼接一串json:e1:参数三eid,e2:参数二finger和一些常量,e3:时间戳
这一串json会进行压缩操作,返回值:comp_json

接下来是获取一块0x100自定义加密数据:buf_sf_0x100

然后将buf_sf_0x100和comp_json进行处理获得新的:comp_json

最后对comp_json进行base64即可获得b4

首先对b1进行计算

然后对参数一的comm_body也进行同样处理,

然后对body_eny 进行md5得到md5_text

然后通过Findcrpy知道md5_text要进行aes加密得到aes_text,key和iv是内存值,不难找

接下来是sha1加密算法,明文comm_body+" "+aes_text

结果就是b5

b6的参数是拼接值

然后和b5相同的算法结构,得到b6

  这个算法过程其实非常适合新手进阶,并没有混淆和魔改,但遇到的问题都非常典型,文章的本身也是抱着锻炼的想法写的,不喜勿喷,希望大家可以互相交流,一起进步。

// jdgs初始化
public void doInit()
{
    //init
    System.out.println("=== init begin ====");
    Object[] init_param = new Object[1];
    Integer init = 103;
    DvmObject<?> ret = module.callStaticJniMethodObject(
         emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
         init,
         ProxyDvmObject.createObject(vm, init_param));
    System.out.println("=== init end ====");
}
// jdgs初始化
public void doInit()
{
    //init
    System.out.println("=== init begin ====");
    Object[] init_param = new Object[1];
    Integer init = 103;
    DvmObject<?> ret = module.callStaticJniMethodObject(
         emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
         init,
         ProxyDvmObject.createObject(vm, init_param));
    System.out.println("=== init end ====");
}
//patch C0 53 00 B5, 将反转条件跳转CBZ-->CBNZ  会报:[main]E/JDG: [make_header:491] [-] Error pp not init
byte[] CBNZ = new byte[]{(byte) 0xC0, (byte)0x53, (byte)0x00, (byte)0xB5};
UnidbgPointer p = UnidbgPointer.pointer(emulator,dm.getModule().base + 地址);
p.write(CBNZ);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address) -> {
    System.out.println("====== patch 反转条件跳转CBZ-->CBNZ ======");
    return true;
});
 
//干掉一个free  (这个会影响结果) 会报:[main]E/JDG: [make_header:491] [-] Error pp not init
byte[] NOP = new byte[]{(byte)0xC1F, (byte)0xC20, (byte)0xC03, (byte)0xD5};
UnidbgPointer p = UnidbgPointer.pointer(emulator,dm.getModule().base + 地址 );
p.write(NOP);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address) -> {
    System.out.println("======= 干掉一个free =======");
    return true;
});
//patch C0 53 00 B5, 将反转条件跳转CBZ-->CBNZ  会报:[main]E/JDG: [make_header:491] [-] Error pp not init
byte[] CBNZ = new byte[]{(byte) 0xC0, (byte)0x53, (byte)0x00, (byte)0xB5};
UnidbgPointer p = UnidbgPointer.pointer(emulator,dm.getModule().base + 地址);
p.write(CBNZ);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address) -> {
    System.out.println("====== patch 反转条件跳转CBZ-->CBNZ ======");
    return true;
});
 
//干掉一个free  (这个会影响结果) 会报:[main]E/JDG: [make_header:491] [-] Error pp not init
byte[] NOP = new byte[]{(byte)0xC1F, (byte)0xC20, (byte)0xC03, (byte)0xD5};
UnidbgPointer p = UnidbgPointer.pointer(emulator,dm.getModule().base + 地址 );
p.write(NOP);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address) -> {
    System.out.println("======= 干掉一个free =======");
    return true;
});
{
"b1":".jdg.xbt文件名",
"b2":"***",
"b3":"***",
"b4":"yY8lbpaUOZeQ3fyCiccRrM66O+Nzo/mhwP4wIa8C8JOZ6aJgSdfTJl2a6Q4oeMBx+2P4ySmoN/AtDHutJNGd/lImZaXQkwd00ZyfFGn2PmTk4uorMcnQUrKbmPRHlcKx6iOwmt8RoYf9C7l7bGWQ/COl6HcUT199wCWGjI5+u4mxfvLmiCSqhJ8qbLgVx9KQrRLXW1oDY1sf1RdNl1cYe6GfpF8kwgNMQJif9EIUBw0Td64cduT7MKAFjA3oew02IyWX2aSJaOuWaULTUqO4al9SIyRYojxQCEiMzF5UMxV6Zwu2lw1uZ6+22fJgxbEBv2LeGUpPPzXGF6E2vC0vb9sE5in3CkrKHwM+QfA5CasSPwpAmzQyr5iGyl9o6g==",
"b5":"7e640fcb8293d390b3758974b75e9dad5082bed9",
"b7":"1724176633106",
"b6":"30ed898f8d129b6d16c3f0c49efae07e8de4ee0e"}
{
"b1":".jdg.xbt文件名",
"b2":"***",
"b3":"***",
"b4":"yY8lbpaUOZeQ3fyCiccRrM66O+Nzo/mhwP4wIa8C8JOZ6aJgSdfTJl2a6Q4oeMBx+2P4ySmoN/AtDHutJNGd/lImZaXQkwd00ZyfFGn2PmTk4uorMcnQUrKbmPRHlcKx6iOwmt8RoYf9C7l7bGWQ/COl6HcUT199wCWGjI5+u4mxfvLmiCSqhJ8qbLgVx9KQrRLXW1oDY1sf1RdNl1cYe6GfpF8kwgNMQJif9EIUBw0Td64cduT7MKAFjA3oew02IyWX2aSJaOuWaULTUqO4al9SIyRYojxQCEiMzF5UMxV6Zwu2lw1uZ6+22fJgxbEBv2LeGUpPPzXGF6E2vC0vb9sE5in3CkrKHwM+QfA5CasSPwpAmzQyr5iGyl9o6g==",
"b5":"7e640fcb8293d390b3758974b75e9dad5082bed9",
"b7":"1724176633106",
"b6":"30ed898f8d129b6d16c3f0c49efae07e8de4ee0e"}
// 固定时间戳 修改获取毫秒时间戳系统函数返回值十六进制
        hook.replace(dm.getModule().base + 地址, new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                return super.onCall(emulator, originFunction);
            }
 
            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println("\n=========== HooZz 修改固定时间戳 =========\n");
                return super.onCall(emulator, context, originFunction);
            }
 
            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                long a = (long) emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0);
                System.out.println("修改前时间戳:"+Long.toHexString(a));
                emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W0,0x18f70ef8d12L);
                System.out.println("修改后时间戳:"+ Long.toString(0x18f70ef8d12L,16));
            }
        },true);
// 固定时间戳 修改获取毫秒时间戳系统函数返回值十六进制
        hook.replace(dm.getModule().base + 地址, new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                return super.onCall(emulator, originFunction);
            }
 
            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println("\n=========== HooZz 修改固定时间戳 =========\n");
                return super.onCall(emulator, context, originFunction);
            }
 
            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                long a = (long) emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0);
                System.out.println("修改前时间戳:"+Long.toHexString(a));
                emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W0,0x18f70ef8d12L);
                System.out.println("修改后时间戳:"+ Long.toString(0x18f70ef8d12L,16));
            }
        },true);
# 压缩算法
def fun_compress(self, json):
    # json_len=len(json)
    # # 使用compressBound计算压缩后的最大可能字节数
    # comp_bound = zlib.compressBound(json_len)
    # 使用compress方法压缩数据
    comp_data = zlib.compress(json.encode('utf-8'))
    return bytearray(comp_data)
# 压缩算法
def fun_compress(self, json):
    # json_len=len(json)
    # # 使用compressBound计算压缩后的最大可能字节数
    # comp_bound = zlib.compressBound(json_len)
    # 使用compress方法压缩数据
    comp_data = zlib.compress(json.encode('utf-8'))
    return bytearray(comp_data)
# salt = 时间戳+一段0x28固定值
def fun_sf(self, salt):
    salt = bytearray(salt, "utf-8")
    # 使用列表推导式创建一个从0到255的整数列表
    int_list = [i for i in range(256)]
    # 将整数列表转换为 bytearray
    ret_arr = bytearray(int_list)  # X0
 
    var2 = 0  # W11
 
    salt_len = len(salt)  # W10
    for i in range(0x100):
        # print(f"{i:02x}")
        salt_chunk = int(i / salt_len)  # W13                       SDIV            W13, W10, W2
        ret_i = ret_arr[i]  # W12                                   LDRB            W12, [X0,X10]
        salt_chunk = i - salt_chunk * salt_len  # W13               MSUB            W13, W13, W2, W10
        salt_chunk = salt[salt_chunk]  # W13                        LDRB            W13, [X1,W13,UXTW]
        var2 = ret_i + var2  # W11                                  ADD             W11, W12, W11
        var2 = var2 + salt_chunk  # W11                             ADD             W11, W11, W13
 
        salt_chunk = var2 & 0xff  # X13                             AND             X13, X11, #0xFF
        ret_arr[i] = ret_arr[salt_chunk]  # W14                     LDRB            W14, [X0,X13]
        #                                                           W13   STRB            W14, [X0,X10]
        ret_arr[salt_chunk] = ret_i  # W12                          STRB            W12, [X0,X13]
 
    return ret_arr
# salt = 时间戳+一段0x28固定值
def fun_sf(self, salt):
    salt = bytearray(salt, "utf-8")
    # 使用列表推导式创建一个从0到255的整数列表
    int_list = [i for i in range(256)]
    # 将整数列表转换为 bytearray
    ret_arr = bytearray(int_list)  # X0
 
    var2 = 0  # W11
 
    salt_len = len(salt)  # W10
    for i in range(0x100):
        # print(f"{i:02x}")
        salt_chunk = int(i / salt_len)  # W13                       SDIV            W13, W10, W2
        ret_i = ret_arr[i]  # W12                                   LDRB            W12, [X0,X10]
        salt_chunk = i - salt_chunk * salt_len  # W13               MSUB            W13, W13, W2, W10
        salt_chunk = salt[salt_chunk]  # W13                        LDRB            W13, [X1,W13,UXTW]
        var2 = ret_i + var2  # W11                                  ADD             W11, W12, W11
        var2 = var2 + salt_chunk  # W11                             ADD             W11, W11, W13
 
        salt_chunk = var2 & 0xff  # X13                             AND             X13, X11, #0xFF
        ret_arr[i] = ret_arr[salt_chunk]  # W14                     LDRB            W14, [X0,X13]
        #                                                           W13   STRB            W14, [X0,X10]
        ret_arr[salt_chunk] = ret_i  # W12                          STRB            W12, [X0,X13]
 
    return ret_arr
# 寄存器格式为dword格式
def tool_range0xff(self, var):
       return var & 0xff
def fun_xor(self, buf_sf, comp_json):
       buf_sf.append(0# 扩容到0x102
       buf_sf.append(0# 扩容到0x102
       self.jdgstools.tool_bytearray2str(buf_sf)
       comp_json_len = len(comp_json)
       i = 0
       # try:
       while True:
           comp_json_len -= 1  # SUBS            X10, X10, #1            ; X0=X0-1=--len
           buf_0x100 = self.jdgstools.tool_range0xff(
               buf_sf[0x100])  # LDRB            W11, [X0,#0x100]        ; W11=X0[0x100]=buf[0x100]
           buf_0x101 = self.jdgstools.tool_range0xff(
               buf_sf[0x101])  # LDRB            W12, [X0,#0x101]        ; W12=buf_0x101 =X0[0x101]=*(buf + 0x101);
 
           buf_0x100_i = self.jdgstools.tool_range0xff(
               buf_0x100 + 1# ADD             W11, W11, #1            ; W11=W11+1=buf[0x100]+1
 
           buf_sf[0x100] = buf_0x100_i  # STRB            W11, [X0,#0x100]        ; X0[0x100]=W11
 
           buf_0x100_i = buf_0x100_i & 0xff  # AND             X11, X11, #0xFF         ; X11=W11&0xff
           buf_var = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x100_i])  # LDRB            W13, [X0,X11]           ; W13=X0[X11]
           buf_0x101 = buf_0x101 + buf_var  # ADD             W12, W12, W13           ; W12=W12+W13
 
           buf_sf[0x101] = self.jdgstools.tool_range0xff(
               buf_0x101)  # STRB            W12, [X0,#0x101]        ; X0[0x101]=W12
           buf_0x101 = buf_0x101 & 0xff  # AND             X12, X12, #0xFF         ; X12=X12&0xff
 
           buf_var = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x101])  # LDRB            W13, [X0,X12]           ; W13=X0[X12]
           var = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x100_i])  # LDRB            W14, [X0,X11]           ; W14=X0[X11]
           buf_sf[buf_0x100_i] = buf_var  # STRB            W13, [X0,X11]           ; X0[X11]=W13
           buf_sf[buf_0x101] = var  # STRB            W14, [X0,X12]           ; X0[X12]=W14
           buf_0x100_i = self.jdgstools.tool_range0xff(
               buf_sf[0x100])  # LDRB            W11, [X0,#0x100]        ; W11=X0[0x100]
           buf_0x101 = self.jdgstools.tool_range0xff(
               buf_sf[0x101])  # LDRB            W12, [X0,#0x101]        ; W12=X0[0x101]
           buf_0x100_i = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x100_i])  # LDRB            W11, [X0,X11]           ; W11=X0[X11]
           buf_0x101 = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x101])  # LDRB            W12, [X0,X12]           ; W12=X0[X12]
 
           var_comp_json = self.jdgstools.tool_range0xff(
               comp_json[i])  # LDRB            W13, [X1],#1            ; W13=*X1+1
           # i += 1
 
           buf_0x100_i = buf_0x101 + buf_0x100_i  # ADD             W11, W12, W11           ; W11=W12+W11
           buf_0x100_i = buf_0x100_i & 0xFF  # AND             X11, X11, #0xFF         ; X11=X11&0xFF
 
           buf_0x100_i = self.jdgstools.tool_range0xff(
               buf_sf[buf_0x100_i])  # LDRB            W11, [X0,X11]           ; W11=X0[X11]
           buf_0x100_i = buf_0x100_i ^ var_comp_json  # EOR             W11, W11, W13           ; W11=W11^W13
 
           comp_json[i] = buf_0x100_i  # STRB            W11, [X2],#1            ; *X2+1=W11
 
           i += 1
           # print(f"i:{hex(i)}  {hex(buf_0x100_i)}")
           # comp_json[i] = buf_sf[buf_sf[buf_sf[0x101]] + buf_sf[buf_sf[0x100]]] ^ var_comp_json
 
           if comp_json_len == 0:
               break
       # except:
       #     print("error i:",i)
 
       return comp_json
# 寄存器格式为dword格式
def tool_range0xff(self, var):
       return var & 0xff
def fun_xor(self, buf_sf, comp_json):
       buf_sf.append(0# 扩容到0x102
       buf_sf.append(0# 扩容到0x102
       self.jdgstools.tool_bytearray2str(buf_sf)
       comp_json_len = len(comp_json)
       i = 0
       # try:
       while True:
           comp_json_len -= 1  # SUBS            X10, X10, #1            ; X0=X0-1=--len
           buf_0x100 = self.jdgstools.tool_range0xff(
               buf_sf[0x100])  # LDRB            W11, [X0,#0x100]        ; W11=X0[0x100]=buf[0x100]
           buf_0x101 = self.jdgstools.tool_range0xff(
               buf_sf[0x101])  # LDRB            W12, [X0,#0x101]        ; W12=buf_0x101 =X0[0x101]=*(buf + 0x101);
 
           buf_0x100_i = self.jdgstools.tool_range0xff(
               buf_0x100 + 1# ADD             W11, W11, #1            ; W11=W11+1=buf[0x100]+1
 
           buf_sf[0x100] = buf_0x100_i  # STRB            W11, [X0,#0x100]        ; X0[0x100]=W11

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (15)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-8-5 16:02
0
雪    币: 664
活跃值: (1983)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
算法流程mtsign有九分相似
2024-8-5 17:20
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
大佬 key和iv 能再详细指导下吗
2024-8-16 17:19
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
大佬想要完整的unidbg代码
2024-8-17 18:24
0
雪    币: 2719
活跃值: (1574)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
b4 那里不是一个rc4 吗 你干嘛要自己写一个
2024-8-30 15:57
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
楼主,有人转你文章没标记出处噢。https://blog.csdn.net/wei_java144/article/details/141869200
2024-9-11 19:54
1
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
大佬,想要完整的unidbg代码,有偿
2024-9-13 11:03
0
雪    币: 728
活跃值: (1005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
b5,b6那里算法有问题,不应该是aes,这是错误分支,正确的是sha256后两次处理再拼接url后sha1,sha256位置是vmp,大厂核心算法都是vmp,你这没涉及到vmp
2024-9-14 12:18
1
雪    币: 1352
活跃值: (2055)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
杨如画 b5,b6那里算法有问题,不应该是aes,这是错误分支,正确的是sha256后两次处理再拼接url后sha1,sha256位置是vmp,大厂核心算法都是vmp,你这没涉及到vmp
Sha256也没魔改
2024-9-15 03:34
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
大佬,我直接定位到了java层jni接口的main函数,第一步除了101、103还有102,104,是怎么分析出103是初始化的呢,就直接看它引用位置写到代码里的明文日志吗
2024-9-19 21:35
0
雪    币: 728
活跃值: (1005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
菜勾 大佬,我直接定位到了java层jni接口的main函数,第一步除了101、103还有102,104,是怎么分析出103是初始化的呢,就直接看它引用位置写到代码里的明文日志吗[em_78]
frida hook init再 call fun
2024-9-19 23:04
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
杨如画 frida hook init再 call fun
感谢,我研究研究
2024-10-8 11:45
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
mb_fqwtuctk 楼主,有人转你文章没标记出处噢。https://blog.csdn.net/wei_java144/article/details/141869200
可能就是本人
9小时前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
杨如画 frida hook init再 call fun
如有可能,希望大佬也出一遍,这篇很多问题,比如APP版本号没有,初始化实际操作也不少问题,无法初始化,其实重点就是初始化,楼主关键点都没说明,对小白用户参考意义不大,大佬又不需要参考
9小时前
0
雪    币: 728
活跃值: (1005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16

发过,不过已经被删了

最后于 7小时前 被杨如画编辑 ,原因:
7小时前
0
游客
登录 | 注册 方可回帖
返回
//