#!/usr/bin/env python3

# Deobfuscator for beware.js
# Copyright (C) 2021 Zuyoutoki

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys

# Print usage if needed
if (len(sys.argv) != 2):
    sys.stderr.write('usage: ./beware-deobfuscator.py <file.js>\n')
    exit(1)

# Reimplementation of nuptias()
def decrypt(s):
    res = []
    val = 0
    i = len(s)-2
    while i >= 0:
        if i == len(s)-2:
            c = chr(int(s[i:i+2],16))
        else:
            c = chr(int(s[i:i+2],16) - ord(res[val]))
            val += 1
        res += c
        i -= 2
    return ''.join(res)[::-1]

# The good stuff happen below
def main():
    # Load each line in memory, skip empty lines
    with open(sys.argv[1], 'r') as handle:
        dirty_code = [ line.lstrip() for line in handle.read().split('\n') if len(line) != 0 ]
    
    # Variables declarations
    clean_code = list()
    mathvars = list()
    arrayname = None
    arrayvalues = None
    funcname = None
    in_func = False

    for line in dirty_code:
        # Remove decryptor function
        if line.find('function') != -1:
            # Check if we already know the decryptor name and save it if not
            if funcname is None:
                funcname = line[9:].split('(')[0]
                in_func = True
            continue
        # Drop lines if still in decryptor function
        elif in_func:
            if line.find('};') != -1:
                in_func = False;
            continue
        # Check for variable initialization
        elif line.find('var ') == 0:
            # Append to mathvars for the first three variables
            if len(mathvars) < 3:
                mathvars.append(line[4:].split('=')[0].strip())
                continue
            # Save the name and value of the encrypted array from variable #4
            else:
                if arrayname is None:
                    arrayname = line[4:].split('=')[0].strip()
                    arrayvalues = eval(line.split('=')[1].strip().split(';')[0])
                    continue
        # Skip if in mathvars
        elif line.find(mathvars[0]) == 0: continue
        elif line.find(mathvars[1]) == 0: continue
        elif line.find(mathvars[2]) == 0: continue
        # If we know funcname and arrayname, let's replace calls to the decryptor will plaintext strings
        if (funcname is not None and arrayname is not None):
            decrypt_index = line.find(funcname+'('+arrayname+'[')
            while decrypt_index != -1:
                decrypt_index = line.find(funcname+'('+arrayname+'[')
                # Replace the call if we detect it in the line
                if decrypt_index != -1:
                    tmpline1 = line[:decrypt_index]
                    tmpline2 = line[(decrypt_index+len(funcname+arrayname)+2):]
                    tmpindex = ""
                    # Grab the array index
                    while tmpline2[0].isdigit():
                        tmpindex += tmpline2[0]
                        tmpline2 = tmpline2[1:]
                    tmpindex = int(tmpindex)
                    # Skip all the characters until it finds ')', allowing to remove the second parameter
                    while tmpline2[0].find(')') != 0:
                        tmpline2 = tmpline2[1:]
                    tmpline2 = tmpline2[1:]
                    line = tmpline1 + repr(decrypt(arrayvalues[tmpindex])) + tmpline2
        # If it made it there, the code is good to be appended
        clean_code.append(line)
    # Print code to stdout, you should pipe that into a new file
    print('\n'.join(clean_code))

if __name__ == '__main__':
    main()

