197 lines
4.4 KiB
Python
197 lines
4.4 KiB
Python
'''
|
|
KeyBoy Config file decoder
|
|
From: It's Parliamentary: KeyBoy and the targeting of the Tibetan Community
|
|
Permalink: https://citizenlab.org/2016/11/parliament-keyboy/
|
|
|
|
Adam Hulcoop / September 19, 2016
|
|
adam.hulcoop[@]citizenlab.ca
|
|
'''
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
|
|
LookupTable = {}
|
|
|
|
# Function to compute 9-bit sliding window values and return list of converted indices
|
|
def byteDecoder(rawBytes):
|
|
#rawBytes should be bytearray of values
|
|
k=0
|
|
ba = []
|
|
|
|
bytex=0
|
|
while( bytex < len(rawBytes) -1):
|
|
|
|
index = 0
|
|
ix = bytex
|
|
t=0
|
|
while ( index < 9):
|
|
|
|
while (k<8):
|
|
|
|
if( ( ord(rawBytes[ix]) & (1 << (7-k))) > 0 ):
|
|
t = 2*t +1
|
|
else:
|
|
t = 2*t
|
|
|
|
index+=1
|
|
k+=1
|
|
|
|
if( index > 8):
|
|
break
|
|
|
|
if ( k==8 ):
|
|
ix+=1
|
|
k=0
|
|
|
|
|
|
if (index<=8):
|
|
k=0
|
|
elif ( (index == 9) and (k==8)):
|
|
k=0
|
|
bytex+=1
|
|
#t should hold the resultant value
|
|
ba.append(t)
|
|
bytex+=1
|
|
|
|
|
|
return ba
|
|
|
|
|
|
|
|
|
|
def CreateLookupTable():
|
|
for x in xrange(0,256):
|
|
LookupTable[x] = "{:x}".format(x)
|
|
|
|
# 0x101 is used as the exit value
|
|
LookupTable[257] = "reserved"
|
|
|
|
return
|
|
|
|
|
|
|
|
def BuildExtraLookupTable(curIndex, prevIndex):
|
|
#curIndex and prevIndex are indices from the decoded input file
|
|
startFrom = len(LookupTable)
|
|
|
|
if( curIndex > startFrom) :
|
|
# if we are looking up an index beyond the existing table size
|
|
vPrev = LookupTable.get(prevIndex)
|
|
vCur = LookupTable.get(prevIndex)
|
|
else:
|
|
vPrev = LookupTable.get(prevIndex)
|
|
vCur = LookupTable.get(curIndex)
|
|
|
|
newVal = vPrev
|
|
|
|
|
|
if( len(vCur.split(',')) > 1 ):
|
|
augItem = vCur.split(',')[0]
|
|
else:
|
|
augItem = vCur
|
|
|
|
newVal = ','.join([vPrev,augItem])
|
|
|
|
LookupTable[startFrom+1] = newVal
|
|
logging.debug("0x%x => %s" % ((startFrom+1),newVal ))
|
|
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = argparse.ArgumentParser(description='Decode KeyBoy backdoor configuration files')
|
|
parser.add_argument('FILE', help='KeyBoy encoded config file')
|
|
parser.add_argument('--verbose','-v',action='store_true',help='Enable verbose output')
|
|
parser.add_argument('--skip','-s',type=int,help='Skip over <SKIP> bytes at beginning of file')
|
|
args = parser.parse_args()
|
|
|
|
if args.verbose:
|
|
logging.basicConfig(format='%(message)s',level=logging.DEBUG)
|
|
else:
|
|
logging.basicConfig(format='%(message)s',level=logging.WARNING)
|
|
|
|
|
|
print "============================"
|
|
print "{KeyBoy Config file Decoder}"
|
|
print "============================"
|
|
print ""
|
|
|
|
with open(args.FILE, "rb") as f:
|
|
|
|
if args.skip is None:
|
|
f.seek(0,0)
|
|
else:
|
|
logging.warn("Skipping %i bytes.." % args.skip)
|
|
f.seek(args.skip,0)
|
|
|
|
szfile = f.read(8)
|
|
szf_dat = int(szfile,16)
|
|
logging.debug("Size of decoded config file:\t%i bytes" % szf_dat)
|
|
|
|
szconf = f.read(8)
|
|
szc_dat = int(szconf,16)
|
|
logging.debug("Size of encoded config data:\t%i bytes" % szc_dat)
|
|
logging.debug("")
|
|
|
|
bytesRaw = f.read(szc_dat)
|
|
ba = byteDecoder(bytesRaw)
|
|
|
|
#create the initial Lookup table
|
|
CreateLookupTable()
|
|
|
|
ResultsList = []
|
|
|
|
#Build the extra lookup table values
|
|
logging.debug("Dynamic lookup table entries:")
|
|
for val in range(len(ba)):
|
|
|
|
#for each entry in the bytes array,
|
|
# we need to look up the value in the table.
|
|
# As we go, we need to build new entries on the table
|
|
# corresponding to the value from this lookup, concatenated with the first character from the
|
|
# the value printed in the previous lookup. BuildExtraLookupTable function does that step.
|
|
|
|
if( val == 0):
|
|
continue
|
|
elif (val == 1):
|
|
ResultsList.append(LookupTable.get(ba[val]))
|
|
prevValue = ba[val]
|
|
else:
|
|
curValue = ba[val]
|
|
if (curValue != 257):
|
|
BuildExtraLookupTable(curValue, prevValue)
|
|
ResultsList.append(LookupTable.get(ba[val]))
|
|
prevValue= curValue
|
|
|
|
logging.debug("")
|
|
logging.debug("Decoded index list:")
|
|
logging.debug(['0x%x' % j for j in ba])
|
|
logging.debug("")
|
|
|
|
|
|
resultData = ",".join(ResultsList)
|
|
hChars = resultData.split(',')
|
|
Output = ""
|
|
for h in hChars:
|
|
Output += chr(int(h,16))
|
|
|
|
logging.debug("Retrieved config character array:")
|
|
logging.debug(['0x%x' % int(m,16) for m in hChars])
|
|
logging.debug("")
|
|
|
|
TokenizedOutput = Output.split('\r\n')
|
|
ConfigSections = ['Identity Code:','C2 Host/IP #1:','C2 Host/IP #2:','C2 Host/IP #3:','C2 Port #1:','C2 Port #2:','C2 Port #3:','Password:','Campaign ID:','']
|
|
|
|
logging.warn("Configuration Data:")
|
|
headr = "=" * 30
|
|
logging.warn(headr)
|
|
|
|
for x in range(len(TokenizedOutput)-1):
|
|
logging.warn("%s\t%s" % (ConfigSections[x],TokenizedOutput[x]))
|
|
|
|
logging.warn("")
|
|
|
|
|
|
|