''' 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 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("")