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

news2024/11/15 18:35:34

某东东的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

结尾

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络安全】IDOR之敏感数据泄露

未经许可,不得转载。 文章目录 正文正文 在测试“添加到收藏夹”功能时,我拦截了发送到服务器的请求,请求体如下: {“uriTemplate”:“asset/{assetId}/favorite”,“version”:“v2”,“type”:“POST”,“req_service”:“pict”,“url”:“asset/VICTIMS_ASS…

【论文阅读】DivTheft: An Ensemble Model Stealing Attack by Divide-and-Conquer(2023)

摘要 Recently, model stealing attacks(模型窃取攻击) are widely studied(广泛研究) but most of them are focused on stealing a single non-discrete model(窃取单个非离散模型), e.g., neural networks(神经网络). For ensemble models(对于集成模型), these …

分钟快速搭建分班查询系统,支持查班级群二维码

新学期已经开始了&#xff0c;老师们又要忙活起来了。但是&#xff0c;别担心&#xff0c;现在有个超方便的工具&#xff0c;能帮大家快速搞定分班的事情&#xff0c;还能让家长们一键加入班级群&#xff0c;省时又省力&#xff01; 以前分班可麻烦了&#xff0c;老师们得一个…

集群的调度和策略

集群的调度&#xff1a; 怎么把pod部署到节点的方法。 调度的过程&#xff1a; scheduler是集群的调度器&#xff0c;主要任务就是把pod部署到节点上。 自动调度&#xff1a; 1、公平&#xff0c;保证每个可用的节点都可以部署pod 2、资源的高效利用&#xff0c;集群当中…

C/C++内存详解

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 C/C内存模型C语言动态内存管理mallocrealloccallocfree C动态内存申请new 操作符delete 操作符注意事项用法示例 operator new和operator delete函数内存泄露 C/C内存模型 让我们先来看看这段代码&a…

四、SPI——2、NOR FLASH

一、NOR FLASH介绍 FLASH是常用的用于储存数据的半导体器件&#xff0c;它具有容量大&#xff0c;可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。 FLASH是有一个物理特性&#xff1a;只能写0&#xff0c;不能写1&#xff0c;写1靠擦除。 FLASH主要有NOR Flash和…

【JavaEE初阶】JVM内存划分和类加载过程以及垃圾回收

目录 &#x1f332;内存划分 &#x1f6a9;堆&#xff08;线程共享&#xff09; &#x1f6a9;栈 &#x1f6a9;元数据区 &#x1f343;类加载过程 &#x1f6a9;双亲委派模型 &#x1f384;垃圾回收机制&#xff08;GC&#xff09; &#x1f6a9;找到谁是垃圾(不被继续…

纷享销客CRM渠道分销之多维度数据分析介绍

预设渠道报表驾驶舱 基于渠道分销场景&#xff0c;系统预设了一个全面的渠道订货数据驾驶舱&#xff0c;旨在通过直观的数据分析&#xff0c;为企业提供深度的市场洞察和业务决策支持。该驾驶舱提供渠道订货的概览&#xff0c;快速把握整体订货动态。 渠道订货波动分析&#…

Scratch 角色绘制

引言 在Scratch这款强大的可视化编程环境中&#xff0c;不仅可以通过编程来实现各种有趣的互动项目&#xff0c;还能利用内置的绘图编辑器来创造独一无二的角色。本文将引导你如何使用Scratch中的绘图编辑器&#xff0c;绘制出属于你自己的简单图形角色。 准备工作 首先&#…

【数据分享】2000—2023年我国250米分辨率逐月植被覆盖度(FVC)栅格数据

植被覆盖度&#xff08;Fractional Vegetation Cover&#xff0c;简称FVC&#xff09;是指植被&#xff08;包括叶、枝、茎&#xff09;在水平地面的垂直投影面积占研究区总面积的百分比。植被覆盖度是生态学、地理学、气候学等多个学科研究的基础数据&#xff0c;对于理解生态…

多线程——创建

线程的创建与启动 Java中&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。 三种创建方式&#xff1a; 集成Thread类创建线程类 继承Thread类&#xff0c;重写run方法&#xff0c;run方法的方法体代表线程需要完成的任务&#xff0c;称为线程执行体。 创建子类的实…

鸿蒙开发5.0【Code Linter实现代码检查】

Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 检查方法&#xff1a; 编辑器自带Code Linter。 在已打开的代码编辑器窗口单击右键点击Code Linter&#xff0c;或在工程管理窗口中鼠标选中单个或多个工程文件/目录&#xff0c;右键选择Code Linter执行代码检…

2024年医疗器械企业5款CRM系统对比评测

医疗器械行业是一个多学科交叉、知识密集型、资金密集型的高新技术产业&#xff0c;进入门槛较高&#xff0c;产品种类繁多&#xff0c;技术含量较高。 随着医改的深入推进&#xff0c;医疗器械集采常态化成为行业新常态&#xff0c;中国的医疗器械行业不仅面临着巨大的市场潜…

zabbix6.4配置监控k8s 1.28集群

zabbix6.4配置监控rke2 rancher k8s集群 1. 说明1.1 为什么要使用zabbix6.x监控k8s1.2 部署环境1.3 部署前的一些问题 2. 使用helm3部署zabbix proxy和zabbix agent2.1 添加仓库2.2 修改配置2.3 部署2.4 确认部署情况 3. 在zabbix web页面配置连接zabbix proxy3.1 添加Proxy代理…

kali (linux) 配置windows远程桌面(mstsc.exe)连接

Kali 安装 tightvncserver 一、软件说明 1) tightvncserver是一个轻量级&#xff0c;只能建立桌面&#xff0c;不能查看TTY7/TTY1正在显示的桌面,但x11vnc可以&#xff0c;相比x11vnc安全传输差一些。反之&#xff0c;x11 vnc:安全传输较好&#xff0c;但占用资源比 tightvncs…

PB级内存计算项目实战-富华保险

一、保险项目的基本介绍 项目名称:富华阳光人寿保险 1. 行业背景介绍 在保险行业中,最为核心技术就是精算,精算简单来说就是根据人的年龄来计算应交保费问题,通过精算,让整个保险行业更加专业化,精细化 从而取代之间依靠经验判断的方式 精算到目前为止,并不仅仅计算保费,主要包…

Leetcode 1143. 最长公共子序列 记忆化搜索 优化 C++实现

Leetcode 1143. 最长公共子序列 问题&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列&#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对…

一体化智能电动窗帘:开启智能生活新时尚

史新华 在科技不断进步的今天&#xff0c;人们对生活品质的追求也越来越高。电动窗帘作为智能家居的重要组成部分&#xff0c;以其便捷、智能、时尚的特点&#xff0c;正逐渐走进千家万户。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 一、电动窗帘…

KEYSIGHT U2020 X系列 USB峰值和均值功率传感器

​ _是德(KEYSIGHT) _ U2020 X系列 USB峰值和均值功率传感器 苏州新利通仪器仪表 U2020 X 系列功率传感器得到 Keysight BenchVue 软件的支持。使用 BenchVue 软件&#xff0c;您无需编程便可轻松控制功率计记录数据&#xff0c;并以各种形式显示测量结果。 只需将传感器…

AI大模型与量子纠缠理论的结合,以及相关应用思考

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下AI大模型与量子纠缠理论的结合&#xff0c;以及相关应用思考。将大模型&#xff08;LLM&#xff09;的基本原理与量子纠缠理论相结合是一个高度抽象的概念。我们首先需要理解这两个领域的基本原理&#xff0c;然后探…