零基础算法还原01以及使用python和JS还原C++部分细节

题目一

使用jadx 打开algorithmbase_10.apk

image-20231101144427292

JAVA层

使用Frida获取先生成的随机字符串

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

// 定义一个名为hook_js的JavaScript函数

function hook_js(){

    // 使用Java.perform()函数来执行JavaScript代码

    Java.perform(function(){

        // 使用Java.use()函数来获取Java类com.kanxue.algorithmbase.MainActivity

        var m_randomAscii = Java.use("com.kanxue.algorithmbase.MainActivity")

        // 检查获取的类对象是否存在

        if(m_randomAscii!=undefined){

            console.log("开始Hook"); // 打印开始Hook的消息

            // 重写函数encodeFromJni_11的实现

            m_randomAscii.encodeFromJni_11.implementation = function(input){

                // 获取输入参数input

                var res = this.encodeFromJni_11(input);

                // 打印输入参数和返回值

                console.log("input:==>"+input);

                console.log("res:==>"+res);

                // 返回结果

                return res;

            }

        }

    })

}

function main(){

    hook_js();

}

setImmediate(main);

传入的随机字符串和字符串在Native层加密后的结果如下

1

2

input:==> meUx3DppB%Gj]-2J

res:==> LCllTadbMHYZ0kNnDitri5== 

  • 随机字符串:meUx3DppB%Gj]-2J
  • 加密后的结果: LCllTadbMHYZ0kNnDitri5==

image-20231101145832850

我们进入Native层查看加密后的结果

Native层

使用unzip命令解压apk获取so文件

unzip algorithmbase_10.apk -d fileso10

image-20231101144703961

使用Ida打开so文件,在Export表输入JNI查看加密函数的位置

image-20231101150231637

使用快捷键YN修改传入的参数名称,以便我们方便我们后续分析

image-20231101150546220

返回值是v13

我们从下往上回溯来到 sub_EE38(v9, v10, v11) 函数

使用Frida 对sub_EE38 函数进行hook,目的是为了查看传入的参数和传出的参数

脚本如下

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

function hook_js(){

    Java.perform(function(){

        var m_randomAscii = Java.use("com.kanxue.algorithmbase.MainActivity"//获取MainActivity类

        if(m_randomAscii!=undefined){

            console.log("开始Hook");

            m_randomAscii.encodeFromJni_11.implementation = function(input){ //找到encodeFromJni_11方法,并拦截调用

                var res = this.encodeFromJni_11(input); //调用原始方法

                console.log("input:==>"+input); //打印输入参数

                console.log("res:==>"+res); //打印返回结果

                return res; //返回结果

            }

        }

    })

}

function hook_native(){

    Java.perform(function(){

        //找到基地址

        var base_address = Module.getBaseAddress("libnative-lib.so"//获取libnative-lib.so的基地址

        var sub_EE38 = base_address.add(0xEE38); //计算函数偏移地址

        //开启拦截器

        Interceptor.attach(sub_EE38,{

            //进入函数

            onEnter:function(args){

            this.arg0 = args[0]; //保存第一个参数

            console.log("======>onENter<==========");

            console.log("第一个参数未处理前===>"+args[0].readCString()); //打印第一个参数内容

            console.log("第二个参数未处理前===>"+args[1].readCString()); //打印第二个参数内容

            console.log("第三个参数未处理前===>"+args[2]); //打印第三个参数内容

            },

            onLeave:function(nresult){  

                console.log("======>onLeave<==========");

                console.log("第一个参数处理后======>"+this.arg0.readCString()); //打印处理后的第一个参数

            }

        })

    })

}

function main(){

    hook_js();

    hook_native();

}

setImmediate(main);

点击app按钮,Hook的代码如下所示

image-20231101151718613

我们获取到如下参数

sub_EE38(_BYTE *a1, __int64 a2, int a3)第一个参数第二个参数第三个参数
未处理前""meUx3DppB%Gj]-2J0x10
处理后后LCllTadbMHYZ0kNnDitri5==

1

2

3

4

5

6

7

第一个参数未处理前===>

第二个参数未处理前===>meUx3DppB%Gj]-2J

第三个参数未处理前===>0x10

======>onLeave<==========

第一个参数处理后======>LCllTadbMHYZ0kNnDitri5==

input:==>meUx3DppB%Gj]-2J

res:==>LCllTadbMHYZ0kNnDitri5== 

也就是说 sub_EE38 就是我们找的加密call

我们不妨进入sub_EE38 查看加密细节

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

__int64 __fastcall sub_EE38(_BYTE *a1, __int64 a2, int a3)

{

  __int64 v3; // x9,循环计数器

  _BYTE *v4; // x12,用于存储结果的指针

  unsigned __int8 *v5; // x10,用于指向输入数据的指针

  unsigned __int64 v6; // x13,临时变量

  _BYTE *v7; // x10,下一个结果的存储位置

  __int64 v8; // x11,临时变量

  char v9; // w8,临时变量

  __int64 v10; // x9,临时变量

  __int64 result; // x0,函数返回值

  if (a3 - 2 < 1) // 当数据长度小于等于2时

  {

    LODWORD(v3) = 0;

    v7 = a1;

    if (a3 <= 0)

      goto LABEL_11;

  }

  else

  {

    v3 = 0LL;

    v4 = a1;

    do

    {

      v5 = (unsigned __int8 *)(a2 + v3); // 从输入数据中取出三个字节进行处理

      v6 = *(unsigned __int8 *)(a2 + v3);

      v3 += 3LL;

      *v4 = aAyzabfghz0cmbd[v6 >> 2]; // 取出第一个字节的前6位对应的字符

      v4[1] = aAyzabfghz0cmbd[(16 * (unsigned int)*v5) & 0x30LL | ((unsigned __int64)v5[1] >> 4)]; // 取出第二个字节的前4位和第一个字节的后2位所对应的字符

      v4[2] = aAyzabfghz0cmbd[(4 * (unsigned int)v5[1]) & 0x3CLL | ((unsigned __int64)v5[2] >> 6)]; // 取出第三个字节的前2位和第二个字节的后4位所对应的字符

      LOBYTE(v6) = aAyzabfghz0cmbd[v5[2] & 0x3F]; // 取出第三个字节的后6位对应的字符

      v7 = v4 + 4; // 指向下一个结果的存储位置

      v4[3] = v6; // 存储刚才取出的字符

      v4 += 4; // 指向下一个处理位置

    }

    while (v3 < a3 - 2); // 处理到倒数第三个字节为止

    if ((int)v3 >= a3) // 如果处理到倒数第二个字节或最后一个字节

      goto LABEL_11;

  }

  *v7 = aAyzabfghz0cmbd[(unsigned __int64)*(unsigned __int8 *)(a2 + (unsigned int)v3) >> 2]; // 取出最后一个字节的前6位对应的字符

  v8 = (16 * (unsigned int)*(unsigned __int8 *)(a2 + (unsigned int)v3)) & 0x30LL; // 取出最后一个字节的前4位

  if ((_DWORD)v3 == a3 - 1) // 如果只有最后一个字节

  {

    v7[1] = aAyzabfghz0cmbd[v8]; // 存储最后一个字节的前4位对应的字符

    v9 = 61; // 存储'='字符

  }

  else

  {

    v10 = a2 + (unsigned int)v3; // 最后两个字节的指针

    v7[1] = aAyzabfghz0cmbd[v8 | ((unsigned __int64)*(unsigned __int8 *)(v10 + 1) >> 4)]; // 存储最后一个字节的前4位和倒数第二个字节的后2位对应的字符

    v9 = aAyzabfghz0cmbd[(4 * (unsigned int)*(unsigned __int8 *)(v10 + 1)) & 0x3CLL]; // 存储倒数第二个字节的后4位对应的字符

  }

  v7[2] = v9; // 存储倒数第二个字节的后4位或'='字符

  v7[3] = 61; // 存储'='字符

  v7 += 4; // 指向下一个结果的存储位置

LABEL_11:

  result = (unsigned int)((_DWORD)v7 - (_DWORD)a1 + 1); // 计算结果的长度

  *v7 = 0; // 结果字符串结尾添加NULL字符

  return result; // 返回结果长度

}

根据call,还原加密算法

用C代码还原加密算法如下

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

70

71

72

73

74

75

// 01.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

//

#include <iostream>

#include <memory>

#include <Windows.h> 

#include <minwindef.h>

#include <rpcndr.h>

using namespace std;

unsigned char* sub_EE38(char* strResult, long long inPut, signed int nCnt)

{

    long long v3 = 0;

    unsigned char* v4 = new unsigned char[100];

    unsigned char* v5 = nullptr;

    unsigned long long v6 = 0;

    char aAyzabfghz0cmbd[] = "AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4";

    long long result = 0;

    unsigned long long v8 = 0;

    char v9;

    unsigned char* v7;

    long long v10;

    //记录v4初始

    unsigned char* vCount = v4;

    do

    {

        *v4 = 0;

        v5 = (unsigned char*)(inPut + v3);

        v6 = *(unsigned char*)(inPut + v3);

        v3 += 3;

        *v4 = aAyzabfghz0cmbd[v6 >> 2];

        v4[1] = aAyzabfghz0cmbd[(16 * (unsigned int)*v5) & 0x30 | ((unsigned long long)v5[1] >> 4)];

        v4[2] = aAyzabfghz0cmbd[(4 * (unsigned int)v5[1]) & 0x3C | ((unsigned long long)v5[2] >> 6)];

        unsigned char lowByte = aAyzabfghz0cmbd[v5[2] & 0x3F];

        v6 = (v6 & ~(0xFFull)) | lowByte;

        v7 = v4 + 4;

        v4[3] = v6;

        v4 += 4;

    while (v3 < nCnt - 2);

    if ((int)v3 >= nCnt)

        goto LABEL_11;

    *v7 = aAyzabfghz0cmbd[(unsigned long long) * (unsigned char*)(inPut + (unsigned int)v3) >> 2];

    v8 = (16 * (unsigned int)*(unsigned __int8*)(inPut + (unsigned int)v3)) & 0x30LL;

    if (v3 == nCnt - 1)

    {

        v7[1] = aAyzabfghz0cmbd[v8];

        v9 = 61;

    }

    else

    {

        v10 = inPut + (unsigned int)v3;

        v7[1] = aAyzabfghz0cmbd[v8 | ((unsigned long long) * (unsigned char*)(v10 + 1) >> 4)];

        v9 = aAyzabfghz0cmbd[(4 * (unsigned int)*(unsigned char*)(v10 + 1)) & 0x3CLL];

    }

    v7[2] = v9;

    v7[3] = 61;

    v7 += 4;

    std::cout << (unsigned char*)vCount;

     return (unsigned char*)vCount;

LABEL_11:

    result = (unsigned int)((DWORD)v7 - (DWORD)strResult + 1);

    *v7 = 0;

    delete[] v4;

    std::cout << result;

    return (unsigned char*)result;

}

int main()

{

    char str[] = "";

    unsigned char* str2=  sub_EE38(str, (__int64)"meUx3DppB%Gj]-2J", 0x10);

     std::cout << str2;

}

image-20231101171459516

还原得到加密结果

1

LCllTadbMHYZ0kNnDitri5==

使用JS还原加密算法如下

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

function stringToUint8Array(str){

    var arr = [];

    for (var i=0;i<str.length;i++)

    {

     arr.push(str.charCodeAt(i));

    }

    var outputbytes = new Uint8Array(arr);

    return outputbytes;

}

function sub_EE38(str,input,nCnt){

    var input_bytes = stringToUint8Array(input);

    var aAyzabfghz0cmbd = "AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4";

    var result =0;

    var v4 ="";

    var  v7=0;

    var v3 =0;

    var v6 =0

    var v8 =0

    var v9 = ""

    while (v3 <nCnt-2){

    var v5 = input_bytes[v3];

    var v5_1 = input_bytes[v3+1]

    var v5_2 = input_bytes[v3+2]

    v6 = input_bytes[v3];

    v3 +=3;

    v4 +=  aAyzabfghz0cmbd.charAt(v6 >>2)

    v4 +=  aAyzabfghz0cmbd.charAt(((16*v5)& 0x30) | v5_1 >>4);

    v4 += aAyzabfghz0cmbd.charAt((4*v5_1)&0x3C |v5_2 >>6 );

    v6 = aAyzabfghz0cmbd[v5_2 & 0x3F]

    v4 += v6;

    }

    v4 +=aAyzabfghz0cmbd[(input_bytes[v3]) >> 2];

    v8 = 16 * input_bytes[v3] & 0x30

    if (v3 >= nCnt -1){

        v4 += aAyzabfghz0cmbd[v8]

        v9 = "="

    }else{

        v4 += aAyzabfghz0cmbd[v8 |(v4[v3+1] >>4)]

        v9 = aAyzabfghz0cmbd[(4*v4[v3+1] & 0x3C)]

    }

    v4 += v9

    v4 += "="

    return v4

}

console.log(sub_EE38("","meUx3DppB%Gj]-2J",0x10))

image-20231101171616527

使用Python还原算法如下

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

def sub_EE38(strResult,inPut,nCnt):

    v3 =0

    v4 = bytes(inPut"utf-8")

    aAyzabfghz0cmbd ="AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"

    v6=0

    result =0

    v8=0

    output = ""

    v7 =0

    while True:

        v5= v4[v3]

        v5_1 = v4[v3+1]

        v5_2 = v4[v3+2]

        v6 =v4[v3]

        v3 +=3

        output += aAyzabfghz0cmbd[v6>>2]

        output+= aAyzabfghz0cmbd[(16*v5) & 0x30 | (v5_1 >>4)]

        output+= aAyzabfghz0cmbd[(4*v5_1)&0x3C | (v5_2>>6)]

        v6 = aAyzabfghz0cmbd[v5_2&0x3F]

        output +=v6

        v7 +=4

        if v3>=nCnt-2:

            break

    output += aAyzabfghz0cmbd[v4[v3] >>2]

    v8 = 16 * v4[v3] & 0x30

    if(v3 >=nCnt -1):

        output +=  aAyzabfghz0cmbd[v8]

        v9 = '='

    else:

        output += aAyzabfghz0cmbd[v8 | (v4[v3+1]>>4)]

        v9 = aAyzabfghz0cmbd[(4*v4[v3+1]&0x3C)]

    output += v9

    output += '='

    return output

# Press the green button in the gutter to run the script.

if __name__ == '__main__':

     print(sub_EE38("","meUx3DppB%Gj]-2J",0x10))

image-20231101171651251

题目二

algorithmbase_12.apk

image-20231104100416930

使用apkInfo打开发现是32位程序

JAVA层

image-20231104100119863

分析如图,先生成随机字符串(我们可以通过Hook encodeFromJni_12 函数来查看生成的随机字符串的值),然后把生成的随机字符串丢到encodeFromJni_12里面处理

我们先Hook一下encodeFromJni_12函数,查看加密前的字符串和加密后的字符串

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 定义一个名为hook_js的函数

function hook_js() {

    // 使用Java.perform方法执行函数体

    Java.perform(function() {

        // 定义变量enrandomcode并使用Java.use方法获取com.kanxue.algorithmbase.MainActivity类

        var enrandomcode = Java.use("com.kanxue.algorithmbase.MainActivity")

        // 如果enrandomcode已定义

        if(enrandomcode != undefined) {

            // 打印开始JAVA层Hook

            console.log("开始JAVA层Hook");

            // 重写encodeFromJni_12方法的实现

            enrandomcode.encodeFromJni_12.implementation = function(intput) {

                // 调用原始的encodeFromJni_12方法,并将结果赋值给变量res

                var res = this.encodeFromJni_12(intput);

                // 打印传入参数的值

                console.log("传入参数是====>" + intput);

                // 打印加密后的参数的值

                console.log("加密后的参数是====>" + res);

                // 返回加密后的结果

                return res;

            }

        }

    })

}

结果如下

image-20231104101314842

1

2

传入参数是====>xVjHx-D&nji8i*rBZ)j

加密后的参数是====>`!^fZFrxI^q~[+\~p<yT#p~F==

加密的结果看起来很像是Base64 ,我们用64编码后的结果对比一下

image-20231104101716116

不是64编码,但是结果很像。猜测可能是换了码表或并非完全的base64加密

so

打开IDA,来到加密函数Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_112(int a1, int a2, int a3, char *a4) 里面 进行分析

从下往上回溯,定位到sub_8B04 函数

image-20231104103843906

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

70

71

72

73

74

75

76

// 定义一个名为sub_8B04的函数,参数为a1、a2和a3,返回值为_BYTE类型的指针

_BYTE *__fastcall sub_8B04(int a1, int a2, int a3)

{

  int v3; // r12

  int v6; // r6

  int v7; // r5

  _BYTE *v8; // r3

  int v9; // r2

  unsigned int v10; // r0

  char v11; // r4

  int v12; // r6

  char v13; // r1

  // 计算v3的值

  v3 = a3 - 2;

  v6 = 0;

  v7 = 0;

  // 进入while循环,直到v7 >= v3

  while ( 1 )

  {

    // 获取a1+v6处的_BYTE类型指针

    v8 = (_BYTE *)(a1 + v6);

    if ( v7 >= v3 )

      break;

    // 计算v9的值

    v9 = a2 + v7;

    // v6加上4

    v6 += 4;

    // 将aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2]的值赋给v8指向的地址

    *v8 = aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2];

    // 计算v10的值

    v10 = *(unsigned __int8 *)(a2 + v7 + 1);

    // 获取a2+v7处的值

    v11 = *(_BYTE *)(a2 + v7);

    // v7加上3

    v7 += 3;

    // 将aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))]的值赋给v8指向的地址+1处

    v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];

    // 将aAyzpq23ijrtffg[(*(unsigned __int8 *)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(_BYTE *)(v9 + 1) & 0xF))]的值赋给v8指向的地址+2处

    v8[2] = aAyzpq23ijrtffg[(*(unsigned __int8 *)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(_BYTE *)(v9 + 1) & 0xF))];

    // 将*(_BYTE *)(v9 + 2) & 0x3F的值赋给v8指向的地址+3处

    v8[3] = aAyzpq23ijrtffg[*(_BYTE *)(v9 + 2) & 0x3F];

  }

  // 如果v7 < a3,执行以下代码

  if ( v7 < a3 )

  {

    // 将aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2]的值赋给v8指向的地址

    *v8 = aAyzpq23ijrtffg[*(unsigned __int8 *)(a2 + v7) >> 2];

    // 计算v12的值

    v12 = (16 * *(unsigned __int8 *)(a2 + v7)) & 0x30;

    // 如果a3 - 1 == v7,执行以下代码

    if ( a3 - 1 == v7 )

    {

      v13 = 61;

      // 将aAyzpq23ijrtffg[v12]的值赋给v8指向的地址+1处

      v8[1] = aAyzpq23ijrtffg[v12];

    }

    else

    {

      // 将aAyzpq23ijrtffg[v12 | (*(unsigned __int8 *)(a2 + v7 + 1) >> 4)]的值赋给v8指向的地址+1处

      v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned __int8 *)(a2 + v7 + 1) >> 4)];

      // 将aAyzpq23ijrtffg[4 * (*(_BYTE *)(a2 + v7 + 1) & 0xF)]的值赋给v13

      v13 = aAyzpq23ijrtffg[4 * (*(_BYTE *)(a2 + v7 + 1) & 0xF)];

    }

    // 将61的值赋给v8指向的地址+3处

    v8[3] = 61;

    // 将v13的值赋给v8指向的地址+2处

    v8[2] = v13;

    // v8加上4

    v8 += 4;

  }

  // 将0赋给v8指向的地址

  *v8 = 0;

  // 返回&v8[-a1 + 1]的值

  return &v8[-a1 + 1];

}

进行hook

这里有个坑,就是在使用拦截器对sub_8B04 位置进行定位,如果使用var sub_8B04 = base_address.add(0x8B04); 拦截出错就加一

这样才能进入函数里面

1

var sub_8B04 = base_address.add(0x8B04+1);

hook代码如下

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

function hook_native(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        if(base_address !=undefined){

        console.log("开始native层hook");

        //定位拦截位置

        var sub_8B04 = base_address.add(0x8B04+1);

        console.log("sub_8b04:",sub_8B04)

        if(sub_8B04!=undefined){

        console.log("进入native层0x8B04");

        }else{

        console.log("拦截器加载失败");

        }

        //使用拦截器

        Interceptor.attach(sub_8B04,{

        //开始进入函数,传入了三个参数依次打印

        onEnter:function(args){

            //保存参数

            this.arg0 = args[0];

            this.arg1 = args[1];

            this.arg2 = args[2];

            console.log("========>ENTER<========");

            console.log("sub_8B04未初始化时第一个参数是===>"+this.arg0.readCString());

            console.log("sub_8B04未初始化时第二个参数是===>"+this.arg1.readCString());

            console.log("sub_8B04未初始化时第三个参数是===>"+this.arg2.readCString());           

        },

        onLeave:function(result){

            console.log("========>LEAVE<========");

            console.log("sub_8B04已经初始化时第一个参数是===>"+this.arg0.readCString());

        }

        }

        )

        }

    })

}

hook显示的结果如下

1

2

3

4

sub_8B04未初始化时第一个参数是===>

sub_8B04未初始化时第二个参数是===>xVjHx-D&nji8i*rBZ)j

sub_8B04未初始化时第三个参数是===>0x13(传入参数的字符串大小)

sub_8B04已经初始化时第一个参数是===>`!^fZFrxI^q~[+\~p<yT#p~F==

使用IDA 对libnative-lib.so进行动态调试的时候会发现 aAyzpq23ijrtffg 字符串和我们之前记录的字符串(aAyzpq23ijrtffg DCB "AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4",0)并不相同,结合前面的思考,换了码表可能性更大

image-20231104105122522

我们开始hook aAyzpq23ijrtffg 码表(0x1B000),这个有个hook技巧: 复制使用readCString打印字符串 可能会造成数据丢失,所以我们使用hexdump(16进制)显示码表的内容会更精确

hook代码如下

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

function hook_native_sub_8B04(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        if(base_address !=undefined){

        console.log("开始native层hook");

        //定位拦截位置

        var sub_8B04 = base_address.add(0x8B04+1);

        if(sub_8B04){

        console.log("进入native层0x8B04");

    }else{

        console.log("拦截器加载失败");

    }

        //使用拦截器

        Interceptor.attach(sub_8B04,{

        //开始进入函数,传入了三个参数依次打印

        onEnter:function(args){

            //保存第一个参数

            this.arg0 = args[0];

            this.arg1 = args[1];

            this.arg2 = args[2];

            console.log("========>ENTER<========");

            // console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));

            // console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg1,{length:32,header:false}));

            //console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));

            var hook_aAyzpq23ijrtffg = base_address.add(0x1B000);

            if(hook_aAyzpq23ijrtffg!=undefined){console.log("aAyzpq23ijrtffg进入8B04的形状=====>"+hexdump(hook_aAyzpq23ijrtffg))};

     

        },

        onLeave:function(result){

            console.log("========>LEAVE<========");

            console.log("sub_8B04第一个参数处理后======>"+hexdump(this.arg0)); //打印处理后的第一个参数

            //console.log("sub_8B04得到的结果是====>"+result.readCString());

            // console.log("sub_8B04处理后第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));

            console.log("sub_8B04处理后时第一个参数是===>"+result);

            // console.log("sub_8B04处理后时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));

        }

        })

        }

    })

}

码表果然发生改变

image-20231104105804418

看看是不是只是简单换了码表,其他地方有没有进行加密

image-20231104110924545

是的,和猜测的结果完全一致!

再来看看码表是在哪个位置发生改变的,在sub_8ABC 找到码表发生变化的位置,并且发现传参(a1)就是加密前的字符串被int强转

image-20231104111158979

完整的hook代码如下

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

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

function hook_js(){

    Java.perform(function(){

    var enrandomcode = Java.use("com.kanxue.algorithmbase.MainActivity")

    //如果定义好了

    if(enrandomcode !=undefined)

        {

          console.log("开始JAVA层Hook");

          enrandomcode.encodeFromJni_12.implementation = function(intput){

            var res = this.encodeFromJni_12(intput);

            console.log("传入参数是====>"+intput);

            console.log("加密后的参数是====>"+res);

            return res;

          }

           

        }

    })

}

function hook_native(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        if(base_address !=undefined){

        console.log("开始native层hook");

        //定位拦截位置

        var sub_8B04 = base_address.add(0x8B04+1);

        console.log("sub_8b04:",sub_8B04)

        if(sub_8B04){

        console.log("进入native层0x8B04");

        }else{

        console.log("拦截器加载失败");

        }

        //使用拦截器

        Interceptor.attach(sub_8B04,{

        //开始进入函数,传入了三个参数依次打印

        onEnter:function(args){

            //保存第一个参数

            this.arg0 = args[0];

            this.arg1 = args[1];

            this.arg2 = args[2];

            console.log("========>ENTER<========");

            // console.log("sub_8B04未初始化时第一个参数是===>"+this.arg0.readCString());

            // console.log("sub_8B04未初始化时第二个参数是===>"+this.arg1.readCString());

            //console.log("sub_8B04未初始化时第三个参数是===>"+hexdump(this.arg2,{length:64,header:false}));

            var hook_aAyzpq23ijrtffg = base_address.add(0x1B000);

             

            if(hook_aAyzpq23ijrtffg!=undefined){console.log("aAyzpq23ijrtffg进入8B04的形状=====>"+hook_aAyzpq23ijrtffg.readCString().length())};

             

        },

        onLeave:function(result){

            console.log("========>LEAVE<========");

            // console.log("sub_8B04已经初始化时第一个参数是===>"+hexdump(this.arg0,{length:64,header:false}));

            // console.log("sub_8B04已经初始化时第二个参数是===>"+hexdump(this.arg1,{length:64,header:false}));

            //console.log("sub_8B04已经初始化时第三个参数是===>"+hexdump(this.arg2,{length:64,header:false}));

        }

        }

        )

        }

    })

}

function hookencodeFromJni(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        var base_address_method = Module.findExportByName("libnative-lib.so","Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_112")

        if(base_address_method!=undefined)

        {

            console.log("=======>进入初始界面进行Hook<=======");

            //使用拦截器

            Interceptor.attach(base_address_method,{

                onEnter:function(args){

                    //打印我们想要的param1和param2

                    // console.log("args[0]===>"+args[0]);

                    // console.log("args[1]===>"+args[1]);

                     

                    // console.log("args[2]===>"+args[2]);

                    // console.log("args[3]===>"+args[3]);

                    var hook_aAyzpq23ijrtffghijklmn = base_address.add(0x1B000+1);

                    console.log("aAyzpq23ijrtffghijklmn在导出函数里面=====>"+hook_aAyzpq23ijrtffghijklmn.readCString());

                     

                },

                onLeave:function(nreval){

                    nreval.replace(0);

                    console.log(nreval);

                }

            })

        }

    })

}

function hook_native_sub_8B04(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        if(base_address !=undefined){

        console.log("开始native层hook");

        //定位拦截位置

        var sub_8B04 = base_address.add(0x8B04+1);

        if(sub_8B04){

        console.log("进入native层0x8B04");

    }else{

        console.log("拦截器加载失败");

    }

        //使用拦截器

        Interceptor.attach(sub_8B04,{

        //开始进入函数,传入了三个参数依次打印

        onEnter:function(args){

            //保存第一个参数

            this.arg0 = args[0];

            this.arg1 = args[1];

            this.arg2 = args[2];

            console.log("========>ENTER<========");

            // console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));

            // console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg1,{length:32,header:false}));

            //console.log("sub_8B04未初始化时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));

            var hook_aAyzpq23ijrtffg = base_address.add(0x1B000);

            if(hook_aAyzpq23ijrtffg!=undefined){console.log("aAyzpq23ijrtffg进入8B04的形状=====>"+hexdump(hook_aAyzpq23ijrtffg))};

     

        },

        onLeave:function(result){

            console.log("========>LEAVE<========");

            console.log("sub_8B04第一个参数处理后======>"+hexdump(this.arg0)); //打印处理后的第一个参数

            //console.log("sub_8B04得到的结果是====>"+result.readCString());

            // console.log("sub_8B04处理后第一个参数是===>"+hexdump(this.arg0,{length:32,header:false}));

            console.log("sub_8B04处理后时第一个参数是===>"+result);

            // console.log("sub_8B04处理后时第一个参数是===>"+hexdump(this.arg2,{length:32,header:false}));

        }

        })

        }

    })

}

function main(){

    hook_js();

    hook_native_sub_8B04()

    hook_native();

    hookencodeFromJni()

}

setImmediate(main);

使用C++还原加密流程

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

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

#include <iostream>

#include <memory>

#include <Windows.h> 

#include <minwindef.h>

#include <rpcndr.h>

#include <intsafe.h>

#include <iostream>

#include <sstream>

#include <string>

#include <vector>

#define  _DWORD DWORD

using namespace std;

DWORD v16[2] = { 1, 2 };  // 将数组的所有元素初始化为0

string Hex2Ascii(string input) {

    std::stringstream ss(input);

    std::string token;

    std::vector<char> characters;

    while (std::getline(ss, token, ' ')) {

        int ascii = std::stoi(token, nullptr, 16);

        char c = static_cast<char>(ascii);

        characters.push_back(c);

    }

    std::string result(characters.begin(), characters.end());

    return result;

}

unsigned char* sub_8B04(int a1, int a2, int a3)

{

    int v3 =0; // r12

    int v6 =0; // r6

    int v7 =0; // r5

    unsigned char* v8 = new unsigned char[100]; // r3

    unsigned char* address = v8;//保存v8的首地址

    memset(v8, '\0', 100); // 将 v8 的所有元素都设置为 '1'

    int v9 =0; // r2

    unsigned int v10 =0; // r0

    char v11 ='\0'// r4

    int v12 =0; // r6

    char v13 ='\0'// r1

    std::string input = "52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27";

    string aAyzpq23ijrtffg = Hex2Ascii(input);

    v3 = a3 - 2;

    v6 = 0;

    v7 = 0;

    while (1)

    {

        v8 = (unsigned __int8*)(address + v6);

        if (v7 >= v3)

            break;

        v9 = a2 + v7;   //开始出现a2(传进来的参数)

        v6 += 4;

        *v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2];

        v10 = *(unsigned __int8*)(a2 + v7 + 1);

        v11 = *(unsigned char*)(a2 + v7);

        v7 += 3;

        v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];

        v8[2] = aAyzpq23ijrtffg[(*(unsigned __int8*)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(unsigned char*)(v9 + 1) & 0xF))];

        v8[3] = aAyzpq23ijrtffg[*(unsigned char*)(v9 + 2) & 0x3F];

         

         

    }

    if (v7 < a3)

    {

        *v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2];

        v12 = (16 * *(unsigned __int8*)(a2 + v7)) & 0x30;

        if (a3 - 1 == v7)

        {

            v13 = 61;

            v8[1] = aAyzpq23ijrtffg[v12];

        }

        else

        {

            v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned __int8*)(a2 + v7 + 1) >> 4)];

            v13 = aAyzpq23ijrtffg[4 * (*(unsigned __int8*)(a2 + v7 + 1) & 0xF)];

        }

         

        v8[2] = v13;

        v8[3] = 61;

        v8 += 4;

    }

    *v8 = 0;

    return &v8[-a1 + 1];

}

__int64 sub_882C(__int64 result)

{

    int i; // r2

    result &= 0xFFFFFFFFFFFFFFFFULL;  // 将result的低64位保留,高64位设置为0

    for (i = 0; i != 3; ++i)

        *(_DWORD*)(result + 4 * i) = 0;

    return result;

}

DWORD* sub_8740(__int64 a1)

{

    int v1=0; // r4

    v1 = a1;

    *(_DWORD*)a1 = 0;

    *(_DWORD*)(a1 + 4) = 0;

    *(_DWORD*)(a1 + 8) = 0;

    sub_882C(a1);

    return (_DWORD*)v1;

}

int main()

{  

    char str1[] = "xVjHx-D&nji8i*rBZ)j";

    string str(str1);

    int n = str.length();

    int param2 = 0;

    char* v17=nullptr;

    v17 = (char*)param2;

    unsigned char* str2 = sub_8B04(int(""), (int)str1, n);

}

image-20231104111904900

使用JS对加密算法进行还原

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

70

71

function Hex2Ascii(string){

    var hexArray = string.split(" ");   // 拆分字符串为数组

    var asciiArray = hexArray.map(function(hex) {

        var decimal = parseInt(hex, 16);  // 将16进制数转换为10进制数

        return String.fromCharCode(decimal);  // 将10进制数转换为对应的ASCII字符

    });

    var result = asciiArray.join("");  // 将字符数组合并为一个字符串

    return result;

}

function stringToUint8Array(str){

    var arr = [];

    for (var i=0;i<str.length;i++)

    {

        arr.push(str.charCodeAt(i));

    }

    var outputbytes = new Uint8Array(arr);

    return outputbytes;

}

function sub_8B04(str,len){

    var string = "52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27"

    var aAyzpq23ijrtffg = Hex2Ascii(string);    //生成码表

    var v8 ="";

    var v3 = len-2;

    var v6 =0;

    var v7=0;

    var v9=0;

    var v12 =0;

    var a2 = stringToUint8Array(str);

    while (1){

    if (v7>=v3){

        break;

    }

    v9 = a2[v7];

    var v9_1 = a2[v7+1];

    var v9_2 =a2[v7+2];

    v6 +=4;

    v8 += aAyzpq23ijrtffg.charAt(a2[v7] >>2 );

    var v10 = a2[v7+1];

    var v11 = a2[v7];

    v7 +=3;

    v8 += aAyzpq23ijrtffg.charAt((v10>>4)&0xFFFFFFCF |(16* (v11 &3)));

    v8 += aAyzpq23ijrtffg.charAt(((v9_2) >>6)& 0xFFFFFFC3 | 4*((v9_1)&0xF));

    v8 += aAyzpq23ijrtffg[(v9_2)&0x3F]

    }

    if(v7 <len){

        v8+=aAyzpq23ijrtffg[a2[v7]];

        v12 =(16 * a2[v7])&0x03;

        if (len -1 ==v7){

            var v13 = 61;

            v8 += aAyzpq23ijrtffg[v12];

        }

        else {

            v8 +=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4];

            v13 = aAyzpq23ijrtffg[4*a2[v7+1] & 0xF]

        }

        v8 += String.fromCharCode(v13);

        v8 += String.fromCharCode(61);

        return v8;

    }

}

var str = "xVjHx-D&nji8i*rBZ)j";

console.log(sub_8B04(str,str.length))

image-20231104111644647

使用python还原加密流程

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

def Hex2Ascii(string):

    hex_list = string.split()  # 分割字符串生成包含十六进制数的列表

    ascii_list = []

    for hex_str in hex_list:

        decimal = int(hex_str, 16)  # 将十六进制数转换为十进制数

        ascii_char = chr(decimal)  # 将十进制数转换为ASCII字符

        ascii_list.append(ascii_char)

    result = ''.join(ascii_list)  # 将ASCII字符列表合并为一个字符串

    return  result

def sub_8B04(str,len):

    string = "52 4a 49 63 62 21 20 5a 59 61 47 55 75 74 7b 7a 79 78 7f 50 57 56 22 58 5f 5e 7e 51 77 76 60 67 46 26 25 24 2b 54 5b 69 23 70 66 65 64 72 71 5d 2a 38 3c 45 44 4b 7d 7c 5c 43 42 41 40 6b 6a 27"

    # 生成码表

    aAyzpq23ijrtffg = Hex2Ascii(string)

    v8 =""

    v3 = len-2

    v6 =0

    v7=0

    v9=0

    a2 = bytes(str,"utf-8")

    while True:

        if v7>=v3:

            break

        v9=a2[v7]

        v9_1 = a2[v7+1]

        v9_2 =a2[v7+2]

        v6+=4

        v8 += aAyzpq23ijrtffg[a2[v7] >>2]

        v10 =a2[v7+1]

        v11 = a2[v7]

        v7+=3

        v8+=aAyzpq23ijrtffg[(v10>>4)&0xFFFFFFCF |(16* (v11 &3))]

        v8+=aAyzpq23ijrtffg[((v9_2) >>6)& 0xFFFFFFC3 4*((v9_1)&0xF)]

        v8+=aAyzpq23ijrtffg[(v9_2)&0x3F]

    if v7<len:

        v8 += aAyzpq23ijrtffg[a2[v7] >>2]

        v12 = (16* a2[v7]) &0x30

        if(len -1 == v7):

            v13 =61

            v8 +=aAyzpq23ijrtffg[v12]

        else:

            v8+=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4]

            v13 = aAyzpq23ijrtffg[4*a2[v7+1] &0xF]

        v8+=chr(v13)

        v8+=chr(61)

        return v8

if __name__ == '__main__':

    str1="xVjHx-D&nji8i*rBZ)j"

    result=sub_8B04(str1,len(str1))

    print(result)

image-20231104112034664

题目三:

JAVA层

algorithmbase_13.apk

使用JADX打开apk文件

image-20231104215709725

通过调用MainActivity类中的encodeFromJni_12方法,将randomAscii字符串进行加密,并将加密后的结果存储在encodeFromJni_12字符串中。然后,使用Log.e方法将randomAscii字符串和加密后的结果一起打印出来,以便进行调试和分析。

SO

使用IDA打开,按照以前的思路继续从下往上分析

按照第二题的思路,来到sub_8B04

image-20231104220620285

发现和上一题加密没啥区别就只是多了和参数a3的异或

使用Frida Hook 一下传入参数和传出参数

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

function hook_js(){

    Java.perform(function(){

    var enrandomcode = Java.use("com.kanxue.algorithmbase.MainActivity")

    //如果定义好了

    if(enrandomcode !=undefined)

        {

          console.log("开始JAVA层Hook\r\n");

          enrandomcode.encodeFromJni_13.implementation = function(intput){

            var res = this.encodeFromJni_13(intput);

            console.log("\n传入参数是====>"+intput);

            console.log("加密后的参数是====>"+res);

            return res;

          }

        }

    })

}

function hook_native(){

    Java.perform(function(){

        var base_address = Module.getBaseAddress("libnative-lib.so");

        if(base_address!=undefined)

        {

            console.log("获取到基地址");

            var sub_8B04=base_address.add(0x8B04+1);

            //开启拦截器

            Interceptor.attach(sub_8B04,{

                onEnter:function(args){

                    this.arg0 = args[0];

                    this.arg1 = args[1];

                    this.arg2 = args[2];

                    console.log("sub_80B4传进来的第一个参数是===>"+this.arg0.readCString());

                    console.log("sub_80B4传进来的第二个参数是===>"+this.arg1.readCString());

                    //console.log("sub_80B4传进来的第三个参数是===>"+this.arg2.readCString());

                    var sub_1B000 = base_address.add(0x1B000);

                    if(sub_1B000!=undefined){

                    console.log("aAyzpq23ijrtffg===>"+ hexdump(sub_1B000));

                    }

     

                },onLeave:function(nresult){

                    console.log("sub_80B4对传入参数处理后的结果是===>"+nresult);

                    console.log("sub_80B4对传入参数处理后的结果是===>"+this.arg0.readCString());

     

                }

            })

        }

    })

}

function main(){

    hook_js();

    hook_native();

}

setImmediate(main);

image-20231104220256358

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

[LGE Nexus 5X::com.kanxue.algorithmbase]-> 开始JAVA层Hook

获取到基地址

sub_80B4传进来的第一个参数是===>

sub_80B4传进来的第二个参数是===>cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z

aAyzpq23ijrtffg===>           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF

ca86a000  5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74  \DGml/.TWoI[{zut

ca86a010  77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69  wvq^YX,VQPp_yxni                                                                                 

ca86a020  48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53  H(+*%ZUg-~hkj|.S

ca86a030  24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29  $62KJEsrRMLONed)

ca86a040  00 00 00 00 fd 91 85 ca d9 92 85 ca b8 47 86 ca  .............G..

ca86a050  78 a2 86 ca 00 00 00 00 00 00 00 00 00 00 00 00  x...............

ca86a060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a080  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a090  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0a0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0b0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0c0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0d0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0e0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ca86a0f0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

sub_80B4对传入参数处理后的结果是===>0x29

sub_80B4对传入参数处理后的结果是===>LEYutgckTV+[d.E-eKeL1UOOD/Dskseims0h,E-=

hook的结果更加验证了我们的思路

直接开始还原算法验证

C++代码如下

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

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

#include <iostream>

#include <memory>

#include <Windows.h> 

#include <minwindef.h>

#include <rpcndr.h>

#include <intsafe.h>

#include <iostream>

#include <sstream>

#include <string>

#include <vector>

#define  _DWORD DWORD

using namespace std;

DWORD v16[2] = { 1, 2 };  // 将数组的所有元素初始化为0

string Hex2Ascii(string input) {

    std::stringstream ss(input);

    std::string token;

    std::vector<char> characters;

    while (std::getline(ss, token, ' ')) {

        int ascii = std::stoi(token, nullptr, 16);

        char c = static_cast<char>(ascii);

        characters.push_back(c);

    }

    std::string result(characters.begin(), characters.end());

    return result;

}

unsigned char* sub_8B04(int a1, int a2, int a3)

{

    int v3 = 0; // r12

    int v6 = 0; // r6

    int v7 = 0; // r5

    unsigned char* v8 = new unsigned char[100]; // r3

    unsigned char* address = v8;//保存v8的首地址

    memset(v8, '\0', 100); // 将 v8 的所有元素都设置为 '1'

    int v9 = 0; // r2

    unsigned int v10 = 0; // r0

    char v11 = '\0'// r4

    int v12 = 0; // r6

    char v13 = '\0'// r1

    std::string input = "5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29";

    string aAyzpq23ijrtffg = Hex2Ascii(input);

    v3 = a3 - 2;

    v6 = 0;

    v7 = 0;

    while (1)

    {

        v8 = (unsigned __int8*)(address + v6);

        if (v7 >= v3)

            break;

        v9 = a2 + v7;   //开始出现a2(传进来的参数)

        v6 += 4;

        *v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2] ^a3;

        v10 = *(unsigned __int8*)(a2 + v7 + 1);

        v11 = *(unsigned char*)(a2 + v7);

        v7 += 3;

        v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | (16 * (v11 & 3))];

        v8[2] = aAyzpq23ijrtffg[(*(unsigned __int8*)(v9 + 2) >> 6) & 0xFFFFFFC3 | (4 * (*(unsigned char*)(v9 + 1) & 0xF))] ^ a3;

        v8[3] = aAyzpq23ijrtffg[*(unsigned char*)(v9 + 2) & 0x3F];

    }

    if (v7 < a3)

    {

        *v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2];

        v12 = (16 * *(unsigned __int8*)(a2 + v7)) & 0x30;

        if (a3 - 1 == v7)

        {

            v13 = 61;

            v8[1] = aAyzpq23ijrtffg[v12];

        }

        else

        {

            v8[1] = aAyzpq23ijrtffg[v12 | (*(unsigned __int8*)(a2 + v7 + 1) >> 4)];

            v13 = aAyzpq23ijrtffg[4 * (*(unsigned __int8*)(a2 + v7 + 1) & 0xF)];

        }

        v8[2] = v13;

        v8[3] = 61;

        v8 += 4;

    }

    *v8 = 0;

    return &v8[-a1 + 1];

}

__int64 sub_882C(__int64 result)

{

    int i; // r2

    result &= 0xFFFFFFFFFFFFFFFFULL;  // 将result的低64位保留,高64位设置为0

    for (i = 0; i != 3; ++i)

        *(_DWORD*)(result + 4 * i) = 0;

    return result;

}

DWORD* sub_8740(__int64 a1)

{

    int v1 = 0; // r4

    v1 = a1;

    *(_DWORD*)a1 = 0;

    *(_DWORD*)(a1 + 4) = 0;

    *(_DWORD*)(a1 + 8) = 0;

    sub_882C(a1);

    return (_DWORD*)v1;

}

int main()

{

    char str1[] = "cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z";

    string str(str1);

    int n = str.length();

    int param2 = 0;

    char* v17 = nullptr;

    v17 = (char*)param2;

    unsigned char* str2 = sub_8B04(int(""), (int)str1, n);

    std::cout << str2 << std::endl;

}

image-20231104220755239

JS代码如下

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

70

71

72

function Hex2Ascii(string){

    var hexArray = string.split(" ");   // 拆分字符串为数组

    var asciiArray = hexArray.map(function(hex) {

        var decimal = parseInt(hex, 16);  // 将16进制数转换为10进制数

        return String.fromCharCode(decimal);  // 将10进制数转换为对应的ASCII字符

    });

    var result = asciiArray.join("");  // 将字符数组合并为一个字符串

    return result;

}

function stringToUint8Array(str){

    var arr = [];

    for (var i=0;i<str.length;i++)

    {

        arr.push(str.charCodeAt(i));

    }

    var outputbytes = new Uint8Array(arr);

    return outputbytes;

}

function sub_8B04(str,len){

    var string =  "5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29";

    var aAyzpq23ijrtffg = Hex2Ascii(string);    //生成码表

    var v8 ="";

    var v3 = len-2;

    var v6 =0;

    var v7=0;

    var v9=0;

    var v12 =0;

    var a2 = stringToUint8Array(str);

    var a3 = len;

    while (1){

    if (v7>=v3){

        break;

    }

    v9 = a2[v7];

    var v9_1 = a2[v7+1];

    var v9_2 =a2[v7+2];

    v6 +=4;

    v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(a2[v7] >>2 ) ^ a3);

    var v10 = a2[v7+1];

    var v11 = a2[v7];

    v7 +=3;

    v8 += aAyzpq23ijrtffg.charAt((v10>>4)&0xFFFFFFCF |(16* (v11 &3))) ;

    v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(((v9_2) >>6)& 0xFFFFFFC3 | 4*((v9_1)&0xF)) ^ a3);

    v8 += aAyzpq23ijrtffg[(v9_2)&0x3F]

    }

    if(v7 <a3){

        v8+=aAyzpq23ijrtffg[a2[v7] >>2];

        v12 =(16 * a2[v7])& 0x30;

        if (len -1 ==v7){

            var v13 = 61;

            v8 += aAyzpq23ijrtffg[v12];

        }

        else {

            v8 +=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4];

            v13 = aAyzpq23ijrtffg[4*(a2[v7+1] & 0xF)]

        }

        v8 += v13;

        v8 += String.fromCharCode(61);

        return v8;

    }

}

var str = "cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z";

console.log(sub_8B04(str,str.length))

image-20231104224728140

python代码如下

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

def Hex2Ascii(string):

    hex_list = string.split()  # 分割字符串生成包含十六进制数的列表

    ascii_list = []

    for hex_str in hex_list:

        decimal = int(hex_str, 16)  # 将十六进制数转换为十进制数

        ascii_char = chr(decimal)  # 将十进制数转换为ASCII字符

        ascii_list.append(ascii_char)

    result = ''.join(ascii_list)  # 将ASCII字符列表合并为一个字符串

    return  result

def sub_8B04(str,len):

    string = "5c 44 47 6d 6c 2f 2e 54 57 6f 49 5b 7b 7a 75 74 77 76 71 5e 59 58 2c 56 51 50 70 5f 79 78 6e 69 48 28 2b 2a 25 5a 55 67 2d 7e 68 6b 6a 7c 7f 53 24 36 32 4b 4a 45 73 72 52 4d 4c 4f 4e 65 64 29";

    # 生成码表

    aAyzpq23ijrtffg = Hex2Ascii(string)

    v8 =""

    v3 = len-2

    v6 =0

    v7=0

    v9=0

    a2 = bytes(str,"utf-8")

    a3 = len

    while True:

        if v7>=v3:

            break

        v9=a2[v7]

        v9_1 = a2[v7+1]

        v9_2 =a2[v7+2]

        v6+=4

        v8 += chr(ord(aAyzpq23ijrtffg[a2[v7] >>2]) ^ a3)

        v10 =a2[v7+1]

        v11 = a2[v7]

        v7+=3

        v8+=aAyzpq23ijrtffg[(v10>>4)&0xFFFFFFCF |(16* (v11 &3))]

        v8+= chr(ord(aAyzpq23ijrtffg[((v9_2) >>6)& 0xFFFFFFC3 4*((v9_1)&0xF)])^ a3)

        v8+=aAyzpq23ijrtffg[(v9_2)&0x3F]

    if v7<a3:

        v8 += aAyzpq23ijrtffg[a2[v7] >>2]

        v12 = (16* a2[v7]) &0x30

        if(a3 -1 == v7):

            v13 =61

            v8 +=aAyzpq23ijrtffg[v12]

        else:

            v8+=aAyzpq23ijrtffg[v12 | (a2[v7+1]) >>4]

            v13 = aAyzpq23ijrtffg[4*(a2[v7+1] &0xF)]

        v8+=v13

        v8+=chr(61)

        return v8

if __name__ == '__main__':

    str1="cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z"

    result=sub_8B04(str1,len(str1))

    print(result)

image-20231104231103817

JS还原C++函数总结

字符串篇
C++字符串型的传参在JS中的变化

C++的函数传参是如果char数组,当使用JS对其进行还原时候,为了方便后面字符串型参数在JS中计算字符串偏移,最好把字符串转为8位无符号整型数组Uint8Array(对应是C++中的(unsigned char*))

如下

1

2

3

4

5

6

7

8

9

10

function stringToUint8Array(str){

    var arr = [];

    for (var i=0;i<str.length;i++)

    {

        arr.push(str.charCodeAt(i));

    }

    var outputbytes = new Uint8Array(arr);

    return outputbytes;

}

这么看不太直观。拿刚才的第三题举例
C++中

1

2

3

4

5

6

7

char str1[] = "cPN~zk)|Kpehw7zZn;PU6Gg_kj*[Z";

string str(str1);

int n = str.length();

int param2 = 0;

char* v17 = nullptr;

v17 = (char*)param2;

unsigned char* str2 = sub_8B04(int(""), (int)str1, n);

这里面str1是char数组被强转为整型当成传参进入的sub_8B04 函数

但在JS中,我们进入sub_8B04 就是传参str是字符串,不需要变整型

image-20231105115216312

为了方便后面的字节偏移计算,我们再把字符串str 转为8位无符号整型数组

1

var a2 = stringToUint8Array(str);

C++ 函数里面与字符串类型传参相关的参数 在JS中的还原

1

2

3

4

5

unsigned char* sub_8B04(int a1, int a2, int a3)

{  

    int v7 = 0; // r5

    v9 = a2 + v7;   //开始出现a2(传进来的参数)

}

在JS中还原C++函数sub_8B04里面的参数V9思路:

因为a2 原先是被int强转char数组。v9是(char*)a2数组偏移v7个字节后的参数
在前面的步骤中,a2在JS中已经被我们设置为8位无符号整型数组,所以v9=a2[v7]

1

2

3

4

function sub_8B04(str,len){

    var a2 = stringToUint8Array(str);

    v9 = a2[v7];

}  

根据这个思路

使用JS还原一下C++代码中* (unsigned __int8*)(v9 + 2) 和 * (unsigned __int8*)(v9 + 2) 代码如下

1

2

var v9_1 = a2[v7+1];

var v9_2 =a2[v7+2];

字节运算

由于JS语言的特性,不能直接把字符和数字进行直接的运算,所以要把字符转为Unicode 编码与数字进行运算后,再把得到的结果重新转为字符

需要用js的charat charCodeAt fromCharCode 三个函数进行转化这里面贴一下介绍

charAt 方法用于返回指定索引位置的字符。索引位置从0开始计数。

语法: string.charAt(index)

示例:

Copy

1

2

3

var str = "Hello World";

console.log(str.charAt(0)); // 输出 "H"

console.log(str.charAt(6)); // 输出 "W"

charCodeAt 方法返回指定索引位置的字符的Unicode编码。索引位置从0开始计数。

语法: string.charCodeAt(index)

示例:

1

2

3

var str = "Hello World";

console.log(str.charCodeAt(0)); // 输出 72

console.log(str.charCodeAt(6)); // 输出 87

fromCharCode 方法从Unicode编码创建一个字符串。

语法: String.fromCharCode(number1, number2, ... , numberX)

示例:

1

console.log(String.fromCharCode(72, 101, 108, 108, 111)); // 输出 "Hello"

现在分析一下前面第三题贴的C代码

1

*v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2] ^a3;

这段C代码的含义是将变量a2和v7的和作为地址,取出该地址处的一个字节数据,将其右移两位后转换为无符号8位整数,然后使用这个值作为索引,从数组aAyzpq23ijrtffg中取出对应位置的值,与变量a3进行异或操作,并将结果赋给变量v8。

在JS中aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2] 这个字符要和a3进行异或,a3是整型不能和字符异或,所以我们要把先把字符转为Unicode码再和a3进行异或运算,然后转为字符串如下

1

v8 += String.fromCharCode(aAyzpq23ijrtffg.charCodeAt(a2[v7] >>2 ) ^ a3);

Python 还原C++函数总结

字符串篇
C++字符串型的传参在python中的变化

C++的函数传参是如果char数组,当使用python对其进行还原时候,为了方便后面字符串型参数在python中计算字符串偏移,最好把字符串转为bytes数组(对应是C++中的(unsigned char*))

如下

1

2

def sub_8B04(str,len):

    a2 = bytes(str,"utf-8")

C++ 函数里面与字符串类型传参相关的参数 在python中的还原

1

2

3

4

5

unsigned char* sub_8B04(int a1, int a2, int a3)

{  

    int v7 = 0; // r5

    v9 = a2 + v7;   //开始出现a2(传进来的参数)

}

在python中还原C++函数sub_8B04里面的参数V9思路:

因为a2 原先是被int强转char数组。v9是(char*)a2数组偏移v7个字节后的参数
在前面的步骤中,a2在python中已经被我们设置为bytes数组,所以v9=a2[v7]

1

v9 = a2[v7];

根据这个思路

使用python还原一下C++代码中* (unsigned __int8*)(v9 + 2) 和 * (unsigned __int8*)(v9 + 2) 代码如下

1

2

v9_1 = a2[v7+1]

v9_2 =a2[v7+2]

字节运算

由于python语言的特性,不能直接把字符和数字进行直接的运算,所以要把字符转为Unicode 编码与数字进行运算后,再把得到的结果重新转为字符

chr和ord是Python中的内置函数,用于字符和对应的Unicode编码之间的转换。

chr函数接受一个整数参数,返回对应的字符。例如:

1

2

3

print(chr(65))  # 输出A

print(chr(97))  # 输出a

print(chr(8364))  # 输出€

ord函数接受一个字符参数,返回对应的Unicode编码。例如:

1

2

3

print(ord('A'))  # 输出65

print(ord('a'))  # 输出97

print(ord('€'))  # 输出8364

通过chr和ord函数,我们可以方便地在字符和对应的Unicode编码之间进行转换。

现在分析一下前面第三题贴的C代码

1

*v8 = aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2] ^a3;

这段C代码的含义是将变量a2和v7的和作为地址,取出该地址处的一个字节数据,将其右移两位后转换为无符号8位整数,然后使用这个值作为索引,从数组aAyzpq23ijrtffg中取出对应位置的值,与变量a3进行异或操作,并将结果赋给变量v8。

在python中aAyzpq23ijrtffg[*(unsigned __int8*)(a2 + v7) >> 2] 这个字符要和a3进行异或,a3是整型不能和字符异或,所以我们要把先把字符转为Unicode码再和a3进行异或运算,然后转为字符串如下

1

v8 += chr(ord(aAyzpq23ijrtffg[a2[v7] >>2]) ^ a3)

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

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

相关文章

微信支付服务商消费者投诉及时处理与商户违规及时通知,支持多服务商

大家好&#xff0c;我是小悟 微信直连商户处理消费者投诉的功能解决了很多商户对于投诉处理不及时而导致商户号出现异常的问题&#xff0c;可以说解决了实实在在的问题。 很多小伙伴私信说自己是服务商角色&#xff0c;也需要微信支付服务商处理消费者投诉的功能&#xff0c;…

JS移动端触屏事件

在我们PC端中有许多的事件&#xff0c;那我们在移动端有没有事件呢&#xff1f;让我为大家介绍一下移动端常用的事件&#xff0c;触屏事件 触屏事件 touch (也称触摸事件)&#xff0c;Android 和IOS 都有 touch 对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一…

加班把数据库重构完毕

加班把数据库重构完毕 本文的数据库重构是基于 clickhouse 时序非关系型的数据库。该数据库适合存储股票数据&#xff0c;速度快&#xff0c;一般查询都是 ms 级别&#xff0c;不需要异步查询更新界面 ui。 达到目标效果&#xff1a;数据表随便删除&#xff0c;重新拉数据以及指…

Matter学习笔记(2)——数据模型和设备类型

一、设备数据模型 Matter 中的设备具有明确定义的 数据模型(DM)&#xff0c;它是设备功能的分层建模。使用 属性(Attribute)、命令(Command) 和 事件(Event) 的概念描述 Matter 节点支持的远程操作&#xff0c;并分组为称为集群的逻辑块。Matter 应用集群规范中包含的集群具有…

POJ 3254 Corn Fields 状态压缩DP(铺砖问题)

一、题目大意 我们要在N * M的田地里种植玉米&#xff0c;有如下限制条件&#xff1a; 1、对已经种植了玉米的位置&#xff0c;它的四个相邻位置都无法继续种植玉米。 2、题目中有说一些块无论如何&#xff0c;都无法种植玉米。 求所有种植玉米的方案数&#xff08;不种植也…

【Java 进阶篇】JQuery DOM操作:轻松驾驭网页内容的魔法

在前端开发的舞台上&#xff0c;DOM&#xff08;文档对象模型&#xff09;是我们与网页内容互动的关键。而JQuery作为一个轻量级的JavaScript库&#xff0c;为我们提供了便捷而强大的DOM操作工具。在本篇博客中&#xff0c;我们将深入探讨JQuery的DOM内容操作&#xff0c;揭开这…

外星人笔记本键盘USB协议逆向

前言 我朋友一台 dell g16 购买时直接安装了linux系统&#xff0c;但是linux上没有官方的键盘控制中心&#xff0c;所以无法控制键盘灯光&#xff0c;于是我就想着能不能逆向一下键盘的协议&#xff0c;然后自己写一个控制键盘灯光的程序。我自己的外星人笔记本是m16&#xff…

阿里系APP崩了?回应来了!

最近&#xff0c;阿里云遭遇了一场可怕的疑似故障&#xff0c;引起了广泛的关注和热议。各种消息纷传&#xff0c;阿里云盘崩了&#xff0c;淘宝又崩了&#xff0c;闲鱼也崩了&#xff0c;连钉钉也不幸中招。这一系列故障让人不禁发问&#xff1a;阿里系的APP都崩了&#xff0c…

【Unity每日一记】“调皮的协程”,协程和多线程的区别在哪里

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

msvcp120.dll丢失的6种解决方法,教你如何修复dll文件丢失

“找不到msvcp120dll,无法继续执行代码的6个修复方案”。我相信很多朋友在运行某些程序时&#xff0c;可能会遇到这样的错误提示&#xff1a;“找不到msvcp120dll&#xff0c;无法继续执行代码”。那么&#xff0c;msvcp120dll究竟是什么&#xff1f;为什么会丢失呢&#xff1f…

发布订阅者模式(观察者模式)

目录 应用场景 1.结构 2.效果 3.代码 3.1.Main方法的类【ObserverPatternExample】 3.2.主题&#xff08;接口&#xff09;【Subject】 3.3.观察者&#xff08;接口&#xff09;【Observer】 3.4.主题&#xff08;实现类&#xff09;【ConcreteSubject】 3.5.观察者&a…

qemu 之 uboot、linux 启动

目录 编译uboot、kernel 编译启动从 uboot 中引导启动 linux注参考 本文主要说明 arm64 在 qemu 上的相关启动。 编译 使用的是 qemu-8.1.1 版本&#xff0c;编译命令如下: ../configure --cc/usr/local/bin/gcc --prefix/home/XXX/qemu_out --enable-virtfs --enable-slir…

Three.js——基于原生WebGL封装运行的三维引擎

文章目录 前言一、什么是WebGL&#xff1f;二、Three.js 特性 前言 Three.js中文官网 Three.js是基于原生WebGL封装运行的三维引擎&#xff0c;在所有WebGL引擎中&#xff0c;Three.js是国内文资料最多、使用最广泛的三维引擎。既然Threejs是一款WebGL三维引擎&#xff0c;那么…

RAG相关内容介绍

本文记录在查找RAG相关内容时所整合的一些资料与内容&#xff0c;还有一个组会报告的PPT 文章目录 定义LLM的知识更新难题 RAG是什么&#xff1f;-“开卷考试”RAG原理与技术RAG技术细节一、数据索引• 数据提取• 分块&#xff08;Chunking&#xff09;分块方式确定应用程序的…

摊牌 了,我不藏了,上线了一年多的网站还是广而告之吧!

大家好&#xff0c;我是大明哥&#xff0c;一个专注「死磕 Java」系列文章创作的程序员。 本文已收录到我的小站&#xff1a;https://skjava.com。 从去年开始一直有小伙伴问我&#xff0c;大明哥&#xff0c;你的网站怎么打不开了&#xff1f;我只能苦涩地跟他说&#xff0c;没…

DevChat智能编程助手:小白也能轻松上手的开发利器

DevChat智能编程助手&#xff1a;小白也能轻松上手的开发利器 一、DevChat介绍1.1 DevChat简介1.2 DevChat特点1.3 DevChat官网 二、注册DevChat账号2.1 访问DevChat官网2.2 注册账号2.3 复制Access Key2.4 登录DevChat 三、安装DevChat3.1 打开VS Code软件3.2 安装DevChat3.3 …

(免费领源码)java#ssm#mysql在线学习平台85204-计算机毕业设计项目选题推荐

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;在线学习平台当然也不能排除在外。在线学习平台是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#x…

Linux下C++调用python脚本实现LDAP协议通过TNLM认证连接到AD服务器

1.前言 首先要实现这个功能&#xff0c;必须先搞懂如何通过C调用python脚本文件最为关键&#xff0c;因为两者的环境不同。本质上是在 c 中启动了一个 python 解释器&#xff0c;由解释器对 python 相关的代码进行执行&#xff0c;执行完毕后释放资源。 2 模块功能 2.1python…

基于樽海鞘群算法优化概率神经网络PNN的分类预测 - 附代码

基于樽海鞘群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于樽海鞘群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于樽海鞘群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

CnosDB 在最近新发布的 2.4.0 版本中增加对时空函数的支持。

CnosDB 在最近新发布的 2.4.0 版本中增加对时空函数的支持。 概述 时空函数是一种用于描述时空结构和演化的函数。它在物理学、数学和计算机科学等领域中都有广泛的应用。时空函数可以描述物体在时空中的位置、速度、加速度以及其他相关属性。 用法 CnosDB 将使用一种全新的…