Monday, August 7, 2017

De-obfuscate videos from pluralsight

I recently subscribed to pluralsight and needed to access the videos out of their walled app. A quick look at lsof when pluralsight is running shows that it's accessing a cached video in my ~/Library.
❯ ls ~/Library/Application\ Support/com.pluralsight.pluralsight-mac/ClipDownloads | head [11:55:18]
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm00.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm10.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm11.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm12.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm13.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm14.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm20.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm21.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm22.psv
buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm23.psv
❯ hexdump -C buildingsentimentanalysissystemspythonbuildingsentimentanalysissystemspythonm00.psv | head [11:55:30]
00000000 65 65 65 41 03 11 1c 15 08 15 51 57 65 65 65 65 |eeeA......QWeeee|
00000010 0c 16 0a 08 0c 16 0a 57 04 13 06 54 08 15 51 54 |.......W...T..QT|
00000020 08 15 51 57 65 65 bf 71 08 0a 0a 13 65 65 65 09 |..QWee.q....eee.|
00000030 08 13 0d 01 65 65 65 65 b1 ec b4 b7 b1 ec b4 b7 |....eeee........|
00000040 65 65 66 8d 65 64 e6 6d 65 64 65 65 64 65 65 65 |eef.ed.medeedeee|
00000050 65 65 65 65 65 65 65 65 65 64 65 65 65 65 65 65 |eeeeeeeeedeeeeee|
*
00000070 65 65 65 65 65 65 65 65 25 65 65 65 65 65 65 65 |eeeeeeee%eeeeeee|
00000080 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 |eeeeeeeeeeeeeeee|
00000090 65 65 65 65 65 65 65 66 65 65 f4 13 11 17 04 0e |eeeeeeefee......|
view raw pluralsight hosted with ❤ by GitHub
A quick look at the psv file using the file command fails. Using hexdump doesn't reveal any magic strings. Time to look for other clues. Next step was to sign in onto their website and play videos using the browser and at the same time take a tcpdump..

Comparing the first few bytes from the website and from the PSV reveals something interesting

obfuscated = map( lambda x: int(x, 16), "65 65 65 41 03 11 1c 15".split(" "))
original = map( lambda x: int(x, 16), "00 00 00 24 66 74 79 70".split(" "))
print obfuscated
# [101, 101, 101, 65, 3, 17, 28, 21]
print original
# [0, 0, 0, 36, 102, 116, 121, 112]
# Every place we have a 0 in the original, the obfuscated file has 101. Sounds suspicious... ;)
# Let's xor every byte in the source with the equivalent in the destination
for i, obs in enumerate(obfuscated):
orig = original[i]
print "%s -> %s : xor: %s" % (obs, orig, obs^orig)
# Boom! Headshot!
#101 -> 0 : xor: 101
#101 -> 0 : xor: 101
#101 -> 0 : xor: 101
#65 -> 36 : xor: 101
#3 -> 102 : xor: 101
#17 -> 116 : xor: 101
#28 -> 121 : xor: 101
#21 -> 112 : xor: 101
view raw pluralsight1.py hosted with ❤ by GitHub
Aha, it looks like we have a simple XOR obfuscation with a key 0x65. Can we write  a simple de-obfuscation script?

You bet!
def deobs_pluralsight(fpath, target_dir):
fname = os.path.basename(fpath)
target_fname = fname.replace('psv', 'mp4')
target_file_path = os.path.join(target_dir, target_fname)
with open(target_file_path, "wb") as ofh:
for byte in bytearray(open(fpath, "rb").read()):
ofh.write(chr(byte ^ 101))
def deobfuscate(arg,dirname,fnames):
if fnames:
for fname in fnames:
fpath = os.path.join(dirname, fname)
print fpath
deobs_pluralsight(fpath, target_dir)
source_dir = "/Users/<me>/Library/Application Support/com.pluralsight.pluralsight-mac/ClipDownloads/"
target_dir = "/Users/<me>/Documents/learning/pluralsight"
if not os.path.isdir(target_dir):
mkpath(target_dir)
os.path.walk(source_dir, deobfuscate, None)

Now I can play my learning videos on my TV w/out hooking up my laptop.