Jump to content
Tuts 4 You

Titanium (Python Obfuscator)


Go to solution Solved by Extreme Coders,

Recommended Posts

Posted

Titanium (Python Obfuscator)


This is my first crackme, so I may have screwed up some things, but overall it should be a fun challenge. The file is protected with my own packer/obfuscator, Titanium, and compiled with pyinstaller. Read the readme before reversing.


 

  • 2 months later...
  • Solution
Extreme Coders
Posted

Correct Key:

Spoiler
jtr|Y@Z2oK:Mf|P8eMQT[,^#}7X0l~

WriteUp:

Running crackme.exe prompts for a key and prints "Bad key!" if its incorrect.

Spoiler

image.png.8c9b6aa8f3123a8646b7ae581b62f51b.png

As per the challenge description, this crackme is a PyInstaller generated executable. The "protection" is implemented in the C extension (native library) titanium.pyd which we can obtain after extracting with Pyinstxtractor.

Decompiling crackme.pyc with uncompyle6, we get this code.

# uncompyle6 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.12 (default, Oct 17 2021, 23:37:02) [MSC v.1929 64 bit (AMD64)]
# Embedded file name: crackme.py
import lzma, base64
from titanium import __titanium_runtime__
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import os, zlib
__titanium_runtime__('linear', b'x\x9c%\xd79\xb2$!......[snip].....x9e\xf8', 10, False)
# okay decompiling crackme.pyc

The protection tries to mimic pyarmor in the sense that pyarmor also has a pyarmor_runtime function which is called with similar set of arguments. __titanium_runtime__ is defined in the C extension titanium.pyd. Python extensions are commonly written in C but can also be written in Python itself using Cython. The crackme follows the later approach. Cython "compiles" the python bytecode to native code which is generally difficult to RE than plain Python code.

At this point it may look as if we've to analyze titanium.pyd to understand what its going on, but there easier ways. :)

The fastest way

Note the string "Bad key!". We can search for this string in memory of the crackme process. Since this is a pyinstaller generated executable there will be two crackme processes. We need to scan the child process, (with PID 5600 in the image below)

Spoiler

image.png.f5a0cd15f48f8bec6d2ceba633b4b1d2.png

Searching for "Bad key!" we get to

Spoiler

image.png.7840a839cdd2feb91112af5132bc8a02.png

A little above is the base64 encoded correct key

anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+

which after decoding reveals the correct key.

Spoiler

image.png.eedb9612279ef6c3595699e83c20a869.png

The logical way

Instead of searching for "Bad key!" in memory its also possible to have the crackme print its real code bypassing titanium entirely.
Note the lzma, zlib imports at the top of crackme.py. Obfuscators commonly compress and then encrypt the code. Doing the other way doesn't make sense as encrypted code won't be compressible. This implies while executing its decryption followed by decompression.

Python is a dynamic language which makes it possible to replace the implementation of any function at runtime. Using this we can "hook" lzma.decompress and check out the decompressed result.

Spoiler
# uncompyle6 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.12 (default, Oct 17 2021, 23:37:02) [MSC v.1929 64 bit (AMD64)]
# Embedded file name: crackme.py
import lzma, base64
from titanium import __titanium_runtime__
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import os, zlib

original_lzma_decompress = lzma.decompress

def lzma_decompress(x):
	d = original_lzma_decompress(x)
	print (d)
	return d

lzma.decompress = lzma_decompress	
__titanium_runtime__('linear', b'x\x9c%\xd79\xb2$!......[snip].....x9e\xf8', 10, False)
# okay decompiling crackme.pyc

Before running the above code in Python 3.7, ensure that PyCryptoDome is installed and titanium.pyd is in the same directory as the crackme.

Spoiler

image.png.3886a22e94cd20bf596dca80191ba0ab.png

Base64 decoding the exec-d string "X0I9YidVbXhHY1Z........WNsYXJlX18oKQ==", we get this minified Python  code.

Spoiler
_B=b'UmxGcVQxSlZXbU5tV2tvellVNTRSRE13TUVabFExWXpWVXRJYmxCbE1Xdz0='
_A=b'WnZhSzhsSGFlaEdkamUyZFlhWmN3THAxdldKWlZyQlI='
import base64
asd=b'RlFqT1JVWmNmWkozYU54RDMwMEZlQ1YzVUtIblBlMWw='
asda=_A
asdaa=b'V25aaFN6aHNTR0ZsYUVka2FtVXlaRmxoV21OM1RIQXhkbGRLV2xaeVFsST0='
asdaaa=_B
asdaaaa=b'YXNkYWFhYWFhYWFhYWE='
asdaaaaa=b'anRyfFlAWjJvSzpNZnxQFGVNUVRbLF4jfTdYMGx='
asdaaaaaa=b'b2hfaG93X2RpZF95b3VfZmluZF90aGVfa2V5Pw=='
asdaaaaaaa=b'RlFqT1JVFmNmWkozYU54RDMwMEZlQ1QzVUtIblBHMWw='
asdaaaaaaaa=_A
asdaaaaaaaaa=b'Q25aaFN6aHNTR0ZsYUVka2FtVXlaRmXoV21OM1fIQXhkbGRLV2xaeVFsST0='
asdaaaaaaaaaa=_B
asdaaaaaaaaaaa=b'VXNkYWFhYWFhYWFAWb5tj43HTK2h'
asdaaaaaaaaaaaa=b'FlAWjJvSzpNZnxQOGVNUVRbLF4FlAWjJvSzpNZnxQOGVNUVRbLF4'
asdaaaaaaaaaaaaa=b'abFExWXpWVXRJYmabFExWXpWVXRJYmabFExWXpWVXRJYm'
asdaaaaaaaaaaaaaa=b'V2VsY29tZSEgUGxlYXNlIGVudGVyIHlvdXIga2V5OiA='
try:exec(asd)
except:pass
try:exec(asda)
except:pass
try:exec(asdaa)
except:pass
try:exec(asdaaa)
except:pass
try:exec(asdaaaa)
except:pass
try:exec(asdaaaaaaaaaaaa)
except:pass
class __declare__:
	def __init__(self):self.start()
	def start(self):done=False;exec(f"print('{base64.b64decode(asdaaaaaaaaaaaaaa).decode()}')");exec("k = input('')");exec(f"if k == '{base64.b64decode('anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+').decode()}':\n    print('Congrats! You cracked the program! :D')\nelse:print('Bad key!')");input('')
if __name__=='__main__':__declare__()

 

which after formatting comes to

Spoiler
_B = b"UmxGcVQxSlZXbU5tV2tvellVNTRSRE13TUVabFExWXpWVXRJYmxCbE1Xdz0="
_A = b"WnZhSzhsSGFlaEdkamUyZFlhWmN3THAxdldKWlZyQlI="
import base64

asd = b"RlFqT1JVWmNmWkozYU54RDMwMEZlQ1YzVUtIblBlMWw="
asda = _A
asdaa = b"V25aaFN6aHNTR0ZsYUVka2FtVXlaRmxoV21OM1RIQXhkbGRLV2xaeVFsST0="
asdaaa = _B
asdaaaa = b"YXNkYWFhYWFhYWFhYWE="
asdaaaaa = b"anRyfFlAWjJvSzpNZnxQFGVNUVRbLF4jfTdYMGx="
asdaaaaaa = b"b2hfaG93X2RpZF95b3VfZmluZF90aGVfa2V5Pw=="
asdaaaaaaa = b"RlFqT1JVFmNmWkozYU54RDMwMEZlQ1QzVUtIblBHMWw="
asdaaaaaaaa = _A
asdaaaaaaaaa = b"Q25aaFN6aHNTR0ZsYUVka2FtVXlaRmXoV21OM1fIQXhkbGRLV2xaeVFsST0="
asdaaaaaaaaaa = _B
asdaaaaaaaaaaa = b"VXNkYWFhYWFhYWFAWb5tj43HTK2h"
asdaaaaaaaaaaaa = b"FlAWjJvSzpNZnxQOGVNUVRbLF4FlAWjJvSzpNZnxQOGVNUVRbLF4"
asdaaaaaaaaaaaaa = b"abFExWXpWVXRJYmabFExWXpWVXRJYmabFExWXpWVXRJYm"
asdaaaaaaaaaaaaaa = b"V2VsY29tZSEgUGxlYXNlIGVudGVyIHlvdXIga2V5OiA="
try:
    exec(asd)
except:
    pass
try:
    exec(asda)
except:
    pass
try:
    exec(asdaa)
except:
    pass
try:
    exec(asdaaa)
except:
    pass
try:
    exec(asdaaaa)
except:
    pass
try:
    exec(asdaaaaaaaaaaaa)
except:
    pass


class __declare__:
    def __init__(self):
        self.start()

    def start(self):
        done = False
        exec(f"print('{base64.b64decode(asdaaaaaaaaaaaaaa).decode()}')")
        exec("k = input('')")
        exec(
            f"if k == '{base64.b64decode('anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+').decode()}':\n    print('Congrats! You cracked the program! :D')\nelse:print('Bad key!')"
        )
        input("")


if __name__ == "__main__":
    __declare__()

The correct key is in the last exec-d string as we also found earlier.

  • Like 5
  • Thanks 3
  • 1 month later...
Sean Park - Lovejoy
Posted

good reversing skill of yours. good for you.

thank you for your explanation.

regards.

sean.

  • Like 1
  • 2 months later...
Posted
On 11/13/2021 at 12:56 PM, Extreme Coders said:

Correct Key:

  Hide contents
jtr|Y@Z2oK:Mf|P8eMQT[,^#}7X0l~

WriteUp:

Running crackme.exe prompts for a key and prints "Bad key!" if its incorrect.

  Hide contents

image.png.8c9b6aa8f3123a8646b7ae581b62f51b.png

As per the challenge description, this crackme is a PyInstaller generated executable. The "protection" is implemented in the C extension (native library) titanium.pyd which we can obtain after extracting with Pyinstxtractor.

Decompiling crackme.pyc with uncompyle6, we get this code.

# uncompyle6 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.12 (default, Oct 17 2021, 23:37:02) [MSC v.1929 64 bit (AMD64)]
# Embedded file name: crackme.py
import lzma, base64
from titanium import __titanium_runtime__
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import os, zlib
__titanium_runtime__('linear', b'x\x9c%\xd79\xb2$!......[snip].....x9e\xf8', 10, False)
# okay decompiling crackme.pyc

The protection tries to mimic pyarmor in the sense that pyarmor also has a pyarmor_runtime function which is called with similar set of arguments. __titanium_runtime__ is defined in the C extension titanium.pyd. Python extensions are commonly written in C but can also be written in Python itself using Cython. The crackme follows the later approach. Cython "compiles" the python bytecode to native code which is generally difficult to RE than plain Python code.

At this point it may look as if we've to analyze titanium.pyd to understand what its going on, but there easier ways. :)

The fastest way

Note the string "Bad key!". We can search for this string in memory of the crackme process. Since this is a pyinstaller generated executable there will be two crackme processes. We need to scan the child process, (with PID 5600 in the image below)

  Hide contents

image.png.f5a0cd15f48f8bec6d2ceba633b4b1d2.png

Searching for "Bad key!" we get to

  Hide contents

image.png.7840a839cdd2feb91112af5132bc8a02.png

A little above is the base64 encoded correct key

anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+

which after decoding reveals the correct key.

  Hide contents

image.png.eedb9612279ef6c3595699e83c20a869.png

The logical way

Instead of searching for "Bad key!" in memory its also possible to have the crackme print its real code bypassing titanium entirely.
Note the lzma, zlib imports at the top of crackme.py. Obfuscators commonly compress and then encrypt the code. Doing the other way doesn't make sense as encrypted code won't be compressible. This implies while executing its decryption followed by decompression.

Python is a dynamic language which makes it possible to replace the implementation of any function at runtime. Using this we can "hook" lzma.decompress and check out the decompressed result.

  Hide contents
# uncompyle6 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.12 (default, Oct 17 2021, 23:37:02) [MSC v.1929 64 bit (AMD64)]
# Embedded file name: crackme.py
import lzma, base64
from titanium import __titanium_runtime__
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import os, zlib

original_lzma_decompress = lzma.decompress

def lzma_decompress(x):
	d = original_lzma_decompress(x)
	print (d)
	return d

lzma.decompress = lzma_decompress	
__titanium_runtime__('linear', b'x\x9c%\xd79\xb2$!......[snip].....x9e\xf8', 10, False)
# okay decompiling crackme.pyc

Before running the above code in Python 3.7, ensure that PyCryptoDome is installed and titanium.pyd is in the same directory as the crackme.

  Hide contents

image.png.3886a22e94cd20bf596dca80191ba0ab.png

Base64 decoding the exec-d string "X0I9YidVbXhHY1Z........WNsYXJlX18oKQ==", we get this minified Python  code.

  Reveal hidden contents
_B=b'UmxGcVQxSlZXbU5tV2tvellVNTRSRE13TUVabFExWXpWVXRJYmxCbE1Xdz0='
_A=b'WnZhSzhsSGFlaEdkamUyZFlhWmN3THAxdldKWlZyQlI='
import base64
asd=b'RlFqT1JVWmNmWkozYU54RDMwMEZlQ1YzVUtIblBlMWw='
asda=_A
asdaa=b'V25aaFN6aHNTR0ZsYUVka2FtVXlaRmxoV21OM1RIQXhkbGRLV2xaeVFsST0='
asdaaa=_B
asdaaaa=b'YXNkYWFhYWFhYWFhYWE='
asdaaaaa=b'anRyfFlAWjJvSzpNZnxQFGVNUVRbLF4jfTdYMGx='
asdaaaaaa=b'b2hfaG93X2RpZF95b3VfZmluZF90aGVfa2V5Pw=='
asdaaaaaaa=b'RlFqT1JVFmNmWkozYU54RDMwMEZlQ1QzVUtIblBHMWw='
asdaaaaaaaa=_A
asdaaaaaaaaa=b'Q25aaFN6aHNTR0ZsYUVka2FtVXlaRmXoV21OM1fIQXhkbGRLV2xaeVFsST0='
asdaaaaaaaaaa=_B
asdaaaaaaaaaaa=b'VXNkYWFhYWFhYWFAWb5tj43HTK2h'
asdaaaaaaaaaaaa=b'FlAWjJvSzpNZnxQOGVNUVRbLF4FlAWjJvSzpNZnxQOGVNUVRbLF4'
asdaaaaaaaaaaaaa=b'abFExWXpWVXRJYmabFExWXpWVXRJYmabFExWXpWVXRJYm'
asdaaaaaaaaaaaaaa=b'V2VsY29tZSEgUGxlYXNlIGVudGVyIHlvdXIga2V5OiA='
try:exec(asd)
except:pass
try:exec(asda)
except:pass
try:exec(asdaa)
except:pass
try:exec(asdaaa)
except:pass
try:exec(asdaaaa)
except:pass
try:exec(asdaaaaaaaaaaaa)
except:pass
class __declare__:
	def __init__(self):self.start()
	def start(self):done=False;exec(f"print('{base64.b64decode(asdaaaaaaaaaaaaaa).decode()}')");exec("k = input('')");exec(f"if k == '{base64.b64decode('anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+').decode()}':\n    print('Congrats! You cracked the program! :D')\nelse:print('Bad key!')");input('')
if __name__=='__main__':__declare__()

 

which after formatting comes to

  Reveal hidden contents
_B = b"UmxGcVQxSlZXbU5tV2tvellVNTRSRE13TUVabFExWXpWVXRJYmxCbE1Xdz0="
_A = b"WnZhSzhsSGFlaEdkamUyZFlhWmN3THAxdldKWlZyQlI="
import base64

asd = b"RlFqT1JVWmNmWkozYU54RDMwMEZlQ1YzVUtIblBlMWw="
asda = _A
asdaa = b"V25aaFN6aHNTR0ZsYUVka2FtVXlaRmxoV21OM1RIQXhkbGRLV2xaeVFsST0="
asdaaa = _B
asdaaaa = b"YXNkYWFhYWFhYWFhYWE="
asdaaaaa = b"anRyfFlAWjJvSzpNZnxQFGVNUVRbLF4jfTdYMGx="
asdaaaaaa = b"b2hfaG93X2RpZF95b3VfZmluZF90aGVfa2V5Pw=="
asdaaaaaaa = b"RlFqT1JVFmNmWkozYU54RDMwMEZlQ1QzVUtIblBHMWw="
asdaaaaaaaa = _A
asdaaaaaaaaa = b"Q25aaFN6aHNTR0ZsYUVka2FtVXlaRmXoV21OM1fIQXhkbGRLV2xaeVFsST0="
asdaaaaaaaaaa = _B
asdaaaaaaaaaaa = b"VXNkYWFhYWFhYWFAWb5tj43HTK2h"
asdaaaaaaaaaaaa = b"FlAWjJvSzpNZnxQOGVNUVRbLF4FlAWjJvSzpNZnxQOGVNUVRbLF4"
asdaaaaaaaaaaaaa = b"abFExWXpWVXRJYmabFExWXpWVXRJYmabFExWXpWVXRJYm"
asdaaaaaaaaaaaaaa = b"V2VsY29tZSEgUGxlYXNlIGVudGVyIHlvdXIga2V5OiA="
try:
    exec(asd)
except:
    pass
try:
    exec(asda)
except:
    pass
try:
    exec(asdaa)
except:
    pass
try:
    exec(asdaaa)
except:
    pass
try:
    exec(asdaaaa)
except:
    pass
try:
    exec(asdaaaaaaaaaaaa)
except:
    pass


class __declare__:
    def __init__(self):
        self.start()

    def start(self):
        done = False
        exec(f"print('{base64.b64decode(asdaaaaaaaaaaaaaa).decode()}')")
        exec("k = input('')")
        exec(
            f"if k == '{base64.b64decode('anRyfFlAWjJvSzpNZnxQOGVNUVRbLF4jfTdYMGx+').decode()}':\n    print('Congrats! You cracked the program! :D')\nelse:print('Bad key!')"
        )
        input("")


if __name__ == "__main__":
    __declare__()

The correct key is in the last exec-d string as we also found earlier.

what program do you use to edit hex?

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...