====== IPA Patch-o-matic (InspectorGadget) ====== This document will explain how to systematically scriptable build IOS apps. For the document, I will start with a virtual server running Centos 7.x - with a "minimal" server template. (Almost nothing is installed). This is a two step install. The first stage sets some basic security parameters (like disabling selinux enforcing)... which is required in later stages... and creates a build user - so we don't install all of this crap as root. After this stage the server is rebooted, if selinux was running. (The reboot is skipped if selinux is not running). The second stage does the main build under our newly created user, using sudo where required. This base server requirement is Centos 7 minimal operating system. The details are enclosed below - but essentially, just run the first few commands here on a clean install and it will be ready to go. ===== Setup Process - Stage 1 ===== Login as root to your newly created Centos 7 minimal server build, and execute the following command. This will pull the stage 1 script listed below and execute it. bash <(curl -s https://dji.retroroms.info/_export/code/og/ipabuild/start?codeblock=0) ===== Setup Process - Stage 2 ===== If SeLinux is installed, the server will reboot. Login as the build user when it comes back and continue with this command. If selinux is not installed, the first script will automatically SU to this user. bash <(curl -s https://dji.retroroms.info/_export/code/og/ipabuild/start?codeblock=1) ===== Setup Scripts ===== ==== Bootstrap ==== #!/bin/bash # # Setup our build user and install sudo etc # USER=og adduser ${USER} echo ${USER}:redherring | chpasswd yum -y install epel-release yum -y install sudo banner echo "${USER} ALL = NOPASSWD: ALL" > /etc/sudoers.d/build if [ -e /etc/sysconfig/selinux ]; then sed -i "s/enforcing$/permissive/g" /etc/sysconfig/selinux echo "Run the next step as username: ${USER}" banner "Rebooting" reboot else banner "Continue as ${USER}" #su -c "bash <(curl -s https://dji.retroroms.info/_export/code/og/ipabuild/start?codeblock=1)" -s /bin/bash ${USER} su - ${USER} fi ==== Main setup ==== if [ "`whoami`" = "root" ]; then echo Run this as the created user, not root exit 1 fi # # Get some basic RPM's aboard # banner "yum update" sudo yum -y update banner "yum install required packages" sudo yum -y install python-pip git ruby gem ruby-devel libimobiledevice libimobiledevice-utils gcc-c++ \ make patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel \ bzip2 autoconf automake libtool bison iconv-devel sqlite-devel which zip unzip openssl file sudo pip freeze > /tmp/freeze0 banner "Upgrade pip" sudo pip install --upgrade pip sudo pip freeze > /tmp/freeze1 if [ 1 -eq 0 ]; then banner "Install construct" # The latest construct that is known to work with iSign won't install with pip - We need to get the URL and fetch manually CONVERSION=2.5.5 CONURL=`wget -q -O- https://pypi.org/simple/construct/ | sed -e "s/-reupload.tar.gz /tmp/freeze2 banner "Install isign" git clone https://github.com/apperian/isign.git cd isign /usr/bin/perl -pi -e "if (/pyOpenSSL/) { s/=.*[0-9]/==`pip freeze | grep ^pyOpenSSL | cut -d "=" -f 3`/ }" setup.py /usr/bin/perl -pi -e "if (/construct/) { s/=.*[0-9]/==`pip freeze | grep ^construct | cut -d "=" -f 3`/ }" setup.py /usr/bin/perl -pi -e "if (/ak-construct/) { s/=.*[0-9]/==`pip freeze | grep ^ak-construct | cut -d "=" -f 3`/ }" setup.py sed -i "s/apt-get/echo apt-get/" INSTALL.sh if [ ! -e ~/.isign ]; then mkdir ~/.isign; fi #sudo ./INSTALL.sh read more sudo rm -rf build dist isign.egg-info cd fi # Install newer non-standard GCC package required for insert_dylib banner "install centos-release-scl" sudo yum -y install centos-release-scl banner "install devtoolset-4-gcc" sudo yum -y install devtoolset-4-gcc* banner "install insert_dylib" git clone https://github.com/LeanVel/insert_dylib cd ~/insert_dylib scl enable devtoolset-4 "bash -c 'gcc -I ./insert_dylib/include/ -o ./insert_dylib/insert_dylib ./insert_dylib/main.c'" sudo mv ~/insert_dylib/insert_dylib/insert_dylib /usr/local/bin/ rm -rf ~/insert_dylib # # Install a newer version of Ruby (RBENV method) # See https://www.digitalocean.com/community/tutorials/how-to-install-ruby-on-rails-with-rbenv-on-centos-7 # banner install rbenv cd git clone git://github.com/sstephenson/rbenv.git .rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile echo 'eval "$(rbenv init -)"' >> ~/.bash_profile export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" banner install ruby-build git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH" banner "install ruby" # # Install a newer ruby version and set it as our global version for now # # Prevent generation of local documentation for each gem installed (It's slow!) echo "gem: --no-document" > ~/.gemrc VERSION=`curl -s https://www.ruby-lang.org/en/downloads/ | grep pub | grep -v rc | sed -e "s/.tar.gz.*//" -e "s/.*ruby-//" | grep ^[0-9] | sort -nr | head -1` rbenv install -v $VERSION rbenv global $VERSION banner install fastlane gem install fastlane banner install pry gem install pry banner install son gem install json banner install genProvisioningProfile.rb wget -O genProvisioningProfile.rb https://dji.retroroms.info/_export/code/og/ipabuild/start?codeblock=2 sed -i "s/sensepost/`whoami`/" genProvisioningProfile.rb # TODO: Add wget here for our custom iinject.sh file #cd ~ #git clone https://github.com/LeanVel/iInject #sed -i "s/^checkProvisioning$/checkProvisioning/" iInject/iInject.sh #sed -i "s/#Installing/cd \"\$currPath\"\ncleanup\nexit 0\n#Installing/" iInject/iInject.sh #curl https://build.frida.re/frida/ios/lib/FridaGadget.dylib --output iInject/FridaGadget.dylib ## ## Install rails ## # #VERSION=`curl -s http://railsapps.github.io/rails-release-history.html | grep "was released" | head -1 | sed -e "s/ was.*//" -e "s/.* //"` #gem install rails -v $VERSION # # # ## ## Import apple cert ## # #cd /usr/share/pki/ca-trust-source/anchors #sudo wget https://raw.githubusercontent.com/saucelabs/isign/master/isign/apple_credentials/applecerts.pem #sudo update-ca-trust # ## ## Install MySQL ## # #sudo yum -y install mariadb mariadb-server #sudo systemctl enable mariadb.service #sudo systemctl start mariadb.service ## mysql_secure_installation # Recommend to do this as well - but commented out now for scripted setup # ## ## Install MySQL gem file ## # #sudo yum -y install mysql-devel #gem install mysql2 # ## ## Build MySQL-udf-http ## # #sudo yum -y install libcurl-devel #cd #git clone https://github.com/y-ken/mysql-udf-http.git #cd mysql-udf-http #chmod 700 configure #./configure --libdir=/usr/lib64/mysql/plugin/ --with-mysql=/bin/mysql_config #make #sudo make install #sudo sh -c 'echo /usr/lib64/mysql/plugin/ > /etc/ld.so.conf.d/mysql.conf' #sudo /sbin/ldconfig # #echo create function http_get returns string soname \'mysql-udf-http.so\'\; | mysql #echo create function http_post returns string soname \'mysql-udf-http.so\'\; | mysql #echo create function http_put returns string soname \'mysql-udf-http.so\'\; | mysql #echo create function http_delete returns string soname \'mysql-udf-http.so\'\; | mysql # ## ## Install a web server ## # #sudo yum -y install lighttpd #sudo /usr/bin/perl -pi -e "if (/server.use-ipv6/) { s/enable/disable/ }" /etc/lighttpd/lighttpd.conf # ## ## Install PHP ## ## #sudo yum -y install install php-fpm lighttpd-fastcgi #sudo /usr/bin/perl -pi -e "if (/^user|^group/) { s/apache/lighttpd/ }" /etc/php-fpm.d/www.conf # ## ## Start web server and php-fpm ## # #sudo systemctl enable php-fpm.service #sudo systemctl start php-fpm.service #sudo systemctl enable lighttpd.service #sudo systemctl start lighttpd.service # ## ## Install InspectorGadget ## # ##cd /var/www/lighttpd # ## TO BE CONTINUED # # ## ## Install rails code ## #sudo yum -y install nodejs # #cd ~ #mkdir rails #cd rails # #rails new gadget #cd gadget # #### ADD RAILS CODE HERE ### # #rails server & # # # Cleanup # #sudo rm -f /etc/sudoers.d/build exit ===== genProvisioningProfile.rb ===== Some DRAFT code to handle all of the interaction with Apple's API #!/usr/local/rvm/rubies/ruby-2.4.2/bin/ruby #:set paste require "spaceship" if ARGV.length < 4 print "Usage : ./genProvisionProfile.rb \n" exit(1) else user = ARGV[0] pass = ARGV[1] iDevice = ARGV[2] iName = ARGV[3] end #TODO: Validate arguments. #Login to developer portals Spaceship::Portal.login(user, pass) #TODO: Check if there is currently install private key has a valid certificate. #Create new key pair for this project csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request #Create Directory FileUtils.mkdir_p "#{Dir.home}/.isign" #Save private key if (File.exists? "#{Dir.home}/.isign/key.pem") print "Removing exisitng private key\n" File.delete("#{Dir.home}/.isign/key.pem") end print "Writing new private key\n" File.write("#{Dir.home}/.isign/key.pem", pkey) #TODO: If maximum amount of certificates reached revoke the last one... certs = Spaceship::Portal.certificate.development.all if (certs.length == 1) certs.first.revoke! end #Create certificate Spaceship::Portal.certificate.development.create!(csr: csr) #Get newest certifiate cert = Spaceship::Portal.certificate.development.all.first #Save certificate if (File.exists? "#{Dir.home}/.isign/certificate.pem") print "Removing exisitng certificate\n" File.delete("#{Dir.home}/.isign/certificate.pem") end print "Writing new certificate\n" File.write("#{Dir.home}/.isign/certificate.pem", cert.download) #Check if com.sensepost.djigo4 is already registered app = Spaceship::Portal.app.find("com.sensepost.djigo4") if (app.nil?) # Create a new app Spaceship::Portal.app.create!(bundle_id: "com.sensepost.djigo4", name: "XC com sensepost djigo4") end print "Adding UDID: #{iDevice} Name: '#{iName}'\n" #Register new device #TODO: Check when maximum device limitation reached. Spaceship::Portal.device.create!(name: iName, udid: iDevice) print "Deleting old provisioning profile\n" old_profile = Spaceship::Portal.provisioning_profile.development.all.first old_profile.delete! print "Creating a new provisioning profile will all UDIDs\n" # Create a new provisioning profile with all devices (by default) profile = Spaceship::Portal.provisioning_profile.development.create!(bundle_id: "com.sensepost.djigo4", certificate: cert, name: "NLD Users") print "Repairing provisioning profiles and certificates\n" # Select all 'Invalid' or 'Expired' provisioning profiles broken_profiles = Spaceship::Portal.provisioning_profile.all.find_all do |profile| # the below could be replaced with `!profile.valid? || !profile.certificate_valid?`, which takes longer but also verifies the code signing identity (profile.status == "Invalid") end # Iterate over all broken profiles and repair them broken_profiles.each do |profile| profile.repair! # yes, that's all you need to repair a profile end print "Downloading new provisioning profile\n" # Get all Development profiles profiles_dev = Spaceship::Portal.provisioning_profile.development.all first_profile = profiles_dev.first File.write("#{Dir.home}/.isign/isign.mobileprovision", first_profile.download) #!/bin/bash #TODO # - Add *proper* support for online/offline dylib provision # - Add support for optional vervosity NORMAL=$(tput sgr0) RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3) #Switches #TODO: use proper options to change the swithces from the command line removePlugIns=false useLocalCopy=true cleanUpEnabled=true logEnabled=true workDirectory=/tmp/iInject #Clean up function definition cleanUp () { if [ "$cleanUpEnabled" = true ] then printf "${NORMAL}%s${NORMAL}\n" "Cleaning up work directory "$workDirectory" " echo "rm -rf "$workDirectory"" rm -rf "$workDirectory" fi } #Main script start #Verify arguments if [ $# -lt "1" ] then printf "${RED}%s${NORMAL}\n" "Usage: "$0" " exit 1 fi ipaFile="$1" if [ $# -eq "1" ] useLocalCopy=false dylibFile="" else dylibFile="$2" fi dylibName=$(basename "$dylibFile") ipaFilename=$(basename -s .ipa "$ipaFile") ipaDirname=$(dirname "$ipaFile") #Set up Logging if [ "$logEnabled" = true ] then debugDir=$(pwd)/"iInject.log" printf "${GREEN}%s${NORMAL}\n" "Log is going to be saved in ""$debugDir" else debugDir="/dev/null" fi #Start log echo `date`" Embedding started" >> "$debugDir" 2>&1 #Verify that provisioning is installed #checkProvisioning #Making work directory mkdir "$workDirectory" >> "$debugDir" 2>&1 #Uncompressing IPA file printf "${NORMAL}%s${NORMAL}\n" "Uncompressing ""$ipaFilename"" in ""$workDirectory"" " unzip $ipaFile -d "$workDirectory"/"$ipaFilename" >> "$debugDir" 2>&1 if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" "File ""$ipaFilename"" uncompressed correctly in "$workDirectory" " else printf "${RED}%s${NORMAL}\n" "Error while uncompressing "$ipaFilename" in "$workDirectory" " cleanUp exit 1 fi workDirectory="$workDirectory"/"$ipaFilename" if [ "$removePlugIns" = true ] then #Checking for PlugIns directory printf "${NORMAL}%s${NORMAL}\n" "Checking for PlugIns directory" if [ -d "$workDirectory"/Payload/*/PlugIns ] then printf "${NORMAL}%s${NORMAL}\n" "PlugIns directory found, it will be deleted" echo "rm -rf "$workDirectory"/Payload/*/PlugIns" rm -rf "$workDirectory"/Payload/*/PlugIns if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" " "$workDirectory"/Payload/*/PlugIns deleted sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while deleting "$workDirectory"/Payload/*/PlugIns" cleanUp exit 1 fi fi fi #Getting Binary to be patched binaryName=`file "$workDirectory"/Payload/*/* | grep -i mach | cut -d ":" -f1 | grep -vi dylib` numberOfBinaries=`echo "$binaryName" | tr -s "\n" "|" | awk -F'|' '{print NF-1}'` if [ $numberOfBinaries -gt 1 ] then printf "${RED}%s${NORMAL}\n" "To many binaries files in the directory "$workDirectory"/Payload/*/*" echo "$binaryName" cleanUp exit 1 fi #Patch Binary printf "${NORMAL}%s${NORMAL}\n" "Patching Binary "$binaryName" " insert_dylib --strip-codesig --inplace "@executable_path/Frameworks/FridaGadget.dylib" "$binaryName" >> "$debugDir" 2>&1 printf "Binary name: "$binaryName" " if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" "Binary "$binaryName" patched sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while patching binary "$binaryName"" cleanUp exit 1 fi # Gadget obtention binaryDirectory=$(dirname "$binaryName") if [ "$useLocalCopy" = false ] then #Download Fridagadget in the right directory printf "${NORMAL}%s${NORMAL}\n" "Downloading Fridagadget in $binaryDirectory/Frameworks/ " curl https://build.frida.re/frida/ios/lib/FridaGadget.dylib --output "$binaryDirectory"/Frameworks/FridaGadget.dylib if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" " Gadget downloaded sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while downloading Gadget" cleanUp exit 1 fi else # Use local copy of the Gadget printf "${NORMAL}%s${NORMAL}\n" "Coping local gadget $dylibFile to $binaryDirectory/Frameworks/ " cp "FridaGadget.config" "$binaryDirectory/Frameworks"/ >> "$debugDir" 2>&1 printf "${NORMAL}%s${NORMAL}\n" "Coping local gadget config file to $binaryDirectory/ " cp "$dylibFile" "$binaryDirectory/Frameworks"/ >> "$debugDir" 2>&1 if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" "Gadget copied sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while coping Gadget" cleanUp exit 1 fi fi #Adjusting direcorties before ziping currPath=`pwd` cd "$workDirectory" #Creating new IPA printf "${NORMAL}%s${NORMAL}\n" "Creating new IPA file in "$workDirectory"/"$ipaFilename"-patched.ipa" zip -r "$ipaFilename"-patched.ipa Payload/ >> "$debugDir" 2>&1 if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" ""$workDirectory"/"$ipaFilename"-patched.ipa created sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while creating "$workDirectory"/"$ipaFilename"-patched.ipa" cleanUp exit 1 fi #Signing new IPA printf "${NORMAL}%s${NORMAL}\n" "Signing IPA file "$workDirectory"/"$ipaFilename"-patched.ipa" isign -v -o "$ipaFilename"-patched-isigned.ipa "$ipaFilename"-patched.ipa >> "$debugDir" 2>&1 if [ "$?" -eq "0" ] then printf "${GREEN}%s${NORMAL}\n" ""$workDirectory"/"$ipaFilename"-patched-isigned.ipa created sucessfully" else printf "${RED}%s${NORMAL}\n" "Error while signing "$workDirectory"/"$ipaFilename"-patched.ipa" cleanUp exit 1 fi cp "$ipaFilename-patched-isigned.ipa" "$currPath" cd "$currPath" cleanUp exit 0 ===== Draft rails code ===== NOTE: the rails code needs to be loaded up - thats not included above. Work so far.. rails generate controller content class ContentController < ApplicationController def home require 'openssl' rsa_key = OpenSSL::PKey::RSA.new(2048) private_key = rsa_key.public_key.export @greeting = private_key render plain: private_key end end <%= @greeting %> Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root to: 'content#home' end Links that will help out... * http://iridakos.com/tutorials/2013/11/24/saying-hello-world-with-ruby-on-rails.html * https://launchschool.com/books/demystifying_rails/read/routes_and_resources ===== Workflow ===== ==== 1. Generate a private key ===== if [ ! -e ~/.isign/key.pem ]; then openssl genrsa -out ~/.isign/key.pem 2048 ; fi ===== Manual Process ===== To make this all work, you will need an apple developer account. This is possible without a paid account - but we require this for our purposes to allow longer lasting applications. The actual signing of an application requires three files using isign. ^certificate.pem|Your signing certificate| ^key.pem|Your apple account private key| ^isign.mobileprovision|An apple mobile provision file| So. How do we get these three files. The instructions below assume you have already installed tools required. I found many of the details below from [[http://rickluna.com/wp/2016/08/create-mobile-provisions-and-p12-files-without-a-mac-for-cloud-build-services/|here]] ==== 1. Install Intermediate Certificates ==== To do all of this, you will need to install intermediate certificates. The links below contain useful data. * [[https://developer.apple.com/certificationauthority/AppleWWDRCA.cer|Worldwide Developer Relations Certificate Authority]] * [[https://raw.githubusercontent.com/saucelabs/isign/master/isign/apple_credentials/applecerts.pem]] sudo curl -s https://raw.githubusercontent.com/saucelabs/isign/master/isign/apple_credentials/applecerts.pem > /usr/share/pki/ca-trust-source/anchors/applecerts.pem sudo update-ca-certificates ==== 2. Create an AppID ==== - [[https://developer.apple.com/account/ios/identifier/bundle/create|Login to the Apple Developer Portal]] - Click the “+” icon * Name: InspectorGadget * Explicit App ID: "com.inspectorgadget.djigo4" - Click Continue - Click Register - Click Done * FIXME: Need to work out how this can be scripted. ==== 3. Generate a private key ==== if [ ! -e ~/.isign ]; then mkdir ~/.isign; fi if [ ! -e ~/.isign/key.pem ]; then openssl genrsa -out ~/.isign/key.pem 2048 ; fi ==== 4. Create a Certificate Signing Request ==== openssl req -new -key ~/.isign/key.pem -out ~/.isign/certificate.csr -subj "/emailAddress=inspectorgadget@example.com, CN=InspectorGadget Dev, C=US" ==== 5. Generate Your Certificate ==== - [[https://developer.apple.com/account/ios/certificate/|Login to the Apple Developer Portal]] - Click the “+” icon - Select "iOS App Development" Certificate Type, and click Continue - Click Continue - Upload the file you created above. - Download your certificate - Make backup copies of your private and public keys in a safe location (not in Github!) * FIXME: Need to work out how this can be scripted. * FIXME: Need to confirm this is the correct certificate type ==== 6. Create a device ==== - [[https://developer.apple.com/account/ios/device/|Log into the Apple Developer Portal]] - Click the “+” icon - Enter your DeviceName and UDID - Click Continue ==== 7. Generate your provisioning profile ==== - [[https://developer.apple.com/account/ios/profile/landing|Log into the Apple Developer Portal]] - Click the “+” icon - Select "iOS App Development" Profile Type, and click Continue - Select the AppID you created previously, and click Continue - Select the certificate you created previously, and click Continue - Select at least one device - Enter a Profile Name "isign" and click "Continue"