Information Disclosure of Hardcoded Keys (in SQLite) and Encryption Algorithm (in AesFormula.js File) Resulting in Compromised the Real Credentials.
In the name of Allah, the Most Gracious, the Most Merciful.
This marks the third part (2nd section) of a series of articles exploring the fundamentals of security testing for Electron-based applications.
In this part, we will explore a unique case study we encountered regarding the extraction result of the .asar file.
If you are just beginning to read this article, in the previous section, we explained the importance of extracting and analyzing .asar files to gather useful information.
📌 This write-up is also available on Medium in the publication of the company where I’m employed. Feel free to check it out if you find Medium’s format more convenient: https://medium.com/haktrak-cybersecurity-squad/electron-based-app-security-testing-fundamentals-part-3-2nd-section-extract-analyze-asar-9d3e9241bb92
4.4. Case Study
The case study in this section will discuss one of our test results, regarding a client-server model of Electron-based application.
4.4.1. TL;DR
Here are the simple points about this issue:
- Found the “remember my key” feature in the application.
- Searching for the existence of the username within the directory owned by the target application (such as application support and the like).
- Finding the username in the SQLite stored in the .app_target directory within the /Users/Username/ directory.
- We noticed that the key appeared to be encrypted.
- The app.asar application was extracted to search for the encryption formula used for the key.
- AesFormula.js was found.
- Searching for the key storage location (named EncryptionMaterial).
- The key, IV, and salt storage locations were discovered within the same table as the username.
- Discovering the key, IV, and salt storage location within the same table as the username.
- A custom decryptor was created by invoking functions in AesFormula.js.
- The decryption of the key was successful.
4.4.2. The Situation
Upon initially running the related desktop application, we noticed that the application has a feature to remember the entered credentials. This seemed reasonable, as the default provided credentials by the application were quite complex and difficult to remember.
(To maintain the privacy of the program owner, we do not use real images and codes in this case study. However, to facilitate the discussion, we will attempt to create sketches the images and remake the codes that roughly represent the explanations we provide).

However, there is a qoidah that we need to understand in the concept of native applications. When we use the “remember me” feature provided by an application, essentially the application will store our credentials in the local operating system being used. Despite the differences in storage methods and forms, this qoidah must be firmly adhered to for a clear understanding of a flow.
So, where is the location of this data? Technically, the location of third-party application data storage varies across operating systems. For instance, application data on macOS is typically stored in the “Application Support” directory within the user’s “Library” directory. Meanwhile, on Windows, third-party application data is often stored in a hidden folder within the user’s “AppData” directory. And on Linux, application data is often stored in the .config directory within the user’s directory.
Since the testing was conducted on an application for macOS at that time, then the explanation in the next step will be focused on details specific to macOS.
4.4.3. Information Gathering
Note: to try to analyze the behavior of this application, we need to attempt to log in first and check the option “Remember my key.”
4.4.3.1. Check on Application Support and Preferences Directory
In our initial experiments in the macOS environment, we conducted an examination of the application directory, which is generally located in “Application Support” and “Preferences”.
As an information, the “Application Support” directory is basically used by macOS-based applications to store data needed to run the application itself properly, or at least to obtain data needed by users related to the application. This can include configuration files, local databases, caches, or other files needed to run the application smoothly. For example, a data processing application may store document templates here, or a game application may store user game progress data here.

In other side, the “Preferences” directory is basically used to store configuration files of applications customized by users. It includes user preferences such as display settings, language preferences, keyboard preferences, and so on. In this situation, user settings are typically “stored” in .plist files.

Then how do we do it? In this situation, we employ a fairly traditional method, which is using the `grep` command accompanied by a specific search for the username we possess to log in to the app.
The following is the command we used:
grep -r username_of_the_apphere /Users/username/Library/Application\ Support/target_app/
And here is a simple explanation of the command:
- grep: this is a command that used to match a specific string pattern in the given input.
- -r: this is an option used to search recursively within directories and subdirectories.
- username_of_the_apphere: this is the string that we want to search for — which is the username that we used in the application.
- /Users/username/Library/Application\ Support/target_app/: This is the directory where we want to perform the search. In this case, grep will search for the string within all files in the directory `/Users/username/Library/Application\ Support/target_app/` and its subdirectories.

We also did the same thing with the “target_app” file under preferences directory.
grep -r username_of_the_apphere /Users/username/Library/Preferences/com.electron.target-app.plist

So what’s the result? In short, from this analysis, we did not find any data related to the username. After that, we tried to move on to another location.
There might be a question arising as to why we are so confident that this username is stored in cleartext (unprotected). Actually the answer is, we are not certain about the state of this stored username, but there’s no harm in trying to search for it.
4.4.3.2. Check on the Possibility of Hidden Directory (dot Directory)
One thing we should all be aware of, within macOS (and Unix systems in general), there are directories that are hidden from the user’s view. These directories typically start with a dot (.) — and are located within /Users/username.
In its implementation, third-party applications sometimes use these types of directories to store application-related information, which technically does not need to be accessed by the user.
Note: Occasionally, there are also applications that place their data in the directory /Users/Username/.local/share.
Based on this situation, we took the initiative to examine the state of /Users/Username to see if this type of directory exists. Long story short, we found a positive result, namely that target_app also has such a directory.


When we first noticed the existence of the .data.db file, we were already quite confident that there was a possibility that this file contained the credentials we were looking for. However, to confirm, we proceeded to perform a traditional search using grep as we mentioned earlier. And indeed, the result was as predicted, with this username found within the related .db.

4.4.3.3. Opening the .db file with DB Browser for SQLite
Generally, an application (whether mobile or desktop) will use a lightweight DBMS to perform storage on a local computer. One common choice is SQLite.
After ensuring that it is indeed SQLite, we opened it with a simple tool called DB Browser for SQLite (although other tools can be used).

Long story short, we found what we were looking for, namely the columns for username, URL, and the key.


However, after further examination of the values we used, it turned out that the value of key did not match the expected value. From its format, we believe that this is an AES encryption format, a commonly used algorithm for data security.
Value: eLy2xThk+y7Rki4J13zvGVxrnqZbrOtznvNbhOKIgWo=
Once more, it should be noted that, to simplify the discussion while still hiding any encryption values and codes, we have replaced them with other samples (and yes, it’s a remake from ChatGPT).
4.4.3.4. Looking for Encryption Algorithm
From the situation we’ve described, we proceeded with testing by exploring the potential algorithms and formulas used to “protect” the value of key. In short, considering that the target_app application is built on Electron, we attempted to extract the app.asar file found within the package content.
Please note that:
- To learn how to detect Electron-based applications, readers can refer to Chapter 3.
- As for the extraction process, readers can revisit the beginning of this Chapter 4.
After the extraction process is successful, we can proceed with the search for the algorithm and formula used by the application to protect the value of the key mentioned earlier.
4.4.3.5. Found the Encryption Algorithm
After carefully examining each file within the extracted directory, we found a file that caught our attention, namely AesFormula.js:


4.4.3.6. Understanding the Encryption Flow
Upon examining the code flow within the AesFormula.js file, we identified a segment responsible for generating encryption keys and initialization vectors (IVs) dynamically. This segment consists of two functions: generateEncryptionKey and generateIV.
In the generateEncryptionKey function, a random key component is generated using the crypto.randomBytes method with the length specified by KEY_LENGTH. Subsequently, a Salt (SALT) is generated using the generatePBKDF2Salt function.

Those random key component and a default key are then combined through a bitwise XOR operation to produce a unique key. This resulting key, along with the generated SALT, undergoes key derivation using the PBKDF2 algorithm, resulting in a derived key (pbkdfKey). And finally, the first portion of this derived key, equivalent to KEY_LENGTH bytes, is selected as the encryption key for AES encryption.
Similarly, in the generateIV function, a random IV is generated using the crypto.randomBytes method with the length specified by IV_LENGTH. This IV is utilized in the AES encryption process as a unique initialization vector.

These dynamic generation processes ensure that each encryption operation utilizes distinct keys and IVs, enhancing the security of the encryption mechanism.
Technically, AesFormula in general facilitates secure encryption by dynamically generating encryption keys and IVs for each encryption operation.
To confirm our assumption, we attempted to log in and log out several times and observed the changes in the key within the SQLite.
So, what were the results? They were positive. From three valid login attempts, we found that the key value in the SQLite also changed. Here are three different encryption results for the same value:



So what’s next? Surely it’s about finding the values of salt, key, and IV generated by this code. As for the process, we continued studying the flow of the existing code until we finally came across the following snippet:

This snippet shows that the results of all key generation processes will be stored in a section called “encryptionMaterial”.
4.4.3.7. Found the “Key” Location
In this situation, we certainly need to search for the values contained in encryptionMaterial. Long story short, after trying to analyze the contents of the SQLite database, we finally discovered that the values of encryptionMaterial are stored hardcoded in the same table, namely t_user, within the some_secret_name column.


4.4.4. Decrypting the Secret Key Value
We already have the encrypted value of key and we also have the key value used (iv, keyComponentBuf, and salt), so next we will try to decrypt the existing key value.
To perform this decryption operation, one of the easiest ways is to utilize the program itself (in this case, the function available in the AesFormula.js file).
Here is a simple code that can be used to decrypt a value of key:
const AesFormula = require('./AesFormula');
const encryptedValue = 'VQwSxZiICNhGjzLpcLSz1CDP11bEib26LFw+4av6VmE=';
const encryptionMaterial = {
iv: Buffer.from([0xb0, 0xe3, 0x88, 0x26, 0x7c, 0xe3, 0xc3, 0x91, 0x5d, 0x95, 0xec, 0x72, 0xa7, 0x67, 0xd5, 0x20]),
keyComponentBuf: Buffer.from([0xb8, 0x02, 0x63, 0x02, 0xe6, 0xcf, 0x1e, 0x61, 0xa9, 0x56, 0x1f, 0x49, 0xb1, 0x74, 0x77, 0x23, 0xb0, 0x59, 0xca, 0xa3, 0xa2, 0x8b, 0x34, 0x71, 0x23, 0x6c, 0xa5, 0x4d, 0x34, 0x85, 0x78, 0xeb]),
pbkdf2SaltBuf: Buffer.from([0x4e, 0xc4, 0x6f, 0x21, 0x4b, 0x55, 0xc7, 0x58, 0x38, 0x9d, 0x6e, 0x31, 0xc0, 0x37, 0xaa, 0x68])
};
const decryptValue = async () => {
try {
const decryptedResult = await AesFormula.decryptWithAES(encryptedValue, encryptionMaterial);
console.log('Decrypted value:', decryptedResult);
} catch (error) {
console.error('Error while decrypting:', error);
}
};
decryptValue();
All we need to do is input the values from encryptionMaterial (iv, keyComponentBuf, and salt) into it. Save this code (for example with name: decrypt.js). Then, place AesFormula.js in the same directory as where this code is located.
To execute this code, we need the assistance of node.js. Here is the final result of running the program:

In summary, the PoC involves:
- Get the key value from the key column in the t_user table in the data.db file.
- Get the encryptionMaterial value from the some_secret_name column in the t_user table in the data.db file.
- Copy the key value into the decrypt.js code that we previously created.
- Copying the encryptionMaterial value (one by one for iv, keyComponentBuf, and salt) into the decrypt.js code that we created.
- Place the AesFormula.js file in the same directory as the decrypt.js file.
- Run the decrypt.js code with node.js using the command `node decrypt.js`.
4.4.5. Additional Information
4.4.5.1. Point of View
From this case, we can also acknowledge that the encryption implementation performed by the application is quite robust, such as utilizing dynamic keys that undoubtedly make it challenging for an attacker to guess. However, it’s the storage of the key formula and the key itself that ultimately becomes the focus of the issue discussed in this case study.
The execution of this issue may raise a question: “wouldn’t an attacker who gains local access be able to directly log in through the target_app that has activated the ‘Remember my key’ feature?”
In essence, this is indeed a valid question. However, we need to delve deeper into the fact that the presence of an attacker on a local target is typically time-bound. Exploring every piece of data within the target_app would undoubtedly be hindered and not optimal for the attacker.
So, by exploiting this issue that described in this report, the attacker would no longer be constrained by time in stealing data from the user’s target_app. This is because the attacker can swiftly execute the theft by accessing the hardcoded usernames, URL, key, and encryptionMaterial stored in the SQLite database. Subsequently, the attacker can explore them from various locations outside the local target.
Nevertheless, regardless of the arguments about the validity of this issue in some programs, it is certainly beneficial during red teaming exercises.
4.4.5.2. SafeStorage Module
It should be noted that the Electron Framework has provided a module called SafeStorage, which can be used to secure data stored on disk from being accessed by other applications or users with full disk access. Technically, safeStorage in Electron utilizes native OS secure storage APIs to store data in encrypted form (So this could possibly be a good alternative).
REFERENCES
Most of the references in this section are still the same as in the previous section. As for this part, we will only include new references.
- R. Baylon, “Salt and IV explained,” 6 October 2013. [Online]. Available: https://ricardobaylon.wordpress.com/2013/10/06/salt-and-iv-explained/. [Accessed April 2024].
- Electron, “safeStorage,” [Online]. Available: https://www.electronjs.org/docs/latest/api/safe-storage. [Accessed April 2024].