Last active
May 8, 2021 02:36
-
-
Save kms70847/d09f991dc9394f2feb3aa3910f935447 to your computer and use it in GitHub Desktop.
A reverse-engineering of Aran-Fey's obfuscated script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#copied from https://chat.stackoverflow.com/transcript/message/52153354#52153354 | |
class folly(zip(), zip(__name__), zip(), zip(__file__), zip()): | |
hel = help | |
locals()[r""u'''d | |
'''fr''] = __qualname__ | |
exc = exit | |
def Tel(f, a, k): | |
return getattr(a[0], f)(*a[1:], **dict(tuple(k.items())[2:])) | |
class to_bytes(0x6f20776f726c, 0o6, f'b'"ig"u'', metaclass=Tel): | |
list(zip(folly, [{()}])) | |
class print(__builtins__, next(folly)[2], to_bytes.decode(), metaclass=Tel): | |
try: | |
folly(next) | |
except Exception as exc: | |
([sep, exc, end], __qualname__) = (folly) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 1. identification of red herrings. Anything annotated here with a "v" or "^" is something that | |
#could be replaced with a placeholder without affecting the behavior of the program. | |
#the contents of these objects do not matter, as long as they are all zip objects. | |
# vvvvv vvvvvvvvvvvvv vvvvv vvvvvvvvvvvvv vvvvv | |
class folly(zip(), zip(__name__), zip(), zip(__file__), zip()): | |
#Doesn't matter what value is bound to `hel` | |
# vvvv | |
hel = help | |
locals()[r""u'''d | |
'''fr''] = __qualname__ | |
# ^^^^^^^^^^^^ | |
#value doesn't matter | |
#doesn't matter what name is bound, as long as a name is bound. | |
#`exc` was probably chosen to create confusion with the `exc`s in the final class. | |
#also doesn't matter what value is bound. | |
#vv vvvv | |
exc = exit | |
#not a red herring per se, but feel free to change the names in this function | |
def Tel(f, a, k): | |
return getattr(a[0], f)(*a[1:], **dict(tuple(k.items())[2:])) | |
class to_bytes(0x6f20776f726c, 0o6, f'b'"ig"u'', metaclass=Tel): | |
#contents of second argument to `zip` don't matter as long as the argument is a list of length one | |
# vvvv | |
list(zip(folly, [{()}])) | |
class print(__builtins__, next(folly)[2], to_bytes.decode(), metaclass=Tel): | |
try: | |
#can be any statement, as long as it raises an exception | |
#vvvvvvvvv | |
folly(next) | |
except Exception as exc: | |
#Can be any name that is automatically bound to a class' namespace. So, __module__ or __qualname__. | |
#The value that gets bound doesn't matter. | |
# vvvvvvvvvvvv | |
([sep, exc, end], __qualname__) = (folly) | |
#^ ^ ^ ^ | |
#unnecessary parens |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 2. Some integer and string literals are obfuscated. Let's identify what they are. | |
z = zip() | |
class folly(z,z,z,z,z): | |
hel = None | |
#consecutive string literals, like r"" u'''d\n''', get concatenated into a single string literal. | |
#so this string is equivalent to r'' + u'''d\n''' + fr'', which is just "d\n". | |
# vvvvvvvv | |
locals()[r""u'''d | |
'''fr''] = None | |
cabbage = None | |
def Tel(f, a, k): | |
return getattr(a[0], f)(*a[1:], **dict(tuple(k.items())[2:])) | |
# The ordinal values of the bytes object b"o worl". We'll leave it like this for now. | |
# | | |
# | octal 6, which is just regular 6. | |
# | | | |
# | | "big", obfuscated with the same literal concatenation as folly's "d\n" | |
# | | | | |
# vvvvvvvvvvvvvv vvv vvvvvvvvvvv | |
class to_bytes(0x6f20776f726c, 0o6, f'b'"ig"u'', metaclass=Tel): | |
list(zip(folly, [None])) | |
class print(__builtins__, next(folly)[2], to_bytes.decode(), metaclass=Tel): | |
try: | |
1/0 | |
except Exception as exc: | |
[sep, exc, end], __qualname__ = folly |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 3. Revealing some of the underlying mechanisms of class creation. | |
#(but don't worry, I'm skipping over the really boring parts that don't matter) | |
#One of the most unusual elements of this code is the classes that seemingly inherit from objects that are not types. | |
#This is usually impossible to do, because the `type(name, bases, namespace)` function is typically responsible for | |
#creating types from class definitions, and it crashes if any element of `bases` isn't a type. | |
#But `type` is never called here, because each class has a metaclass that does type's job. | |
z = zip() | |
#Let's use `folly` as our example class illustrating how class creation works. | |
# The class' name will be passed to the metaclass later. | |
# | | |
# | The base types* are evaluated, gathered into a tuple, and passed to the metaclass later. | |
# | (*but they don't _have_ to be types if the metaclass doesn't care, so we can use zip instances here) | |
# | | | |
# vvvvv vvvvvvvvv | |
class folly(z,z,z,z,z): | |
# ^ | |
#If no metaclass is specified, Python chooses one based on the metaclasses of the class' bases. | |
#Normal classes usually supply types for bases, and most types have a metaclass of "type". | |
#but here our bases are zip instances. That means our metaclass is the `zip` type. | |
#The contents of the class block are executed. | |
#The locals() collection initially starts out as a two element dict containing the module's name and the class' name: | |
#{'__module__': '__main__', '__qualname__': 'folly'} | |
hel = None | |
locals()["d\n"] = None | |
cabbage = None | |
#locals is now {'__module__': '__main__', '__qualname__': 'folly', 'hel': None, 'd\n': None, 'cabbage': None}. | |
#this dict will be passed to the metaclass later, as its "namespace". | |
#The class block is over, and it is now "later". The class' name, bases tuple, and namespace dict are passed to the metaclass, | |
#and the result is bound to the class' name. In other words, it does: | |
#folly = zip( | |
# "folly", | |
# (z,z,z,z,z), | |
# {'__module__': '__main__', '__qualname__': 'folly', 'hel': None, 'd\n': None, 'cabbage': None}) | |
def Tel(f, a, k): | |
return getattr(a[0], f)(*a[1:], **dict(tuple(k.items())[2:])) | |
#Same deal as with `folly`, except Python doesn't have to deduce the metaclass, because it's specified right here. | |
class to_bytes(0x6f20776f726c, 6, "big", metaclass=Tel): | |
list(zip(folly, [None])) | |
#this class definition is essentially equivalent to: | |
#list(zip(folly, [None])) | |
#to_bytes = Tel("to_bytes", (0x6f20776f726c, 6, "big"), {'__module__': '__main__', '__qualname__': 'to_bytes'}) | |
#Same deal as the other classes, but watch out for the surprising behavior of Exception variables. | |
class print(__builtins__, next(folly)[2], to_bytes.decode(), metaclass=Tel): | |
try: | |
1/0 | |
except Exception as exc: | |
[sep, exc, end], __qualname__ = folly | |
# ^^^ | |
#despite being assigned to, `exc` doesn't show up in the final namespace that's passed to the metaclass. | |
#this is because an except block's exception variable is always deleted at the end of the block. | |
#Taking exception name trickery into account, this class definition is equivalent to: | |
#bases = (__builtins__, next(folly)[2], to_bytes.decode()) | |
#[sep, _, end], __qualname__ = folly | |
#print = Tel("print", bases, {'__module__': '__main__', '__qualname__': __qualname__, 'sep': sep, 'end': end}) | |
#notice that this is the only class where I broke out `bases = ...` onto its own line. | |
#This is because this is the only class whose base list has a side effect -- the next() call. | |
#It is important that this next() call occurs before `folly` is unpacked in the body. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 4. Analysis of the behavior of Tel. | |
z = zip() | |
folly = zip( | |
"folly", | |
(z,z,z,z,z), | |
{'__module__': '__main__', '__qualname__': 'folly', 'hel': None, 'd\n': None, 'cabbage': None} | |
) | |
#now that we can see where and how Tel is being called, we can deduce its behavior. | |
def Tel(f, a, k): | |
#since this is only called twice, we may as well just substitute in the arguments for both calls. | |
return getattr(a[0], f)(*a[1:], **dict(tuple(k.items())[2:])) | |
#The first call is equivalent to: | |
#return getattr(0x6f20776f726c, "to_bytes")(6, "big", **{}) | |
#or: | |
#return (0x6f20776f726c).to_bytes(6, "big") | |
#the second call is equivalent to: | |
#return getattr(__builtins__, "print")(next(folly)[2], to_bytes.decode(), **{'sep': sep, 'end': end}) | |
#or: | |
#return print(next(folly)[2], to_bytes.decode(), sep=sep, end=end) | |
#(but remember that next(folly)[2] must still occur before `folly` is unpacked for the last time) | |
#So essentially Tel just unscrambles its arguments into a method, an argument list, and a keyword argument dict, | |
#and calls the method with the arguments. | |
list(zip(folly, [None])) | |
to_bytes = Tel("to_bytes", (0x6f20776f726c, 6, "big"), {'__module__': '__main__', '__qualname__': 'to_bytes'}) | |
bases = (__builtins__, next(folly)[2], to_bytes.decode()) | |
[sep, _, end], __qualname__ = folly | |
print = Tel("print", bases, {'__module__': '__main__', '__qualname__': __qualname__, 'sep': sep, 'end': end}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 5. Analysis of the `folly` iterable | |
z = zip() | |
folly = zip( | |
"folly", | |
(z,z,z,z,z), | |
{'__module__': '__main__', '__qualname__': 'folly', 'hel': None, 'd\n': None, 'cabbage': None} | |
) | |
#if you ignore the distinction between an iterable and a sequence, `folly` is essentially a 3x5 matrix that starts out as: | |
#[ | |
# ["f", z, "__module__"], | |
# ["o", z, "__qualname__"], | |
# ["l", z, "hel"], | |
# ["l", z, "d\n"], | |
# ["y", z, "cabbage"] | |
#] | |
#the only salient difference is that a zip object can only be iterated over once. So whenever you call `next`, | |
#or unpack it on the RHS of an assignment statement, etc, then the top of of the matrix permanently pops off. | |
list(zip(folly, [None])) | |
#this line exists purely to pop the first two rows out of `folly`. | |
#The first iteration of this `zip` call pops out the first element of `folly` and [None]`, | |
#and joins them together into `(["f", z, "__module__"], None)`. | |
#The second iteration pops out the next element of `folly`. It tries to pop out the next element of `[None]`, | |
#but there's nothing left to pop, so it raises a StopIteration and the zip is finished. | |
#`["f", z, "__module__"]` is discarded because the list call is not assigned to anything. | |
#`["o", z, "__qualname__"]` is discarded because there was nothing to zip it with. | |
#The final result: `folly` is effectively equivalent to: | |
#[ | |
# ["l", z, "hel"], | |
# ["l", z, "d\n"], | |
# ["y", z, "cabbage"] | |
#] | |
to_bytes = (0x6f20776f726c).to_bytes(6, "big") | |
#You can probably tell by now that this is just b"o worl". | |
x = next(folly)[2] | |
#pops ["l", z, "hel"] out of `folly` and assigns "hel" to x. | |
#`folly` is now effectively equivalent to: | |
#[ | |
# ["l", z, "d\n"], | |
# ["y", z, "cabbage"] | |
#] | |
[sep, _, end], __qualname__ = folly | |
#`[...], __qualname__ = folly` pops out the final two elements of `folly`, assigning the first one to the list, | |
#and the second one to `__qualname__`. | |
#So this is effectively equivalent to: | |
#sep, _, end = ["l", z, "d\n"] | |
#__qualname__ = ["y", z, "cabbage"] | |
#we don't care about the value of `__qualname__` at this point, but we have to unpack folly's final value into something. | |
#so the assignments we actually care about are: | |
#sep = "y" | |
#end = "d\n" | |
print(x, to_bytes.decode(), sep=sep, end=end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simplification 6. Assembling the final output. | |
to_bytes = b"o worl" | |
x = "hel" | |
sep = "l" | |
end = "d\n" | |
print(x, to_bytes.decode(), sep=sep, end=end) | |
#now we know the value of print's arguments, so we can substitute in the literals: | |
#print("hel", "o worl", sep="l", end="d\n") | |
#which is effectively equivalent to: | |
#print("hel" + "l" + "o worl" + "d") | |
#which is equivalent to... <please scroll to the next file dramatically> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
print("hello world") | |
#thank you for coming to my TED talk. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment