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. Download

With that knowledge we can see why our current value is causing some problems… Download

Looks like I’m gonna need a bigger boat Download

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 Download

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.

Download

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.

Download

They surely wouldn’t just use the same example as the GitHub readme to protect themselves…right? Download

".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. Download

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. Download

After a little experimenting, I figured out what the values in the header stood for.

Download

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.

Download

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 Download

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!