[原创]记一次面试启明X辰的样本分析报告
2023-3-5 14:42:0 Author: bbs.pediy.com(查看原文) 阅读量:10 收藏

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

public byte[] G(byte[] bArr) {

    try {

        byte[] bArr2 = new byte[16];

        // 随机生成16个字节

        new Random().nextBytes(bArr2);

        // 实例化一个加密对象

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");

        // 传入密钥和随机数

        cipher.init(1, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));

        // 传入明文加密并得到返回的密文

        byte[] doFinal = cipher.doFinal(bArr);

        // AES 加密后的数据长度是不变的,这里其实就是创建了一个长度32的字节数组

        byte[] bArr3 = new byte[doFinal.length + 16];

        // 从最开始申请的随机数的下标8位开始拷贝,拷贝8个长度,放到 barr3 的起始位置

        System.arraycopy(bArr2, 8, bArr3, 0, 8);

        // 把 AES 加密后的全部数据拷贝到 barr3,从barr3的下标8开始,其实就是接着上边结束位置

        System.arraycopy(doFinal, 0, bArr3, 8, doFinal.length);

        // 从最开始申请的随机数的下标0位开始拷贝,拷贝8个长度,从barr3的下标 doFinal.length+8 开始,其实就是接着上边结束位置

        System.arraycopy(bArr2, 0, bArr3, doFinal.length + 8, 8);

        // 小结一下,bArr3 的组成如下所示:

        // bArr3 = bArr2[8-15] + aes(明文) + bArr2[0-7]

        return bArr3;

    } catch (Exception e2) {

        e2.printStackTrace();

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

private byte[] F(byte[] bArr) {

    try {

        byte[] bArr2 = new byte[16];

        // 把密文的0-7位拷贝到bArr2的8-15

        System.arraycopy(bArr, 0, bArr2, 8, 8);

        // 把密文的末端8位拷贝到bArr2的0-7

        System.arraycopy(bArr, bArr.length - 8, bArr2, 0, 8);

        // 实例化一个加密对象

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");

        // 设置模式为解密,同时传入密钥和随机数的密文

        cipher.init(2, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));

        // 申请16个字节的空间

        byte[] bArr3 = new byte[bArr.length - 16];

        // 密文的下标8开始拷贝,拷贝16个,也就是将密文的[7-23]位拷贝到bArr3

        System.arraycopy(bArr, 8, bArr3, 0, bArr.length - 16);

        // 返回解密后的明文

        return cipher.doFinal(bArr3);

    } catch (Exception e2) {

        e2.printStackTrace();

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_M1(JNIEnv *env, jobject a2, BYTE *bytes_)

{

  jbyteArray v4; // esi

  int v6; // [esp-14h] [ebp-1030h]

  unsigned int user_name_length; // [esp+0h] [ebp-101Ch]

  jbyte *byte_user_name; // [esp+4h] [ebp-1018h]

  char dest[4096]; // [esp+8h] [ebp-1014h] BYREF

  unsigned int v10; // [esp+1008h] [ebp-14h]

  v10 = __readgsdword(0x14u);

  user_name_length = (*env)->GetArrayLength(env, bytes_);

  byte_user_name = (*env)->GetByteArrayElements(env, bytes_, 0);

  memset(dest, 0, sizeof(dest));

  v6 = sub_46660((int)byte_user_name, user_name_length, dest);

  v4 = (*env)->NewByteArray(env, v6);

  (*env)->SetByteArrayRegion(env, v4, 0, v6, dest);

  (*env)->ReleaseByteArrayElements(env, bytes_, byte_user_name, 0);

  return v4;

}

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

int __cdecl sub_46660(BYTE *byte_user_name, unsigned int user_name_length, char *dest)

{

  ......

  canary = __readgsdword(0x14u);                // 开启canary保护

  memset(&key_data[32], 0, 32);

  v3 = -32;

  do

  {

    v4 = lrand48();                             // 产生一个正的长整型随机数

    key_data[v3++ + 64] = v4 + v4 / 255;        // 为 key_data[32-63] 位赋值随机数

  }

  while ( v3 );

  memset(key_data, 0, 32);

  v5 = -32;

  do

  {

    v_eax = lrand48();

    key_data[v5++ + 32] = v_eax + v_eax / 255// 为 key_data[0-31] 位赋值随机数

  }

  while ( v5 );

  memset(hash, 0, sizeof(hash));

  hmac_ctx_hash(&key_data[32], key_data, hash); // 参数1:data 参数2:key 参数3:dest

  strcpy((char *)const_key, "123456awxzcdfqwqt2wetbwerw");

  memset(encrypt_data, 0, sizeof(encrypt_data));

  evp_encrypt((int)hash, (int)&hash[32], 12, const_key, 27, (int)byte_user_name, user_name_length, encrypt_data);// 加密用户名

  memset(encrypt_data2, 0, sizeof(encrypt_data2));

  *(__m128i *)hash_temp = _mm_load_si128((const __m128i *)&hash[48]);// hash的第48个字节开始加载128bits,也就是加载16个字节

  __memcpy_chk((int)encrypt_data2, (int)encrypt_data, user_name_length + 16, 0x2800);

  if ( !(((int)(user_name_length + 16) < 0) ^ __OFADD__(16, user_name_length) | (user_name_length == -16)) )// 用户名长度为16,所以 1 ^ 0 | 0 = 1

                                                // __OFADD__  测试两数相加后是否溢出

  {

    v_esi = 0;

    v8 = user_name_length + 16;                 // v8 = 32

    if ( user_name_length >= 4294967280 )       // //检查用户名长度是否大于4294967280

      goto LABEL_15;

    if ( user_name_length + 15 > 15 )           // 检查用户名长度是否大于 0,此处跳转至LABEL15,感谢手下留情

      goto LABEL_15;

    v_esi = v8 & 0xFFFFFFF0;

    si128 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11535] + 1));

    v10 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92275));

    v11 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11534] + 1));

    v12 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92267));

    v13 = 0;

    v34 = *(__m128i *)hash_temp;

    v31 = *(__m128i *)(&(&off_1A25DC)[-11533] + 1);

    v33 = *(__m128i *)(&off_1A25DC - 92259);

    v32 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11532] + 1));

    ......

    while ( v_esi != v13 );

    if ( v8 != v_esi )

    {

LABEL_15:

      do

      {

        v28 = (v_esi + 1) ^ hash_temp[v_esi & 0xF];// hash48[0-F] 分别与 1 进行异或,也就是说src每一位如果是

                                                // 奇数:减一

                                                // 偶数:不变

        encrypt_data2[v_esi] = v28 ^ (((unsigned __int8)(encrypt_data[v_esi] + v28) >> 4) | (16

                                                                                           * (encrypt_data[v_esi] + v28)));//

                                                // encrypt_data[i] + v28 看成一个整体结果记为 res

                                                // res转成了无符号类型所以是逻辑右移,左侧用零补齐

                                                // res乘以16等价于逻辑左移4

                                                // 其实就是把res的高四位和低四位颠倒了一下

                                                // 最后再和v28异或

      }

      while ( v8 != ++v_esi );                  // 循环32

    }

  }

  memset(s, 0, 960u);

  *(_OWORD *)hash_temp = *(_OWORD *)hash;

  *(_OWORD *)&hash_temp[16] = *(_OWORD *)&hash[16];

  *(_OWORD *)&hash_temp[32] = *(_OWORD *)&hash[32];

  *(_OWORD *)&hash_temp[48] = *(_OWORD *)&hash[48];// hash_temp[0-63] = hash[0-63]

  __memcpy_chk((int)s, (int)encrypt_data2, user_name_length + 16, 960);//这里把密文进行了一次拷贝,但看起来后续没有用到

  *(_OWORD *)v39 = 0LL;

  return encrypt_4(hash_temp, user_name_length + 80, (int)v39, 16, dest);// 第三次加密得到最终的dest

}

这个函数总的来讲就是不断的将求得的hash作为key再次求hash,在分析这个函数时遇到的问题主要在于作者使用了 HMAC_Update 并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,写了一个简单的 demo 进行简单的测试。

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

unsigned int __cdecl hmac_ctx_hash(BYTE *data, BYTE *key, BYTE *res)

{

  int md; // esi

  __int64 v4; // xmm1_8

  __int64 v5; // xmm0_8

  int len; // [esp+8h] [ebp-154h] BYREF

  char v8; // [esp+Eh] [ebp-14Eh] BYREF

  char v9; // [esp+Fh] [ebp-14Dh] BYREF

  BYTE hash3[32]; // [esp+10h] [ebp-14Ch] BYREF

  BYTE hash2[32]; // [esp+30h] [ebp-12Ch] BYREF

  int ctx[52]; // [esp+50h] [ebp-10Ch] BYREF

  BYTE hash1[40]; // [esp+120h] [ebp-3Ch] BYREF

  unsigned int canary; // [esp+148h] [ebp-14h]

  canary = __readgsdword(0x14u);

  memset(hash1, 0, 32);

  HMAC_CTX_init((int)ctx);                      // 初始化一个 HMAC_CTX

  md = EVP_sha256();                            // 返回 sha256 的 EVP_MD

  HMAC_Init_ex((int)ctx, (int)key, 32, md, 0);  // 初始化HAMC_CTX上下文结构,key为秘钥,len为秘钥长度,md为计算hash的函数集合

  HMAC_Update((int)ctx, (int)data, 0);          // 向HMAC上下文输入字节流,加密并输出,传入长度为0

                                                // 经过 demo 验证,该条指令不会对上下文产生影响

  len = 0;

  HMAC_Final((int)ctx, (int)hash1, (int)&len);  // 生成最终的HMAC串,成功时len更新为HMAC的长度。

  memset(hash2, 0, sizeof(hash2));

  v9 = 1;

  HMAC_CTX_init((int)ctx);

  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把 hash1 作为 key

  HMAC_Update((int)ctx, (int)data, 0);

  HMAC_Update((int)ctx, (int)&v9, 1);           // 输入字节流 1,长度为1

  len = 0;

  HMAC_Final((int)ctx, (int)hash2, (int)&len);  // 得到第二次的 hash

  memset(hash3, 0, sizeof(hash3));

  v8 = 2;

  HMAC_CTX_init((int)ctx);

  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把第一次的 hash 作为 key 再次加密

  HMAC_Update((int)ctx, (int)hash2, 32);        // 输入字节流 hash2,长度为32

  HMAC_Update((int)ctx, (int)&v8, 1);           // 输入字节流 2,长度为1

  len = 0;

  HMAC_Final((int)ctx, (int)hash3, (int)&len);  // 得到第三次的 hash

  v4 = *(_QWORD *)&hash2[8];                    // v4 = hash2[8-15]

  *(_QWORD *)res = *(_QWORD *)hash2;            // res[0-7] = hash2[0-7]

  *((_QWORD *)res + 1) = v4;                    // res[8-15] = hash2[8-15]

  *((_QWORD *)res + 2) = *(_QWORD *)&hash2[16]; // res[16-23] = hash2[16-23]

  *((_QWORD *)res + 3) = *(_QWORD *)&hash2[24]; // res[24-31] = hash2[24-31]

  *((_QWORD *)res + 7) = *(_QWORD *)&hash3[24]; // res[56-63] = hash3[24-31]

  *((_QWORD *)res + 6) = *(_QWORD *)&hash3[16]; // res[48-55] = hash3[16-31]

  v5 = *(_QWORD *)hash3;

  *((_QWORD *)res + 5) = *(_QWORD *)&hash3[8];  // res[40-47] = hash3[8-15]

  *((_QWORD *)res + 4) = v5;                    // res[32-39] = hash3[0-7]

  return __readgsdword(0x14u);

}

这个函数的总的来说就是使用上一层传入的 hash 作为 key,加密用户名得到 encrypt_data,然后将 encrypt_data分段赋值给最终的 res_encrypt_data,分析这个函数的问题也还是在于作者使用了 EVP_EncryptUpdate 这个不熟悉的函数,并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,测试了各种情况的结果。

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

int __cdecl evp_encrypt(

        int hash,

        int iv,

        int iv_len,

        BYTE *const_key,

        int const_key_length,

        int byte_user_name,

        int user_name_length,

        char *res_encrypt_data)

{

  int ctx; // esi

  int cipher; // eax

  size_t v10; // eax

  __int64 v11; // xmm1_8

  int v13; // [esp+0h] [ebp-41Ch] BYREF

  size_t encrypt_data_len; // [esp+4h] [ebp-418h] BYREF

  BYTE encrypt_data[1024]; // [esp+8h] [ebp-414h] BYREF

  unsigned int v16; // [esp+408h] [ebp-14h]

  v16 = __readgsdword(0x14u);

  ctx = EVP_CIPHER_CTX_new();                   // 创建加密上下文

  cipher = EVP_aes_256_gcm();                   // 选择一种加密算法

  EVP_EncryptInit_ex(ctx, cipher, 0, 0, 0);     // 初始化密码上下文ctx

  EVP_CIPHER_CTX_ctrl(ctx, 9, iv_len, 0);       //

                                                // 设置向量IV的长度

  EVP_EncryptInit_ex(ctx, 0, 0, hash, iv);      // 使用hash作为key初始化ctx

  EVP_EncryptUpdate(ctx, 0, (int)&encrypt_data_len, (int)const_key, const_key_length);// 加密 const_key,让人困惑的是输出地址为0

                                                // 无法确定是否会对上下文产生影响,经过 demo 测试最终确定加密结果被丢弃

  EVP_EncryptUpdate(ctx, (int)encrypt_data, (int)&encrypt_data_len, byte_user_name, user_name_length);// 加密用户名

  memcpy(res_encrypt_data, encrypt_data, encrypt_data_len);// dest[0-15] = encrypt_data[0-15]

                                                // 将第二次EncryptUpdate的结果拷贝到dest中,而且经过动态调试得知,encrypt_data_len=16

  EVP_EncryptFinal_ex(ctx, (int)encrypt_data, (int)&v13);// 块对齐

  EVP_CIPHER_CTX_ctrl(ctx, 16, 16, (int)encrypt_data);//

  v10 = encrypt_data_len;

  v11 = *(_QWORD *)&encrypt_data[8];            // v11 = encrypt_data[8-15]

  *(_QWORD *)&res_encrypt_data[encrypt_data_len] = *(_QWORD *)encrypt_data;// dest[16-23] = encrypt_data[0-7]

  *(_QWORD *)&res_encrypt_data[v10 + 8] = v11;  // dest[24-31] = v11 = encrypt_data[8-15]

  EVP_CIPHER_CTX_free(ctx);                     // 释放上下文

  return encrypt_data_len + 16;                 // 返回加密结果的长度+16

}

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

int main()

{

    unsigned char key[32] = { 0 };

    unsigned char iv[32] = { 0 };

    unsigned char encrypt_data[32] = { 0 };

    unsigned char user_name[16] = { 'a','b','c','d', 'a','b','c','d','a','b','c','d', 'a','b','c','d' };

    const unsigned char *const_key = (const unsigned char *)"123456awxzcdfqwqt2wetbwerw";

    int len = 0;

    for (int i = 0; i < 32; i++)

    {

        key[i] = i;

        iv[i] = i;

    }

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    const EVP_CIPHER *cipher = EVP_aes_256_gcm();

    EVP_EncryptInit_ex(ctx, cipher, 0, key, iv);

    EVP_CIPHER_CTX_ctrl(ctx, 9, 12, 0);

    //EVP_EncryptUpdate(ctx, 0, &len, const_key, 27);// 该条语句注释未产生任何影响

    len = 0;

    EVP_EncryptUpdate(ctx, encrypt_data, &len, user_name, 16);

    len = 0;

    EVP_EncryptFinal_ex(ctx, encrypt_data, &len);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

int __cdecl Decrypt(int key, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, void *dest)

{

  int ctx; // esi

  int v11; // eax

  int v12; // edi

  size_t v14; // [esp+4h] [ebp-418h] BYREF

  char src[1024]; // [esp+8h] [ebp-414h] BYREF

  unsigned int v16; // [esp+408h] [ebp-14h]

  v16 = __readgsdword(0x14u);

  ctx = EVP_CIPHER_CTX_new();

  v11 = EVP_aes_256_gcm();

  EVP_DecryptInit_ex(ctx, v11, 0, 0, 0);        // 大致浏览一下参数与和之前的基本一致,只是调用的是解密函数

  EVP_CIPHER_CTX_ctrl(ctx, 9, a3, 0);

  EVP_DecryptInit_ex(ctx, 0, 0, key, a2);

  if ( a5 )

    EVP_DecryptUpdate(ctx, 0, &v14, a4, a5);

  EVP_DecryptUpdate(ctx, src, &v14, a8, a9);

  memcpy(dest, src, v14);                       // 这里也是一样,解密后的数据放到最后一个参数地址中

  EVP_CIPHER_CTX_ctrl(ctx, 17, a7, a6);

  v12 = EVP_DecryptFinal_ex(ctx, src, &v14);

  EVP_CIPHER_CTX_free(ctx);

  return v12;

}

分析完 check2 按钮后,发现相比较 generate2 缺少一个流程,那就是求用户名 hash 作为key,于是回溯了一下这个 key,通过追溯发现,这个 key 已经通过两个异或加密函数加密在了 password 中,只需要将其逆运算取出来即可使用。

在分析按钮 generate3 时很多数据静态分析时并不存在,所以这里还需要分析 _init 函数,该函数地址并没有被 IDA 分析出来,可以通过以下两个办法找到:

这个函数首先将 19ca9c + image_base 存到 buff1_60[0-3] 中,根据后面的分析结果这里存的是一个函数地址,之后在 buff1_60[28-43] 中填入了一些随机数,随后调用了 sub_48840,并传入了一个字符串

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

BYTE *__cdecl sub_48840(BYTE *buff1_16, void *src, size_t len_32)

{

  v3 = *buff1_16;

  v4 = 10;

  if ( (v3 & 1) != 0 )                          // 0 & 1 != 0 判断失败

    v4 = (*(_DWORD *)buff1_16 & 0xFFFFFFFE) - 1;

  if ( v4 >= len_32 )                           // 10 >= 32 判断失败

  {

    ......

  }

  else

  {

    if ( (v3 & 1) != 0 )                        // 判断失败

      v5 = *((_DWORD *)buff1_16 + 1);

    else

      v5 = v3 >> 1;                             // v5 = 0

    sub_488E0(buff1_16, v4, len_32 - v4, v5, 0, v5, len_32, src);//

                                                // sub_488e0(buff1_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")

                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49

                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32

                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针

  }

  return buff1_16;

}

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

// sub_488e0(buff1_60_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")

BYTE *__cdecl sub_488E0(BYTE *buff1_60_16,unsigned int a2,unsigned int a3,int a4,size_t n,int a6,size_t a7,void *src)

{

  if ( 0xFFFFFFEE - a2 < a3 )                   // 这里注意a2的类型是无符号类型,-18会转换成一个非常大的正数,所以判断失败

    sub_48A20();

  if ( (*buff1_16 & 1) != 0 )                   // buff1_16 == 0,判断失败

    v8 = (BYTE *)*((_DWORD *)buff1_16 + 2);

  else

    v8 = buff1_16 + 1;                          // v8 = buff1_17

  v18 = v8;                                     // v18 = buff1_17

  v9 = -17;                                     // v9 = -17

  if ( a2 <= 0x7FFFFFE6 )                       // 10 <= 0x7ffffff6,判断成功

  {

    v10 = a2 + a3;                              // v10 = 32; a2 = 10; a3 = 22

    if ( a2 + a3 < 2 * a2 )                     // 10 + 22 < 10 * 2 = false, 判断失败

      v10 = 2 * a2;

    v9 = 11;                                    // v9 = 11

    if ( v10 >= 0xB )                           // 32 >= 0xb,判断成功

      v9 = (v10 + 16) & 0xFFFFFFF0;             // v9 = 48

  }

  v20 = v9;                                     // v20 = 48

  buff_48 = (char *)operator new(v9);           // 申请了48个字节的空间

  buff_48_ = buff_48;

  if ( n )

    memcpy(buff_48, v18, n);

  buff_48__ = buff_48_;

  v13 = a6;                                     // v13 = 0

  v14 = a7;                                     // v14 = a7 = 32

  if ( a7 )

  {

    memcpy(&buff_48_[n], src, a7);              // buff_48[0-31] = "3390fd362dfdda0030d5737632d3d213"

    v13 = a6;                                   // v13= 0

    v14 = a7;                                   // v14 = 32

  }

  v15 = a4 - v13;                               // v15 = 0 - 0 = 0

  if ( a4 - v13 != n )                          // 0 - 0 != 0 该判断失败

  {

    memcpy(&buff_48__[n + v14], &v18[n + a6], v15 - n);

    v14 = a7;

  }

  if ( a2 != 10 )                               // 10 != 10 判断失败

  {

    operator delete(v18);

    v14 = a7;

  }

  result = buff1_16;

  *((_DWORD *)buff1_16 + 2) = buff_48__;        // buff1_16[8-11] = buff_48;注意这里是把申请空间的地址放在了这里

                                                // buff1_16[8-11] 存放了一个堆空间的指针,该指针指向字符串"3390fd362dfdda0030d5737632d3d213"

  *(_DWORD *)buff1_16 = v20 | 1;                // buff1_16[0-3] = 0x00000031

                                                // 小端存放 buff1_16[0-3] = {0x31,00,00,00}

  v17 = v14 + v15;                              // v17 = 32

  *((_DWORD *)buff1_16 + 1) = v17;              // buff1_16[4-7] = 0x00000032

                                                // buff1_16[4-7] = {0x20,0,0,0}

  buff_48__[v17] = 0;                           // 字符串"3390fd362dfdda0030d5737632d3d213"后添加0确保截断

  return result;                                // 总结一下:

                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49

                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32

                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针

}

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

unsigned int __cdecl set_buff2(BYTE *buff2)

{

  v31 = __readgsdword(0x14u);

  *(_DWORD *)buff2 = &off_19CAAC;               // buff2[0-3] = 19aac+imagebase

  *((_DWORD *)buff2 + 2) = 0;                   // buff2[4-19] = 0

  *((_DWORD *)buff2 + 1) = 0;

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

  *((_DWORD *)buff2 + 3) = 0;

  memcpy(dest, byte_151F3A, sizeof(dest));      // 把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data

  memcpy(buff2 + 20, dest, 512u);               // buff2[20-531] = const_data

  RAND_bytes(v29, 32);                          // 生成32字节大小的随机数

  v1 = v29[3];                                  // v1 = v29[3]

  v28[3] = v29[3];

  v2 = v29[2];                                  // v2 = v29[2]

  v28[2] = v29[2];

  v3 = v29[1];                                  // v3 = v29[1]

  v28[1] = v29[1];

  v28[0] = v29[0];                              // v28[0-3] = v29[0-3]

  *(_QWORD *)(buff2 + 532) = v29[0];            // buff2[532-539] = v29[0]

  *(_QWORD *)(buff2 + 540) = v3;                // buff2[540-547] = v3

  *(_QWORD *)(buff2 + 548) = v2;                // buff2[548-555] = v2

  *(_QWORD *)(buff2 + 556) = v1;                // buff2[556-563] = v1

  *(_QWORD *)(buff2 + 588) = v28[3];            // buff2[588-595] = v1

  v4 = v28[1];

  *(_QWORD *)(buff2 + 564) = v28[0];            // buff2[564-571] = v29[0]

  *(_QWORD *)(buff2 + 580) = v28[2];            // buff2[580-587] = v2

  *(_QWORD *)(buff2 + 572) = v4;                // buff2[572-579] = v3

                                                // 小结一下:

                                                // buff2[532-563] = buff2[564-595] 是两组相同的随机数

  v5 = -64;

  v6 = 0;

  do

    v6 = *(_WORD *)&buff2[2 * (buff2[v5++ + 596] ^ HIBYTE(v6)) + 20] ^ (v6 << 8);

  while ( v5 );                                 // 观察这个公式,buff2[v5++ + 596]指定就是最后的64个随机字节,v5++依次取出,这里将这个值记作rand[i]

                                                // 观察buff2[2 * (rand[i] ^ HIBYTE(v6)) + 20],对照当前 buff2 的内存布局,这指的就是之前的 const_data

                                                // 也明白了buff2的空间为什么是596这个奇怪的大小,它的组成是20 + 256*2 + 64

                                                // 需要注意的是最后使用了 *(WORD*)&,也就是 const_data 被当做WORD取出

                                                //

                                                // 小结一下:

                                                // 随机字节和v6异或也是随机的,就记为随机

                                                // 循环将 const_data 以 WORD 类型随机取出,再和上一次结果的高位八位异或

                                                // 总的来讲就是得到了一个随机数 v6

  v27 = v6;

  *(_WORD *)((char *)v28 + 5) = v6;             // 这里修改了v28的第5-6字节为v6

  hmac_ctx_hash((BYTE *)v28, (BYTE *)v29, buff2 + 532);// 这是一个已经分析过的函数,hash和data没有关系

                                                // 把随机数 v29 作为key,hash结果放在buff2[532-595]中

  v7 = -32;

  v8 = 0;

  do

    v8 = *(_WORD *)&buff2[2 * (*((unsigned __int8 *)v29 + v7++) ^ HIBYTE(v8)) + 20] ^ (v8 << 8);

  while ( v7 );                                 // 这个循环和上边差不多,无非是随机数在v29这个随机字节数组中取,最终得到一个随机数 v8

  v9 = lrand48() % 12;                          // v9 是个0-11范围内的随机数

  *(_WORD *)&buff2[v9 + 532] = v8;              // 随机数 v8 被随机的写在 buff2[532-544]中,注意 v8 是 WODD 类型

  *(_WORD *)&buff2[v9 + 534] = v27;             // 一个 0 被随机的写在 buff2[534-546]中,注意 0 是 WODD 类型

  v10 = _mm_xor_ps(*(__m128 *)(buff2 + 36), (__m128)xmmword_148490);

  *(__m128 *)(buff2 + 20) = _mm_xor_ps(*(__m128 *)(buff2 + 20), (__m128)xmmword_148490);// buff2[20-35] ^= 0x00360036003600360036003600360036

  *(__m128 *)(buff2 + 36) = v10;                // buff2[36-51] ^= 0x00360036003600360036003600360036

  v11 = _mm_xor_ps(*(__m128 *)(buff2 + 68), (__m128)xmmword_148490);

  *(__m128 *)(buff2 + 52) = _mm_xor_ps(*(__m128 *)(buff2 + 52), (__m128)xmmword_148490);//

                                                // 发现了规律,其实就是,buff2[20-532] 2字节一组与 0x0036 异或,并更新buff2中的值

                                                // 在这里总结一下:

                                                // buff2[0-3] = 19aac+imagebase

                                                // buff2[4-19] = 0

                                                // buff2[20-531] = const_data ^ 0x0036

                                                // buff2[532-595] = hash

    ......

  v25 = _mm_xor_ps(*(__m128 *)(buff2 + 516), (__m128)xmmword_148490);

  *(__m128 *)(buff2 + 500) = _mm_xor_ps(*(__m128 *)(buff2 + 500), (__m128)xmmword_148490);

  *(__m128 *)(buff2 + 516) = v25;

  return __readgsdword(0x14u);

}

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

unsigned int __cdecl set_buff3(BYTE *buff3)

{

  v32 = __readgsdword(0x14u);

  *(_DWORD *)buff3 = &off_19CA8C;               // buff3[0-3] = 19ca8c + image_base

  *((_DWORD *)buff3 + 18) = 0;                  // buff3[68-83] = 0

  *((_DWORD *)buff3 + 17) = 0;

  *((_DWORD *)buff3 + 20) = 0;

  *((_DWORD *)buff3 + 19) = 0;

  memcpy(dest, &unk_1485A0, sizeof(dest));      // 把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data

  memcpy(buff3 + 84, dest, 512u);               // buff3[84-595] = const_data

  RAND_bytes(key, 32);                          // 生成32字节大小的随机数

  v1 = *(_QWORD *)&key[24];                     // v1 = key[24-31]

  *(_QWORD *)&data[24] = *(_QWORD *)&key[24];

  v2 = *(_QWORD *)&key[16];                     // v2=v32[16-23]

  *(_QWORD *)&data[16] = *(_QWORD *)&key[16];

  v3 = *(_QWORD *)&key[8];                      // v3=key[8-15]

  *(_QWORD *)&data[8] = *(_QWORD *)&key[8];

  *(_QWORD *)data = *(_QWORD *)key;             // data[0-31] = key[0-31]

  *(_QWORD *)(buff3 + 4) = *(_QWORD *)key;      // buff3[4-11] = key[0-7]

  *(_QWORD *)(buff3 + 12) = v3;                 // buff3[12-19] = v3

  *(_QWORD *)(buff3 + 20) = v2;                 // buff3[20-27] = v2

  *(_QWORD *)(buff3 + 28) = v1;                 // buff3[28-35] = v1

  *(_QWORD *)(buff3 + 60) = *(_QWORD *)&data[24];// buff3[60-67] = v1

  v4 = *(_QWORD *)&data[8];

  *(_QWORD *)(buff3 + 36) = *(_QWORD *)data;    // buff3[36-43] = key[0-7]

  *(_QWORD *)(buff3 + 52) = *(_QWORD *)&data[16];// buff3[52-59] = v2

  *(_QWORD *)(buff3 + 44) = v4;                 // buff3[44-51] = v3

                                                // 小结:

                                                // buff3[4-35] = buff3[36-67] 是两组相同的随机数

  v5 = -64;

  v6 = 0;

  do

    v6 = *(_WORD *)&buff3[2 * (buff3[v5++ + 68] ^ HIBYTE(v6)) + 84] ^ (v6 << 8);// 这里和 setbuff2 基本一致,总的来讲就是得到了一个随机数 v6

  while ( v5 );

  v28 = v6;

  *(_WORD *)&data[5] = v6;                      // data 的第5-6字节被赋值为v6

  get_hash2(v27, (int)data, (int)key, buff3 + 4);// buff3[4-67] = hash

  v7 = -32;

  v8 = 0;

  do

    v8 = *(_WORD *)&buff3[2 * (key[v7++] ^ HIBYTE(v8)) + 84] ^ (v8 << 8);// 通过 key 得到一个随机的 v8

  while ( v7 );

  v9 = lrand48() % 30;                          // v9 一个范围在 0-29 的随机数

  *(_WORD *)&buff3[v9 + 4] = v8;                // buff3[4-31] 随机位置被赋值 v8

  *(_WORD *)&buff3[v9 + 6] = v28;               // buff3[6-35]随机位置被赋值v6

  v10 = _mm_xor_ps(*(__m128 *)(buff3 + 100), (__m128)xmmword_1483D0);// buff3[84-595] 2字节一组与 0x0042 异或,并更新buff3中的值

                                                // 总结:

                                                // buff3[0-3] = 19ca8c + image_base

                                                // buff3[4-67] = hash

                                                // buff3[68-83] = 0

                                                // buff3[84-595] = const_data ^ 0x0042

    ......

  v25 = _mm_xor_ps(*(__m128 *)(buff3 + 580), (__m128)xmmword_1483D0);

  *(__m128 *)(buff3 + 564) = _mm_xor_ps(*(__m128 *)(buff3 + 564), (__m128)xmmword_1483D0);

  *(__m128 *)(buff3 + 580) = v25;

  return __readgsdword(0x14u);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_getbt(JNIEnv *env, jobject a2, void *user_name_, int num_3)

{

  v12 = __readgsdword(0x14u);

  user_name_len = (*env)->GetArrayLength(env, user_name_);

  user_name = (BYTE *)(*env)->GetByteArrayElements(env, user_name_, 0);

  dword_1A50BC[0] = dword_1A50BC[num_3];

  fun3 = (int (__cdecl ***)(_DWORD, BYTE *, int, char *))dword_1A50BC[0];

  memset(password, 0, sizeof(password));

  len = (**fun3)(fun3, user_name, user_name_len, password);

  v6 = (*env)->NewByteArray(env, len);

  (*env)->SetByteArrayRegion(env, v6, 0, len, password);

  (*env)->ReleaseByteArrayElements(env, user_name_, (jbyte *)user_name, 0);

  return v6;

}

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

int __cdecl sub_425F0(int a1,int hash,int iv,int num_12,int username_reverse,int username_reverse_len,int key_hash,int key_hash_len,char *dest)

{

  int ctx; // esi

  int cipher; // eax

  size_t v11; // eax

  __int64 v12; // xmm1_8

  int v14; // [esp+0h] [ebp-41Ch] BYREF

  size_t len; // [esp+4h] [ebp-418h] BYREF

  BYTE outbuff[1024]; // [esp+8h] [ebp-414h] BYREF

  unsigned int v17; // [esp+408h] [ebp-14h]

  v17 = __readgsdword(0x14u);

  ctx = EVP_CIPHER_CTX_new();

  cipher = EVP_aes_256_gcm();

  EVP_EncryptInit_ex(ctx, cipher, 0, 0, 0);     // 初始化 ctx

  EVP_CIPHER_CTX_ctrl(ctx, 9, num_12, 0);       //

                                                // 设置向量IV的长度为12

  EVP_EncryptInit_ex(ctx, 0, 0, hash, iv);      // 使用 hash 作为 key 初始化 ctx

  EVP_EncryptUpdate(ctx, 0, (int)&len, username_reverse, username_reverse_len);// 输出地址为0,也就是说username没有参与加密

  EVP_EncryptUpdate(ctx, (int)outbuff, (int)&len, key_hash, key_hash_len);

  memcpy(dest, outbuff, len);                   // dest[0-1] = encrydata[0-1]

  EVP_EncryptFinal_ex(ctx, outbuff, &v14);

  EVP_CIPHER_CTX_ctrl(ctx, 16, 16, (int)outbuff);// 获取 16 字节长度的 tag

  v11 = len;                                    // v11 = 2

  v12 = *(_QWORD *)&outbuff[8];                 // v12 = tag[8-15]

  *(_QWORD *)&dest[len] = *(_QWORD *)outbuff;   // dest[2-9] = tag[0-7]

  *(_QWORD *)&dest[v11 + 8] = v12;              // dest[10-17] = tag[8-15]

  EVP_CIPHER_CTX_free(ctx);                     // 总结:

                                                // dest[0-1] = encrydata[0-1]

                                                // dest[2-9] = tag[0-7]

                                                // dest[10-17] = tag[8-15]

  return len + 16;

}

主要功能为设置 src 区域的内存数据,并返回一个影响密码长度的值,另外我记着在分析这个函数的时候遇到了阻碍 IDA 分析的一连串 nop,我的解决方法是把 nop 改为 mov eax, eax ,然后依次使用快捷键 u 、c、p、f5,重新识别。

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

size_t __cdecl sub_43070(_DWORD *fun3, _BYTE *password, size_t num_20, _BYTE *src)

{

  v22 = __readgsdword(0x14u);

  do

    v4 = get_hash3_and_change_fun3(fun3);       // 这里把结果的低两字节放到了v4中

                                                // 此处通过位移修改了 fun3 中的数据

  while ( (((_BYTE)v4 - 8) & 0xC0) == 0 );      // 循环判断 v4-8 后[2-3]位的值是否为 C

                                                // 直到不为C结束

  *(_WORD *)src = v4;

  src[2] = BYTE1(num_20);                       // src[2] = n的高位 = 0

  src[3] = num_20;                              // src[3] = 20

  memcpy(src + 4, password, num_20);            // src[4-23] = password

  v5 = (unsigned __int16)(num_20 + 4);          // v5 = 24

  v6 = 32 - num_20;                             // v6 = 12

  if ( 300 - v5 > 32 - num_20 )

    v6 = 300 - v5;                              // v6 = 276

  v16 = 1399 - (v6 + v5);                       // v16 = 1099

  v7 = get_hash3_and_change_fun3(fun3);         // hash3

  v8 = 0;

  v18 = v6 + v7 % v16 - 3 * (((v6 + v7 % v16) / 3) & 0xFFFFFFF0);// v18 = 276 + v7%16 - 3 *((276+v7%1099)/3) & 0xfffffff0

  v9 = v18 + 32;                                // v9 = v18 + 32

  memset(s, 0, sizeof(s));

  v17 = v18 + 32;

  do

  {

    v10 = v9 - v8;

    if ( v10 < 8 )

    {

      v19 = get_hash3_and_change_fun3(fun3);    // 这里再次计算hash

      memcpy(&s[v8], &v19, v10);                // hash复制到数组 s 中

    }

    else

    {

      v11 = get_hash3_and_change_fun3(fun3);    // 再次计算hash

      v19 = v11;

      *(_DWORD *)&s[v8 + 4] = HIDWORD(v11);     // 设置数组 s 的内容

      *(_DWORD *)&s[v8] = v11;

    }

    v8 += 8;                                    // v8 += 8

    v9 = v18 + 32;

  }

  while ( v17 > v8 );                           // 循环直到 v17 <= v8 结束

  v12 = num_20 + v18;

  memcpy(&src[num_20 + 4], s, v17);             // 这里将 s 拷贝到 src

  v13 = *(_QWORD *)&src[num_20 + 20 + v18];

  v20 = *(_QWORD *)&src[num_20 + 28 + v18];

  v19 = v13;

  sub_43990((int)fun3, (int)(src + 2), num_20 + v18 + 18, (unsigned int *)&src[num_20 + 20 + v18]);// 通过一系列的变换,修改了src中的值

  v14 = v20;

  *(_QWORD *)&src[v12 + 20] = v19;              // src[20 + v18 + 20] = src[num_20 + 20 + v18]

  *(_QWORD *)&src[v12 + 28] = v14;              // src[20 + v18 + 28] = src[num_20 + 28 + v18]

  return num_20 + v18 + 36;                     // 总结:

                                                // 首先修改fun3的数据并计算出一个hash1,放到src[0-1]

                                                // 再次修改fun3的数据并计算出一个hash2,通过hash2和一些常数的计算

                                                // 得到贯穿整个函数的v18,在43-60行的代码中,反复修改fun3的数据并计算hash

                                                // 来设置数组 s, 随后将数组 s拷贝到 src 中,此时函数先将src的[40 + v18]到[40 + v18]保存了一份

                                                // 调用函数 sub_43990 中通过一系列的位移变换,再次修改src中的数据

                                                // 然后将保存的数据进行恢复,返回值为 v18 + 56

}

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

__int64 __cdecl sub_43D00(_DWORD *fun3)

{

  v1 = fun3[17];

  v2 = fun3[18];

  if ( *(_QWORD *)(fun3 + 17) )

  {

    v3 = fun3[20];

    v4 = fun3[19];

  }

  else

  {

    gettimeofday(&tv, 0);

    v5 = tv.tv_usec ^ (*(unsigned __int64 *)&tv >> 9) ^ HIDWORD(v31) ^ (HIDWORD(v31) >> 26) ^ ((tv.tv_usec ^ (unsigned int)(*(unsigned __int64 *)&tv >> 9)) >> 17);

    v6 = tv.tv_sec ^ (tv.tv_sec << 23) ^ v31 ^ (v31 >> 26) ^ ((*(_QWORD *)&tv ^ (unsigned __int64)(*(_QWORD *)&tv << 23)) >> 17);

    v7 = v31 ^ ((_DWORD)v31 << 23);

    LODWORD(v31) = HIDWORD(v31) ^ (v31 >> 9);

    v8 = ((unsigned int)v31 >> 17) ^ (v5 >> 26) ^ v5 ^ v31;

      ......

    LODWORD(v10) = v25 ^ (v25 << 23);

    v4 = v1 ^ (__PAIR64__(v2, v1) >> 26) ^ v10 ^ (v10 >> 17);

    v3 = v2 ^ (v2 >> 26) ^ HIDWORD(v10) ^ (HIDWORD(v10) >> 17);

    fun3[17] = v1;

    fun3[18] = v2;

    fun3[19] = v4;

    fun3[20] = v3;

  }

  v26 = v2 ^ (__PAIR64__(v2, v1) >> 9);

  HIDWORD(v27) = (v3 >> 26) ^ v26 ^ v3 ^ (v26 >> 17);

  HIDWORD(v28) = v26;

  LODWORD(v28) = v1 ^ (v1 << 23);

  LODWORD(v27) = (__PAIR64__(v3, v4) >> 26) ^ (v28 >> 17) ^ v4 ^ v28;

  fun3[17] = v4;

  fun3[18] = v3;

  *(_QWORD *)(fun3 + 19) = v27;

  return __PAIR64__(v3, v4) + v27;              //

                                                // 其实就是拼接成648字节的宏

                                                // 接下来这些位运算居然还有奇数是最棘手的,想不到化简的办法

                                                // 但可以知道,结果是fun3[20]和fun[19]拼接的值加上 v27

                                                // v27 这些位运算得来的,而整个函数其实就是在做一件事:

                                                //

                                                // 通过这些位移修改了 fun3[68-80]区间内的数据

                                                //

                                                //

                                                // // v1 = fun3[17];

                                                // // v2 = fun3[18];

                                                // // v3 = fun3[20];

                                                // // v4 = fun3[19];

                                                //

                                                //   v26 = fun3[18] ^ (__PAIR64__(fun3[18], fun3[17]) >> 9);

                                                //   HIDWORD(v27) = (fun3[20] >> 26) ^ v26 ^ fun3[20] ^ (v26 >> 17);

                                                //   HIDWORD(v28) = v26;

                                                //   LODWORD(v28) = fun3[17] ^ (fun3[17] << 23);

                                                //   LODWORD(v27) = (__PAIR64__(fun3[20], fun3[19]) >> 26) ^ (v28 >> 17) ^ fun3[19] ^ v28;

                                                //   fun3[17] = fun3[19];

                                                //   fun3[18] = fun3[20];

                                                //   *(_QWORD *)(fun3 + 19) = v27;

                                                //   return __PAIR64__(fun3[20], fun[19]) + v27;

}

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

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

size_t __cdecl sub_42940(_DWORD *fun3, BYTE *user_name, int user_name_len, char *password)

{

  v73 = __readgsdword(0x14u);

  v4 = -64;

  v5 = 0;

  do

    v5 = *((_WORD *)fun3 + (*((unsigned __int8 *)fun3 + v4++ + 68) ^ HIBYTE(v5)) + 42) ^ (v5 << 8);

  while ( v4 );                                 // 又见到了这个算法,这个算法已经出现了不止一次

                                                // 不过我突然觉着这个算法可能没有我之前分析的那么简单

                                                // 因为我发现对于整个算法来讲,最重要的是v5的高位

                                                // 参与算法的并不是当前v5的低位,而是上一次 v5 的低位

                                                // 这种情况可以将v5拆成两个uint8,以便更好的分析算法

                                                // 这里使用 res 记录当前v5高位,old_data 记录上次 v5 低位

                                                //

                                                // 这里对这个算法进行重新分析:

                                                // 将 v5 转为 uint8 类型后整个算法逐渐变的更加清晰

                                                // 首先通过公式 id=(data[i] ^ v5) * 2 + 84 得到 id 作为下一次从 data 中取数据的下标

                                                // 随后通过 id + 1 将高位取出,再和上一次的 v5 低位异或得到该轮循环的值

                                                // 注意:这种情况最终的结果将不再是 uint16 类型,因为这里是为了更清晰的分析算法

                                                // 如果需要,可以添加一个一直指向 res 的指针,最终取出 uint16 的数据

                                                // 通过之前分析 buff3 的内存结构可以知道,data 的数据是应用启动时生成的一段随机 hash

                                                // 这里返回的 v5 如果应用不重启,那么这个值是是固定的,如果应用重启这个值就会变化

                                                //

                                                //     int id = 0;

                                                //     unsigned char res = 0;

                                                //     unsigned char old_data = 0;

                                                //

                                                //     for (int i = 4; i < 68; i++)

                                                //     {

                                                //         id = (data[i] ^ res) * 2 + 84;

                                                //         res = data[id + 1] ^ old_data;

                                                //         old_data = data[id];

                                                //     }

  v62 = v5;                                     // v61 = v5

  *((_WORD *)password + 1) = v5;                // password[2-3] = v5

  if ( user_name )

  {

    if ( user_name_len > 0 )                    // 判断用户名长度大于0

    {

      v6 = 0;

      if ( (unsigned int)user_name_len < 0x10 ) // 判断用户名长度小于 16

        goto LABEL_33;

      v6 = user_name_len & 0xFFFFFFF0;          // v16 = 16

      v7 = 0;                                   // v7 = 0

      si128 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11539] + 1));// si128 = 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F

      v59 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92307));// v59 = 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F

      v9 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11538] + 1));// v9 = 3030 3030 3030 3030 3030 3030 3030 3030

      v10 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92299));// v10 = 5757 5757 5757 5757 5757 5757 5757 5757

      v11 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11537] + 1));// v11 = 0909 0909 0909 0909 0909 0909 0909 0909

      do

      {

        v12 = _mm_loadu_si128((const __m128i *)&user_name[v7]);// 将 username 加载到寄存器 v12 中

                                                //

        v13 = _mm_and_si128(_mm_srli_epi16(v12, 4u), si128);// username分为 8 16bit 的数据,对它们分别逻辑右移4位,高位补零

                                                // 再与上 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F 0F0F

                                                // v13 = username>>4^si128

                                                //

        v14 = _mm_cmpeq_epi8(_mm_min_epu8(v12, v59), v12);// username 和 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F 9F9F 的最小值

                                                // 而字符串必然小于9F,所以v14 = FFFFFFFFFFFFFFFF

                                                //

        v15 = _mm_or_si128(_mm_andnot_si128(v14, _mm_add_epi8(v13, v10)), _mm_and_si128(_mm_or_si128(v13, v9), v14));//

                                                // v15 = (!v14 & (v13+v10))|((v13|v9)&v14)

                                                // 注意 andnot 会将 v14 反转,后边的 v14 不再是ffff而是0

                                                // 稍微化简即可得到   v15 = v13|v9

        v16 = _mm_and_si128(v12, si128);        // v16 = username & si128

        v17 = _mm_cmpeq_epi8(_mm_subs_epu8(v16, v11), (__m128i)0LL);// (v16分组减v11) 和 0比较,v16 每个字节不会超过 0f, v11 每个字节是 09

                                                // 所以 v17 为 ff 和 00 组成

        v18 = _mm_or_si128(_mm_andnot_si128(v17, _mm_add_epi8(v16, v10)), _mm_and_si128(_mm_or_si128(v16, v9), v17));// 通过动态调试得知,v18 = username

        *(__m128i *)&key[2 * v7 + 16] = _mm_unpackhi_epi8(v15, v18);// 将 v15 和 v18 的低 64 位数以 8 位为单位进行交错

                                                // key[16-31] = username>>4^si128 低64位分组交错 username

        *(__m128i *)&key[2 * v7] = _mm_unpacklo_epi8(v15, v18);// key[0-15] = username>>4^si128 高64位分组交错 username

        v7 += 16;

      }

      while ( v6 != v7 );                       // 看似循环,实际只执行一次

                                                // 小结:

                                                // key[0-15] = (username 8bit分组右移四位) ^ si128 再高64位分组交错 username

                                                // key[16-31] = (username 8bit分组右移四位) ^ si128 再低64位分组交错 username

                                                //

      if ( v6 != user_name_len )                // 判断不成立

      {

          ......

      }

    }

  }

  HMAC_CTX_init(ctx);

  md = EVP_sha256();

  HMAC_Init_ex(ctx, key, 32, md, 0);

  HMAC_Update((int)ctx, (int)&v62, 2);          // 这里的 v61 就是每次重启应用才会改的那个值,计算这个值的hash

  len = 0;

  HMAC_Final((int)ctx, (int)hash, (int)&len);   // 得到 hash

  v26 = -32;

  key_hash_ = 0;

  do

    key_hash_ = *((_WORD *)fun3 + ((unsigned __int8)v71[v26++] ^ HIBYTE(key_hash_)) + 42) ^ (key_hash_ << 8);

  while ( v26 );                                // 越界访问 key,通过(key[i] ^ v27) * 2 + 84 得到 id

                                                // 作为下一次从 data 中取数据的下标,最终得到 v27

  key_hash = key_hash_;                         // v76 = v27

  uesrname_reverse = 0LL;

  v28 = user_name;

  if ( user_name_len > 0 )

  {

    v29 = 0;

    if ( (unsigned int)user_name_len <= 0x1F )

      goto LABEL_21;                            // 跳转到 LABEL_21

    ......

    v28 = user_name;

    if ( v29 != user_name_len )

    {

LABEL_21:

      v35 = user_name_len - v29;                // v35 = 16

      uesrname_reverse_ = &ctx[v29 - 16];       // v36 = __int128 v69 = 0

      do

        *uesrname_reverse_++ = v28[--v35];      // 将 username 倒序存入 username_reverse 中

      while ( v35 );

    }

  }

  username_ = (int)v28;

  memset(dest, 0, sizeof(dest));

  sub_425F0(v57, (int)hash, username_, 12, (int)&uesrname_reverse, 16, (int)&key_hash, 2, dest);// 总结:

                                                // dest[0-1] = encrydata[0-1]

                                                // dest[2-9] = tag[0-7]

                                                // dest[10-17] = tag[8-15]

  *(_WORD *)password = *(_WORD *)dest;          // password[0-1] = dest[0-1]

  v38 = *(_QWORD *)&dest[2];

  *(_QWORD *)(password + 12) = *(_QWORD *)&dest[10];// password[12-19] = dest[10-17]

  *(_QWORD *)(password + 4) = v38;              // password[4-11] = dest[2-9]

  memset(password_, 0, 0x400u);                 // v77 被清零,v65也被越界清空

  v65 = *((_DWORD *)password + 4);              // v64 = password[16-19]

  v39 = *(_QWORD *)password;                    // v39 = password[0-7]

  *(_QWORD *)&password_[8] = *((_QWORD *)password + 1);// v77[8-15] = password[8-15]

  *(_QWORD *)password_ = v39;                   // v77[0-7] = password[0-7]

  v40 = sub_43070(fun3, password_, 0x14u, src); //

                                                // 首先修改fun3的数据并计算出一个hash1,放到src[0-1]

                                                // 再次修改fun3的数据并计算出一个hash2,通过hash2和一些常数的计算

                                                // 得到贯穿整个函数的v18(这个值最终决定了password长度),

                                                // 43-60行的代码中,反复修改fun3的数据并计算hash

                                                // 来设置数组 s, 随后将数组 s拷贝到 src 中,此时函数先将src的[40 + v18]到[40 + v18]保存了一份

                                                // 调用函数 sub_43990 中通过一系列的位移变换,再次修改src中的数据

                                                // 然后将保存的数据进行恢复,返回值为 v18 + 56

  memset(v72, 0, 256);

  memset(v71, 0, sizeof(v71));

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

  {

    v71[i] = i;                                 // 初始化v71 = 0 1 2 3 4 5 6 ...

    v72[i] = *((_BYTE *)fun3 + (i & 0x3F) + 4); // 使用 fun3 的数据初始化 v72

  }

  v42 = 0;

  v43 = -256;

  do

  {

    v44 = (unsigned __int8)v72[v43];

    v45 = v44 + v42 + (unsigned __int8)v72[v43 + 256];

    v45 %= 256;

    v46 = v71[v45];

    v71[v45] = v44;

    v72[v43++] = v46;                           // 不断变换交换 v71 和 v72 的数据

    v42 = v45;

  }

  while ( v43 );

  v58 = v40;

  if ( v40 > 0 )                                // v40 = 上个函数中的v18 + 56 判断成立

  {

    v47 = 0;

    v48 = 0;

    src_ = src;

    v50 = v58;

    do

    {

      v51 = v47 + 1 - ((v47 + ((unsigned int)((v47 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00);

      v52 = (unsigned __int8)v71[v51];

      v48 = (v52 + v48) % 256;

      v53 = v50;

      v54 = v71[v48];

      v71[v48] = v52;                           // 变换 v71 中的数据

      v71[v51] = v54;

      *src_++ ^= v71[(unsigned __int8)(v71[v48] + v54)];// 循环将 v71 的数据异或再赋值给src

      v50 = v53 - 1;

      v55 = v53 == 1;

      v47 = v51;

    }

    while ( !v55 );

  }

  memcpy(password, src, v58);                   // 将 src 的数据拷贝到 password

  return v58;                                   // 返回拷贝数据的长度

}

可以发现主要就是在调用函数 dword_1A50BC[0] + 4 而这个地址其实就是在 _init 初始化的 [19ca8c + image_base]+4(函数sub_43280)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

int __cdecl Java_com_test_pac_demo_MainActivity_getck(JNIEnv *a1, int a2, void *username_, void *password_)

{

  username_len = (*a1)->GetArrayLength(a1, username_);

  username = (*a1)->GetByteArrayElements(a1, username_, 0);

  password_len = (*a1)->GetArrayLength(a1, password_);

  password = (*a1)->GetByteArrayElements(a1, password_, 0);

  v8 = (*(int (__cdecl **)(int, jbyte *, jsize, jbyte *, jsize))(*(_DWORD *)dword_1A50BC[0] + 4))(

         dword_1A50BC[0],

         username,

         username_len,

         password,

         password_len);

  (*a1)->ReleaseByteArrayElements(a1, username_, username, 0);

  (*a1)->ReleaseByteArrayElements(a1, password_, password, 0);

  return v8;

}

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

int __cdecl sub_43280(BYTE *fun3, BYTE *username, int username_len, char *password, int password_len)

{

  result = 0;

  if ( username_len == 16 )

  {

    memset(password_encrypt, 0, 256);

    memset(v71, 0, 256);

      ......

        v15 = v13 + 1 - ((v13 + ((unsigned int)((v13 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00);

        v16 = (unsigned __int8)v71[v15];

        v14 = (v16 + v14) % 256;

        v17 = v71[v14];

        v71[v14] = v16;

        v71[v15] = v17;

        password[v12++] ^= v71[(unsigned __int8)(v71[v14] + v17)];//

                                                // 上面的代码在 sub_42940 是分析过的

                                                // 注意和 password 进行异或的值都是通过

                                                // fun3 初始化 v71、72在通过相同的位运算计算出来的

                                                // 也就是说这里就是在对 password 进行解密

        v13 = v15;

      }

      while ( password_len != v12 );

      if ( password_len >= 32 )

      {

        v19 = password;

        sub_43990((int)fun3, (int)(password + 2), password_len - 18, (unsigned int *)&password[password_len - 16]);//

                                                // 这里的第二次调用 sub_43990 也是同样的道理

                                                // 也是异或相同的值对 password 进行解密

        v20 = (unsigned __int8)password[3];

        v21 = 16 * (unsigned __int8)password[2];

        v22 = v20 + v21 == 0;

        v23 = v20 + v21;

        v18 = 0;

        if ( !v22 )

        {

          v24 = v20 + 16 * (unsigned __int8)password[2];

          v25 = 0;

          if ( v24 < 0x20 )

            goto LABEL_17;                      // 动态调试得知此处跳转至 LABEL_17

          v25 = v24 & 0xFFFFFFE0;

          ......

          if ( v24 != v25 )

          {

LABEL_17:

            v29 = v24 - v25;

            v30 = &v19[v25 + 4];

            do

            {

              *(v30 - 4) = *v30;                // v30 就是 password,在函数 sub_42940 中也是

                                                // 存在这段代码的,这里还是对password进行解密

              ++v30;

              --v29;

            }

            while ( v29 );

          }

          v18 = v23;                            // v20 = (unsigned __int8)password[3];

                                                // v21 = 16 * (unsigned __int8)password[2];

                                                // v22 = v20 + v21 == 0;

                                                // v23 = v20 + v21;

                                                //

                                                // v18 = password[2]*16 + password[3]

        }

      }

      else

      {

        v18 = 0;

      }

    }

    v31 = v18;                                  // v31 = password[2]*16 + password[3]

                                                //

                                                // 回去翻看了一下 sub_43070 的分析过程,发现这个值是固定的 = 20

                                                //   src[2] = BYTE1(num_20);                       // src[2] = n的高位 = 0

                                                //   src[3] = num_20;                              // src[3] = 20

    memset(password_encrypt, 0, sizeof(password_encrypt));

    v32 = sub_43860(v47, password, v31, password_encrypt, COERCE_FLOAT(120));// 这里出现了一个在加密过程中没有出现过的函数

                                                // 这个函数将password加密的数据存放到 password_encrypt 中

                                                // 参数 v31 应该是欲加密的长度为20,返回值 v32 = v31 = 20

    result = 0;

    if ( v32 == 20 )

    {

      if ( username )

      {

        v33 = _mm_loadu_si128((const __m128i *)username);

        si128 = _mm_load_si128((const __m128i *)&xmmword_148380);

        ......

        *(__m128i *)&key[16] = _mm_unpackhi_epi8(v39, v42);

        *(__m128i *)key = _mm_unpacklo_epi8(v39, v42);// 这段循环之前在 sub_42940 也是分析过的

                                                // 显然这就是把用户名变换一下,然后当做下面求 hash 的key使用

                                                // key[0-15] = (username 8bit分组右移四位) ^ si128 再高64位分组交错 username

                                                // key[16-31] = (username 8bit分组右移四位) ^ si128 再低64位分组交错 username

      }

      HMAC_CTX_init(ctx);

      v43 = EVP_sha256();

      HMAC_Init_ex(ctx, key, 32, v43, 0);

      HMAC_Update((int)ctx, (int)&password_encrypt[2], 2);// 将 password_encrypt[2-3] 传入

      v49 = 0;

      HMAC_Final((int)ctx, (int)hash, (int)&v49);// 得到 hash

      v44 = -32;

      v45 = 0;

      do

        v45 = *(_WORD *)&fun3[2 * (key[v44++ + 32] ^ HIBYTE(v45)) + 84] ^ (v45 << 8);

      while ( v44 );                            // 这个算法之前也分析过,把 key[i]*2 * v45 当做id

                                                // 求得fun3数据区的hash

      v48 = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)username), (__m128i)xmmword_1483E0);//

                                                // xmmword_1483E0 = 102030405060708090A0B0C0D0E0Fh

                                                // 对 username 的顺序进行变换,传入下边的 sub_424e0

                                                // 不过这个参数并不影响结果

      memset(v71, 0, sizeof(v71));

      result = sub_424E0(                       // EVP_DecryptFinal_ex 失败,result = 0

                 v46,(int)hash,(int)username,12,(int)&v48,16,(int)&password_encrypt[4],

                 16,(int)password_encrypt,2,v71);

      if ( !result || v45 != *(_WORD *)v71 )

        return 0;                               // 返回 0

    }

  }

  return result;

}

EVP_DecryptFinal_ex 解密失败,这里我查阅了一下 openssl 源码,结合动态调试返回的地方,对应在 EVP_R_INVALID_OPERATION 处返回,正好源码里有注释,翻译了一下是:防止解密时意外使用加密上下文。 应该是解密的数据不对导致的失败返回 0。

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

int __cdecl sub_424E0(int a1,int hash,BYTE *username,int num_12,int a5,int num_16_,

        BYTE *password_encrypt_4,int num_16,BYTE *password_encrypt,int num_2,BYTE *dest)

{

  ctx = EVP_CIPHER_CTX_new();

  cipher = EVP_aes_256_gcm();

  EVP_DecryptInit_ex(ctx, cipher, 0, 0, 0);     // 使用 chiper 初始化 ctx

  EVP_CIPHER_CTX_ctrl(ctx, 9, num_12, 0);       // 设置向量长度

  EVP_DecryptInit_ex(ctx, 0, 0, hash, username);// 使用hash 作为 key,使用username作为iv

  if ( num_16_ )

    EVP_DecryptUpdate(ctx, 0, &v15, a5, num_16_);// 输出地址为0,无意义

  EVP_DecryptUpdate(ctx, src, &v15, password_encrypt, num_2);// 将 password_encrpt 前两个字节输入并解密,解密数据放到src中

  memcpy(dest, src, v15);                       // 拷贝解密数据到 dest 中

  EVP_CIPHER_CTX_ctrl(ctx, 0x11, num_16, (int)password_encrypt_4);//

                                                // 设置 password_encrypt[4]作为 TAG

  v13 = EVP_DecryptFinal_ex(ctx, src, &v15);    // 此处在动态调试的时候发现解密失败,跟进看了一下

                                                //

                                                //   *a3 = 0;

                                                //   v3 = *a1;

                                                //   if ( (*(*a1 + 18) & 0x10) != 0 )

                                                //   {

                                                //     savedregs = 0;

                                                //     v16 = (*(v3 + 24))();

                                                //     if ( v16 < 0 )

                                                //     {

                                                //       return 0; //会在此处返回

                                                //     }

                                                //    

                                                // 查阅了一下 openssl 源码,这里对应的应该是下边这一句

                                                // 翻译一下是:防止解密时意外使用加密上下文,应该是解密的数据不对

                                                // 失败就也挺合理,因为前面调用了一个password_encrypt 是调用了

                                                // 一个和 check_bt 无法逆向对应的一个函数

                                                //

                                                //     /* Prevent accidental use of encryption context when decrypting */

                                                //     if (ctx->encrypt) {

                                                //         ERR_raise(ERR_LIB_EVP, EVP_R_INVALID_OPERATION);

                                                //         return 0;

                                                //     }

                                                //    

  EVP_CIPHER_CTX_free(ctx);

  return v13;                                   // 返回失败

}

和 generate3 相比,check3 的代码量少了许多,并没有像check2那样完全逆向流程解密,导致验证失败,而且我思考了一下,注册机应该是写不了的,因为最开始在 _init 函数中初始化的数据是随机的。

本次样本分析共历时七天,遇到了数不清的问题,但总的来讲主要集中在 OpenSSL 函数、SSE指令集的用法的细节问题,但最终经过网络搜索、动态调试和搭建实验环境调试 demo 予以解决,这七天的时间可以说是收获满满,增加了大量的实战经验,为今后的工作与学习指明方向。

https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE2&text=_mm_cmpeq_epi32&expand=773


文章来源: https://bbs.pediy.com/thread-276325.htm
如有侵权请联系:admin#unsafe.sh