#!/usr/bin/env python3

# Deobfuscator for the second stage of 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

# Reimplementation of urbes()
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]

# Reimplementation of nominibus()
def xor_decrypt(a, b):
    c = 0
    d = list()
    i = 0
    while i < len(b):
        c += ord(b[i])
        i += 1
    i = 0
    while i < len(a):
        d.append(chr(a[i] ^ c))
        i += 1
    return ''.join(d)

# 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()
    int_array_name = None
    int_array_values = None
    string_array_name = None
    string_array_values = None
    in_func = False
    in_trycatch = False
    decrypt_name = None
    xordecrypt_name = None

    for line in dirty_code:
        # Remove try/catch
        if line.find('try') != -1: 
            in_trycatch = True
            continue
        elif line.find('catch') != -1:
            in_trycatch = True
            continue
        # Remove functions
        elif line.find('function') != -1:
            # Check if we already know decrypt_name and save it if not
            if decrypt_name is None:
                decrypt_name = line[9:].split('(')[0]
                in_func = True
            # Check if we already know xordecrypt_name and save it if not
            elif xordecrypt_name is None:
                xordecrypt_name = line[9:].split('(')[0]
                in_func = True
            continue
        elif dirty_code.index(line) < 27:
            continue
        # Check for variable initialization
        elif line.find('var ') == 0:
            # Save name and value of int and string array
            if int_array_name is None:
                int_array_name = line[4:].split('=')[0].strip()
                int_array_values = eval(line.split('=')[1].strip().split(';')[0])
            elif string_array_name is None:
                string_array_name = line[4:].split('=')[0].strip()
                string_array_values = eval(line.split('=')[1].strip().split(';')[0])
                continue
        # If we know decrypt_name and string_array_name, let's replace calls to the decryptor with plaintext strings
        if (decrypt_name is not None and string_array_name is not None):
            decrypt_index = line.find(decrypt_name+'('+string_array_name+'[')
            while decrypt_index != -1:
                decrypt_index = line.find(decrypt_name+'('+string_array_name+'[')
                # Replace the call if we detect it in the line
                if decrypt_index != -1:
                    tmpline1 = line[:decrypt_index]
                    tmpline2 = line[(decrypt_index+len(decrypt_name+string_array_name)+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 ')'
                    while tmpline2[0].find(')') != 0:
                        tmpline2 = tmpline2[1:]
                    tmpline2 = tmpline2[1:]
                    line = tmpline1 + repr(decrypt(string_array_values[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__':
    # Deobfuscate if using 1 argument
    if len(sys.argv) == 2:
        main()
    # Prompt for xor_decrypt
    elif len(sys.argv) == 1:
        print("xor_decrypt(arg1, arg2)")
        arg1 = eval(input("  arg1="))
        arg2 = input("  arg2=")
        print(xor_decrypt(arg1, arg2))
    # Print usage if needed
    else:
        sys.stderr.write('usage: ./beware-deobfuscator2.py <file.js>\n')
        exit(1)


