Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 271 additions & 0 deletions lara/classes/zipmgr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,274 @@ public func createZipArchive(fromDirectory sourceURL: URL, to destinationURL: UR
writeLE16(0, to: &eocd)
try fh.write(contentsOf: eocd)
}

// MARK: - Streaming writer (constant memory) for App Decrypt
//
// createZipArchive() above loads *every* file of the bundle into RAM with
// Data(contentsOf:) and keeps all of them alive in `fileEntries` until the
// archive is finished. A 2.4 GB app therefore needs >2.4 GB of resident
// memory inside lara and is killed by jetsam. It also uses ZIP32 fields only.
// This variant streams one file at a time, keeps only small central-directory
// records in memory, and emits ZIP64 records when offsets/sizes need them.

private func writeLE64(_ value: UInt64, to data: inout Data) {
withUnsafeBytes(of: value.littleEndian) { data.append(contentsOf: $0) }
}

private func streamCRC32(of url: URL) throws -> (crc: UInt32, size: UInt64) {
guard let input = InputStream(url: url) else {
throw ZipError.corruptArchive("(zip) cannot open \(url.lastPathComponent)")
}
input.open()
defer { input.close() }

let chunkSize = 1 << 20
var buffer = [UInt8](repeating: 0, count: chunkSize)
var crc: uLong = 0
var size: UInt64 = 0

while true {
let n = buffer.withUnsafeMutableBytes { raw -> Int in
guard let base = raw.baseAddress else { return 0 }
return input.read(base.assumingMemoryBound(to: UInt8.self), maxLength: chunkSize)
}
if n < 0 {
throw ZipError.corruptArchive("(zip) read failed on \(url.lastPathComponent)")
}
if n == 0 { break }
crc = buffer.withUnsafeBytes { raw -> uLong in
guard let base = raw.baseAddress else { return crc }
return crc32(crc, base.assumingMemoryBound(to: Bytef.self), uInt(n))
}
size += UInt64(n)
}
return (UInt32(crc & 0xffff_ffff), size)
}

private func appendZip64Extra(usize: UInt64, csize: UInt64, offset: UInt64?, to data: inout Data) {
writeLE16(0x0001, to: &data)
writeLE16(UInt16(offset == nil ? 16 : 24), to: &data)
writeLE64(usize, to: &data)
writeLE64(csize, to: &data)
if let offset = offset { writeLE64(offset, to: &data) }
}

public func createZipArchiveStreaming(fromDirectory sourceURL: URL,
to destinationURL: URL,
progress: ((Double) -> Void)? = nil) throws {
let mgr = laramgr.shared
let fm = FileManager.default
let resolvedSource = sourceURL.resolvingSymlinksInPath()

guard let enumerator = fm.enumerator(at: resolvedSource,
includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey],
options: [.skipsHiddenFiles]) else {
mgr.logmsg("(zip) cannot enumerate source directory")
throw ZipError.corruptArchive("(zip) cannot enumerate source directory")
}

// Only paths are retained here, never file contents.
var files: [(url: URL, name: String)] = []
var dirs: [String] = []
var totalBytes: UInt64 = 0
for case let fileURL as URL in enumerator {
let resolvedFile = fileURL.resolvingSymlinksInPath()
guard let relRange = resolvedFile.path.range(of: resolvedSource.path + "/") else { continue }
let relPath = String(resolvedFile.path[relRange.upperBound...])
var isDir: ObjCBool = false
guard fm.fileExists(atPath: fileURL.path, isDirectory: &isDir) else { continue }
if isDir.boolValue { dirs.append(relPath + "/") }
else {
files.append((fileURL, relPath))
if let sz = try? fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize {
totalBytes += UInt64(sz)
}
}
}

try? fm.removeItem(at: destinationURL)
guard fm.createFile(atPath: destinationURL.path, contents: nil, attributes: nil) else {
mgr.logmsg("(zip) cannot create output file")
throw ZipError.corruptArchive("(zip) cannot create output file")
}
let fh = try FileHandle(forWritingTo: destinationURL)
defer { try? fh.close() }

var cdEntries: [Data] = []
cdEntries.reserveCapacity(files.count + dirs.count)
let chunkSize = 1 << 20
var bytesDone: UInt64 = 0
var lastReportedBytes: UInt64 = 0

for entry in files {
let nameData = entry.name.data(using: .utf8) ?? Data()
guard nameData.count <= 0xffff else { continue }

// Pass 1: CRC + real size (constant memory).
let (crc, size) = try streamCRC32(of: entry.url)
let offset = UInt64(try fh.offset())
let zip64 = size > 0xffff_ffff || offset > 0xffff_ffff
let size32: UInt32 = zip64 ? 0xffff_ffff : UInt32(size)
let off32: UInt32 = zip64 ? 0xffff_ffff : UInt32(offset)

var lfhExtra = Data()
if zip64 { appendZip64Extra(usize: size, csize: size, offset: nil, to: &lfhExtra) }

var lfh = Data()
writeLE32(lfhSignature, to: &lfh)
writeLE16(zip64 ? 45 : 20, to: &lfh) // version needed
writeLE16(0x0800, to: &lfh) // UTF-8 filenames
writeLE16(0, to: &lfh) // stored, no compression
writeLE16(0, to: &lfh) // time
writeLE16(0, to: &lfh) // date
writeLE32(crc, to: &lfh)
writeLE32(size32, to: &lfh)
writeLE32(size32, to: &lfh)
writeLE16(UInt16(nameData.count), to: &lfh)
writeLE16(UInt16(lfhExtra.count), to: &lfh)
try fh.write(contentsOf: lfh)
try fh.write(contentsOf: nameData)
if !lfhExtra.isEmpty { try fh.write(contentsOf: lfhExtra) }

// Pass 2: stream the payload straight to the archive.
guard let input = InputStream(url: entry.url) else {
throw ZipError.corruptArchive("(zip) cannot reopen \(entry.name)")
}
input.open()
var buffer = [UInt8](repeating: 0, count: chunkSize)
var written: UInt64 = 0
while true {
let n = buffer.withUnsafeMutableBytes { raw -> Int in
guard let base = raw.baseAddress else { return 0 }
return input.read(base.assumingMemoryBound(to: UInt8.self), maxLength: chunkSize)
}
if n <= 0 { break }
try fh.write(contentsOf: Data(buffer[0..<n]))
written += UInt64(n)
}
input.close()
guard written == size else {
throw ZipError.corruptArchive("(zip) short write for \(entry.name)")
}

// Report archive progress on ~32 MiB boundaries so the progress bar
// keeps moving during the multi-GB packing phase instead of jumping.
bytesDone += written
if bytesDone - lastReportedBytes >= 32 * 1024 * 1024 {
lastReportedBytes = bytesDone
let frac = totalBytes > 0 ? Double(bytesDone) / Double(totalBytes) : 1.0
progress?(min(max(frac, 0.0), 1.0))
}

var cdExtra = Data()
if zip64 { appendZip64Extra(usize: size, csize: size, offset: offset, to: &cdExtra) }

var cd = Data()
writeLE32(cdSignature, to: &cd)
writeLE16(20, to: &cd) // version made by
writeLE16(zip64 ? 45 : 20, to: &cd) // version needed
writeLE16(0x0800, to: &cd)
writeLE16(0, to: &cd) // stored
writeLE16(0, to: &cd); writeLE16(0, to: &cd)
writeLE32(crc, to: &cd)
writeLE32(size32, to: &cd)
writeLE32(size32, to: &cd)
writeLE16(UInt16(nameData.count), to: &cd)
writeLE16(UInt16(cdExtra.count), to: &cd)
writeLE16(0, to: &cd) // comment length
writeLE16(0, to: &cd) // disk number
writeLE16(0, to: &cd) // internal attrs
writeLE32(0x81A40000, to: &cd) // external attrs (0644)
writeLE32(off32, to: &cd)
cd.append(nameData)
cd.append(cdExtra)
cdEntries.append(cd)
}

for dirName in dirs {
let nameData = dirName.data(using: .utf8) ?? Data()
guard nameData.count <= 0xffff else { continue }
let offset = UInt64(try fh.offset())
let zip64 = offset > 0xffff_ffff
let off32: UInt32 = zip64 ? 0xffff_ffff : UInt32(offset)

var lfhExtra = Data()
if zip64 { appendZip64Extra(usize: 0, csize: 0, offset: nil, to: &lfhExtra) }
var lfh = Data()
writeLE32(lfhSignature, to: &lfh)
writeLE16(zip64 ? 45 : 20, to: &lfh)
writeLE16(0x0800, to: &lfh)
writeLE16(0, to: &lfh)
writeLE16(0, to: &lfh); writeLE16(0, to: &lfh)
writeLE32(0, to: &lfh)
writeLE32(zip64 ? 0xffff_ffff : 0, to: &lfh)
writeLE32(zip64 ? 0xffff_ffff : 0, to: &lfh)
writeLE16(UInt16(nameData.count), to: &lfh)
writeLE16(UInt16(lfhExtra.count), to: &lfh)
try fh.write(contentsOf: lfh)
try fh.write(contentsOf: nameData)
if !lfhExtra.isEmpty { try fh.write(contentsOf: lfhExtra) }

var cdExtra = Data()
if zip64 { appendZip64Extra(usize: 0, csize: 0, offset: offset, to: &cdExtra) }
var cd = Data()
writeLE32(cdSignature, to: &cd)
writeLE16(20, to: &cd)
writeLE16(zip64 ? 45 : 20, to: &cd)
writeLE16(0x0800, to: &cd)
writeLE16(0, to: &cd)
writeLE16(0, to: &cd); writeLE16(0, to: &cd)
writeLE32(0, to: &cd)
writeLE32(zip64 ? 0xffff_ffff : 0, to: &cd)
writeLE32(zip64 ? 0xffff_ffff : 0, to: &cd)
writeLE16(UInt16(nameData.count), to: &cd)
writeLE16(UInt16(cdExtra.count), to: &cd)
writeLE16(0, to: &cd); writeLE16(0, to: &cd); writeLE16(0, to: &cd)
writeLE32(0x41ED0000, to: &cd)
writeLE32(off32, to: &cd)
cd.append(nameData)
cd.append(cdExtra)
cdEntries.append(cd)
}

let cdOffset = UInt64(try fh.offset())
var cdSize: UInt64 = 0
for cd in cdEntries {
try fh.write(contentsOf: cd)
cdSize += UInt64(cd.count)
}
let total = UInt64(cdEntries.count)
let useZip64 = cdOffset > 0xffff_ffff || cdSize > 0xffff_ffff || total > 0xffff

if useZip64 {
let z64Offset = UInt64(try fh.offset())
var z64 = Data()
writeLE32(zip64EOCDRecordSignature, to: &z64)
writeLE64(44, to: &z64)
writeLE16(45, to: &z64); writeLE16(45, to: &z64)
writeLE32(0, to: &z64); writeLE32(0, to: &z64)
writeLE64(total, to: &z64); writeLE64(total, to: &z64)
writeLE64(cdSize, to: &z64); writeLE64(cdOffset, to: &z64)
try fh.write(contentsOf: z64)

var loc = Data()
writeLE32(zip64EOCDLocatorSignature, to: &loc)
writeLE32(0, to: &loc)
writeLE64(z64Offset, to: &loc)
writeLE32(1, to: &loc)
try fh.write(contentsOf: loc)
}

var eocd = Data()
writeLE32(eocdSignature, to: &eocd)
writeLE16(0, to: &eocd); writeLE16(0, to: &eocd)
writeLE16(useZip64 ? 0xffff : UInt16(total), to: &eocd)
writeLE16(useZip64 ? 0xffff : UInt16(total), to: &eocd)
writeLE32(useZip64 ? 0xffff_ffff : UInt32(cdSize), to: &eocd)
writeLE32(useZip64 ? 0xffff_ffff : UInt32(cdOffset), to: &eocd)
writeLE16(0, to: &eocd)
try fh.write(contentsOf: eocd)

progress?(1.0)
mgr.logmsg("(zip) streaming done: \(cdEntries.count) entries, \(cdOffset) bytes of payload")
}
5 changes: 5 additions & 0 deletions lara/kexploit/TaskRop/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ struct vmobj vmgetobject(uint64_t map, uint64_t address);
struct vmshmem vmcreateshmemwithobj(struct vmobj *object);
struct vmshmem vmmapremotepage(uint64_t vmMap, uint64_t address);

/* Releases the one-page mapping AND the memory-entry Mach port created by
* vmcreateshmemwithobj(). Call this for every successful vmmapremotepage();
* otherwise each decrypted page leaks a port plus a kernel named entry. */
void vmreleaseshmem(struct vmshmem *shmem);

bool VM_PACKING_IS_BASE_RELATIVE(struct vmpackingparams *p);
uint64_t vmunpackptr(uint64_t packed, struct vmpackingparams *params);
uint64_t vmpackptr(uint64_t ptr, struct vmpackingparams *params);
Expand Down
26 changes: 26 additions & 0 deletions lara/kexploit/TaskRop/vm.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

#import <Foundation/Foundation.h>
#import <mach/mach.h>
#import "remotecall.h"
#import "vm.h"
#import "offsets.h"
Expand Down Expand Up @@ -173,6 +174,7 @@ struct vmshmem vmcreateshmemwithobj(struct vmobj *object) {
if (entry.vme_kernel_object || entry.is_sub_map) {
printf("(vm) entry cannot be a submap or kernel object\n");
mach_vm_deallocate(mach_task_self_, localaddr, roundedsize);
mach_port_deallocate(mach_task_self_, memobj);
return shmem;
}

Expand Down Expand Up @@ -200,6 +202,11 @@ struct vmshmem vmcreateshmemwithobj(struct vmobj *object) {
if (ret != KERN_SUCCESS) {
printf("(vm) mach_vm_map failed: %s\n", mach_error_string(ret));
mappedaddr = 0;
// Callers only release shmem when `used` is true, so a failed map must
// drop the memory-entry port here or it leaks on every bad page.
mach_vm_deallocate(mach_task_self_, localaddr, roundedsize);
mach_port_deallocate(mach_task_self_, memobj);
return shmem;
}

ret = mach_vm_deallocate(mach_task_self_, localaddr, roundedsize);
Expand All @@ -225,3 +232,22 @@ struct vmshmem vmmapremotepage(uint64_t vmmap, uint64_t address) {

return vmcreateshmemwithobj(&vmobject);
}

void vmreleaseshmem(struct vmshmem *shmem) {
if (!shmem) return;

// mach_vm_map() mapped exactly PAGE_SIZE bytes from the memory entry.
if (shmem->localAddress) {
mach_vm_deallocate(mach_task_self_, (mach_vm_address_t)shmem->localAddress, PAGE_SIZE);
shmem->localAddress = 0;
}

// vmcreateshmemwithobj() hands back the send right obtained from
// mach_make_memory_entry_64(); nobody was releasing it before.
if (shmem->port) {
mach_port_deallocate(mach_task_self_, (mach_port_t)shmem->port);
shmem->port = 0;
}

shmem->used = false;
}
Loading