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:
- 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
.
- 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.
- 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.