帮我扭转这个

逆向工程 反编译 C 解密 异或
2021-06-17 00:49:32

已经有 4 个晚上了,我一直在努力反编译这个。这是我通过 IDA 运行以获取 C 代码的 Android 本地库。

Java签名:

byte[] resultArray = new byte[-2 + dataArray.length];
dataLength= dataArray.length;
decryptData(byte[] resultArray, byte[] dataArray, int dataLength, int enumValue /* in our case should be 01 */, long paramLong /* dunno */)

C 中的反汇编:

//----- (00001354) --------------------------------------------------------
int __fastcall doXor(int result, int a2, int a3, int a4, int a5, int a6, char a7)
{
  int v7; // r5@1
  int v8; // r6@1
  int v9; // r7@1
  int v10; // r1@1
  char v11; // lr@3
  char v12; // t1@3
  char v13; // t1@3

  v7 = a2 - 1;
  v8 = a3 - 1;
  v9 = result - 1;
  v10 = a2 + a5 - 1;
  while ( v7 != v10 )
  {
    v12 = *(_BYTE *)(v7++ + 1);
    v11 = v12;
    v13 = *(_BYTE *)(v8++ + 1);
    *(_BYTE *)(v9++ + 1) = v11 ^ v13;
  } // ====== so far this one just does a xor in the full array
  //   ===>what does this one do?
  *(_BYTE *)(result + a5) = *(_BYTE *)(a3 + a5) ^ a7;

  return result;
}

//----- (00001384) --------------------------------------------------------
int getNumber()
{
  __int32 v0; // r0@1

  v0 = time(0);
  srand48(v0);
  return (unsigned __int8)lrand48();
}

//----- (00001398) --------------------------------------------------------
int __fastcall getKey(void *a1, unsigned int a2, unsigned __int8 a3, int a4, char a5)
{
  void *v5; // r8@1
  unsigned int v6; // r7@1
  unsigned int v7; // r10@1
  void *v8; // r5@1
  __int64 v9; // r0@1
  signed int v10; // r6@1
  __int64 v12; // [sp+8h] [bp-30h]@1
  int v13; // [sp+14h] [bp-24h]@1

  v5 = a1;
  v6 = a2;
  v7 = a2 >> 3;
  v8 = a1;
  v13 = _stack_chk_guard; //a stack guard
  LODWORD(v9) = crc64(a3, (int)&a5, _stack_chk_guard, 8);
  v10 = 0;
  v12 = v9;
  do
  {
    ++v10;
    if ( 8 * v10 > v6 )
    {
      if ( v6 >= 8 * v10 - 8 )
        LODWORD(v9) = memcpy(v8, &v12, (size_t)((char *)v5 + v6 - (_DWORD)v8));
    }
    else
    {
      v9 = v12;
      *(_QWORD *)v8 = v12;
    }
    v8 = (char *)v8 + 8;
  }while ( v10 <= (signed int)v7 );
  if ( v13 != _stack_chk_guard )
    _stack_chk_fail(v9);
  return v9;
}


//----- (000014E4) --------------------------------------------------------
signed int __fastcall decryptData(void *a1, unsigned int *a2, int a3, int a4, __int64 a5)
{
  int v5; // r4@1
  void *v6; // r11@1
  unsigned int *v7; // r10@1
  unsigned int v8; // r7@3
  int v9; // r9@3
  int v10; // ST10_4@3
  void *v11; // r8@3
  const void *v12; // r5@3
  int v13; // r6@3
  signed int result; // r0@5

  v5 = a3;
  v6 = a1;
  v7 = a2;
  if ( check == 1 )
  {
    if ( a5 )
    {
      result = 0;
    }
    else
    {
      v8 = *(_BYTE *)a2;
      v9 = a4 + v8;
      v10 = *((_BYTE *)a2 + a3 - 1);
      v11 = malloc(a3 - 1);
      v12 = malloc(v5 - 1);
      getKey(v11, (unsigned __int16)(v5 - 1), v9, (2596069104u * (unsigned __int64)v8 >> 32) + 305419896 * v8, -16 * v8);
      v13 = v5 - 2;
      doXor((int)v12, (int)((char *)v7 + 1), (int)v11, v10, v13, (unsigned __int64)v13 >> 32, v10);
      memcpy(v6, v12, v5 - 2);
      if ( *((char *)v12 + v5 - 2) != v9 )
        v13 = 0;
      if ( v11 )
        free(v11);
      free((void *)v12);
      result = v13;
    }
  }
  else
  {
    result = -1;
  }
  return result;
}

我有crc64的实现。我不明白的是它从哪里获取 getKey 的数据数组中的种子。

我不确定,我认为它在最后 2 个字节中存储了用于为 xor 生成更大密钥的密钥。请帮忙,我真的很挣扎,我的 C 技能生锈了。

这是一组数据:

$type = "01";
$length = "37";
$data="ea8bf72287a0af8aa65edf259a43".
"e1d8a67f71bce448273199848e401b33".
"da379966a12ce4442e31991b71bde449".
"39bb907d71bce448cc";

通常,前 8 个字节给出纬度和第二个经度,在这种情况下是: f8869e63e888bb3f29ae997e0bc6e93f

所以基本上我假设我们可以期望这是编码版本: ea8bf72287a0af8aa65edf259a43e1d8 xor 的逆是一个 xor 所以在 xor 之后它给出: 120d69416f2814b58ff0465b918508e7

假设是我们的部分异或键。

现在问题:

  • 存储在数据中的种子的长度是多少?
  • 异或键是如何计算的?
  • 我们确定数据中的纬度/经度位置吗?

如果您想在堆栈外提供帮助并讨论此问题,请与我联系。非常感谢

1个回答

这是您的代码,其中大多数变量都具有名称。这是相当多的代码,所以我将尝试只迭代重要的部分。我还在代码中添加了一些注释以帮助阅读,尽管我没有尝试用注释覆盖所有代码。确保你检查了命名参数,我相信这些会帮助你快速理解代码。在语法高亮编辑器中查看代码也会有所帮助(无法突出显示)。

请添加对任何不够清楚的评论。

NI前缀是我名字的缩写,可以忽略它。

首先,doXor

该函数只是对除最后一个字节之外的所有字节进行异或运算,后者的处理方式略有不同,但稍后会详细介绍。这不是while循环的一部分,因为它在doXor. 这背后的一个可能原因是强制任何用户doXor显式处理这个值,因为它对于断言解密消息的有效性有些重要。

//----- (00001354) --------------------------------------------------------
int __fastcall doXor(int result, int a2_NI_source, int a3_NI_key, int a4_NI_unused_copy_source_last_byte, int a5_NI_length, int a6_NI_unused, char a7_NI_source_last_byte)
{
  int v7; // r5@1
  int v8; // r6@1
  int v9; // r7@1
  int v10; // r1@1
  char v11; // lr@3
  char v12; // t1@3
  char v13; // t1@3

  v7_NI_source_pos = a2_NI_source - 1;
  v8_NI_key_pos = a3_NI_key - 1;
  v9_NI_result_pos = result - 1;
  v10_NI_source_end_loc = a2_NI_source + a5_NI_length - 1;
  while ( v7_NI_source_pos != v10_NI_source_end_loc )
  {
    // Ready bytes from v7_NI_source_pos and v8_NI_key_pos
    v12 = *(_BYTE *)(v7_NI_source_pos++ + 1); // READ BYTE OF a2 + offset
    v11 = v12;
    v13 = *(_BYTE *)(v8_NI_key_pos++ + 1); // READ BYTE OF a2 + offset

    // Xor two values and place in v9_NI_result_pos
    *(_BYTE *)(v9_NI_result_pos++ + 1) = v11 ^ v13;
  } // ====== so far this one just does a xor in the full array
  //   ===>what does this one do?

  // XOR LAST BYTE OF key with a7_NI_source_last_byte (see decryptData for code that retreives the byte)
  *(_BYTE *)(result + a5_NI_length) = *(_BYTE *)(a3_NI_key + a5_NI_length) ^ a7_NI_source_last_byte;

  return result;
}

二、getNumber

这从未实际使用过,但会生成单个字节的随机数据,这对于值 255 而言有些偏向,因为将“在 0 和 2^31 之间均匀分布的非负长整数”转换为无符号字节,在大多数情况下,会产生255 以上的数字。

time将返回自 Epoch 以来的当前本地时间,以秒为单位,srand48将使用该结果作为内置 PRNG 的种子,lrand48返回随机数。

//----- (00001384) --------------------------------------------------------
// This is never used in provided code! :S
int getNumber()
{
  __int32 v0; // r0@1

  // Seed srand48 using current local time in seconds since Epoch
  v0 = time(0);
  srand48(v0);
  // Return 1 byte integer casted from nonnegative long integer uniformly distributed between 0 and 2^31 
  return (unsigned __int8)lrand48();
}

三、getKey

[编辑]基于传递值的简单流填充。目前尚不清楚crc64它的参数是什么以及如何使用,但它似乎没有收到缓冲区。返回的低位双字crc64被重复复制以创建密钥序列,并用作内部 PRNG 状态。crc64是用于创建初始状态的函数,还是用于geyKey. 它有一些反编译膨胀(即,由于反编译器没有尽其所能,导致额外的冗余 C 语句),但基本上这个函数会一遍又一遍地用相同的值填充请求的缓冲区。

//----- (00001398) --------------------------------------------------------
int __fastcall getKey(void *a1_NI_key_buffer, unsigned int a2_NI_key_length, unsigned __int8 a3, int a4_NI_unused, char a5)
{
  void *v5; // r8@1
  unsigned int v6; // r7@1
  unsigned int v7; // r10@1
  void *v8; // r5@1
  __int64 v9; // r0@1
  signed int v10; // r6@1
  __int64 v12; // [sp+8h] [bp-30h]@1
  int v13; // [sp+14h] [bp-24h]@1

  v5_NI_key_buffer = a1_NI_key_buffer;
  v6_NI_key_length = a2_NI_key_length;
  v7_NI_key_8byte_chunks = a2_NI_key_length >> 3;
  v8_NI_key_buffer_pos = a1_NI_key_buffer;
  v13_NI_stack_guard = _stack_chk_guard; //a stack guard
  LODWORD(v9_NIl_partial_crc_state) = crc64(a3, (int)&a5, _stack_chk_guard, 8);
  v10_NI_current_8byte_chunk = 0;
  v12_NI_full_crc_state = v9_NIl_partial_crc_state;

  // Loop on 8 byte long chunks of the required key length
  do
  {
    // increase the counter for the current 8byte chunk we're using
    ++v10_NI_current_8byte_chunk;

    // If current 8byte chunk exceeds the required length
    if ( 8 * v10_NI_current_8byte_chunk > v6_NI_key_length )
    {
      // If some bytes of the 8byte chunks are needed
      if ( v6_NI_key_length >= 8 * v10_NI_current_8byte_chunk - 8 )
      {
        // Copy portion of v12_NI_full_crc_state needed to fill the buffer
        LODWORD(v9_NIl_partial_crc_state) = memcpy(v8_NI_key_buffer_pos, &v12_NI_full_crc_state, (size_t)((char *)v5_NI_key_buffer + v6_NI_key_length - (_DWORD)v8_NI_key_buffer_pos));
      }
    }
    else
    {
      // Set v9_NIl_partial_crc_state to the initial v12_NI_full_crc_state
      v9_NIl_partial_crc_state = v12_NI_full_crc_state;
      *(_QWORD *)v8_NI_key_buffer_pos = v12_NI_full_crc_state;
    }
    v8_NI_key_buffer_pos = (char *)v8_NI_key_buffer_pos + 8;
  }while ( v10_NI_current_8byte_chunk <= (signed int)v7_NI_key_8byte_chunks );

  // Make sure stack wasn't damaged in the process
  if ( v13_NI_stack_guard != _stack_chk_guard )
    _stack_chk_fail(v9_NIl_partial_crc_state);
  return v9_NIl_partial_crc_state;
}

最后但并非最不重要的是decryptData

这就是魔法发生的地方,购买它并不太神奇。基本上,第一个字节用于getKey提供状态,以及a4_NI_unknown_constant传递给 的参数decryptData这两个字节决定了整个getKey函数。

最后一个字节(被奇怪地处理doXor)用作基本的清理/错误检测字节,并且必须产生正确的值才能接受和正确解密消息。

//----- (000014E4) --------------------------------------------------------
signed int __fastcall decryptData(void *a1_NI_result, unsigned int *a2_NI_source, int a3_NI_length, int a4_NI_unknown_constant, __int64 a5)
{
  int v5; // r4@1
  void *v6; // r11@1
  unsigned int *v7; // r10@1
  unsigned int v8; // r7@3
  int v9; // r9@3
  int v10; // ST10_4@3
  void *v11; // r8@3
  const void *v12; // r5@3
  int v13; // r6@3
  signed int result; // r0@5

  v5_NI_length_copy = a3_NI_length;
  v6_NI_result_copy = a1_NI_result;
  v7_NI_source_copy = a2_NI_source;
  if ( check == 1 )
  {
    if ( a5 )
    {
      result = 0;
    }
    else
    {
      v8_NI_source_first_byte = *(_BYTE *)a2_NI_source;
      v9_NI_unknown_plus_first_byte = a4_NI_unknown_constant + v8_NI_source_first_byte;
      v10_NI_source_last_byte = *((_BYTE *)a2_NI_source + a3_NI_length - 1);
      v11_NI_key = malloc(a3_NI_length - 1);
      v12_NI_temp_result = malloc(v5_NI_length_copy - 1);

      // generate xor key based on:
      // 1. length of data
      // 2. unknown constant provided to decryptData as a4_NI_unknown_constant
      // 3. first byte of encrypted string
      getKey(v11_NI_key, (unsigned __int16)(v5_NI_length_copy - 1), v9_NI_unknown_plus_first_byte, (2596069104u * (unsigned __int64)v8_NI_source_first_byte >> 32) + 305419896 * v8_NI_source_first_byte, -16 * v8_NI_source_first_byte);
      v13_result_length = v5_NI_length_copy - 2;

      // XOR
      doXor((int)v12_NI_temp_result, (int)((char *)v7_NI_source_copy + 1), (int)v11_NI_key, v10_NI_source_last_byte, v13_result_length, (unsigned __int64)v13_result_length >> 32, v10_NI_source_last_byte);

      // Copy result from v12_NI_temp_result to user provided reulst buffer v6_NI_result_copy
      memcpy(v6_NI_result_copy, v12_NI_temp_result, v5_NI_length_copy - 2);

      // If last byte in v12_NI_temp_result is not the same as v9_NI_unknown_plus_first_byte, return Null
      if ( *((char *)v12_NI_temp_result + v5_NI_length_copy - 2) != v9_NI_unknown_plus_first_byte )
        v13_result_length = 0;

      // If key allocated, free it
      if ( v11_NI_key )
        free(v11_NI_key);

      // Free v12_NI_temp_result
      free((void *)v12_NI_temp_result);

      // return v13_result_length
      result = v13_result_length;
    }
  }
  else
  {
    result = -1;
  }
  return result;
}

最后,具体回答您的问题:

  1. 种子的长度是单个字节,如在 中生成getNumber并用于 中的密钥生成getKeygetKey通过扩展它和其他基于它计算的参数来使用v9_NI_unknown_plus_first_byte以生成QWORD.
  2. 为此,您需要进一步 REcrc64功能。对于那个很抱歉。但是getKey更好地理解该函数可以使用另一种(更有趣的)方法来解密数据流。由于我们现在知道“密钥”是一个重复使用的 8 字节序列,因此原始消息的每个已知字节都会显示相同的 8 字节偏移量处的所有其他字节。带有固定标头或已知类型数据(例如文本)的消息会揭示有关异或键的大量信息。
  3. 我不知道数据的实际内容是什么,也不知道如何解释它。这是验证类型和使用所必需的。您应该收集足够的样本集,解密它们并查看它们是否看起来像有效的纬度/经度值。

我认为您现在继续进行的最佳方法是实现一个模拟器,该模拟器接收消息并尝试使用上述信息对其进行解密。我可能犯了错误或忽略了一些小细节,但通过查看生成的解密中的错误并跟进这些错误,这些将更容易识别。