InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties…

Follow publication

Electron JS ASAR Integrity Bypass

I recently created an Electron JS-based Windows and MacOS application. The newer version of Electron has Integrity detection which prevents tampering with the application code.

What is Electron JS

Electron JS is a cross-platform desktop application framework that allows you to create desktop applications using JavaScipt, HTML and CSS. It allows you to use the same code for Windows, Linux and macOS, The Electron JS uses the Chromium engine and NodeJS to work with the different OS and load the JS and HTML code as desktop applications. If you need more details on how it works, there is a detailed version available.

What is ASAR

When you compile the Electron JS code you get the main executable as well as the ASAR file. The main executable file will have the C++ and other code related to the Electron JS framework responsible for loading the relevant files and libraries and maintaining the process of the application. The thing to note is developer's written JS/HTML/CSS code is not part of the main executable.

There is a file called ASAR file, which will have all the HTML/JS or image files as well as other node modules. The ASAR file will be a packed archive file which will contain all the code. Since all the logic will be in this ASAR file we might need to read/modify some logic based on certain scenarios.

From the newer version of the Electron JS, the main executable file verifies the integrity of the ASAR file, if you unpack modify and repack the ASAR file. The application will not run due to the integrity detection.

How Integrity Detection Works

The logic for detecting integrity is a part of the Electron JS framework's C++ code. If we open the GitHub code for the Electron JS at:

void ValidateIntegrityOrDie(const char* data,
size_t size,
const IntegrityPayload& integrity)
{
if (integrity.algorithm == HashAlgorithm::kSHA256) {
uint8_t hash[crypto::kSHA256Length];
auto hasher = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
hasher->Update(data, size);
hasher->Finish(hash, sizeof(hash));
const std::string hex_hash =
base::ToLowerASCII(base::HexEncode(hash, sizeof(hash)));

if (integrity.hash != hex_hash) {
LOG(FATAL) << "Integrity check failed for asar archive ("
<< integrity.hash << " vs " << hex_hash << ")";
}
} else {
LOG(FATAL) << "Unsupported hashing algorithm in ValidateIntegrityOrDie";
}
}

The code is simple, during the compilation the electron JS generate the sha256 hash of the ASAR file, and the value of the hash is stored in the main executable file. The above code verifies the hash stored in the code with the new hash at the run time. The logic is quite simple and can be bypassed without using any reverse engineering.

If you need to know how to implement it you can check the documentation at

Bypass Integrity Detection

Since it already has a hash in the binary we can modify the hash. I have a simple application built with Electron JS.

$> ls | grep Integrity-Bypass.exe 
Integrity-Bypass.exe

$> strings Integrity-Bypass.exe | grep '"alg":"SHA256"'
[{"file":"resources\\app.asar","alg":"SHA256","value":"2a6e8324f650f57819d9130b7e52a1a3ab7265851b611ffd3c5f07cd67263744"}]

Now we know the hash stored in the binary is, it is the sha256 hash of the app.asar file but not as straightforward as it looks.

To verify if we can check that as well, the ASAR file will be in the resource folder. If we move to the folder and calculate the hash of the ASAR file.

Integrity-Bypass-win32-x64/resources$ cd resources/
Integrity-Bypass-win32-x64/resources$ ls
app.asar
Integrity-Bypass-win32-x64/resources$ sha256sum app.asar
070b0da7337c63b8db23de52e99a235df9858cf95dbb585c03fcdc594530a33a app.asar

The hash of the asar file is 070b0da7337c63b8db23de52e99a235df9858cf95dbb585c03fcdc594530a33a what we got is different from what is stored in the binary file. Both hash are Sha256 but there is a difference in how it is calculated. If we go to the ASAR integrity documentation page.

How ASAR hash generated

It's not generating sha256 of the asar file as we did with sha256sum, The Electron JS create a JSON object with each file inside the asar file, The object contains the name, size and hash of each file inside the asar archive. Later the whole JSON object is used to get the sha256 hash. The example object looks like this.

{
headerString: '{"files":{"forge.config.js":{"size":1172,"offset":"0","integrity":{"algorithm":"SHA256","hash":"5414a6b629ecad5022698cb1694fe92dcf28b39afd0116951c595d0e80eab64f","blockSize":4194304,"blocks":["5414a6b629ecad5022698cb1694fe92dcf28b39afd0116951c595d0e80eab64f"]}}',
header: {
files: {
'forge.config.js': [Object],
'index.html': [Object],
'index.js': [Object],
node_modules: [Object],
'package.json': [Object],
'preload.js': [Object]
}
},
headerSize: 9820
}

To generate a hash as well as unpack and pack the asar file we need to install the electron asar module.

Once it is installed we can save the below JavaScript code to get the JSON object as well as its hash.

const asar = require('@electron/asar');
const crypto = require('crypto');
const path = require('path');

const filePath = path.resolve(__dirname, 'resources', 'app.asar');

async function main() {
try {
const rawHeader = asar.getRawHeader(filePath);
//console.log(rawHeader)
const hash = crypto.createHash('sha256')
.update(rawHeader.headerString)
.digest('hex');

console.log('SHA256 Hash of ASAR Header:', hash);
} catch (error) {
console.error('Error processing ASAR file:', error.message);
}
}

main();

If we run the above code we get the same hash of the asar file as the binary.

Integrity-Bypass-win32-x64$ ls | grep Integrity-Bypass.exe 
Integrity-Bypass.exe
Integrity-Bypass-win32-x64$ node get-hash.js
SHA256 Hash of ASAR Header: 2a6e8324f650f57819d9130b7e52a1a3ab7265851b611ffd3c5f07cd67263744

Now we can generate the hash, we can modify the asar archive file and bypass the integrity detection.

Integrity-Bypass-win32-x64\resources$ asar extract app.asar unpacked-asar
Integrity-Bypass-win32-x64\resources$ asar pack unpacked-asar app.asar
Integrity-Bypass-win32-x64\resources$ cd ..
Integrity-Bypass-win32-x64$ node get-hash.js

With the above command we can unpack the archive and store the output in the unpacked-asar folder, we can edit the JS or any files in the unpacked-asar folder and repack the folder to the updated asar file.

Once we have updated the ASAR file can use the same JavaScript code to get the hash of it. Now we need to replace the hash in the main executable file, We use tools like Ghidra or IDA to edit the hash in the binary but the easiest way is to use sed command in Linux to replace the string in the binary.

Integrity-Bypass-win32-x64$ sed 's/old-hash/new-hash/' Integrity-Bypass.exe

Bonus Point

Hash from Error

We can also use the command to get the error from the application, the Electron JS code shows the issues with the integrity and allows you to get the new hash of the ASAR file directly.

Integrity-Bypass-win32-x64$ Start-Process -FilePath .\Integrity-Bypass.exe -NoNewWindow -Wait

[11912:0719/144412.786:FATAL:asar_util.cc(163)] Integrity check failed for asar archive
(2a6e8324f650f57819d9130b7e52a1a3ab7265851b611ffd3c5f07cd67263744 vs e68bc08744b5dc9ffd749f30f8212f2cdfd898755cb9b6afed56647e2683c8fc)
[11912:0719/144412.786:ERROR:crashpad_client_win.cc(868)] not connected

This shows the difference between the hash stored in the binary and the hash generated at the run time of the updated asar file. But it's good to know how it generates the hash so in case an error is not there we can still generate the new hash.

Verify Integrity

There may be no integrity detection implemented. It is good to verify if integrity detection is there or not. We can use fuse to verify the same with the below command.

Integrity-Bypass-win32-x64$ npm i @electron/fuses
Integrity-Bypass-win32-x64$electron-fuses read --app app.exe
EnableEmbeddedAsarIntegrityValidation is Enabled

You can find me on the following channels:

Twitter/X — https://x.com/Ano_F_

GitHub — https://github.com/Anof-cyber/

Linkedin — https://www.linkedin.com/in/sourav-kalal/

Published in InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. Subscribe to our weekly newsletter for the coolest infosec updates: https://weekly.infosecwriteups.com/

Written by Sourav Kalal

• Application Security • Security Engineer and Consultant • Open Source Developer • Security Automation

No responses yet

Write a response