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

news2024/11/17 10:05:51

题目一

使用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)

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

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

相关文章

Git Commit 之道:规范化 Commit Message 写作指南

1 commit message 规范 commit message格式都包括三部分&#xff1a;Header&#xff0c;Body和Footer <type>(<scope>): <subject><body><footer>Header是必需的&#xff0c;Body和Footer则可以省略 1.1 Header Type&#xff08;必需&#xf…

【Qt之Model/View】编程

Model/View编程介绍 Qt包含一组使用模型/视图架构来管理数据和用户呈现的关系的视图类。此架构引入的功能分离使开发人员可以更灵活地自定义项的呈现方式&#xff0c;并提供标准的模型接口&#xff0c;以允许各种数据源与现有项视图一起使用。在本文档中&#xff0c;我们简要介…

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…

网络原理-UDP/TCP详解

一. UDP协议 UDP协议端格式 由上图可以看出&#xff0c;一个UDP报文最大长度就是65535. • 16位长度&#xff0c;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的最大长度&#xff08;注意&#xff0c;这里的16位UDP长度只是一个标识这个数据报长度的字段&#xff0…

[Android]修改应用包名、名称、版本号、Icon以及环境判断和打包

1.修改包名 在Android Studio中更改项目的包名涉及几个步骤&#xff1a; 打开项目结构: 在Android Studio中&#xff0c;确保您处于Android视图模式&#xff08;在左侧面板顶部有一个下拉菜单可以选择&#xff09;。 重命名包名: 在项目视图中&#xff0c;找到您的包名&…

Linux如何修改主机名(hostname)(亲测可用)

文章目录 背景Linux如何修改主机名&#xff08;hostname&#xff09;方法方法1. 使用 hostnamectl 命令示例 2. 编辑 /etc/hostname 文件注意事项 背景 我创建虚拟机的时候没设置主机名&#xff0c;现在显示localhost&#xff0c;有点尴尬&#x1f605;&#xff1a; 需要重新设…

深入了解JVM和垃圾回收算法

1.什么是JVM&#xff1f; JVM是Java虚拟机&#xff08;Java Virtual Machine&#xff09;的缩写&#xff0c;是Java程序运行的核心组件。JVM是一个虚拟的计算机&#xff0c;它提供了一个独立的运行环境&#xff0c;可以在不同的操作系统上运行Java程序。 2.如何判断可回收垃圾…

单链表按位序与指定结点 删除

按位序删除(带头结点) #define NULL 0 #include<stdlib.h>typedef struct LNode {int data;struct LNode* next; }LNode, * LinkList;//按位序删除&#xff08;带头结点&#xff09; bool ListInsert(LinkList& L, int i, int& e) {if (i < 1)return false;L…

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

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

基于Matlab+ AlexNet神经网络的动物识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Matlab和AlexNet神经网络的动物识别系统可以用于自然图像识别等场景&#xff0c;以下是一个基本的介绍设计步骤…

ARM64 linux并发与同步之内存屏障

1.2 内存屏障 1.2.1 概念理解 原理部分比较苦涩难懂&#xff0c;我们先不过多详细介绍这部分的由来和经过&#xff0c;接下来着重讲解什么用途和实现&#xff1b; ARM64架构中提供了3条内存屏障指令。 数据存储屏障(Data Memory Barrier, DMB)指令。数据同步屏障(Data Synch…

Thinkphp8 - 连接多个数据库

// 数据库连接配置信息connections > [mysql > [// 数据库类型type > mysql,// 服务器地址hostname > 127.0.0.1,// 数据库名database > thinkphp,// 用户名username > env(DB_USER, root),// 密码password >…

Yolo自制detect训练

Install 把代码拉下来 GitHub - ultralytics/yolov5 at v5.0 然后 pip install -r requirements.txt 安装完了,运行一下detect.py即可 结果会保存在对应的目录下 Intro ├── data:主要是存放一些超参数的配置文件(这些文件(yaml文件)是用来配置训练集和测试集还有验…

clouldcompare工具使用

文章目录 1.界面1.1 布局1.3 视觉显示方向1.4 放大镜1.5 建立旋转中心2.快速入门2.1 剪裁2.2 多点云拼接 1.界面 1.1 布局 参考&#xff1a;https://blog.csdn.net/lovely_yoshino/article/details/129595201 1.3 视觉显示方向 1.4 放大镜 1.5 建立旋转中心 2.快速入门 2.1 …

腾讯域名优惠卷领取

腾讯域名到到期了&#xff0c;听说申请此计划&#xff0c;可获得优惠卷&#xff0c;看到网上5年域名只需要10元&#xff0c;姑且试试看。 我的博客即将同步至腾讯云开发者社区&#xff0c;邀请大家一同入驻&#xff1a;https://cloud.tencent.com/developer/support-plan?in…

UE地形系统材质混合实现和Shader生成分析(UE5 5.2)

前言 随着电脑和手机硬件性能越来越高&#xff0c;游戏越来越追求大世界&#xff0c;而大世界非常核心的一环是地形系统&#xff0c;地形系统两大构成因素&#xff1a;高度和多材质混合&#xff0c;此篇文章介绍下UE4/UE5 地形的材质混合方案----基于WeightMap混合。 材质层 …

每日一题(LeetCode)----数组--长度最小的子数组

每日一题(LeetCode)----数组–长度最小的子数组 1.题目&#xff08; 209.长度最小的子数组&#xff09; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &…

敏捷开发是什么?敏捷开发流程是怎么样的?

1. 什么是敏捷开发&#xff1f; 敏捷开发是一种迭代、增量式的软件开发方法&#xff0c;旨在通过灵活、协作和快速响应变化的方式&#xff0c;提高开发团队的效率和产品的质量。相较于传统的瀑布式开发模型&#xff0c;敏捷开发更加注重用户需求的响应和团队协作&#xff0…

elastic-job 完结篇

一 elastic-job 1.1 案例场景分析 1.设置4个分片&#xff0c;10秒执行一次。 分片弹性扩容缩容机制测试&#xff1a; 测试1&#xff1a;测试窗口1不关闭&#xff0c;再次运行main方法查看控制台日志&#xff0c;注意修改application.properties中的 server.port&#xf…

7.运算符

目录 一.算数运算符 1、算术运算符 2、比较运算符 1、等号()用来判断数字、字符串和表达式是否相等。 2、安全等于运算符(<>) 3、不等于运算符(<>或者!) 4、小于或等于运算符(<) 5、小于运算符(<) 6、IS NULL(IS NULL)&#xff0c;IS NOT NULL 运算…