2

I'v been trying to reverse-engineer a BLE "Smartlock" to open it using Python instead of the App that it came with.

I progressed quite far with disassembling the SDK they provide, identifying BLE packets, headers and all, but now I'm stuck. It contains a .so file inside the Java library which is reponsible for encoding and decoding the data that is sent from and to the lock. It takes two arguments and returns the en- or decrypted data:

// .java - The data as a byte array and a single byte to somehow encrypt it all
public static native byte[] decodeWithEncryptByte(byte[] dataByteArray, byte encryptByte);

Maybe it's even an algorighm that is well known/has a name, but I'm completely stuck. Any tip would be helpful, even in human-readable pseudocode!

The decodeWithEncriptByte part follows:

.text:00000a20 <Java_com_scaf_android_client_CodecUtils_decodeWithEncrypt>:
.text:00000a20 55                               push   %ebp
.text:00000a21 57                               push   %edi
.text:00000a22 56                               push   %esi
.text:00000a23 53                               push   %ebx
.text:00000a24 e8 97 fb ff ff                   call   0x000005c0
.text:00000a29 81 c3 b7 25 00 00                add    $0x25b7,%ebx
.text:00000a2f 8d 64 24 c4                      lea    -0x3c(%esp),%esp
.text:00000a33 8b 74 24 50                      mov    0x50(%esp),%esi
.text:00000a37 0f b6 44 24 5c                   movzbl 0x5c(%esp),%eax
.text:00000a3c 8b 4c 24 58                      mov    0x58(%esp),%ecx
.text:00000a40 88 44 24 2f                      mov    %al,0x2f(%esp)
.text:00000a44 8b 06                            mov    (%esi),%eax
.text:00000a46 89 4c 24 04                      mov    %ecx,0x4(%esp)
.text:00000a4a c7 44 24 08 00 00 00 00          movl   $0x0,0x8(%esp)
.text:00000a52 89 34 24                         mov    %esi,(%esp)
.text:00000a55 ff 90 e0 02 00 00                call   *0x2e0(%eax)
.text:00000a5b 8b 7c 24 58                      mov    0x58(%esp),%edi
.text:00000a5f 89 44 24 20                      mov    %eax,0x20(%esp)
.text:00000a63 8b 06                            mov    (%esi),%eax
.text:00000a65 89 7c 24 04                      mov    %edi,0x4(%esp)
.text:00000a69 89 34 24                         mov    %esi,(%esp)
.text:00000a6c ff 90 ac 02 00 00                call   *0x2ac(%eax)
.text:00000a72 89 c5                            mov    %eax,%ebp
.text:00000a74 8b 06                            mov    (%esi),%eax
.text:00000a76 89 6c 24 04                      mov    %ebp,0x4(%esp)
.text:00000a7a 89 34 24                         mov    %esi,(%esp)
.text:00000a7d ff 90 c0 02 00 00                call   *0x2c0(%eax)
.text:00000a83 8b 4c 24 20                      mov    0x20(%esp),%ecx
.text:00000a87 89 c7                            mov    %eax,%edi
.text:00000a89 8b 06                            mov    (%esi),%eax
.text:00000a8b 89 4c 24 10                      mov    %ecx,0x10(%esp)
.text:00000a8f 89 6c 24 0c                      mov    %ebp,0xc(%esp)
.text:00000a93 c7 44 24 08 00 00 00 00          movl   $0x0,0x8(%esp)
.text:00000a9b 89 7c 24 04                      mov    %edi,0x4(%esp)
.text:00000a9f 89 34 24                         mov    %esi,(%esp)
.text:00000aa2 ff 90 40 03 00 00                call   *0x340(%eax)
.text:00000aa8 8b 06                            mov    (%esi),%eax
.text:00000aaa c7 44 24 08 00 00 00 00          movl   $0x0,0x8(%esp)
.text:00000ab2 89 7c 24 04                      mov    %edi,0x4(%esp)
.text:00000ab6 89 34 24                         mov    %esi,(%esp)
.text:00000ab9 ff 90 e0 02 00 00                call   *0x2e0(%eax)
.text:00000abf 89 44 24 24                      mov    %eax,0x24(%esp)
.text:00000ac3 8b 06                            mov    (%esi),%eax
.text:00000ac5 89 7c 24 04                      mov    %edi,0x4(%esp)
.text:00000ac9 89 34 24                         mov    %esi,(%esp)
.text:00000acc ff 90 ac 02 00 00                call   *0x2ac(%eax)
.text:00000ad2 89 44 24 28                      mov    %eax,0x28(%esp)
.text:00000ad6 85 c0                            test   %eax,%eax
.text:00000ad8 7e 3f                            jle    0x00000b19
.text:00000ada 8b 7c 24 24                      mov    0x24(%esp),%edi
.text:00000ade 89 c1                            mov    %eax,%ecx
.text:00000ae0 89 74 24 50                      mov    %esi,0x50(%esp)
.text:00000ae4 0f b6 e8                         movzbl %al,%ebp
.text:00000ae7 0f b6 74 24 2f                   movzbl 0x2f(%esp),%esi
.text:00000aec 89 f8                            mov    %edi,%eax
.text:00000aee 01 cf                            add    %ecx,%edi
.text:00000af0 8b 8b fc ff ff ff                mov    -0x4(%ebx),%ecx
.text:00000af6 8d 76 00                         lea    0x0(%esi),%esi
.text:00000af9 8d bc 27 00 00 00 00             lea    0x0(%edi,%eiz,1),%edi
.text:00000b00 89 f2                            mov    %esi,%edx
.text:00000b02 83 c0 01                         add    $0x1,%eax
.text:00000b05 32 50 ff                         xor    -0x1(%eax),%dl
.text:00000b08 88 50 ff                         mov    %dl,-0x1(%eax)
.text:00000b0b 32 14 29                         xor    (%ecx,%ebp,1),%dl
.text:00000b0e 88 50 ff                         mov    %dl,-0x1(%eax)
.text:00000b11 39 f8                            cmp    %edi,%eax
.text:00000b13 75 eb                            jne    0x00000b00
.text:00000b15 8b 74 24 50                      mov    0x50(%esp),%esi
.text:00000b19 8b 7c 24 28                      mov    0x28(%esp),%edi
.text:00000b1d 8b 06                            mov    (%esi),%eax
.text:00000b1f 89 34 24                         mov    %esi,(%esp)
.text:00000b22 89 7c 24 04                      mov    %edi,0x4(%esp)
.text:00000b26 ff 90 c0 02 00 00                call   *0x2c0(%eax)
.text:00000b2c 8b 4c 24 24                      mov    0x24(%esp),%ecx
.text:00000b30 8b 16                            mov    (%esi),%edx
.text:00000b32 89 7c 24 0c                      mov    %edi,0xc(%esp)
.text:00000b36 89 4c 24 10                      mov    %ecx,0x10(%esp)
.text:00000b3a 89 44 24 04                      mov    %eax,0x4(%esp)
.text:00000b3e 89 34 24                         mov    %esi,(%esp)
.text:00000b41 c7 44 24 08 00 00 00 00          movl   $0x0,0x8(%esp)
.text:00000b49 89 44 24 24                      mov    %eax,0x24(%esp)
.text:00000b4d ff 92 40 03 00 00                call   *0x340(%edx)
.text:00000b53 8b 44 24 20                      mov    0x20(%esp),%eax
.text:00000b57 8b 16                            mov    (%esi),%edx
.text:00000b59 89 34 24                         mov    %esi,(%esp)
.text:00000b5c 89 44 24 08                      mov    %eax,0x8(%esp)
.text:00000b60 8b 44 24 58                      mov    0x58(%esp),%eax
.text:00000b64 c7 44 24 0c 00 00 00 00          movl   $0x0,0xc(%esp)
.text:00000b6c 89 44 24 04                      mov    %eax,0x4(%esp)
.text:00000b70 ff 92 00 03 00 00                call   *0x300(%edx)
.text:00000b76 8b 44 24 24                      mov    0x24(%esp),%eax
.text:00000b7a 8d 64 24 3c                      lea    0x3c(%esp),%esp
.text:00000b7e 5b                               pop    %ebx
.text:00000b7f 5e                               pop    %esi
.text:00000b80 5f                               pop    %edi
.text:00000b81 5d                               pop    %ebp
.text:00000b82 c3                               ret    
.text:00000b83 8d b6 00 00 00 00                lea    0x0(%esi),%esi
.text:00000b89 8d bc 27 00 00 00 00             lea    0x0(%edi,%eiz,1),%edi

Similarly, the encode process is probably very similar.

I have used a online disassembler to analyse the Assembly and the opened file is available over there as well, if anyone has a clue what's going on here!

Any help is greatly appreciated!

EDIT: The file is available here: https://github.com/ttlock/Android_SDK_Demo/blob/master/app/libs/ttlock-release-3.0.7.aar

To view the specific file in question, you only need to download it and extract it, inside is a folder named jni/ with the different architectures of the same thing

The device is a TTLock and I'm looking to reverse-engineer it to make it fully open-source, so that people can make custom gateways to the locks, permitting it to work locally (current Gateway only supports Web-operation... and phones China for that). I have 5 TTLocks here, although they're nice locks, it's a bummer that they don't work over LAN when the internet is down.

7
  • 2
    Ghidra can generate pseudocode, however there are some indirect calls that may be important. You could live-debug the binary to check what is going on.
    – morsisko
    Aug 28, 2020 at 0:28
  • Thanks for the suggestion. I tried it but couldn't really get an usable result @morsisko
    – Fusseldieb
    Aug 28, 2020 at 1:43
  • Try to using r2dec or IDA Decompiler with static analysis will be very helpful
    – nornor
    Aug 29, 2020 at 11:26
  • 1
    Without having the whole .so it is hard to tell anything more helpful
    – morsisko
    Aug 31, 2020 at 13:58
  • 1
    @julian I included the file and a better description on what I'm planning to do
    – Fusseldieb
    Aug 31, 2020 at 21:29

2 Answers 2

2
+100

With a 256-byte table dscrc_table declared, the decryption loop is a one-liner:

  for( int i = 0; i < vNewByteArrayLen; ++i )
    vNewByteArrayElts[i] ^= a4KeyByte ^ dscrc_table[vNewByteArrayLen];

See my gist on GitHub.

Edit: is this thing really being used to protect your door from being opened by unauthorized people? Throw it away!

13
  • Wow! That's just amazing! I'll take a look at it, but I'm still a bit overthrown by all the C++ code. The whole Java code is already a mess, one function calls the other, all without proper names due being decompiled. You helped me a ton with that! If you're interested in this little journey to opensource the lock, let me know, but no need for that! :)
    – Fusseldieb
    Sep 1, 2020 at 3:58
  • 1
    It's nearly identical. Since that function does not take a key byte as input, it instead uses the last byte as the key. If you were to take your input array, chop off the last byte (making the array one byte smaller), and pass the smaller array and the key byte to the function above, the results would be the same. Sep 1, 2020 at 4:31
  • 1
    In more specific detail, the differences are: 1) since that function does not take a key byte as argument, it uses the last element in the array as the key byte; 2) the for loop goes for one fewer iteration (not processing the last, key, byte); 3) the expression for accessing dscrc_table is almost the same, but put a -1 at the end of it inside of the square brackets; 4) since the for loop ignores the last byte (as that is used as the key), the size of the output array is one byte smaller. Sep 1, 2020 at 4:32
  • 1
    I have to reiterate, if this is the encryption scheme being used to protect your front door, REMOVE IT AND THROW IT AWAY. Sep 1, 2020 at 4:41
  • 1
    Each output byte is the XOR of three things: 1) the input byte from the array; 2) the byte passed as argument; 3) the one pulled from dscrc_table[len(array)]. Note that #2 and #3 are the same for every iteration of the loop; only #1 changes. Sep 1, 2020 at 23:35
2

Rolf Gave a nice Answer comparing his gist with a few minutes of ghidra on the file you edited in ghidra does give a nearly identical Pseudo Code

looking at JniEnv it is defined like

struct {
const struct func* Funcs;
jwhatever GetSomeJava(jsomething foo, jotherthing blah) {
return funcs->GetSomeJava(…...);
}
229 function ptrs like this 
}

so creating a fake struct with 230 PVOIDS and retyping the param 1 as JniEnv* ghidra makes the *code (blah + foo ) as Env->xn the dscrc is a dallas semiconductor crc lookup table

here is the pseudo code from ghidra

/* WARNING: Function: __i686.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4
Java_com_scaf_android_client_CodecUtils_decodeWithEncrypt
          (JniEnv *Env,int param_2,byte *dallasarray,byte key)

{
  byte bVar1;
  int local_EAX_53;
  int local_EAX_76;
  int local_EAX_93;
  byte *local_EAX_153;
  uint uVar2;
  int i;
  byte *pbVar3;
  undefined4 uVar4;
  
  local_EAX_53 = (**(code **)((int)Env->field_0x0 + 0x2e0))(Env,dallasarray,0);
  local_EAX_76 = (**(code **)((int)Env->field_0x0 + 0x2ac))(Env,dallasarray);
  local_EAX_93 = (**(code **)((int)Env->field_0x0 + 0x2c0))(Env,local_EAX_76);
  (**(code **)((int)Env->field_0x0 + 0x340))(Env,local_EAX_93,0,local_EAX_76,local_EAX_53);
  local_EAX_153 = (byte *)(**(code **)((int)Env->field_0x0 + 0x2e0))(Env,local_EAX_93,0);
  uVar2 = (**(code **)((int)Env->field_0x0 + 0x2ac))(Env,local_EAX_93);
  if (0 < (int)uVar2) {
    i = (int)local_EAX_153;
    do {
      pbVar3 = (byte *)(i + 1);
      bVar1 = *(byte *)i;
      *(byte *)i = key ^ bVar1;
      *(byte *)i = key ^ bVar1 ^ dscrc_table[uVar2 & 0xff];
      i = (int)pbVar3;
    } while (pbVar3 != local_EAX_153 + uVar2);
  }
  uVar4 = (**(code **)((int)Env->field_0x0 + 0x2c0))(Env,uVar2);
  (**(code **)((int)Env->field_0x0 + 0x340))(Env,uVar4,0,uVar2,local_EAX_153);
  (**(code **)((int)Env->field_0x0 + 0x300))(Env,dallasarray,local_EAX_53,0);
  return uVar4;
}

importing data types from this ghidra Extension JniAnalyzer the PseudoCode becomes Still Better

F:\>mkdir jniana

F:\>cd jniana

F:\jniana>f:\git\bin\git.exe clone https://github.com/Ayrx/JNIAnalyzer.git

Cloning into 'JNIAnalyzer'...
remote: Enumerating objects: 347, done.
Rremote: Total 347 (delta 0), reused 0 (delta 0), pack-reused 347                                                       Recei
Receiving objects: 100% (347/347), 14.27 MiB | 102.00 KiB/s, done.
Resolving deltas: 100% (92/92), done.

F:\jniana>dir /b /s *.gdt
F:\jniana\JNIAnalyzer\JNIAnalyzer\data\jni_all.gdt

F:\jniana>f:\ghidra912\ghidraRun.bat

click the small black arrow in Data Type Manager 
Open File Archieve 
Navigate to this gdt and load it 

Select the Function in decompiler and Edit Function Signature 
Retype the First Argument to JniEnv* and see the differnece 
as below 

Pseudo Code After Adding the Correct type for first Argument JniEnv * param1

jbyteArray
Java_com_scaf_android_client_CodecUtils_decodeWithEncrypt
          (JNIEnv *Env,int Unused,jbyteArray param_3,byte param_4)

{
  byte bVar1;
  jbyte *buf;
  jsize len;
  jbyteArray array;
  byte *buf_00;
  uint len_00;
  byte *pbVar2;
  byte *pbVar3;
  
  buf = (*(*Env)->GetByteArrayElements)((JNIEnv *)Env,param_3,(jboolean *)0x0);
  len = (*(*Env)->GetArrayLength)((JNIEnv *)Env,(jarray)param_3);
  array = (*(*Env)->NewByteArray)((JNIEnv *)Env,len);
  (*(*Env)->SetByteArrayRegion)((JNIEnv *)Env,array,0,len,buf);
  buf_00 = (byte *)(*(*Env)->GetByteArrayElements)((JNIEnv *)Env,array,(jboolean *)0x0);
  len_00 = (*(*Env)->GetArrayLength)((JNIEnv *)Env,(jarray)array);
  if (0 < (int)len_00) {
    pbVar2 = buf_00;
    do {
      pbVar3 = pbVar2 + 1;
      bVar1 = *pbVar2;
      *pbVar2 = param_4 ^ bVar1;
      *pbVar2 = param_4 ^ bVar1 ^ dscrc_table[len_00 & 0xff];
      pbVar2 = pbVar3;
    } while (pbVar3 != buf_00 + len_00);
  }
  array = (*(*Env)->NewByteArray)((JNIEnv *)Env,len_00);
  (*(*Env)->SetByteArrayRegion)((JNIEnv *)Env,array,0,len_00,(jbyte *)buf_00);
  (*(*Env)->ReleaseByteArrayElements)((JNIEnv *)Env,param_3,buf,0);
  return array;
}
1
  • Thanks for helping out! Yes, I saw a very similar, if not identical code, but I was kinda overwhelmed with all those variables. Does it XOR every variable like Rolf mentioned (and then XOR it back to the "original" array?) I'm rewriting a very crude C++ program that does this process so that I can run the Wireshark value through it and see what comes out, but I'm still a little bit stuck.
    – Fusseldieb
    Sep 1, 2020 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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