text: //
  // Copyright © 2020 osy. All rights reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //     http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  
  // Parts taken from iSH: https://github.com/ish-app/ish/blob/master/app/AppGroup.m
  //  Created by Theodore Dubois on 2/28/20.
  //  Licensed under GNU General Public License 3.0
  
  #import <Foundation/Foundation.h>
  #include <dlfcn.h>
  #include <errno.h>
  #include <mach/mach.h>
  #include <mach-o/loader.h>
  #include <mach-o/getsect.h>
  #include <pthread.h>
  #include <stdio.h>
  #include <string.h>
  #include <sys/mman.h>
  #include <sys/sysctl.h>
  #include <TargetConditionals.h>
  #include <unistd.h>
  #include "UTMJailbreak.h"
  
  struct cs_blob_index {
      uint32_t type;
      uint32_t offset;
  };
  
  struct cs_superblob {
      uint32_t magic;
      uint32_t length;
      uint32_t count;
      struct cs_blob_index index[];
  };
  
  struct cs_entitlements {
      uint32_t magic;
      uint32_t length;
      char entitlements[];
  };
  
  extern int csops(pid_t pid, unsigned int ops, void * useraddr, size_t usersize);
  extern boolean_t exc_server(mach_msg_header_t *, mach_msg_header_t *);
  extern int ptrace(int request, pid_t pid, caddr_t addr, int data);
  
  #define    CS_OPS_STATUS        0    /* return status */
  #define CS_DEBUGGED 0x10000000  /* process is currently or has previously been debugged and allowed to run with invalid pages */
  #define PT_TRACE_ME     0       /* child declares it's being traced */
  #define PT_SIGEXC       12      /* signals as exceptions for current_proc */
  
  kern_return_t catch_exception_raise(mach_port_t exception_port,
                                      mach_port_t thread,
                                      mach_port_t task,
                                      exception_type_t exception,
                                      exception_data_t code,
                                      mach_msg_type_number_t code_count) {
      fprintf(stderr, "Caught exception %d (this should be EXC_SOFTWARE), with code 0x%x (this should be EXC_SOFT_SIGNAL) and subcode %d. Forcing suicide.", exception, *code, code[1]);
      // _exit doesn't seem to work, but this does. ¯\_(ツ)_/¯
      return KERN_FAILURE;
  }
  
  static void *exception_handler(void *argument) {
      mach_port_t port = *(mach_port_t *)argument;
      mach_msg_server(exc_server, 2048, port, 0);
      return NULL;
  }
  
  static bool am_i_being_debugged() {
      int flags;
      return !csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) && flags & CS_DEBUGGED;
  }
  
  static NSDictionary *parse_entitlements(const void *entitlements, size_t length) {
      char *copy = malloc(length);
      memcpy(copy, entitlements, length);
      
      // strip out psychic paper entitlement hiding
      if (@available(iOS 13.5, *)) {
      } else {
          static const char *needle = "<!---><!-->";
          char *found = strnstr(copy, needle, length);
          if (found) {
              memset(found, ' ', strlen(needle));
          }
      }
      NSData *data = [NSData dataWithBytes:copy length:length];
      free(copy);
      
      return [NSPropertyListSerialization propertyListWithData:data
                                                       options:NSPropertyListImmutable
                                                        format:nil
                                                         error:nil];
  }
  
  static NSDictionary *app_entitlements(void) {
      // Inspired by codesign.c in Darwin sources for Security.framework
      
      // Find our mach-o header
      Dl_info dl_info;
      if (dladdr(app_entitlements, &dl_info) == 0)
          return nil;
      if (dl_info.dli_fbase == NULL)
          return nil;
      char *base = dl_info.dli_fbase;
      struct mach_header_64 *header = dl_info.dli_fbase;
      if (header->magic != MH_MAGIC_64)
          return nil;
      
      // Simulator executables have fake entitlements in the code signature. The real entitlements can be found in an __entitlements section.
      size_t entitlements_size;
      uint8_t *entitlements_data = getsectiondata(header, "__TEXT", "__entitlements", &entitlements_size);
      if (entitlements_data != NULL) {
          NSData *data = [NSData dataWithBytesNoCopy:entitlements_data
                                              length:entitlements_size
                                        freeWhenDone:NO];
          return [NSPropertyListSerialization propertyListWithData:data
                                                           options:NSPropertyListImmutable
                                                            format:nil
                                                             error:nil];
      }
      
      // Find the LC_CODE_SIGNATURE
      struct load_command *lc = (void *) (base + sizeof(*header));
      struct linkedit_data_command *cs_lc = NULL;
      for (uint32_t i = 0; i < header->ncmds; i++) {
          if (lc->cmd == LC_CODE_SIGNATURE) {
              cs_lc = (void *) lc;
              break;
          }
          lc = (void *) ((char *) lc + lc->cmdsize);
      }
      if (cs_lc == NULL)
          return nil;
  
      // Read the code signature off disk, as it's apparently not loaded into memory
      NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:NSBundle.mainBundle.executableURL error:nil];
      if (fileHandle == nil)
          return nil;
      [fileHandle seekToFileOffset:cs_lc->dataoff];
      NSData *csData = [fileHandle readDataOfLength:cs_lc->datasize];
      [fileHandle closeFile];
      const struct cs_superblob *cs = csData.bytes;
      if (ntohl(cs->magic) != 0xfade0cc0)
          return nil;
      
      // Find the entitlements in the code signature
      for (uint32_t i = 0; i < ntohl(cs->count); i++) {
          struct cs_entitlements *ents = (void *) ((char *) cs + ntohl(cs->index[i].offset));
          if (ntohl(ents->magic) == 0xfade7171) {
              return parse_entitlements(ents->entitlements, ntohl(ents->length) - offsetof(struct cs_entitlements, entitlements));
          }
      }
      return nil;
  }
  
  bool jb_has_jit_entitlement(void) {
      static NSDictionary *entitlements = nil;
      if (!entitlements) {
          entitlements = app_entitlements();
      }
      return [entitlements[@"dynamic-codesigning"] boolValue];
  }
  
  bool jb_enable_ptrace_hack(void) {
  #if defined(NO_PTRACE_HACK)
      return false;
  #else
      bool debugged = am_i_being_debugged();
      
      // Thanks to this comment: https://news.ycombinator.com/item?id=18431524
      // We use this hack to allow mmap with PROT_EXEC (which usually requires the
      // dynamic-codesigning entitlement) by tricking the process into thinking
      // that Xcode is debugging it. We abuse the fact that JIT is needed to
      // debug the process.
      if (ptrace(PT_TRACE_ME, 0, NULL, 0) < 0) {
          return false;
      }
      
      // ptracing ourselves confuses the kernel and will cause bad things to
      // happen to the system (hangs…) if an exception or signal occurs. Setup
      // some "safety nets" so we can cause the process to exit in a somewhat sane
      // state. We only need to do this if the debugger isn't attached. (It'll do
      // this itself, and if we do it we'll interfere with its normal operation
      // anyways.)
      if (!debugged) {
          // First, ensure that signals are delivered as Mach software exceptions…
          ptrace(PT_SIGEXC, 0, NULL, 0);
          
          // …then ensure that this exception goes through our exception handler.
          // I think it's OK to just watch for EXC_SOFTWARE because the other
          // exceptions (e.g. EXC_BAD_ACCESS, EXC_BAD_INSTRUCTION, and friends)
          // will end up being delivered as signals anyways, and we can get them
          // once they're resent as a software exception.
          mach_port_t port = MACH_PORT_NULL;
          mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
          mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
          task_set_exception_ports(mach_task_self(), EXC_MASK_SOFTWARE, port, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
          pthread_t thread;
          pthread_create(&thread, NULL, exception_handler, (void *)&port);
      }
      
      return true;
  #endif
  }
  