对某旅行APP的检测以及参数计算分析【新手向-Simplesign篇】

简要概述:

目标so:scmain.so
讨论的生成过程:SimpleSign
使用工具:IDA pro 7.7、 Binary Ninja、Frida、Frida Stalker
本篇文章实现: SimpleSign的计算过程,包括前、中、后、变换四个主体阶段,文章中会详细介绍。

正文

1. 起手准备

上篇文章中,我们定位到了SimpleSign函数所在的地址偏移,所以我们根据offset去IDA定位其反汇编的代码,先观察其展示出来的东西是否满足我们的推倒过程。
SimpleSign的native函数偏移为0x7D4B4

 

结果很明显,代码做了混淆,但是其中我们可以发现一些反射调用的特征,GetByteArrayElements,GetArrayLength,GetStringUTFChars等,因为我们在JNI Native中知道SS函数传入的参数是一个字节数组和一个字符串,所以我们推断出此处跟我们要找的函数入口有关联。我们看一下sub_7d4b4的网状结构 

因为本文是新手向,我们就介绍一些简单点、通俗易懂的方法来分析(难的我也不会)

2. Trace - Frida Stalker

关于Stalker我在上一篇中已经介绍过了,包括对msaoaidsec.so的anti操作。我们直接跳到使用。

关于Stalker的起始位置,以及长度,这一块需要我们自己去试,调整长度,因为很多时候有一些汇编指令的地址并不在我们trace的范围之内,会造成指令流trace的log记录不到的情况存在。
另外,记得要对Java native函数也hook上,方便我们对传入的参数有更直观的展示以及返回值的分析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Java.perform(function () {

    let SecurityUtil = Java.use("ctrip.android.security.SecurityUtil");

    SecurityUtil["simpleSign"].implementation = function (bArr, str) {

        console.log('simpleSign is called' ', ' 'bArr: ' + bArr + ', ' 'str: ' + str);

        let ret = this.simpleSign(bArr, str);

        console.log('simpleSign ret value is ' + ret);

        return ret;

    };

    SecurityUtil["init"].overload('android.content.Context''int').implementation = function (context, i2) {

        console.log(`SecurityUtil.init is called: context=${context}, i2=${i2}`);

        this["init"](context, i2);

    };

});

关于trace,有几点要讲:

  1. 我们对msaoaidsec已经进行了anti操作,但是并不影响其有一些其他的检测手段,会造成进程被kill
  2. 我测试了几个版本的frida,貌似16.1.0可以完整trace下来,我有点记不清了
  3. 魔改frida,这个是另一个范畴了,暂时不表,后续会对其检测能力做更深的剖析。

Frida Stalker trace 的过程时间其实是比较长的,日志大概是60MB左右,90万行左右,其中有一些在MD5算法的部分漏掉了,我没有重新跑,范围大概锁定在这个区间内,给大家一个参考。
展示下trace的结果。

 

以上就是一个几乎完整的SimpleSign的计算过程。下面我们开始着手分析

3. 分析前32位

3.1 设想

起初,我认为结果这75位的字符串应该是MD5 + 某些特征 + MD5组成的,可是通过Frida Hook Native函数发现,前32位几乎是不变的。第41-43位也是几乎不变的。那么我假设,此部分的构成是由一个特征(32位) + 每次都会变化的特征(8位) + 不变的(3位) + 疑似MD5(32位)组成的。

 

可以看到图中我做了标识,E0AA是由d2538处的汇编代码执行了异或运算,我们试着在IDA中去d2538处观察其计算逻辑。 

 

猜测v17的值应该就是我们的0x45304141了,推测sub_D1DB4的参数a2是用来存放前32位的地址,我们验证一下v5的记过是否是5-8位,鼠标放在v5处,Tab切换到汇编代码,根据其地址在trace日志中搜索。

1

2

3

libscmain.so d23dc      ldr x16, [sp, #0x50] ;   x16 = 0x7190e94e3d --> 0x7189db0948   (1e0f3c2d5a4b7869)

libscmain.so d23e0      ldr w18, [x16, #4] ;     x18 = 0x7 --> 0x64326333

libscmain.so d23e4      eor w15, w15, w18 ;      x15 = 0x57765407 --0x33443734

0x33443734恰恰就是第5-8位,那么我们几乎就确定了这个函数就是我们要找的前32位生成的位置,但是此方法中只有4个变量来存放结果,但是我们在trace日志中所搜该地址发现结果是2个,那么我们可以假设此方法执行了两次,两次的执行结果相加正好是32位
 


至此,我们确定了此函数的作用,以及参数a2的功能,那么下一步我们要确认如下几点:

  1. a1、a3参数
  2. 此函数的调用过程是怎样的。

对于调用过程,可以参考IDA 的X键,查看交叉引用,但是如果存在过多的调用情况排查起来其实略麻烦,配合trace日志能更方便的节省一点时间,但是也有可能存在跳转指令是处在花指令的范围内,如果这样的话那根据日志排查起来就略微有一点点麻烦。还有就是可以用frida打印调用栈,这个方法略微有一些看脸。
碰碰运气

1

2

3

4

5

6

7

8

9

10

11

12

libscmain.so d5f2c      ldp x0, x30, [sp], #0x10 ;   x0 = 0xc4 --> 0x71edd1f2f0   (���*�g�Iԥxt.��)   sp = 0x7189db0860 --> 0x7189db0870

libscmain.so d5f30      ldur x0, [x29, #-8] ; 

libscmain.so d5f34      ldr x1, [sp, #0x10] ; 

libscmain.so d5f38      ldr x2, [sp, #8] ; 

libscmain.so d5f3c      bl #0x7190d1adb4 ; 

libscmain.so d1db4      stp x0, x30, [sp, #-0x10]! ;     sp = 0x7189db0870 --> 0x7189db0860   (����q)

libscmain.so d1db8      ldr w0, #0x7190d1adc0 ;      x0 = 0x71edd1f2f0 --> 0xd1

libscmain.so d1dbc      bl #0x7190d1ae60 ; 

libscmain.so d1e60      sub x0, x0, #0x11 ;      x0 = 0xd1 --> 0xc0

libscmain.so d1e64      eor x0, x0, #0xc0 ;      x0 = 0xc0 --> 0x0   (null)

libscmain.so d1e68      add x0, x0, #1 ;     x0 = 0x0 --> 0x1

libscmain.so d1e6c      ldr w0, [x30, x0, sxtx #2] ;     x0 = 0x1 --> 0xb8

查看trace日志发现,D1DB4方法调用的上方代码块有可能是正常的代码,根据地址d5f38去IDA中查看

继续向上找

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

libscmain.so ed09c      ldr w0, #0x7190d360a4 ;      x0 = 0x71edd1f2f0 --> 0xec

libscmain.so ed0a0      bl #0x7190d35dec ; 

libscmain.so ecdec      bl #0x7190d35ecc ; 

libscmain.so ececc      eor x0, x0, #0xc0 ;      x0 = 0xec --> 0x2c

libscmain.so eced0      lsr x0, x0, #0 ; 

libscmain.so eced4      add x0, x0, #1 ;     x0 = 0x2c --> 0x2d

libscmain.so eced8      ldr w0, [x30, x0, sxtx #2] ;     x0 = 0x2d --> 0x748

libscmain.so ecedc      add x30, x30, x0 ; 

libscmain.so ecee0      ret ; 

libscmain.so ed538      ldp x0, x30, [sp], #0x10 ;   x0 = 0x748 --> 0x71edd1f2f0   (���*�g�Iԥxt.��)  sp = 0x7189db0890 --> 0x7189db08a0

libscmain.so ed53c      mov w3, wzr ;    x3 = 0x7190e94d78 --0x0   (null)

libscmain.so ed540      bl #0x7190d1ee5c ; 

libscmain.so d5e5c      stp x0, x30, [sp, #-0x10]! ;     sp = 0x7189db08a0 --> 0x7189db0890   (����q)

libscmain.so d5e60      ldr w0, #0x7190d1ee68 ;      x0 = 0x71edd1f2f0 --> 0x536

libscmain.so d5e64      bl #0x7190d1ee80 ; 

libscmain.so d5e80      sub x0, x0, #0x3a ;      x0 = 0x536 --> 0x4fc

libscmain.so d5e84      eor x0, x0, #0xfc ;      x0 = 0x4fc --> 0x400

libscmain.so d5e88      lsr x0, x0, #0xa ;   x0 = 0x400 --> 0x1

试试ecedc

 

定位到了sub_ECDE4,我们继续向上走,根据ecde4在日志中查找上层

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

libscmain.so efcf8      eor x0, x0, #0xe0 ;      x0 = 0xf6 --> 0x16

libscmain.so efcfc      sub x0, x0, #0x10 ;      x0 = 0x16 --> 0x6

libscmain.so efd00      add x0, x0, #1 ;     x0 = 0x6 --> 0x7

libscmain.so efd04      ldr w0, [x30, x0, sxtx #2] ;     x0 = 0x7 --> 0x1e0

libscmain.so efd08      add x30, x30, x0 ; 

libscmain.so efd0c      ret ; 

libscmain.so efe78      ldp x0, x30, [sp], #0x10 ;   x0 = 0x1e0 --> 0x71edc30020   (TracerPid)    sp = 0x7189db0a00 --> 0x7189db0a10

libscmain.so efe7c      adrp x0, #0x7190eb7000 ;     x0 = 0x71edc30020 --> 0x7190eb7000

libscmain.so efe80      add x0, x0, #0x1bc ;     x0 = 0x7190eb7000 --> 0x7190eb71bc   (ed756e23400710596bbd71988670248va4c8db2ae867a149d4a578742e90ec)

libscmain.so efe84      sub x1, x29, #0x10 ;     x1 = 0x20 --> 0x7189db0a80   ( )

libscmain.so efe88      bl #0x7190d35de4 ; 

libscmain.so ecde4      stp x0, x30, [sp, #-0x10]! ;     sp = 0x7189db0a10 --> 0x7189db0a00   (�q��q)

libscmain.so ecde8      ldr w0, #0x7190d35df0 ;      x0 = 0x7190eb71bc --> 0xd4

libscmain.so ecdec      bl #0x7190d35ecc ; 

libscmain.so ececc      eor x0, x0, #0xc0 ;      x0 = 0xd4 --> 0x14

libscmain.so eced0      lsr x0, x0, #0 ; 

此处我们注意到一个字符串,add x0, x0, #0x1bc 此处需要注意的一点是,他的汇编代码与IDA的反汇编并不一致,道理是相同的,粗俗一点理解其实就是根据某个偏移取到了内存空间中的某个值,这个值从哪里来其实我们目前暂时没办法确定,在ida的反汇编中,他的呈现是这样的ADRL X0, unk_26E1BC ,在一个未处理字,暂时推测是某个代码块中应该向其赋了值。
IDA根据此地址跳转,发现找到了上一层调用。

 

sub_EFC8C函数我们查找交叉引用,发现只有一个函数调用了它 -> sub_F10C0, 0xF10C0的交叉引用我们发现他的上一层其实就是我们的sub_7D4B4。至此,整条simplesign的大体执行流程我们已经基本了解了,现在开始详细的解析simplesign是如何生成的。

3.2 详细解剖前32位是如何组成的

上面的快速预览中,我们知道了前32位的前置在sub_EFC8C中调用了sub_ECDE4函数,其中有两个参数,第一个就是我们trace中那一个64位的字符串,第二个呢?
v7 = qword_270030(10L) 如果我们点击进去发现并没有什么,因为他是一个数据段,我们点击qword_270030 再点击 X 会发现他其实是指向的是某个函数,这里我们发现他是在so init时候进行了定义

1

2

3

4

qword_270030 = (__int64 (__fastcall *)(_QWORD))dlsym(handle, "malloc");

      qword_270038 = (__int64)dlsym(handle, "calloc");

      qword_270028 = (__int64 (__fastcall *)(_QWORD))dlsym(handle, "free");

      qword_270040 = (__int64 (__fastcall *)(_QWORD, _QWORD))dlsym(handle, "realloc");

那么这里的270030就代表了malloc,申请了一块长度为10的内存空间

3.2.1 分析sub_ECDE4

大致整理了一下,我们看图说

 

致如图所示,需要关注的是v17 = sub_D0404("f0e1d2c3b4a59687", 128LL, v22)
这里不详细分析,因为我们看到传入了3个参数,第一个是f0e1d2c3b4a59687,第二个是128,第三个是v22
第一个参数其实就是个固定值,推测跟版本有关,第二个长度,第三个传入的v22,是决定前32位计算的重要参数,但是我们可以偷个懒,发现前两个参数是固定的,v22用作存储计算后的一个地址指针。所以他的值是固定的,他的计算是通过第一个参数来变换的。这一块还原计算流程也不难,就不占用篇幅了。

3.2.2 sub_D1DB4

直接看图

 试试用python还原一下

与trace的结果一致

4. 分析第33-40位

老样子,根据我们得到的simplesign的第33-40位去trace日志中搜索,得到了ldr x0, [sp, #0xf8] ; x0 = 0x34 --> 0x7189db0d6c (3469dc64E0AA3D74F268AE*****************) ,但是我们无法找到计算或者生成的地方,但是我们之前说过有怀疑这里是时间戳,那么我们对其进行转换,转换成10进制然后再时间戳转换试试,具体过程不细说了,直接说结果,转换的10进制并不符合时间戳,因为在这里要处理端续,转换成0x64dc6934再去匹配发现转换成时间戳就对得上我们trace的时间了。
这块其实是syscall了gettimeofday出来的,可以自行看一下,不多赘述。

4.1 41-43位

至于12C的生成,后续会详细说明。

5. 后32位的逻辑

我们继续假设后32位跟前32位一样的逻辑,进行拆分查找,日志搜索前8位,0x825B340C

 

1

2

搜索到日志的第一条

libscmain.so eef28      ldur x11, [x29, #-0x10] ;    x11 = 0x7189db0d40 --> 0x716d81ec00   (825b340c)

汇编指令ldur,证明是从内存中读取出来了,那么说明我们这个思路可能不正确,可以试试搜索825b发现也是一样,都是出现在了0xeef28位置上,那么我们就需要去分析一下,此位置是一个什么样的结构或者功能。很清晰明了,我们去逆推v3 -> v4 -> v8 -> result = param1(第一个参数)
X键查看sub_EEE38的交叉引用,发现其恰好都在我们上一级sub_F10C0中,花一分钟去trace日志,我们基本可以定位到具体哪里调用了eee38


定位最后一个sub_EEE38的param1 -> v32 ,观察其规律,发现sub_F0E04中有关联,我们试着用frida看一下v32的变化

因为我这图是后补的,所以后32位生成的值与上面不一样,我们只需要看不同的地方,由图可知,sub_F0E04在调用的时候,a1的值应该就是后32位的值了,但是函数执行结束时,a1的值是有变化的,而变化后的值恰恰就是最终生成的simplesign的后32位。那么我们假设,后32位计算后,会经过f0e04这个函数对后10位进行了某些变化。
我们先去分析后32位是怎么生成的再去研究这后10位的变化逻辑。

5.1 后32位的生成过程

我们接着看sub_F10C0,观察v32的轨迹,在IDA中我们观察v32并没有操作什么,那么问题很有可能出在了上一篇中,scmain存在的花指令混淆的原因,试着去修复会很费时间,有没有其他方式能展现出各函数的执行流程呢?我们试一下Binary Ninja去反汇编,看看能不能比IDA展示的更好。
Binary Ninja打开scmain会消耗一段时间,这期间不要管。我们看下结果

 

Binary Ninja 的 sub_f0f0c -> IDA的 sub_F0E04
Binary Ninja 的 sub_F77A4 -> IDA的 sub_F2794 (Binary Ninja识别出了跳转)
为了防止阅读出现混乱,我依旧以IDA的反汇编来分析流程。

5.2 sub_F77A4 (Binary Ninja)

IDA无法对sub_F2794进行有效的反编译,所以我们使用Binary Ninja来分析。
通过Binary Ninja (后续简称BN)分析,sub_F0E04的参数来自sub_F77A4的第三个参数(param3),而且param3在BN中也没有发现有其他函数参与修改、计算,那么我们推测,F77A4是计算后32位的函数,点击进入。

是不是有眼前一亮的感脚,明文的16进制是不是很像MD5中的K表,还有位移数数量也不多不少,正好64个。


看一眼Graph。硬肝控制流对于我们来说没有任何好处与意义。因为我们是新手教程,所以就使用最简单有效的方式
前8位,825B340C 因为我们知道了后面是由MD5生成的,所以端续我们可以确定,去trace日志搜索0xC345B82,第一条结果
 

定位汇编指令位置0x1041b4,在BNG输入跳转,发现会跳转到函数头部,因为BN的逻辑跟IDA不太一样,定位不到具体变量或者参数的位置,以结果所在的寄存器为地址,那么我们试试将我们的指令地址+4或者-4

1

001041b0                      int32_t x18_75 = ror.d(x8_1091 + 0x70363da3 + x13_196 + ((x13_367 | not.d(x9_673)) ^ x8_1070), 0x1a+ x13_367

至此,我们定位到了MD5结果的A所在的位置
我们知道算法的代码了,SV也知道了,但是我们还没有得到入参以及初始化ABCD(魔数),试着在MD5第一行计算中找规律,因为A、B、C、D一定会参与到前4行的运算中

1

2

3

4

int32_t x8_1104 = ror.d(x19_10 + 0x500fe759 + (((var_158.d ^ var_168.d) & var_170.d) ^ var_268.d) + var_178.d, 0x19+ var_270.d

int32_t x9_810 = ror.d(x8_330 + 0x6fa2f477 + var_158.d + (((var_168.d ^ var_170.d) & x8_1104) ^ var_168.d), 0x14+ x8_1104

int32_t x0_127 = ror.d(x0_34 - 0x5cbacc06 + var_168.d + (((var_170.d ^ x8_1104) & x9_810) ^ var_170.d), 0xf+ x9_810

int32_t x8_1220 = ror.d(x18_156 + 0x46d88dcf + var_170.d + ((x9_810 & x0_127) | (x8_1104 & not.d(x0_127))), 0xa+ x0_127

我们可以看到,每一行的结果都会放到下一条计算逻辑的最后去相加
我们梳理一下前两行相加计算的逻辑
line1 = ror( x_19 + 0x500fe759 + var_178 + ((var_158 ^ var_168) & var_170) ^ var_268 , 0x19) + var_170(var_270=var_170)
通过trace日志,或者直接看反汇编,我们知道x_19就是传入的参数M[0],继续简化公式
line1=ror(M[0]+k[0]+A+(异或与运算),移位数)+魔数之一
后面的第二行——第四行,我们就可以知道四个魔数对应的变量,通过trace日志可知道其值。四个魔数变量分别是var_178,var_268 = var_158,var270 = var_170,var_168
可是我们在当前的if分支中,并没有找到var_178var_168
根据汇编指令流,分析当前if分支的第一行运行结果的计算过程,可以得到var_178的值,在trace日志中搜索得到如下内容,我们跳转到了另一个分支中

请无视我的备注,那是还原算法时,做对比用的)
我们发现刚才的MD5的魔数是另一个MD5(我们简称MD5_A)的倒数第4行的计算结果。
那我们是否可以假设,MD5_A的最后四行的结果就是我们之前MD5(简称MD5_B)的魔数呢?
用trace日志做一下验证。
我们发现,MD5_B的魔数是由MD5_A的结果与MD5_A的初始魔数相加而成得,而且MD5_A的计算逻辑与K表以及移位数都是一样的,推断两个MD5的算法是相同的。那么我们先去找MD5_A的魔数来源,根据MD5_A分支,我们推倒出A、B、C、D四个魔数对应的变量值,再去找赋值的来源,发现了一个变量var_e0,继续逆推

data_24c0c0值得我们关注
 

恰好与我们MD5_A的魔数一模一样
完事具备,就差还原了。具体细节闲下来我会在文章内补充,直接看结果

 第一行为MD5_A的结果,也就是MD5_B的四个魔数
第二行为MD5_B的结果,与trace的后32位的前22位完全匹配。
注意! 这里有一个问题,最后的结果不一定是后面多少位会变化,这个具体原因后面会详细讲。

 

至于推导的过程,我建议新手朋友自己动手,能再最大程度上加深印象
下一步我们要继续向上推,因为我们目前还不知道参数是什么。
通过使用BN与IDA的观察,F77A4的上一个函数sub_F01AC的param2 对应sub_F77A4的param1, F01AC的param3对应F77A4的param2。
通过frida hook,我们可以大概得了解到这几个函数参数的对应关系。 

5.3 sub_F10AC

此函数的参数param1+76处,作为一个计算控制器,给参与计算的v6赋值,0xAB或者0xCD。还原起来没有什么难度。直接上结果

 

 

 

param1与param2不变,param3在函数结束时内容被填充,然后在后续的指令中,param3经过了序列化后将地址指针赋给了v21,v21作为刚才讲的sub_F01AC的入参param1进行了运算。
167424前面得F01AC函数就比较简单了,其实就是F10C0传入的参数,也就是SimpleSign的那一串字节数组。
总结一下流程就是
SimpleSign入参 -> F01AC(SimpleSign字节数组作为参数) -> 167424 -> F01AC 再计算一次 -> F77A4 MD5计算生成后32位

5.5 sub_F0E04 及其重要的一个校验点

首先,这个函数中有几个内存段需要先行知晓,例如qword_26FD40 qword_26FE38 byte_26E010等,因为这些地址的内容中有一些是在so init时赋值,有一些是其他环境影响内容变换,所以,要搞清楚这些是做什么的,怎么做的,才能决定最后16位的内容是怎样的。
先给出我的so的备注大概了解一下

 

simplesign最后这16位的组成其实是前8位是当前时间与JNI_OnLoad的时间差的十六进制,高位为0则为0,与原simplesign计算的后16位的前8为逐个异或。
第9-10、11-12则为一个固定数(目前看来)是与0x00和0xff的异或
13-14是取决于byte_26E010是否有改动
15-16则是一个计算公式v18[7] = (v9 << 7) + 8 * v10 + 4 * v11 + 2 * v12 + v13;

5.5.1 时间差

根据分析sub_ED574我们得知,此函数的结果是由获取当前time再减去qword_26FD40得到的。
我们使用IDA的查找交叉引用功能,发现其是在JNI_OnLoad时被写入了内容

5.5.2 qword_26FE38

依旧使用上面的方式查看交叉引用,发现STR操作也在JNI_OnLoad中,赋值了255L,那么其内容为0xff

5.5.3 byte_26E010

默认值是0x2C,但是有几处涉及到更改,后续我们再说。

可能下一篇帖子是补充说明,也可能是bncode

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯刷题(二)

参考大佬代码&#xff1a;&#xff08;区间合并二分&#xff09; import os import sysn, L map(int, input().split()) # 输入n,len arr [list(map(int, input().split())) for _ in range(n)] # 输入Li,Si def check(Ti, arr, L)->bool:sec [] # 存入已打开的阀门在…

如何防御udp攻击

UDP Flood是互联网上最经典的DDoS&#xff08;Distributed Denial of Service&#xff09;攻击之一。攻击者在短时间内向目标设备发送大量的UDP报文&#xff0c;导致链路拥塞甚至网络瘫痪。一般的UDP报文由攻击工具伪造&#xff0c;通常在数据段具备相同的特征&#xff0c;另一…

从3V到12V,升压芯片该选哪个?

在电子系统设计中&#xff0c;升压芯片很重要&#xff0c;将直接影响到系统的稳定性、效率及噪声&#xff0c;特别是在需要将3V升到12V中&#xff0c;选择合适的升压芯片尤为关键&#xff0c;那么如何针对这个需求&#xff0c;合理选择升压芯片&#xff1f; 1、如何选择升压芯片…

阿里云服务器地域怎么选?速度、价格、备案、内网连接考虑因素

阿里云服务器地域选择方法&#xff0c;如何选择速度更快、网络延迟更低的地域节点&#xff0c;地域指云服务器所在的地理位置区域&#xff0c;地域以城市划分&#xff0c;如北京、杭州、深圳及上海等&#xff0c;如何选择地域&#xff1f;建议根据用户所在地区就近选择地域&…

三井住友保险中国区信息技术部负责人陈婧,将出席“ISIG-RPA超级自动化产业发展峰会”

3月16日&#xff0c;第四届「ISIG中国产业智能大会」将在上海中庚聚龙酒店拉开序幕。本届大会由苏州市金融科技协会指导&#xff0c;企智未来科技&#xff08;RPA中国、AIGC开放社区、LowCode低码时代&#xff09;主办。大会旨在聚合每一位产业成员的力量&#xff0c;深入探索R…

HNU-计算机网络-甘晴void学习感悟

前言 计算机网络其实我没太学懂&#xff0c; 仅从应试来说&#xff0c;考试成绩也不太好。 这也是为什么一直没有更新这一学科的学习感悟。 大三下还是有点闲&#xff0c;一周三天小长假&#xff0c;闲来无事还是给写了。 教材使用这本&#xff1a; 总领 期中考试 30% 期…

DeepLearning in Pytorch|我的第一个NN-共享单车预测

目录 概要 一、数据准备 导入数据 数据可视化 二、设计神经网络 版本一 版本二&#xff08;正片&#xff09; 三、测试 小结 概要 我的第一个深度学习神经网络模型---利用Pytorch设计人工神经网络对某地区租赁单车的使用情况进行预测 输入节点为1个&#xff0c;隐含…

RNN(Recurrent Neural Networks)循环神经网络

循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称RNN&#xff09;是一种处理序列数据的神经网络结构&#xff0c;它具有记忆能力&#xff0c;能够捕捉序列中的时序信息。RNN在自然语言处理、时间序列预测等方面有着很多的应用。 一、RNN 的基本结构 RNN的包…

数据结构 - 栈和队列

本篇博客将介绍栈和队列的定义以及实现。 1.栈的定义 栈是一种特殊的线性表&#xff0c;只允许在固定的一端进行插入和删除数据&#xff0c;插入数据的一端叫做栈顶&#xff0c;另一端叫做栈底。栈中的数据遵守后进先出的原则 LIFO (Last In First Out)。 插入数据的操作称为压…

设计模式-行为型模式-迭代器模式

迭代器模式&#xff08;Iterator&#xff09;&#xff0c;提供一种方法顺序访问一个聚合对象中各个元素&#xff0c;而又不暴露该对象的内部表示。[DP] 首先&#xff0c;定义一个接口Iterator&#xff0c;它包含了遍历聚合对象所需的方法&#xff1a; public interface Iterato…

spring boot 集成 mysql ,mybatisplus多数据源

1、需要的依赖&#xff0c;版本自行控制 <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId> </dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java<…

python基础——输入与输出【input 和 print】

&#x1f4dd;前言&#xff1a; 上一篇文章python基础——入门必备知识中讲解了一些关于python的基础知识&#xff0c;可以让我们更好的理解程序代码中内容的含义&#xff0c;不至于一头雾水。今天我就来介绍一下&#xff0c;python中两个常见的输入和输出语句 input 和 print …

论文阅读之Multimodal Chain-of-Thought Reasoning in Language Models

文章目录 简介摘要引言多模态思维链推理的挑战多模态CoT框架多模态CoT模型架构细节编码模块融合模块解码模块 实验结果总结 简介 本文主要对2023一篇论文《Multimodal Chain-of-Thought Reasoning in Language Models》主要内容进行介绍。 摘要 大型语言模型&#xff08;LLM…

Dockerfile的使用,怎样制作镜像

Docker 提供了一种更便捷的方式&#xff0c;叫作 Dockerfile docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build命令参数&#xff1a; --build-arg&#xff0c;设置构建时的变量 --no-cache&#xff0c;默认false。设置该选项&#xff0c;将不使用Build …

Ubuntu18/20运行ORB-SLAM3

ORB-SLAM3复现(ubuntu18/20) 文章目录 ORB-SLAM3复现(ubuntu18/20)1 坐标系与外参Intrinsic parameters2 内参Intrinsic parameters2.1 相机内参① 针孔模型Pinhole② KannalaBrandt8模型③ Rectified相机 2.2 IMU内参 3 VI标定—外参3.1 Visual calibration3.2 Inertial calib…

继承中 隐藏和重写的区别

隐藏&#xff08;重定义&#xff09;&#xff1a;在不同作用域中&#xff08;不同类&#xff09;&#xff0c;函数名相同&#xff0c;当子类对象想要调用这个函数的时候&#xff0c;只能调用到子类中的这个同名函数&#xff0c;父类中的那个被隐藏。子类对象想要调用父类中的那…

S32 Design Studio PE工具配置ADC

工具配置 我这个K1芯片有两个ADC驱动&#xff0c;也就有两个components&#xff0c;点开之后每个components都有四个选项卡converter转换器、channel通道、compare比较器、average求平均。 配置引脚 配置之前&#xff0c;得先配置好引脚&#xff0c;哪个引脚用来采集ADC。 每…

LangChain Experssion Language之CookBook(一)

目录 LangChain Experssion Language简介 CookBook示例大赏 Prompt LLM&#xff1a;正经本分事儿 RAG&#xff1a;检索的时候用上用户自己的数据吧 Multiple chains&#xff1a;玩转chain的叠加合并 Querying a SQL DB&#xff1a;根据用户的问题写SQL检索数据库 Agent…

uniapp使用华为云OBS进行上传

前言&#xff1a;无论是使用华为云还是阿里云&#xff0c;使用其产品的时候必须阅读文档 1、以华为云为例&#xff0c;刚接触此功能肯定是无从下手的情况&#xff0c;那么我们需要思考&#xff0c;我们使用该产品所用到的文档是什么 2、我们要使用obs 文件上传&#xff0c;肯…

iOS-系统弹窗调用

代码&#xff1a; UIAlertController *alertViewController [UIAlertController alertControllerWithTitle:"请选择方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];// style 为 sheet UIAlertAction *cancle [UIAlertAction actionWithTit…