Skip to content

Commit

Permalink
expand Swift support, CS/int tests, Fix for #55
Browse files Browse the repository at this point in the history
  • Loading branch information
0cyn committed Nov 10, 2023
1 parent 0d0a9c9 commit 6a98d4d
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 89 deletions.
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,27 @@ pip3 install k2l
pip3 install --upgrade k2l
```

### Usage
### Usage

ktool is both a convenient CLI toolkit and a library that can be used
in other tools.

##### CLI Usage
```
> $ ktool
Usage: ktool [command] <flags> [filename]
Usage: ktool <global flags> [command] <flags> [filename]
Commands:
GUI (Still in active development) ---
ktool open [filename] - Open the ktool command line GUI and browse a file
MachO Analysis ---
dump - Tools to reconstruct headers and TBDs from compiled MachOs
dump - Tools to reconstruct certain files (headers, .tbds) from compiled MachOs
json - Dump image metadata as json
cs - Codesigning info
kcache - Kernel cache specific tools
list - Print various lists (Classlist, etc.)
list - Print various lists (ObjC Classes, etc.)
symbols - Print various tables (Symbols, imports, exports)
info - Print misc info about the target mach-o
Expand All @@ -64,11 +68,19 @@ MachO Editing ---
Misc Utilities ---
file - Print very basic info about the MachO
img4 - IMG4 Utilities
Run `ktool [command]` for info/examples on using that command
Global Flags:
-f - Force Load (ignores malformations in the MachO and tries to load whatever it can)
-v [-1 through 5] - Log verbosiy. -1 completely silences logging.
-V - Print version string (`ktool -V | cat`) to disable the animation
```


##### Library

Library documentation is located [here](https://ktool.cynder.me/en/latest/ktool.html)

---

written in pure, 100% python for the sake of platform independence when operating on static binaries and libraries.
Expand Down
2 changes: 1 addition & 1 deletion dev_coverage.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PYTHONPATH="./src" coverage run --source=src tests/unit.py; coverage html; open htmlcov/index.html
PYTHONPATH="./src" coverage run --source=src -m pytest tests/unit.py; coverage html; open htmlcov/index.html
5 changes: 4 additions & 1 deletion src/ktool/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ktool_macho.base import Constructable
from ktool.codesign import CodesignInfo
from ktool.exceptions import VMAddressingError, MachOAlignmentError
from ktool.util import bytes_to_hex
from ktool.util import bytes_to_hex, uint_to_int
from lib0cyn.log import log
from ktool.macho import Slice, SlicedBackingFile, MachOImageHeader, Segment, PlatformType, ToolType

Expand Down Expand Up @@ -398,6 +398,9 @@ def read_uint(self, offset: int, length: int, vm=False):
offset = self.vm.translate(offset)
return self.slice.read_uint(offset, length)

def read_int(self, offset: int, length: int, vm=False):
return uint_to_int(self.read_uint(offset, length, vm), length * 8)

def read_bytearray(self, offset: int, length: int, vm=False) -> bytearray:
"""
Get a sequence of bytes from a location
Expand Down
7 changes: 6 additions & 1 deletion src/ktool/ktool.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
pass
from ktool.macho import Slice, MachOFile, SlicedBackingFile
from ktool.objc import ObjCImage, MethodList
from ktool.swift import SwiftImage
from ktool.util import TapiYAMLWriter, ignore

from lib0cyn.log import log
Expand Down Expand Up @@ -141,7 +142,11 @@ def load_objc_metadata(image: Image) -> ObjCImage:
return ObjCImage.from_image(image)


def generate_headers(objc_image: ObjCImage, sort_items=False, forward_declare_private_imports=False) -> Dict[
def load_swift_metadata(objc_image: ObjCImage) -> SwiftImage:
return SwiftImage.from_image(objc_image)


def generate_headers(objc_image: 'ObjCImage', sort_items=False, forward_declare_private_imports=False) -> Dict[
str, Header]:
out = {}

Expand Down
9 changes: 8 additions & 1 deletion src/ktool/ktool_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,14 @@ def _list(args):
for obj_class in objc_image.classlist:
print(f'{obj_class.name}')
elif args.get_swift_types:
load_swift_types(image)
print(f'Swift Types')
swift_image = ktool.ktool.load_swift_metadata(objc_image)
for _type in swift_image.types:
if _type is None:
continue
print(f'{_type.name} ({_type.__class__.__name__})')
for field in _type.fields:
print(' ' + str(field))
elif args.get_protos:
for objc_proto in objc_image.protolist:
print(f'{objc_proto.name}')
Expand Down
27 changes: 25 additions & 2 deletions src/ktool/macho.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,41 @@ def __del__(self):
self.file.close()




class Section:
"""
"""

def __init__(self, segment, cmd):
class SectionIterator:
def __init__(self, sect: 'Section', vm=False, ptr_size=8):
self.ptr_size = ptr_size
self.start = sect.vm_address if vm else sect.file_address
self.end = (sect.vm_address if vm else sect.file_address) + sect.size
self.pos = self.start - self.ptr_size # TODO presumably i am misunderstanding something but this makes it work

def __iter__(self):
return self

def __next__(self):
self.pos += self.ptr_size
if self.pos >= self.end:
self.pos = self.start
raise StopIteration
return self.pos

def __init__(self, segment, cmd, ptr_size):
self.cmd = cmd
self.segment = segment
self.name = cmd.sectname
self.vm_address = cmd.addr
self.file_address = cmd.offset
self.size = cmd.size
self.ptr_size = ptr_size

def __iter__(self):
return Section.SectionIterator(self, False, self.ptr_size)

def serialize(self):
return {'command': self.cmd.serialize(), 'name': self.name, 'vm_address': self.vm_address,
Expand Down Expand Up @@ -216,7 +239,7 @@ def _process_sections(self) -> Dict[str, Section]:

for sect in range(0, self.cmd.nsects):
sect = self.image.read_struct(ea, section_64 if self.is64 else section)
sect = Section(self, sect)
sect = Section(self, sect, 8 if self.is64 else 4)
sections[sect.name] = sect
ea += section_64.SIZE if self.is64 else section.SIZE

Expand Down
143 changes: 111 additions & 32 deletions src/ktool/swift.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
#
# Swift type processing
#
# Comments here are currently from me reverse engineering type ser
#
# I have a habit of REing things that are technically publicly available,
# because this is the way I like to write these parsers, it's far less boring,
# and gives me a better initial understanding/foothold.
#
# So please note that comments, etc may be inaccurate until I eventually get around to
# diving into the swift compiler.
#
# https://belkadan.com/blog/2020/09/Swift-Runtime-Type-Metadata/
# https://knight.sc/reverse%20engineering/2019/07/17/swift-metadata.html
#
# This file is part of ktool. ktool is free software that
# is made available under the MIT license. Consult the
# file "LICENSE" that is distributed together with this file
Expand All @@ -15,12 +27,12 @@
from ktool_macho.base import Constructable
from ktool_swift.structs import *
from ktool_swift.demangle import demangle
from ktool import ObjCImage

from ktool.loader import Image
from ktool.macho import Section
from ktool.objc import Class
from ktool.util import usi32_to_si32
from lib0cyn.log import log
from ktool.util import uint_to_int


class Field:
Expand All @@ -36,27 +48,31 @@ def __str__(self):
class _FieldDescriptor(Constructable):

@classmethod
def from_image(cls, image, location):

def from_image(cls, objc_image, location):
image = objc_image.image
fields = []

fd = image.read_struct(location, FieldDescriptor, vm=True)

for i in range(fd.numFields):
ea = location + (i * 0xc)
record = image.read_struct(ea, FieldRecord, vm=True)
for i in range(fd.NumFields):
ea = location + FieldDescriptor.SIZE + (i * 0xc)
record = image.read_struct(ea, FieldRecord, vm=True, force_reload=True)

flags = record.Flags
type_name_loc = ea + 4 + record.Type
name_loc = ea + 8 + record.Name
type_name_loc = ea + 4 + record.MangledTypeName
name_loc = ea + 8 + record.FieldName
try:
name = image.read_cstr(name_loc, vm=True)
except ValueError:
name = ""
except IndexError:
name = ""
try:
type_name = image.read_cstr(type_name_loc, vm=True)
except ValueError:
type_name = ""
except IndexError:
type_name = ""

fields.append(Field(flags, type_name, name))

Expand All @@ -74,14 +90,40 @@ def __init__(self, fields, desc):
self.desc = desc


class SwiftStruct(Constructable):

@classmethod
def from_image(cls, objc_image: 'ObjCImage', type_location):
image = objc_image.image
struct_desc = image.read_struct(type_location, StructDescriptor, vm=True)
name = image.read_cstr(type_location + 8 + struct_desc.Name, vm=True)

#
field_desc = _FieldDescriptor.from_image(objc_image, type_location + (4*4) + struct_desc.FieldDescriptor)

return cls(name, field_desc)

@classmethod
def from_values(cls, *args, **kwargs):
pass

def raw_bytes(self):
pass

def __init__(self, name, field_desc: _FieldDescriptor):
self.name = name
self.field_desc = field_desc
self.fields = field_desc.fields


class SwiftClass(Constructable):

@classmethod
def from_image(cls, image: Image, objc_image: ObjCImage, type_location):
class_descriptor = image.read_struct(type_location, NominalClassDescriptor, vm=True)
def from_image(cls, image: Image, objc_image: 'ObjCImage', type_location):
class_descriptor = image.read_struct(type_location, ClassDescriptor, vm=True)
name = image.read_cstr(type_location + 8 + class_descriptor.Name, vm=True)
fd_loc = class_descriptor.Fields + type_location + 16
field_descriptor = _FieldDescriptor.from_image(image, fd_loc)
fd_loc = class_descriptor.FieldDescriptor + type_location + 16
field_descriptor = _FieldDescriptor.from_image(objc_image, fd_loc)
ivars = []

for objc_class in objc_image.classlist:
Expand Down Expand Up @@ -109,21 +151,46 @@ def __init__(self, name, fields, class_descriptor=None, field_descriptor=None, i
self.ivars = ivars


class SwiftEnum(Constructable):

@classmethod
def from_image(cls, objc_image, type_location):
image = objc_image.image
enum_descriptor = image.read_struct(type_location, EnumDescriptor, vm=True)
name = image.read_cstr(type_location + 8 + enum_descriptor.Name, vm=True)
field_desc = None
if enum_descriptor.FieldDescriptor != 0:
field_desc = _FieldDescriptor.from_image(objc_image, type_location + (4*4) + enum_descriptor.FieldDescriptor)

return cls(name, field_desc)

@classmethod
def from_values(cls, *args, **kwargs):
pass

def raw_bytes(self):
pass

def __init__(self, name, field_desc: _FieldDescriptor):
self.name = name
self.field_desc = field_desc
self.fields = field_desc.fields if self.field_desc is not None else []


class SwiftType(Constructable):

@classmethod
def from_image(cls, image: Image, objc_image, type_location):
typedesc = image.read_struct(type_location, NominalTypeDescriptor, vm=True)
name = image.read_cstr(type_location + 8 + typedesc.mangledName, vm=True)
kind = ContextDescriptorKind(image.read_uint(typedesc.off, 1, vm=False) & 0x1f)
kind = ContextDescriptorKind(image.read_uint(type_location, 1, vm=True) & 0x1f)

if kind == ContextDescriptorKind.Class:
return SwiftClass.from_image(image, objc_image, type_location)

fd_loc = typedesc.Fields + type_location + 16
field_descriptor = _FieldDescriptor.from_image(image, fd_loc)

return cls(name, kind, typedesc, field_descriptor)
elif kind == ContextDescriptorKind.Struct:
return SwiftStruct.from_image(objc_image, type_location)
elif kind == ContextDescriptorKind.Enum:
return SwiftEnum.from_image(objc_image, type_location)
else:
return None

@classmethod
def from_values(cls, *args, **kwargs):
Expand All @@ -139,18 +206,30 @@ def __init__(self, name, kind, typedesc=None, field_desc=None):
self.field_desc = field_desc


def load_swift_types(image: Image):
objc_image = ObjCImage.from_image(image)
class SwiftImage(Constructable):
def raw_bytes(self):
pass

@classmethod
def from_image(cls, objc_image: 'ObjCImage'):

types: List[SwiftType] = []

image = objc_image.image
swift_type_seg_start_sect: Section = image.segments['__TEXT'].sections['__swift5_types']
for addr in Section.SectionIterator(swift_type_seg_start_sect, vm=True, ptr_size=4):
type_rel = image.read_int(addr, 4)
type_off = addr + type_rel
types.append(SwiftType.from_image(image, objc_image, type_off))

return cls(types)

@classmethod
def from_values(cls):
pass

swift_type_seg_start_sect: Section = image.segments['__TEXT'].sections['__swift5_types']
sect_start = swift_type_seg_start_sect.vm_address
sect_size = swift_type_seg_start_sect.size
def __init__(self, types):
self.types = types

types = []

for ea in range(sect_start, sect_start + sect_size, 4):
typeref = usi32_to_si32(image.read_uint(ea, 4, vm=True))
type_loc = ea + typeref
types.append(SwiftType.from_image(image, objc_image, type_loc))

return types
Loading

0 comments on commit 6a98d4d

Please sign in to comment.