This is the code rewritten in Python 3 from the original code of esterTion's PHP version. Only works with blobs from Android apk!!!
genProto.py
for old versions, genProto.py
for new versions with arm64 blobs.
This is the code rewritten in Python 3 from the original code of esterTion's PHP version. Only works with blobs from Android apk!!!
genProto.py
for old versions, genProto.py
for new versions with arm64 blobs.
from struct import unpack | |
import re | |
import sys | |
dumpcs = open('dump.cs').read() | |
prog = open('libil2cpp.so', 'rb') | |
definedClass = [] | |
targetClass = sys.argv[1] #'SuiteMasterGetResponse' # change to get different classes | |
outputPath = './{}.proto'.format(targetClass) | |
outputFile = open(outputPath, 'w') | |
# write first line | |
outputFile.write('syntax = "proto2";\n') | |
typeMap = { | |
'uint': 'uint32', | |
'string': 'string', | |
'ulong': 'uint64', | |
'float': 'float', | |
'int': 'int32', | |
'double': 'double', | |
'bool': 'bool', | |
'long': 'int64' | |
} | |
def getTag(address): | |
offset = address & 0xFFFFFFFF | |
prog.seek(offset) | |
inst = prog.read(4) | |
inst = int.from_bytes(inst, byteorder='little', signed=False) | |
if inst == 0xe5900004: #0x080440f9: | |
prog.seek(offset + 4) | |
retnum = int.from_bytes(prog.read(2), 'little', signed=False) | |
rotate_flag = int.from_bytes(prog.read(1), 'little', signed=False) | |
if rotate_flag == 0xA0: | |
# rotate tag number | |
rotate_num = (retnum >> 8) & 0xF | |
tag = retnum & 0xFF | |
for i in range(rotate_num * 2): | |
tag = rotr(tag, 32) | |
return tag | |
return retnum & 0xfff | |
elif inst == 0xe92d4c10: | |
prog.seek(offset + 12) | |
return int.from_bytes(prog.read(2), 'little', signed=False) & 0xfff | |
else: | |
print(hex(inst), hex(address)) | |
def rotr(num, bits): | |
num &= (2**bits-1) | |
bit = num & 1 | |
num >>= 1 | |
if(bit): | |
num |= (1 << (bits-1)) | |
return num | |
def writeMessage(target, message): | |
outputFile.write('message {} {{\n'.format(target)) | |
for item, info in message.items(): | |
typ = info[0] | |
if type(info[1]).__name__ == 'str': | |
tag = getTag(int(info[1], 16)) | |
else: | |
tag = info[1] | |
hint = info[2] | |
comment = info[3] | |
if hint == 'map': | |
outputFile.write(' {}{} {} = {};\n'.format(hint, typ, item, tag)) | |
else: | |
outputFile.write(' {} {} {} = {};\n'.format(hint, typ, item, tag)) | |
outputFile.write('}\n') | |
def readClass(level, target): | |
try: | |
definedClass.index(target) | |
shownClass = True | |
except ValueError: | |
definedClass.append(target) | |
shownClass = False | |
message = {} | |
classDef = re.search('\[ProtoContractAttribute\].*?\n.*?class ' + target + ' [^\{\}]*?\{((.*\n)*?)?\s+(//\s+Properties(.*\n)*?)?\s+(//\s+Methods(.*\n)*?)?\}\s*?', dumpcs) | |
if not classDef: | |
print('{} not found'.format(target)) | |
else: | |
propList = re.findall('(\[ProtoMemberAttribute\] //.*Offset: 0x([0-9A-F]+)\n \w+ ([^\ \<]+(\<(.*?)\>)?) ([^\ ;]+))', classDef[0]) | |
for prop in propList: | |
typ = jumpTyp(level, prop[2], prop[5]) | |
message[typ[0]] = [typ[1], prop[1], typ[2], typ[3]] | |
if not shownClass: | |
# print('{} \n'.format(target)) | |
writeMessage(target, message) | |
def jumpTyp(level, typ, name): | |
if typ[-2:] == '[]': | |
sub = jumpTyp(level + 2, typ[0:-2], 'entry') | |
return [name, sub[1], 'repeated', 'array'] | |
elif typ[0:11] == 'Dictionary`': | |
subType = re.search('<(\w+), (\w+)>', typ) | |
readClass(level + 1, subType[2]) | |
# prefix = '{}_{}'.format(subType[1], subType[2]) | |
# try: | |
# definedClass.index(prefix) | |
# shownClass = True | |
# except ValueError: | |
# definedClass.append(prefix) | |
# shownClass = False | |
# message = {} | |
# sub = jumpTyp(level + 1, subType[1], '{}_key'.format(prefix)) | |
# message[sub[0]] = [sub[1], 1, sub[2], sub[3]] | |
# sub = jumpTyp(level + 1, subType[2], '{}_value'.format(prefix)) | |
# message[sub[0]] = [sub[1], 2, sub[2], sub[3]] | |
# if not shownClass: | |
# writeMessage(prefix, message) | |
return [name, '<{}, {}>'.format(typeMap.get(subType[1], subType[1]), typeMap.get(subType[2], subType[2])), 'map', 'dictionary'] | |
elif typ[0:5] == 'List`': | |
subType = re.search('<(\w+)>', typ) | |
sub = jumpTyp(level + 1, subType[1], 'entry') | |
return [name, sub[1], 'repeated', 'list'] | |
elif typ[0:9] == 'Nullable`': | |
subType = re.search('<(\w+)>', typ) | |
sub = jumpTyp(level, subType[1], name) | |
sub[3] = 'nullable' | |
return sub | |
else: | |
expectTyp = ['uint','string','ulong','float','int','double', 'bool','long'] | |
try: | |
expectTyp.index(typ) | |
isType = True | |
except ValueError: | |
expectTyp.append(typ) | |
isType = False | |
if isType: | |
return [name, typeMap[typ], 'optional', 'normal type'] | |
else: | |
readClass(level + 1, typ) | |
return [name, typ, 'optional', 'sub class'] | |
readClass(0, targetClass) |
from struct import unpack | |
import re | |
import sys | |
dumpcs = open('dump.cs', encoding="utf8").read() | |
prog = open('libil2cpp.so', 'rb') | |
definedClass = [] | |
targetClass = sys.argv[1] #'SuiteMasterGetResponse' # change to get different classes | |
outputPath = './{}.proto'.format(targetClass) | |
outputFile = open(outputPath, 'w') | |
# write first line | |
outputFile.write('syntax = "proto2";\n') | |
# outputFile.write('package bang;\n') | |
typeMap = { | |
'uint': 'uint32', | |
'string': 'string', | |
'ulong': 'uint32', | |
'float': 'float', | |
'int': 'int32', | |
'double': 'double', | |
'bool': 'bool', | |
'long': 'int32' | |
} | |
def getTag(address): | |
offset = address & 0xFFFFFFFF | |
prog.seek(offset) | |
inst = prog.read(4) | |
inst = int.from_bytes(inst, byteorder='little', signed=False) | |
if inst == 0xf9400408: | |
prog.seek(offset + 4) | |
inst = int.from_bytes(prog.read(4), 'little', signed=False) | |
elif inst == 0xf81e0ff3: | |
prog.seek(offset + 16) | |
inst = int.from_bytes(prog.read(4), 'little', signed=False) | |
else: | |
print(hex(inst), hex(address)) | |
return None | |
if inst >> 24 == 0x52: | |
return (inst >> 5) & 0xFFFF | |
elif inst >> 24 == 0x32: | |
retnum = (inst >> 8) & 0xFFFF | |
immr = (retnum >> 8) & 0x3F | |
imms = (retnum >> 2) & 0x3F | |
clz = lambda x: "{:032b}".format(x).index("1") | |
_len = 31 - clz((0 << 6) | (~imms & 0x3F)) | |
_size = 1 << _len | |
R = immr & (_size - 1) | |
S = imms & (_size - 1) | |
ret = (1 << (S+1)) - 1 | |
for i in range(immr): | |
ret = rotr(ret, 32) | |
return ret | |
def rotr(num, bits): | |
num &= (2**bits-1) | |
bit = num & 1 | |
num >>= 1 | |
if(bit): | |
num |= (1 << (bits-1)) | |
return num | |
def writeMessage(target, message): | |
outputFile.write('message {} {{\n'.format(target)) | |
for item, info in message.items(): | |
typ = info[0] | |
if type(info[1]).__name__ == 'str': | |
tag = getTag(int(info[1], 16)) | |
else: | |
tag = info[1] | |
hint = info[2] | |
comment = info[3] | |
if hint == 'map': | |
outputFile.write(' {}{} {} = {};\n'.format(hint, typ, item, tag)) | |
else: | |
outputFile.write(' {} {} {} = {};\n'.format(hint, typ, item, tag)) | |
outputFile.write('}\n') | |
def readClass(level, target): | |
try: | |
definedClass.index(target) | |
shownClass = True | |
except ValueError: | |
definedClass.append(target) | |
shownClass = False | |
message = {} | |
classDef = re.search('\[ProtoContractAttribute\].*?\n.*?class ' + target + ' [^\{\}]*?\{((.*\n)*?)?\s+(//\s+Properties(.*\n)*?)?\s+(//\s+Methods(.*\n)*?)?\}\s*?', dumpcs) | |
if not classDef: | |
print('{} not found'.format(target)) | |
else: | |
propList = re.findall('(\[ProtoMemberAttribute\] //.*Offset: 0x([0-9A-F]+).+?\n \w+ ([^\ \<]+(\<(.*?)\>)?) ([^\ ;]+))', classDef[0]) | |
for prop in propList: | |
typ = jumpTyp(level, prop[2], prop[5]) | |
message[typ[0]] = [typ[1], prop[1], typ[2], typ[3]] | |
if not shownClass: | |
# print('{} \n'.format(target)) | |
writeMessage(target, message) | |
def jumpTyp(level, typ, name): | |
if typ[-2:] == '[]': | |
sub = jumpTyp(level + 2, typ[0:-2], 'entry') | |
return [name, sub[1], 'repeated', 'array'] | |
elif typ[0:10] == 'Dictionary': | |
subType = re.search('<(\w+), (\w+)>', typ) | |
readClass(level + 1, subType[2]) | |
return [name, '<{}, {}>'.format(typeMap.get(subType[1], subType[1]), typeMap.get(subType[2], subType[2])), 'map', 'dictionary'] | |
elif typ[0:4] == 'List': | |
subType = re.search('<(\w+)>', typ) | |
sub = jumpTyp(level + 1, subType[1], 'entry') | |
return [name, sub[1], 'repeated', 'list'] | |
elif typ[0:8] == 'Nullable': | |
subType = re.search('<(\w+)>', typ) | |
sub = jumpTyp(level, subType[1], name) | |
sub[3] = 'nullable' | |
return sub | |
else: | |
expectTyp = ['uint','string','ulong','float','int','double', 'bool','long'] | |
try: | |
expectTyp.index(typ) | |
isType = True | |
except ValueError: | |
expectTyp.append(typ) | |
isType = False | |
if isType: | |
return [name, typeMap[typ], 'optional', 'normal type'] | |
else: | |
readClass(level + 1, typ) | |
return [name, typ, 'optional', 'sub class'] | |
readClass(0, targetClass) |