某东东的jdgs算法分析--适合进阶学习

2024-09-04 10:28

本文主要是介绍某东东的jdgs算法分析--适合进阶学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

某东东的jdgs算法分析

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


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


算法分析结构划分

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


java调用部分

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

unidbg模拟执行jdgs流程

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

 

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

1

2

3

4

5

6

7

8

9

10

11

12

13

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

问题三:

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//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;

});

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

 

 

jdgs算法分析

  首先看下jdgs的结果

1

2

3

4

5

6

7

8

{

"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、b7(时间戳,分析过程通过hook固定),
需要分析b4、b5、b6,其实实际走完算法,主要是考验你对标准算法的熟悉程度(ida脚本Findcrypt),因为并没有出现魔改算法,自定义算法也没混淆,难度不大,但详细写篇幅有点大了,适合新手进阶,所以我说下算法具体实现,就不参照ida和unidbg调试过程手摸手复现;

分析前固定时间戳

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

 然后通过unidbg的HookZz实现固定

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 固定时间戳 修改获取毫秒时间戳系统函数返回值十六进制

        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);

b4

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

1

2

3

4

5

6

7

8

# 压缩算法

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)

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

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

# 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

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

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

# 寄存器格式为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

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

1

2

3

4

def fun_base64(self, comp_json):

    ret = base64.b64encode(comp_json)

    return ret

b4 = comp_json.decode('utf-8')

b5

首先对b1进行计算

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

self.b1 = ".jdg.xbt文件名"  # jpg文件名 版本固定

# xbt字节加密iv 版本固定(需要判断下)

self.xbt_eny = "5A 36 58 38 65 66 74 42 4E 6D 53 35 56 4B 6F 47 71 53 2F 71 34 70 44 53 36 76 72 32 53 4B 76 74 34 61 31 49 65 61 37 67 6A 54 35 52 64 32 4C 2F 65 39 76 78 4D 6D 74 69 78 58 57 75 72 75 2B 68"

    # 这个函数xbt & body 字节加密

    def fun_body_eny(self, comm_body, enc):

        # print("准备加密body:",comm_body)

        logger.debug("准备加密body:{}".format(comm_body))

        comm_arr = bytearray(comm_body, 'utf-8')

        enc_arr = self.jdgstools.tool_str2bytearr(enc)

        comm_len = len(comm_arr)

        enc_len = len(enc_arr)

        buf = bytearray(comm_arr)

        i = 0

        if comm_len <= enc_len:

            while True:

                v14 = self.jdgstools.tool_range0xff(enc_arr[i])

                v13 = self.jdgstools.tool_range0xff(i // comm_len)

                var = self.jdgstools.tool_range0xff(i - v13 * comm_len)

                v15 = self.jdgstools.tool_range0xff(comm_arr[var])

                i += 1

                buf[var] = self.jdgstools.tool_range0xff(v14 ^ v15)

                # print(f"i:{hex(i)},v14:{hex(v14)},v15:{hex(v15)},v14 ^ v15:{hex(v14 ^ v15)}")

                if i == enc_len:

                    break

        else:

            while True:

                v11 = self.jdgstools.tool_range0xff(comm_arr[i])

                var = self.jdgstools.tool_range0xff(i - (i // enc_len) * enc_len)

                v12 = self.jdgstools.tool_range0xff(enc_arr[var])

                buf[i] = self.jdgstools.tool_range0xff(v11 ^ v12)

                i += 1

                if i == comm_len:

                    break

        return buf

self.body_eny = self.fun_body_eny(self.b1, self.xbt_eny)

    

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

1

2

# comm_body加密

body_eny = self.fun_body_eny(body, self.body_eny)

然后对body_eny 进行md5得到md5_text

1

2

3

4

5

6

# md5算法

def fun_md5(self, buf):

    var_mad5 = hashlib.md5()

    var_mad5.update(buf.encode("utf-8"))

    return var_mad5.hexdigest()

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

1

2

3

4

5

6

7

8

9

def fun_aes(self, plaintext, key, iv):

    # 对明文进行填充,使其长度为16的倍数

    padded_plaintext = pad(plaintext, AES.block_size)

    # 创建AES的CBC模式对象

    cipher = AES.new(key, AES.MODE_CBC, iv)

    # 加密 bytes

    ciphertext = cipher.encrypt(padded_plaintext)

    return ciphertext

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

1

2

3

4

5

6

# sha1加密

def fun_sha1(self, commbody_aes):

    sha1 = hashlib.sha1()

    sha1.update(commbody_aes)

    # print(sha1.hexdigest())

    return sha1.hexdigest()

结果就是b5

b6

b6的参数是拼接值

1

# b6_data = '{"b1":"{}","b2":"{}","b3":"{}","b4":"{}","b5":"{}","b7":"{}","b6":"{}"}' % b1,b2,b3,b4,b5,b7

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

结尾

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

 

这篇关于某东东的jdgs算法分析--适合进阶学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1135756

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

如何选择适合孤独症兄妹的学校?

在探索适合孤独症儿童教育的道路上,每一位家长都面临着前所未有的挑战与抉择。当这份责任落在拥有孤独症兄妹的家庭肩上时,选择一所能够同时满足两个孩子特殊需求的学校,更显得尤为关键。本文将探讨如何为这样的家庭做出明智的选择,并介绍星贝育园自闭症儿童寄宿制学校作为一个值得考虑的选项。 理解孤独症儿童的独特性 孤独症,这一复杂的神经发育障碍,影响着儿童的社交互动、沟通能力以及行为模式。对于拥有孤独症兄

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置