Deobfuscating Asarmor
Intro
A buddy of mine asked for help when he ran across an Electron application that he wasn’t able to extract using the standard tools. So I figured why not take a look myself to see what the heck was causing the standard tools to break.
Background
Normally when reverse engineering Electron applications you don’t want to immeditely open up the packaged [application].exe with a dissasembler. Instead you want to look for an ASAR file. For an explanation of why this is important I’m going to quote the internet.
An ASAR file is an archive used to package source code for an application using Electron, an open source library used to build cross-platform programs. ASAR files allow developers to package their apps in an archive instead of a folder, which protects the source code of the app from being exposed to other users. You can package the application into an archive using the asar utility included with Electron. - Internet
Another important thing to note about the ASAR format is that it’s open source (https://github.com/electron/asar) and comes with handy extraction utilities. Finally, we don’t have to fully reverse engineer a propreitary format! Now with this background knowledge, I think we are ready to go ahead and try extracting this problem ASAR ourselves. Maybe my friend ran the tool wrong … had to check (sorry). Normally you can just extract with the standard tools using the command below.
asar extract app.asar app.asar.unpacked
But to my suprise when I ran the command I got the following error.
$ asar extract app.asar app.asar.unpacked
The value "1254846026366411" is invalid for option "size"
Looks like the hunt is on!
Teardown Time
Right away we can see what generated our error. That size
key in the JSON is responsible for telling the extractor how many bytes to extract for each file and the offset
key tells it where in the concatenated file blob to grab it.
With that knowledge we can see why our current value is causing some problems…
Looks like I’m gonna need a bigger boat
So clearly this size is garbage, as it’s probably not likely someone is packing a 1.2 Petabyte file inside of an Electron app (I hope not). The next thing I wanted to know is if the standard tool would be able to do anything on the file at all. Normally you are able to list the files within an ASAR without actually extracting it, so let’s give that a shot.
asar list app.asar
Lucky for us, we are able to at least see the files/folders inside the ASAR. For the next little while I was just browsing through the files when something caught my eye.
I was familiar with a bunch of tools that use the naming convention somethingarmor and specifically PyArmor, which deals with obfuscation. So immediately I was curious if this was another obfuscation library following the same naming convention. Looks like the hunch payed off https://github.com/sleeyax/asarmor. While researching asarmor, I noticed that the example code on the readme page had an example that created a dummy .git
with messed up sizes.
They surely wouldn’t just use the same example as the GitHub readme to protect themselves…right?
".git":{"size":1254846026366411,"offset":3277175692},
Oh my…
Build Our Own
Now we know what we have to do, we need to remove this dummy .git
file that is blocking the extraction utilitiy from doing its job. From reading the documentation, we can see that by removing the corrupted JSON entry we will also need to change the header
.
So with this we can think of the simplified format as
HEADER-JSON-FILEBLOB
One note about the format is that while it’s open source and has some good documentation, it seems that the header format is not fully covered as to how the values are generated, but we knew it had to deal with the JSON size. We can also see that the JSON is padded with null terminators to make sure it’s on a 4 byte boundary.
After a little experimenting, I figured out what the values in the header stood for.
Blue - Always 4 (UInt32 Size for Pickle)
Orange - Teal + Green BYTE size(8) + JSON size + Null terminator padding
Teal - Green BYTE size(4) + JSON size + Null terminator padding
Green - Size of JSON string only
Now that we know what to modify (.git
) and how to generate our own header/json we are almost ready to write some code. The last step though is to make sure we include the last part of the ASAR, the FILEBLOB
. Just as a PoC I used dd
and grabbed everything after the last 0x00
and went until the end of the file in the picture below.
With that blob stored seperately, we can generate our header/json and then just append the file blob onto the back since the .git
file never did exist (1.2 petabytes remember) and isn’t stored in blob.
With all the pieces in place I wrote up the following PoC in python to defeat the asarmor obfuscation and generate a working ASAR file.
import struct
import json
asar_fileblob = None
with open('fileblob.bin', 'rb') as fh:
asar_fileblob = fh.read()
with open('app.asar', 'rb') as fh:
fh.seek(4) # pickle var size
header_size = struct.unpack('I', fh.read(4))[0] # JSON_TERMINATOR_SIZE + JSON_SIZE + JSON + NULL_TERMINATORS
json_terminator_size = struct.unpack('I', fh.read(4))[0] # JSON_SIZE + JSON + NULL_TERMINATORS
json_size = struct.unpack('I', fh.read(4))[0] # SIZE OF JSON
header = fh.read(json_size).decode('utf-8')
files = json.loads(header)
print('header_size: ' + hex(header_size))
print('json_terminator_size: ' + hex(json_terminator_size))
print('json_size: ' + hex(json_size))
print('json_string_length: ' + hex(len(header)))
del files['files']['.git']
header = json.dumps(files,separators=(",", ":")).encode('utf-8')
print('modified_json_string_length: ' + hex(len(header)))
pad_size = 0
if len(header) % 4 != 0:
pad_size = 4 - (len(header) % 4)
with open('modded.asar', 'wb') as fh:
fh.write(struct.pack('I', 4)) # pickle var
fh.write(struct.pack('I', 8 + len(header) + pad_size )) # JSON_TERMINATOR_SIZE + JSON_SIZE + JSON + NULL_TERMINATORS
fh.write(struct.pack('I', 4 + len(header) + pad_size)) # JSON_SIZE + JSON + NULL_TERMINATORS
fh.write(struct.pack('I', len(header))) # SIZE OF JSON
fh.write(header)
fh.write(b'\x00' * pad_size)
fh.write(asar_fileblob)
Now we can just run our extractor like normal and
asar extract modded.asar modded.asar.unpacked
Victory!
Recap
With that we come to a close, a successful extraction. Definitely not an Electron app reversing expert but I had a fun time taking this apart. If someone wants to actually turn the PoC into a working script that would be awesome (just give me a shoutout). I wasn’t able to find any other work that has dealt with asarmor so I am really sorry if you have and I didn’t shout you out! As always, happy hunting!