#!/usr/bin/python
# (c) metlstorm 2k6, metlstorm@storm.net.nz
# GNU GPL v2 licensed
#
# Un-obfuscates perl that's been munged with Filter::netcrypt.
# Will only work when you know the key, or the magic values
#	erand0,erand1,erand2 and initial value of emask.
# In the wild, these are derived from a passphrase hardcoded in to the binary.
# 
# There's some known badness around the 44,45 byte boundaries, so if your
# plaintext is a multiple of 44 or 45 bytes, you'll get one or two bytes of
# badness. It should be easy to spot from the context, I guess. Mind you, this is
# perl we're talkinga bout, so perhaps not. Maybe I'll fix this bug if someone cares
# enough.
#
# This thing is actually pretty nasty.
# It's a feedback-XOR cipher, with output substitution and bit-expansion.
#
# It goes roughly:
#
# generateKeyMaterial( passphrase )
# for each 44 byte block of input:
#   starting at the end of the block, and working backwards:
#		ciphertextbyte = plaintextbyte ^ emask
#		emask = plaintextbyte
#	perform byte-substitution on the array of ciphertext based on a subtitution matrix
#	for each 3 bytes of ciphertext:
#		expand to 4 bytes of encoded output via miscellaneous bitmunging
#
# There's some funniness around it inserting 0a to end a block, and sticking 0x1a 
# characters in when there's room and stuff. It's a mess. This is the source of 
# the 44/45 byte bug, I feel.
#
# Now, the passphrase to key generation uses the first 14 bytes of the passphrase
# only, and even then, not all the bits. It uses the 5 LSB of the first 12 bytes, 
# and the 4 LSB of the last 4 bytes, for 76 bits of key material total.
# These are folded into three integers (erand0-2) and an initial char mask (emask).
# So, if you didn't know the passphrase, for some reason...
# ... you've got 2**76 as a search space, which is 75557863725914323419136 
# possibilities.


import sys

def usage():
	print "%s: passphrase filename-to-deobfuscate" % sys.argv[0]

def getHdrInfo(data):
	"""Grabs the size of the data from the header. ARe they on crack?"""
	sz = (ord(data[0]) & 0x3f) - 0x20
	if sz < 0:
		sz += 64
	return (1, sz)

def passphraseToEStuff(key):
	"""Given a passphrase key, returns erand0,1,2 & emask"""
	if len(key) <= 0x0d:
		raise ValueError("key must be >= 14 bytes long")

	er =[]
	for i,m in [(0,0x763d),(4,0x7663),(8,0x7673)]:
		a = ord(key[i]) & 0x1f
		d = a << 0xf
		a = ord(key[i+1]) & 0x1f
		d |= (a << 0xa)
		a = ord(key[i+2]) & 0x1f
		d |= (a << 5)
		a = ord(key[i+3]) & 0x1f
		a |= d
		er.append(a % m)

	a = ord(key[12]) & 0xf
	d = a << 4
	a = ord(key[13]) & 0xf
	a |= d
	
	emask = a
	return (er[0], er[1], er[2], emask)

def unBitExpand(ct, dlen):
	"""Reverses the 3->4byte expansion"""
	
	in_crypt_buffer = {}
	j = 0

	if dlen < 2:
		dlen +=2
		
	for i in range(0,len(ct),4):
		b1 = (ord(ct[i]) - 0x20) & 0x3f
		b2 = (ord(ct[i+1]) - 0x20) & 0x3f
		b3 = (ord(ct[i+2]) - 0x20) & 0x3f
		b4 = (ord(ct[i+3]) - 0x20) & 0x3f

		in_crypt_buffer[j+2] = (b3 & 0x3f) + ((b2 & 0x03) << 6)
		in_crypt_buffer[j] = (b1 << 2) + ((b4 & 0x30) >> 4)
		in_crypt_buffer[j+1] = ((b4 & 0xf) << 4) + ((b2 & 0x3c) >> 2)
		j+=3

	rv = "" 
	for i in range(0,dlen):
		rv+=chr(in_crypt_buffer[i])
		
	return rv

def unPermuteAndXor(cb):
	"""Given encrypted bytes, return plaintext under permutation table PERM for KEY"""
	plaintext = ""
	emask = KEY
	i = len(cb)-1
	while i >= 0:
		ptb = chr(ord(cb[PERM[i]]) ^ emask)
		plaintext = "%s%s" % (ptb, plaintext)
		emask = ord(ptb)
		i-=1
	
	return plaintext

def enPerm(size, erand0, erand1, erand2):
	"""Generate the substitution table. It's called 'enperm' in the original, so
	I guess they think substitution == permutation or something."""

	perm = {}
	for i in range(0,256):
		perm[i] = i

	i = size
	while (i > 0):
		d = erand0
		a = d
		
		a = ((((a << 3) + d) << 1) + d)
		d = a * 8
		a += d
		
		a %= 0x763d
		erand0 = a
		c = a << 0x0f
		a = 0x4548a88bL
		b = ((a * c) & 0xffffffff00000000L) >> 32 + 0xd
	
		a = erand1
		a = ((((((a << 2) + erand1) << 2)+ erand1) << 1) + erand1) * 4

		a %= 0x7663
		erand1 = a
		c = a << 0xf
		a=0x45326b65
		a = ((a * c) & 0xffffffff00000000L) >> 32 + 0xd
		c = a + b 
		
		a = erand2
		d = ((((((a << 2) + erand2) << 2)+ erand2) << 2) + erand2) * 2
		a = d
		a %= 0x7673
		erand2 = a
		
		a = a << 0xf
		local14 = a / 0x7673
		d = local14
		a = d + c
		local_0c = ((a & 0x7fff) * i) >> 0xf
		
		i -= 1
		local_10 = perm[i]
		perm[i] = perm[local_0c]
		perm[local_0c] = local_10

	return perm, erand0, erand1, erand2


if len(sys.argv) < 3:
	usage()
	sys.exit(1)

fp = open(sys.argv[2])
data = fp.read()
fp.close()

erand0, erand1, erand2, KEY = passphraseToEStuff(sys.argv[1])

output = ""
for l in data.split("\x0a")[2:-1]:
	if len(output) > 0:
		KEY = ord(pt[0])

	start, size = getHdrInfo(l)
	
	PERM, erand0, erand1, erand2 = enPerm(size, erand0, erand1, erand2)

	pt = unPermuteAndXor(unBitExpand(l[start:], size))
	
	if pt[-2:] == "\x0a\x1a":
		output += pt[0:-2]
	else:
		output += pt
	
print output

