Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeanEdward python unpacker offset problem #1616

Closed
mFaou opened this issue Jan 10, 2019 · 4 comments
Closed

DeanEdward python unpacker offset problem #1616

mFaou opened this issue Jan 10, 2019 · 4 comments
Milestone

Comments

@mFaou
Copy link
Contributor

mFaou commented Jan 10, 2019

Description

Hi,
The unpacker python script for DeanEdward packer doesn't correctly handle the begin and end offsets.
The result is that the unpacked code is not put at the right offset in the final script. Thus, it breaks the original script.
At line 28, it searches for the beginning offset with
source.replace(' ', '').find('eval(function(p,a,c,k,e,'). However at line 30 when it copies the code with beginstr = source[:mystr], it doesn't take into account the number of spaces. Thus, the unpacked code is put at a "random" place.
For the end offset, the script starts the search at the beginning of the source code while it should be started after the begin offset.

Input

The code looked like this before beautification:

function test (){alert ('This is a test!!!')}; eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0(\'1\');',2,2,'alert|test'.split('|'),0,{}))

Expected Output

The code should have looked like this after beautification:

function test() {
    alert('This is a test!!!')
};
alert('test');

Actual Output

The code actually looked like this after beautification:

function test() {
    alert('This is a test!alert(\'test\');

Steps to Reproduce

Use the last version (1.8.9) of jsbeautifier python version (the javascript seems correct)

Environment

OS: Linux
jsbeautifier 1.8.9

Settings

Default

Solution:

We can use a regex instead of the str.find function. This modified version of unpack.py fixes the bug:

#
# Unpacker for Dean Edward's p.a.c.k.e.r, a part of javascript beautifier
# by Einar Lielmanis <[email protected]>
#
#     written by Stefano Sanfilippo <[email protected]>
#
# usage:
#
# if detect(some_string):
#     unpacked = unpack(some_string)
#

"""Unpacker for Dean Edward's p.a.c.k.e.r"""

import re
import string
from jsbeautifier.unpackers import UnpackingError

PRIORITY = 1


def detect(source):
    global beginstr
    global endstr
    beginstr = ''
    endstr = ''
    begin_offset = -1
    """Detects whether `source` is P.A.C.K.E.R. coded."""
    mystr = re.search('eval[ ]*\([ ]*function[ ]*\([ ]*p[ ]*,[ ]*a[ ]*,[ ]*c[ ]*,[ ]*k[ ]*,[ ]*e[ ]*,[ ]*', source)
    if(mystr):
        begin_offset = mystr.start()
        beginstr = source[:begin_offset]
    if(begin_offset != -1):
        """ Find endstr"""
        source_end = source[begin_offset:]
        if(source_end.split("')))", 1)[0] == source_end):
            try:
                endstr = source_end.split("}))", 1)[1]
            except IndexError:
                endstr = ''
        else:
            endstr = source_end.split("')))", 1)[1]
    return (mystr != -1)


def unpack(source):
    """Unpacks P.A.C.K.E.R. packed js code."""
    payload, symtab, radix, count = _filterargs(source)

    if count != len(symtab):
        raise UnpackingError('Malformed p.a.c.k.e.r. symtab.')

    try:
        unbase = Unbaser(radix)
    except TypeError:
        raise UnpackingError('Unknown p.a.c.k.e.r. encoding.')

    def lookup(match):
        """Look up symbols in the synthetic symtab."""
        word = match.group(0)
        return symtab[unbase(word)] or word

    source = re.sub(r'\b\w+\b', lookup, payload)
    #print(source) #source contains the desobfuscated script
    return _replacestrings(source)


def _filterargs(source):
    """Juice from a source file the four args needed by decoder."""
    juicers = [
        (r"}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)"),
        (r"}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)"),
    ]
    for juicer in juicers:
        args = re.search(juicer, source, re.DOTALL)
        if args:
            a = args.groups()
            if a[1] == "[]":
                a = list(a)
                a[1] = 62
                a = tuple(a)
            try:
                return a[0], a[3].split('|'), int(a[1]), int(a[2])
            except ValueError:
                raise UnpackingError('Corrupted p.a.c.k.e.r. data.')

    # could not find a satisfying regex
    raise UnpackingError(
        'Could not make sense of p.a.c.k.e.r data (unexpected code structure)')


def _replacestrings(source):
    global beginstr
    global endstr
    """Strip string lookup table (list) and replace values in source."""
    match = re.search(r'var *(_\w+)\=\["(.*?)"\];', source, re.DOTALL)

    if match:
        varname, strings = match.groups()
        startpoint = len(match.group(0))
        lookup = strings.split('","')
        variable = '%s[%%d]' % varname
        for index, value in enumerate(lookup):
            source = source.replace(variable % index, '"%s"' % value)
        return source[startpoint:]
    return beginstr + source + endstr


class Unbaser(object):
    """Functor for a given base. Will efficiently convert
    strings to natural numbers."""
    ALPHABET = {
        62: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
        95: (' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ'
             '[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
    }

    def __init__(self, base):
        self.base = base

        # fill elements 37...61, if necessary
        if 36 < base < 62:
            if not hasattr(self.ALPHABET, self.ALPHABET[62][:base]):
                self.ALPHABET[base] = self.ALPHABET[62][:base]
        # attrs = self.ALPHABET
        # print ', '.join("%s: %s" % item for item in attrs.items())
        # If base can be handled by int() builtin, let it do it for us
        if 2 <= base <= 36:
            self.unbase = lambda string: int(string, base)
        else:
            # Build conversion dictionary cache
            try:
                self.dictionary = dict(
                    (cipher, index) for index, cipher in enumerate(
                        self.ALPHABET[base]))
            except KeyError:
                raise TypeError('Unsupported base encoding.')

            self.unbase = self._dictunbaser

    def __call__(self, string):
        return self.unbase(string)

    def _dictunbaser(self, string):
        """Decodes a  value to an integer."""
        ret = 0
        for index, cipher in enumerate(string[::-1]):
            ret += (self.base ** index) * self.dictionary[cipher]
        return ret
@bitwiseman
Copy link
Member

@mFaou
Thanks for this excellent issue filed. How about submitting a PR?

mFaou added a commit to mFaou/js-beautify that referenced this issue Jan 11, 2019
@mFaou
Copy link
Contributor Author

mFaou commented Jan 11, 2019

#1617 :)

mFaou added a commit to mFaou/js-beautify that referenced this issue Jan 14, 2019
@bitwiseman bitwiseman added this to the v1.8.x milestone Jan 15, 2019
@bitwiseman
Copy link
Member

Fixed in #1617.

@bitwiseman bitwiseman modified the milestones: v1.9.x, v1.9.0 Feb 27, 2019
@hatienl0i2612
Copy link

I have a bug when use your def to unpack js code packed,
here is js code https://pastebin.com/sWiQXeu8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants