145 views
# embed any data inside a jar and sign it ###### tags: `blog` ```python= # -*- coding: utf-8 -*- import sys import struct import zipfile import os import base64 import shutil CONST_Utf8 = 1 CONST_Integer = 3 CONST_Float = 4 CONST_Long = 5 CONST_Double = 6 CONST_Class = 7 CONST_String = 8 CONST_Fieldref = 9 CONST_Methodref = 10 CONST_InterfaceMethodref = 11 CONST_NameAndType = 12 JAVA_CLASS_TEMPLATE =\ '''package _a; public class _s { public static final String s = "%s"; }''' # parse a cp_info and return it's length def parse_cp_info(fin): start = fin.tell() tag = struct.unpack('B', fin.read(1))[0] cp_info = {'tag': tag} if tag == CONST_Methodref: class_index, name_and_type_index = struct.unpack('>HH', fin.read(4)) cp_info['class_index'] = class_index cp_info['name_and_type_index'] = name_and_type_index elif tag == CONST_Class: name_index = struct.unpack('>H', fin.read(2))[0] cp_info['name_index'] = name_index elif tag == CONST_Utf8: length = struct.unpack('>H', fin.read(2))[0] bytes = fin.read(length) cp_info['length'] = length cp_info['bytes'] = bytes elif tag == CONST_String: string_index = struct.unpack('>H', fin.read(2))[0] cp_info['string_index'] = string_index elif tag == CONST_NameAndType: name_index, descriptor = struct.unpack('>HH', fin.read(4)) cp_info['name_index'] = name_index cp_info['descriptor'] = descriptor # TODO: remaining parser for other CONST tags ... return fin.tell() - start, cp_info def parse_attribute_info(fin): attribute_name_index, attribute_length =\ struct.unpack('>HI', fin.read(6)) info = fin.read(attribute_length) return {'attribute_name_index': attribute_name_index,\ 'attribute_length': attribute_length,\ 'info': info} def parse_field_info(fin): access_flags, name_index, descriptor_index, attributes_count =\ struct.unpack('>HHHH', fin.read(8)) attributes = [] for i in range(attributes_count): ai = parse_attribute_info(fin) attributes.append(ai) return {'access_flags': access_flags,\ 'name_index': name_index,\ 'descriptor_index': descriptor_index,\ 'attributes_count': attributes_count,\ 'attributes': attributes} def extract_b64str(clzpath): # class structure: # see http://coolshell.cn/articles/9229.html fclz = open(clzpath, 'rb') fclz.seek(8) cp_count = struct.unpack('>H', fclz.read(2))[0] # print 'constant_pool_count:', cp_count const_pool = [] # constant pool fclz.seek(10) for i in range(cp_count - 1): cp_len, cp_info = parse_cp_info(fclz) const_pool.append(cp_info) # print 'cp_tag:%s, cp_len:%d' %(cp_info['tag'], cp_len) # skip access_flags, this_class, super_class and interfaces_count fclz.seek(8, 1) # interfaces_count is zero, so interfaces takes 0 bytes fields_count = struct.unpack('>H', fclz.read(2))[0] b64str = None for i in range(fields_count): fi = parse_field_info(fclz) name_index = fi['name_index'] descriptor_index = fi['descriptor_index'] name_cp = const_pool[name_index - 1] descriptor_cp = const_pool[descriptor_index - 1] if name_cp['bytes'] == 'a' and\ descriptor_cp['bytes'] == 'Ljava/lang/String;': # the String field 'a' is found # CONST_String index index = struct.unpack('>H', fi['attributes'][0]['info'])[0] # CONST_Utf8 index index = const_pool[index - 1]['string_index'] b64str = const_pool[index - 1]['bytes'] fclz.close() return b64str def parse_sign_config(): with open('sign_config.txt', 'r') as f: for line in f.readlines(): eqmark = line.find('=') if eqmark < 0: continue v = line.strip()[eqmark + 1:] if line.startswith('VERSION_NAME'): vn = v elif line.startswith('VERSION_CODE'): vc = v elif line.startswith('BUILDNO'): bn = v elif line.startswith('LC'): lc = v return vn, vc, bn, lc def main(): inputjar = sys.argv[1] zf = zipfile.ZipFile(inputjar) namefmt = '_a/_a%d.class' block = 0 elfname = 'xmoddaemon' if os.path.exists(elfname): os.remove(elfname) tmp_name = 'tmp.class' try: while True: data = zf.read(namefmt %(block)) block += 1 if os.path.exists(tmp_name): os.remove(tmp_name) with open('tmp.class', 'ab') as tmp: tmp.write(data) b64str = extract_b64str(tmp_name) rawdata = base64.b64decode(b64str) print 'block %d size: %d' %(block, len(rawdata)) with open(elfname, 'ab') as elf: elf.write(rawdata) except KeyError: # all classes are read pass finally: zf.close() os.remove(tmp_name) rawlen = os.path.getsize(elfname) print 'raw xmoddaemon file size:', rawlen print '\nsigning ...' os.system('kds_exe_signer -f sign_config.txt %s' %(elfname)) print '' # remove tmpfile os.remove(elfname) for filename in os.listdir('.'): if filename.startswith(elfname) and filename.endswith('.signed'): signedelfname = filename break signedlen = os.path.getsize(signedelfname) print 'signed file size:', signedlen signaturelen = signedlen - rawlen print 'signature length:', signaturelen with open(signedelfname, 'rb') as f: f.seek(rawlen) rawsignature = f.read(signaturelen) signatureb64str = base64.b64encode(rawsignature) print 'signature b64str:', signatureb64str os.remove(signedelfname) outjava = '_s.java' outclz = '_s.class' if os.path.exists(outjava): os.remove(outjava) with open(outjava, 'a') as f: print >>f, JAVA_CLASS_TEMPLATE %(signatureb64str) os.system('javac %s' %(outjava)) os.remove(outjava) idxlastdot = inputjar.rfind('.') if idxlastdot < 0: orginname = inputjar orginext = '' else: orginname = inputjar[:idxlastdot] orginext = inputjar[idxlastdot + 1:] vn, vc, bn, lc = parse_sign_config() verinfo = 'vn%svc%sbn%slc%s' %(vn, vc, bn, lc) outjar = '%s_%s.signed.%s' %(orginname, verinfo, orginext) shutil.copyfile(inputjar, outjar) zf = zipfile.ZipFile(outjar, mode='a') try: print 'adding signature class to %s' %(outjar) zf.write(outclz, arcname='_a/%s' %(outclz)) finally: zf.close() os.remove(outclz) if '__main__' == __name__: main() ```