2

It's been 4 nights I'm struggling decompiling this one. It's an Android native library that I ran through IDA to get C code.

Java signature :

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 */)

The disassembly in 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;
}

I have the implementation of crc64. What I fail to understand is where does it get the seed from the data array for the getKey.

I'm not sure, I think it stores in the last 2 bytes the key that is used to generated the bigger key for xor. Please help, I'm really struggling and my C skills are a rusty.

Here is a set of data :

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

Normally, first 8 bytes gives the latitude and second longitude which in this case is : f8869e63e888bb3f29ae997e0bc6e93f

So basically I assume we can expect this to be the coded version : ea8bf72287a0af8aa65edf259a43e1d8 The inverse of xor is a xor so after xor it gives : 120d69416f2814b58ff0465b918508e7

Which hypothetically is our partial xor key.

Now questions :

  • what is the length of the seed stored in data?
  • how the xor key is computed?
  • are we sure about latitude/longitude position in data?

If you want to help outside stack and discuss this please contact me. Many thanks

2
+100

Here's your code with names given to most variables. That's quite a bit of code so I'll try to only iterate the important parts. I also added a few comments in the code to help reading, although I didn't try to cover all code with comments. Make sure you go over the named parameters, I believe those will help you understand the code quickly. Viewing the code in a syntax highlighting editor will also help (couldn't get SO to highlight).

Please add a comment about anything that's not clear enough.

the NI prefix is my initials, you can ignore it.

First, doXor:

This function simply XORs all bytes except the last byte, which is treated a bit differently, but more on that later. This is not part of the while loop simply because it's recevied differently in doXor. A possible reasoning behind this is to force any user of doXor to explicitly deal with this value, as it's somewhat important for asserting the validity of the decrpyed message.

//----- (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;
}

Second, getNumber:

This is never actually used, but generates a single byte of random data which is somewhat biased for sepcificaly the value 255 because casting a "nonnegative long integer uniformly distributed between 0 and 2^31" to a unsigned byte will, in most cases, yield a number above 255.

time will return the current local time in seconds since Epoch, srand48 will seed the builtin PRNG with that result and lrand48 return the random number.

//----- (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();
}

Third, getKey:

[EDIT] A simple stream padding based on passed values. It is unclear what crc64 does and how are it's parameters used, but it appears as if it does not receive a buffer. The low dword returned from crc64 is copied repeatedly to create the key sequence, and is used as the internal PRNG state. crc64 is there for the function creating the initial state, or the seed function for geyKey. It has some decompliation bloat (that is, extra redundant C statements caused by the decompiler not doing the best job it could) but basically this function fills the requested buffer with the same value over and over.

//----- (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;
}

Last but not least, decryptData:

This is where the magic happens, buy it's not too magical. Basically, the first byte is used to feed getKey with a state, togather with a4_NI_unknown_constant parameter passed to decryptData. These two bytes are what determines the entire getKey function.

The last byte (treated strangly in doXor is used as a basic sanify/error detection byte and must result in the correct value for the message to be accepted and properly decrypted.

//----- (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;
}

Finally, sepcific answers to your questions:

  1. The length of the seed is a single byte, as generated in getNumber and used for key generation in getKey. It is used at getKey by expanding it and the other arguments calculated based on v9_NI_unknown_plus_first_byte to generate a QWORD.
  2. For that you'll need to further RE the crc64 function. Sorry about that. But understanding the getKey function better makes it possible to use another (more interesting) approach to decrypting streams of data. Since we now know the "key" is an 8byte sequeces used repeatedly, every known byte of the original message reveals all other bytes at the same 8byte offset. Messages with fixed headers, or known types of data (textual, for example) reveal a lot of information about the xor key.
  3. I have no idea what's the actual content of the data, nor how to interpret it. That is required in order to validate the type and use. You should gather enough sample sets, decrypt them and see if they look like valid latitude/longitude values.

I think the best way for you to proceed now is implementing a simulator that receives a message and tries to decrypt it using the information above. I may have made mistakes or overlooked some small details, but those will be eaier to identify by seeing errors in the produced decryption and following up on those.

  • thank you, I will review this later and give you feedback and hopefully the bounty. – TecHunter Sep 5 '16 at 8:53
  • You're welcome! If you have any further questions or issues you'd like me to focus more on go ahead and point those out. – NirIzr Sep 5 '16 at 21:26
  • The getKey function is the most important part here. I need to understand how we retrieve the key from the data – TecHunter Sep 6 '16 at 7:09
  • @TecHunter: it's actually simpler than I thought, I described it and now we're after crc64 which is missing here. Mostly, crc64 is used repeatedly as the actual XOR key which makes decrypting messages quite easy. Yay! I've edited my answer to reflect the additional details. – NirIzr Sep 6 '16 at 10:48
  • @Nirlzr great, here is the full C code gist i've reversed completely the crc64 function – TecHunter Sep 6 '16 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.