User Tools

Site Tools

Translations of this page:

howto:fridahooklibrary

Frida Hook Library

The details below is a collection of Frida hooks, which we can use for our aircraft. New hooks will be added progressively. There are some tasks that need to be thought about so that we can make this scalable:

Alternate Config/Parameters Data Structure

We need to build a data structure, and some methods for accessing and updating this data that will be used by all of the other methods. Why? As a longer term goal, it would be good to publish our own NIB files for IOS and equivalent for Android that will allow update of this data.

Hooking Standard Methods

Our work at the moment is focussed on IOS. As a general rule of thumb, we should where possible use the “swizzle” method of hooking. In the near future, this type of hook method will be able to be run on a modified app launched from Springboard. This is pending a patch from the author, but we know that this is a requirement. Not sure yet what standards if any are required for Android to ensure any protection is not tripped up.

Configuration

The config below can be used for stand-alone hooks, allowing you to open DJI GO 4 from springboard.

FridaGadget.config
{
  "interaction": {
    "type": "script",
    "path": "Tweak.js",
    "on_change": "reload"
  },
  "code_signing": "required"
}

Template hook

We should look at creating a few template hooks. So far, we have a simple bool replacement template that we have used in three cases successfully. But, what about other more complex requirements. What if we need to read parameters etc? The author of Frida has advised:

oleavr: method arguments follow self and sel (the first two implicit arguments). you can use var self = new ObjC.Object(handle); then you can access the instance variables of self through self.$ivars depending on whether you want to access method arguments or instance variables (or other things on the instance)

We'll dig into this more later when/if we need to access parameter data etc.

PrettyWoman

As part of this work, I looked at how to “standardise” our hooks, since they all appeared to be based on a couple of patterns. Below, you will find source and a CSV file for “PrettyWoman”. This app name was picked by Jezzab because “PrettyWoman - She was a hooker”.

Anyway. Naming conventions aside. How does it work? There is a shell script that parses a CSV file that contains configuration information for a list of hooks that will be created in the output.

PrettyWoman.sh

PrettyWoman.sh
#!/bin/bash
 
function iosheader {
cat <<EOF
//
// DJI GO 4 - Frida Tweaks
//
 
if (ObjC.available) {
 
EOF
}
 
function iosfooter {
cat <<EOF
}
EOF
}
 
function functionheader {
cat <<EOF
 
  //
  // ${1}
  //
 
EOF
}
function jsobject {
 
cat <<EOF
  var ${1} = ObjC.classes.${1};
  var ${3} = ${1}['${2} ${3}:'];
  var ${3}Impl = ${3}.implementation;
  ${3}.implementation = ObjC.implement(${3}, function (handle, selector, originalResult) {
    ${3}Impl(handle, selector, ${4});
    if ( originalResult != ${4} ) {
      if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) {
        console.log("${5}");
        ${CLASS}${METHOD}SeenReplace = 1;
      }
    } else {
      if ( ${7} == 0 || ${CLASS}${METHOD}SeenHit == 0 ) {
        console.log("${6}");
        ${CLASS}${METHOD}SeenHit = 1;
      }
    }
  });
EOF
}
 
function jsfunction {
cat <<EOF
  var ${1} = ObjC.classes.${1};
  var ${3} = ${1}['${2} ${3}'];
  var ${3}Impl = ${3}.implementation;
  ${3}.implementation = ObjC.implement(${3}, function (handle, selector) {
    var originalResult = ${3}Impl(handle, selector);
    if ( originalResult != ${4} ) {
      if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) {
        console.log("${5}");
        ${CLASS}${METHOD}SeenReplace = 1;
      }
    } else {
      if ( ${7} == 0 || ${CLASS}${METHOD}SeenHit == 0 ) {
        console.log("${6}");
        ${CLASS}${METHOD}SeenHit = 1;
      }
    }
    return ${4};
  });
EOF
}
 
 
function jsfunctioniffcc {
cat <<EOF
  var ${1} = ObjC.classes.${1};
  var ${3} = ${1}['${2} ${3}'];
  var ${3}Impl = ${3}.implementation;
  ${3}.implementation = ObjC.implement(${3}, function (handle, selector) {
    var originalResult = ${3}Impl(handle, selector);
    if ( originalResult != ${4} ) {
      if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) {
        console.log("${5}");
        ${CLASS}${METHOD}SeenReplace = 1;
      }
    }
 
    if( DJIAppSettingssdr_force_fccSeenReplace == 0 ) {
      return ${4}; //mavic
    } else {
      return originalResult;
    }
  });
EOF
}
 
 
function defines {
cat <<EOF
//
// Create variables that we can use for "one shot" rules
//
EOF
 
while read X
do
        #TEMPLATE=`echo $X | cut -d "," -f 1`
        CLASS=`echo $X | cut -d "," -f 2`
        METHOD=`echo $X | cut -d "," -f 3`
	PLUSMINUS=`echo $X | cut -d "," -f 4`
        #REPLACE=`echo $X | cut -d "," -f 5`
        #LOGHIT=`echo $X | cut -d "," -f 6`
        #LOGCHANGED=`echo $X | cut -d "," -f 7`
	#ONESHOT=`echo $X | cut -d "," -f 8`
 
	echo "${CLASS}${METHOD}SeenHit=0;"
	echo "${CLASS}${METHOD}SeenReplace=0;"
 
done < PrettyWoman.csv
}
 
function mainloop {
while read X
do
        TEMPLATE=`echo $X | cut -d "," -f 1`
        CLASS=`echo $X | cut -d "," -f 2`
        METHOD=`echo $X | cut -d "," -f 3`
	PLUSMINUS=`echo $X | cut -d "," -f 4`
        REPLACE=`echo $X | cut -d "," -f 5`
        LOGHIT=`echo $X | cut -d "," -f 6`
        LOGCHANGED=`echo $X | cut -d "," -f 7`
	ONESHOT=`echo $X | cut -d "," -f 8`
 
        functionheader "${CLASS} ${METHOD} ${TEMPLATE}"
        ${TEMPLATE} ${CLASS} ${PLUSMINUS} ${METHOD} ${REPLACE} "${LOGHIT}" "${LOGCHANGED}" "0${ONESHOT}"
done < PrettyWoman.csv
}
 
iosheader
defines
mainloop
iosfooter

PrettyWoman.csv

Below is a CSV config file for PrettyWoman.sh. This file has the following fields:

TemplateThe name of a template, as listed in PrettyWoman.sh
ClassA class name from the DJI Application source code
MethodThe name of a method or function within the class
PlusMinusA plus or a minus sign (as defined in the DJI reversed source code)
OurValueWhat should we replace the original function return value with?
LogMessageTweakedWhat to display in debug when we successfully bypass DJI functions etc
LogSeenWhat to display in debug when we are called, but we don't change anything
OneShotSet this to 1 if you want to see a log message for this once only
PrettyWoman.csv
jsobject,AFSecurityPolicy,setSSLPinningMode,-,0,[*] SSL Pinning BYPASSED, [*] SSL Pinning not used this time,0
jsfunction,DJIAccountManager,checkIsAdminUser,-,1,[*] Flight Records admin user ENABLED,[*] Flight Records admin user already enabled,0
jsfunction,DJIAppSettings,canUseIllegalChannels,-,1,[*] Illegal Channels config (32) ENABLED,[*] Illegal Channels config (32) already enabled,0
jsfunction,DJIRadioLogic,canUseIllegalChannels,-,1,[*] Illegal Channels SDR (32) ENABLED,[*] Illegal Channels SDR(32) already enabled,0
jsfunction,DJIAppSettings,sdr_force_fcc,-,1,[*] Forced FCC Mode ACTIVATED,[*] Forced FCC mode already active,0
jsfunction,DJITermsNotificationController,shouldShowTerms,-,0,[*] Terms and conditions BYPASSED,[*] Terms and conditions already accepted,0
jsfunction,DJIAppForceUpdateManager,hasChecked,-,1,[*] Upgrade Check: DISABLED,[*] Upgrade Check: Already checked,0
jsfunction,DJIUpgradeNotifyViewModel,notifyHidden,-,1,[*] Upgrade Notification DISABLLED,[*] Upgrade Notification already disabled,0
jsfunctioniffcc,DJIProductManager,currentProductCode,+,13,[*] Changed product code to MAVIC,,1
jsfunction,DJILImitDBUpdateLogic,needUpdateType,-,0,[*] Removing NFZ DB Update Message,,0

Tweak.js

This code was generated by PrettyWoman.sh - using the CSV file above. Feel free to just use this set of hooks for your tweaking pleasure.

Tweak.js
//
// DJI GO 4 - Frida Tweaks
//
 
if (ObjC.available) {
 
//
// Create variables that we can use for "one shot" rules
//
AFSecurityPolicysetSSLPinningModeSeenHit=0;
AFSecurityPolicysetSSLPinningModeSeenReplace=0;
DJIAccountManagercheckIsAdminUserSeenHit=0;
DJIAccountManagercheckIsAdminUserSeenReplace=0;
DJIAppSettingscanUseIllegalChannelsSeenHit=0;
DJIAppSettingscanUseIllegalChannelsSeenReplace=0;
DJIRadioLogiccanUseIllegalChannelsSeenHit=0;
DJIRadioLogiccanUseIllegalChannelsSeenReplace=0;
DJIAppSettingssdr_force_fccSeenHit=0;
DJIAppSettingssdr_force_fccSeenReplace=0;
DJITermsNotificationControllershouldShowTermsSeenHit=0;
DJITermsNotificationControllershouldShowTermsSeenReplace=0;
DJIAppForceUpdateManagerhasCheckedSeenHit=0;
DJIAppForceUpdateManagerhasCheckedSeenReplace=0;
DJIUpgradeNotifyViewModelnotifyHiddenSeenHit=0;
DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace=0;
DJIProductManagercurrentProductCodeSeenHit=0;
DJIProductManagercurrentProductCodeSeenReplace=0;
 
  //
  // AFSecurityPolicy setSSLPinningMode jsobject
  //
 
  var AFSecurityPolicy = ObjC.classes.AFSecurityPolicy;
  var setSSLPinningMode = AFSecurityPolicy['- setSSLPinningMode:'];
  var setSSLPinningModeImpl = setSSLPinningMode.implementation;
  setSSLPinningMode.implementation = ObjC.implement(setSSLPinningMode, function (handle, selector, originalResult) {
    setSSLPinningModeImpl(handle, selector, 0);
    if ( originalResult != 0 ) {
      if ( 00 == 0 || AFSecurityPolicysetSSLPinningModeSeenReplace == 0 ) {
        console.log("[*] SSL Pinning BYPASSED");
        AFSecurityPolicysetSSLPinningModeSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || AFSecurityPolicysetSSLPinningModeSeenHit == 0 ) {
        console.log(" [*] SSL Pinning not used this time");
        AFSecurityPolicysetSSLPinningModeSeenHit = 1;
      }
    }
  });
 
  //
  // DJIAccountManager checkIsAdminUser jsfunction
  //
 
  var DJIAccountManager = ObjC.classes.DJIAccountManager;
  var checkIsAdminUser = DJIAccountManager['- checkIsAdminUser'];
  var checkIsAdminUserImpl = checkIsAdminUser.implementation;
  checkIsAdminUser.implementation = ObjC.implement(checkIsAdminUser, function (handle, selector) {
    var originalResult = checkIsAdminUserImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIAccountManagercheckIsAdminUserSeenReplace == 0 ) {
        console.log("[*] Flight Records admin user ENABLED");
        DJIAccountManagercheckIsAdminUserSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIAccountManagercheckIsAdminUserSeenHit == 0 ) {
        console.log("[*] Flight Records admin user already enabled");
        DJIAccountManagercheckIsAdminUserSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJIAppSettings canUseIllegalChannels jsfunction
  //
 
  var DJIAppSettings = ObjC.classes.DJIAppSettings;
  var canUseIllegalChannels = DJIAppSettings['- canUseIllegalChannels'];
  var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation;
  canUseIllegalChannels.implementation = ObjC.implement(canUseIllegalChannels, function (handle, selector) {
    var originalResult = canUseIllegalChannelsImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIAppSettingscanUseIllegalChannelsSeenReplace == 0 ) {
        console.log("[*] Illegal Channels config (32) ENABLED");
        DJIAppSettingscanUseIllegalChannelsSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIAppSettingscanUseIllegalChannelsSeenHit == 0 ) {
        console.log("[*] Illegal Channels config (32) already enabled");
        DJIAppSettingscanUseIllegalChannelsSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJIRadioLogic canUseIllegalChannels jsfunction
  //
 
  var DJIRadioLogic = ObjC.classes.DJIRadioLogic;
  var canUseIllegalChannels = DJIRadioLogic['- canUseIllegalChannels'];
  var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation;
  canUseIllegalChannels.implementation = ObjC.implement(canUseIllegalChannels, function (handle, selector) {
    var originalResult = canUseIllegalChannelsImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIRadioLogiccanUseIllegalChannelsSeenReplace == 0 ) {
        console.log("[*] Illegal Channels SDR (32) ENABLED");
        DJIRadioLogiccanUseIllegalChannelsSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIRadioLogiccanUseIllegalChannelsSeenHit == 0 ) {
        console.log("[*] Illegal Channels SDR(32) already enabled");
        DJIRadioLogiccanUseIllegalChannelsSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJIAppSettings sdr_force_fcc jsfunction
  //
 
  var DJIAppSettings = ObjC.classes.DJIAppSettings;
  var sdr_force_fcc = DJIAppSettings['- sdr_force_fcc'];
  var sdr_force_fccImpl = sdr_force_fcc.implementation;
  sdr_force_fcc.implementation = ObjC.implement(sdr_force_fcc, function (handle, selector) {
    var originalResult = sdr_force_fccImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIAppSettingssdr_force_fccSeenReplace == 0 ) {
        console.log("[*] Forced FCC Mode ACTIVATED");
        DJIAppSettingssdr_force_fccSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIAppSettingssdr_force_fccSeenHit == 0 ) {
        console.log("[*] Forced FCC mode already active");
        DJIAppSettingssdr_force_fccSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJITermsNotificationController shouldShowTerms jsfunction
  //
 
  var DJITermsNotificationController = ObjC.classes.DJITermsNotificationController;
  var shouldShowTerms = DJITermsNotificationController['- shouldShowTerms'];
  var shouldShowTermsImpl = shouldShowTerms.implementation;
  shouldShowTerms.implementation = ObjC.implement(shouldShowTerms, function (handle, selector) {
    var originalResult = shouldShowTermsImpl(handle, selector);
    if ( originalResult != 0 ) {
      if ( 00 == 0 || DJITermsNotificationControllershouldShowTermsSeenReplace == 0 ) {
        console.log("[*] Terms and conditions BYPASSED");
        DJITermsNotificationControllershouldShowTermsSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJITermsNotificationControllershouldShowTermsSeenHit == 0 ) {
        console.log("[*] Terms and conditions already accepted");
        DJITermsNotificationControllershouldShowTermsSeenHit = 1;
      }
    }
    return 0;
  });
 
  //
  // DJIAppForceUpdateManager hasChecked jsfunction
  //
 
  var DJIAppForceUpdateManager = ObjC.classes.DJIAppForceUpdateManager;
  var hasChecked = DJIAppForceUpdateManager['- hasChecked'];
  var hasCheckedImpl = hasChecked.implementation;
  hasChecked.implementation = ObjC.implement(hasChecked, function (handle, selector) {
    var originalResult = hasCheckedImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIAppForceUpdateManagerhasCheckedSeenReplace == 0 ) {
        console.log("[*] Upgrade Check: DISABLED");
        DJIAppForceUpdateManagerhasCheckedSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIAppForceUpdateManagerhasCheckedSeenHit == 0 ) {
        console.log("[*] Upgrade Check: Already checked");
        DJIAppForceUpdateManagerhasCheckedSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJIUpgradeNotifyViewModel notifyHidden jsfunction
  //
 
  var DJIUpgradeNotifyViewModel = ObjC.classes.DJIUpgradeNotifyViewModel;
  var notifyHidden = DJIUpgradeNotifyViewModel['- notifyHidden'];
  var notifyHiddenImpl = notifyHidden.implementation;
  notifyHidden.implementation = ObjC.implement(notifyHidden, function (handle, selector) {
    var originalResult = notifyHiddenImpl(handle, selector);
    if ( originalResult != 1 ) {
      if ( 00 == 0 || DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace == 0 ) {
        console.log("[*] Upgrade Notification DISABLLED");
        DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJIUpgradeNotifyViewModelnotifyHiddenSeenHit == 0 ) {
        console.log("[*] Upgrade Notification already disabled");
        DJIUpgradeNotifyViewModelnotifyHiddenSeenHit = 1;
      }
    }
    return 1;
  });
 
  //
  // DJIProductManager currentProductCode jsfunctioniffcc
  //
 
  var DJIProductManager = ObjC.classes.DJIProductManager;
  var currentProductCode = DJIProductManager['+ currentProductCode'];
  var currentProductCodeImpl = currentProductCode.implementation;
  currentProductCode.implementation = ObjC.implement(currentProductCode, function (handle, selector) {
    var originalResult = currentProductCodeImpl(handle, selector);
    if ( originalResult != 13 ) {
      if ( 01 == 0 || DJIProductManagercurrentProductCodeSeenReplace == 0 ) {
        console.log("[*] Changed product code to MAVIC");
        DJIProductManagercurrentProductCodeSeenReplace = 1;
      }
    }
 
    if( DJIAppSettingssdr_force_fccSeenReplace == 0 ) {
      return 13; //mavic
    } else {
      return originalResult;
    }
  });
 
  //
  // DJILImitDBUpdateLogic needUpdateType jsfunction
  //
 
  var DJILImitDBUpdateLogic = ObjC.classes.DJILImitDBUpdateLogic;
  var needUpdateType = DJILImitDBUpdateLogic['- needUpdateType'];
  var needUpdateTypeImpl = needUpdateType.implementation;
  needUpdateType.implementation = ObjC.implement(needUpdateType, function (handle, selector) {
    var originalResult = needUpdateTypeImpl(handle, selector);
    if ( originalResult != 0 ) {
      if ( 00 == 0 || DJILImitDBUpdateLogicneedUpdateTypeSeenReplace == 0 ) {
        console.log("[*] Removing NFZ DB Update Message");
        DJILImitDBUpdateLogicneedUpdateTypeSeenReplace = 1;
      }
    } else {
      if ( 00 == 0 || DJILImitDBUpdateLogicneedUpdateTypeSeenHit == 0 ) {
        console.log("");
        DJILImitDBUpdateLogicneedUpdateTypeSeenHit = 1;
      }
    }
    return 0;
  });
}
howto/fridahooklibrary.txt · Last modified: 2017/11/12 09:45 by czokie