Skip to content

Write-up: MobileHackingLab - Captain Nohook

Updated: at 09:30 PM

Introduction

Captain Nohook is an iOS lab from MobileHackingLab where you need to bypass various detection mechanisms (debugger, hooking, and jailbreak) in order to retrieve a flag.


Methodology

Explore the application

When you open the application, you are greeted with a big blue button with the label “Flag ‘ere!”.

Captain noHook home screen

When you tap on this button the device pops up a message stating that you are using a non-compliant device.

Captain noHook non compliance message

Tapping on the OK dialog option causes the application to exit.


Review source code

The first hint we have is that there is some sort of compliance check. Search the symbol tree for the word compliant to see if there are any results:

Captain noHook home screen

/* Captain_Nohook.is_noncompliant_device() -> Swift.Bool */

uint Captain_Nohook::is_noncompliant_device(void)

{
  uint uVar1;

  ReverseEngineeringToolsChecker::typeMetadataAccessor();
  uVar1 = ReverseEngineeringToolsChecker::amIReverseEngineered();
  return uVar1 & 1;
}

The next clue is that there is a function called amIReverseEngineered, which by the name of it, probably has some additional detection functions:


/* static Captain_Nohook.ReverseEngineeringToolsChecker.amIReverseEngineered() -> Swift.Bool */

uint __thiscall Captain_Nohook::ReverseEngineeringToolsChecker::amIReverseEngineered(void)

{
  undefined1 auVar1 [16];

  auVar1 = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(performChecks_in__75B14952DDFE2A7 8282659A6E004BB4A)()_->_Captain_Nohook.ReverseEngineeringToolsChecker.ReverseEngineeringToolsStatu s
                     ();
  _swift_bridgeObjectRelease(auVar1._8_8_);
  return (auVar1._0_4_ ^ 1) & 1;
}

We can click through one additional level to view the source code for the static function $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(performChecks_in__75B14952DDFE2A7 8282659A6E004BB4A)()_->_Captain_Nohook.ReverseEngineeringToolsChecker.ReverseEngineeringToolsStatus:


undefined1  [16]
$$static_Captain_Nohook.ReverseEngineeringToolsChecker.(performChecks_in__75B14952DDFE2A78282659A6E0 04BB4A)()_->_Captain_Nohook.ReverseEngineeringToolsChecker.ReverseEngineeringToolsStatus
          (void)

{
  byte bVar1;
  char cVar2;
  void *pvVar3;
  undefined4 uVar4;
  uint uVar5;
  undefined8 uVar6;
  char *pcVar7;
  IndexingIterator IVar8;
  void *pvVar9;
  tuple2.conflict12 tVar10;
  undefined1 auVar11 [16];
  char local_88 [8];
  char *local_80;
  void *local_78;
  char local_70;
  char local_69;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  byte local_48 [8];
  String local_40;
  byte local_29;

  local_50 = 0;
  local_60 = 0;
  local_58 = 0;
  local_70 = '\0';
  local_29 = 1;
  local_48[0] = 1;
  pvVar9 = (void *)0x1;
  local_40 = Swift::String::init("",0,1);
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&
                     $$demangling_cache_variable_for_type_metadata_for_(check:_Captain_Nohook.Failed Check,failMessage:_Swift.String)
            );
  tVar10 = Swift::$_allocateUninitializedArray(0);
  uVar6 = tVar10._0_8_;
  local_50 = uVar6;
  Captain_Nohook::FailedCheck::get_allCases();
  local_68 = uVar6;
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&$$demangling_cache_variable_for_type_metadata_for_[Captain_Nohook.FailedCheck]
            );
  pcVar7 = Swift::Array<>::$lazy_protocol_witness_table_accessor();
  (extension_Swift)::Swift::Collection::$makeIterator();
LAB_10000d760:
  do {
    IVar8.unknown =
         (undefined *)
         ___swift_instantiateConcreteTypeFromMangledName
                   (&
                    $$demangling_cache_variable_for_type_metadata_for_Swift.IndexingIterator<[Captai n_Nohook.FailedCheck]>
                   );
    Swift::IndexingIterator::$next(IVar8);
    cVar2 = local_69;
    if (local_69 == '\n') {
      $$outlined_destroy_of_Swift.IndexingIterator<>(&local_60);
      uVar6 = local_50;
      uVar5 = (uint)local_29;
      _swift_bridgeObjectRetain();
      uVar5 = Captain_Nohook::ReverseEngineeringToolsChecker::ReverseEngineeringToolsStatus::init
                        (uVar5 & 1);
      $$outlined_destroy_of_[(check:_Captain_Nohook.FailedCheck,failMessage:_Swift.String)]
                (&local_50);
      $$outlined_destroy_of_(passed:_Swift.Bool,failMessage:_Swift.String)((long)local_48);
      auVar11._4_4_ = 0;
      auVar11._0_4_ = uVar5 & 1;
      auVar11._8_8_ = uVar6;
      return auVar11;
    }
    local_70 = local_69;
    switch(local_69) {
    case '\x01':
      uVar4 = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkExistenceOfSuspiciousFile s_in__75B14952DDFE2A78282659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
                        ();
      local_48[0] = (byte)uVar4 & 1;
      pvVar3 = local_40.bridgeObject;
      local_40.bridgeObject = pvVar9;
      local_40.str = pcVar7;
      _swift_bridgeObjectRelease(pvVar3);
      break;
    default:
      goto switchD_10000d7dc_caseD_2;
    case '\x06':
      uVar4 = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkDYLD_in__75B14952DDFE2A78 282659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
                        ();
      local_48[0] = (byte)uVar4 & 1;
      pvVar3 = local_40.bridgeObject;
      local_40.bridgeObject = pvVar9;
      local_40.str = pcVar7;
      _swift_bridgeObjectRelease(pvVar3);
      break;
    case '\a':
      uVar4 = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkOpenedPorts_in__75B14952D DFE2A78282659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
                        ();
      local_48[0] = (byte)uVar4 & 1;
      pvVar3 = local_40.bridgeObject;
      local_40.bridgeObject = pvVar9;
      local_40.str = pcVar7;
      _swift_bridgeObjectRelease(pvVar3);
      break;
    case '\b':
      local_48[0] = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkPSelectFlag_in__75B 14952DDFE2A78282659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
                              ();
      pvVar3 = local_40.bridgeObject;
      local_40.bridgeObject = pvVar9;
      local_40.str = pcVar7;
      _swift_bridgeObjectRelease(pvVar3);
    }
    bVar1 = local_48[0];
    if ((local_29 & 1) == 0) {
      bVar1 = 0;
    }
    local_29 = bVar1 & 1;
    if ((local_48[0] & 1) == 0) {
      pcVar7 = local_40.str;
      pvVar3 = local_40.bridgeObject;
      _swift_bridgeObjectRetain();
      local_88[0] = cVar2;
      local_80 = pcVar7;
      local_78 = pvVar3;
      pcVar7 = (char *)___swift_instantiateConcreteTypeFromMangledName
                                 ((long *)&
                                          $$demangling_cache_variable_for_type_metadata_for_[(check: _Captain_Nohook.FailedCheck,failMessage:_Swift.String)]
                                 );
      Swift::Array<undefined>::append(local_88);
    }
  } while( true );
switchD_10000d7dc_caseD_2:
  goto LAB_10000d760;
}

This function is very important as it contains the various checks that the app performs to determine if the device is compliant or not.

There is a lot of source code, but we will only break it down to the four individual functions:

checkExistenceOfSuspiciousFiles

This function checks for the presence of a Frida server at the path: /usr/sbin/frida-server.

If the frida-server is found at the path above the function will return false along with an error message: Suspicious file found: /usr/sbin/frida-server

If no server is found, the function will return true, indicating that the security check has passed successfully.

undefined4
$$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkExistenceOfSuspiciousFiles_in__75B14952 DDFE2A78282659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
          (void)

{
  undefined *puVar1;
  undefined8 uVar2;
  long lVar3;
  undefined8 uVar4;
  IndexingIterator IVar5;
  undefined *puVar6;
  NSString *pNVar7;
  undefined *puVar8;
  undefined8 uVar9;
  DefaultStringInterpolation DVar10;
  tuple2.conflict12 tVar11;
  String SVar12;
  undefined8 local_88;
  long local_80;
  DefaultStringInterpolation local_78;
  undefined8 local_70;
  undefined8 local_68;
  long local_60;
  undefined8 local_58;
  long local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;

  puVar1 = PTR_$$type_metadata_for_Swift.String_10016c750;
  local_30 = 0;
  local_40 = 0;
  local_38 = 0;
  local_68 = 0;
  local_60 = 0;
  tVar11 = Swift::$_allocateUninitializedArray(1);
  SVar12 = Swift::String::init("/usr/sbin/frida-server",0x16,1);
  *(String *)tVar11.1 = SVar12;
  uVar4 = Swift::$_finalizeUninitializedArray(tVar11._0_8_);
  local_30 = uVar4;
  _swift_bridgeObjectRetain();
  local_48 = uVar4;
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&$$demangling_cache_variable_for_type_metadata_for_[Swift.String]);
  Swift::Array<String>::$lazy_protocol_witness_table_accessor();
  (extension_Swift)::Swift::Collection::$makeIterator();
  while( true ) {
    IVar5.unknown =
         (undefined *)
         ___swift_instantiateConcreteTypeFromMangledName
                   (&
                    $$demangling_cache_variable_for_type_metadata_for_Swift.IndexingIterator<[Swift. String]>
                   );
    Swift::IndexingIterator::$next(IVar5);
    lVar3 = local_50;
    uVar2 = local_58;
    if (local_50 == 0) {
      $$outlined_destroy_of_Swift.IndexingIterator<>(&local_40);
      Swift::String::init("",0,1);
      _swift_bridgeObjectRelease(uVar4);
      return 1;
    }
    local_68 = local_58;
    local_60 = local_50;
    puVar6 = &_OBJC_CLASS_$_NSFileManager;
    _objc_opt_self();
    _objc_msgSend();
    _objc_retainAutoreleasedReturnValue();
    _swift_bridgeObjectRetain(lVar3);
    pNVar7 = (extension_Foundation)::Swift::String::_bridgeToObjectiveC();
    _swift_bridgeObjectRelease(lVar3);
    puVar8 = puVar6;
    _objc_msgSend(puVar6,"fileExistsAtPath:",pNVar7);
    _objc_release(pNVar7);
    _objc_release(puVar6);
    if (((ulong)puVar8 & 1) != 0) break;
    _swift_bridgeObjectRelease(lVar3);
  }
  uVar9 = 1;
  local_78 = Swift::DefaultStringInterpolation::init(0x17,1);
  DVar10.unknown = (undefined *)0x1;
  local_70 = uVar9;
  SVar12 = Swift::String::init("Suspicious file found: ",0x17,1);
  Swift::DefaultStringInterpolation::appendLiteral(SVar12,DVar10);
  _swift_bridgeObjectRelease(SVar12.bridgeObject);
  local_88 = uVar2;
  local_80 = lVar3;
  Swift::DefaultStringInterpolation::$appendInterpolation
            (&local_88,puVar1,
             PTR_$$protocol_witness_table_for_Swift.String_:_Swift.CustomStringConvertible_in_Swift_ 10016d0a8
             ,
             PTR_$$protocol_witness_table_for_Swift.String_:_Swift.TextOutputStreamable_in_Swift_100 16d5b0
            );
  DVar10.unknown = (undefined *)0x1;
  SVar12 = Swift::String::init("",0,1);
  Swift::DefaultStringInterpolation::appendLiteral(SVar12,DVar10);
  _swift_bridgeObjectRelease(SVar12.bridgeObject);
  DVar10.unknown = local_78.unknown;
  _swift_bridgeObjectRetain();
  $$outlined_destroy_of_Swift.DefaultStringInterpolation((long)&local_78);
  Swift::String::init(DVar10);
  _swift_bridgeObjectRelease(lVar3);
  $$outlined_destroy_of_Swift.IndexingIterator<>(&local_40);
  _swift_bridgeObjectRelease(uVar4);
  return 0;
}

checkDYLD

This function scans all the dynamically loaded images for the following names: FridaGadget, frida, cynject, and libcycript.

If it finds any of these it will return false along with an error message: Suspicious library loaded: FridaGadget

If no image is found, the function will return true, indicating that the security check has passed successfully.


/* WARNING: Heritage AFTER dead removal. Example location: x0 : 0x00010000df2c */
/* WARNING: Restarted to delay deadcode elimination for space: register */

undefined4
$$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkDYLD_in__75B14952DDFE2A78282659A6E004BB 4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
          (void)

{
  undefined *puVar1;
  long lVar2;
  code *pcVar3;
  bool bVar4;
  Set<undefined> SVar5;
  Set<undefined> SVar6;
  IndexingIterator IVar7;
  UnsafePointer<__int8> cString;
  Iterator IVar8;
  undefined *puVar9;
  String *pSVar10;
  undefined8 uVar11;
  DefaultStringInterpolation DVar12;
  tuple2.conflict12 tVar13;
  String SVar14;
  char *local_1a8;
  void *local_1a0;
  char *local_128;
  void *local_120;
  DefaultStringInterpolation local_118;
  undefined8 local_110;
  undefined8 local_108;
  long local_100;
  char *local_f8;
  void *local_f0;
  undefined8 local_e8;
  long local_e0;
  undefined8 local_d8;
  long local_d0;
  ulong auStack_c8 [5];
  String local_a0;
  uint local_90;
  uint local_88;
  byte local_84;
  undefined4 local_80;
  undefined4 local_7c;
  undefined4 local_78;
  undefined4 local_74;
  undefined4 local_70;
  undefined4 local_6c;
  undefined8 local_68;
  undefined4 local_60;
  undefined *local_58;
  undefined1 auStack_48 [40];

  puVar1 = PTR_$$type_metadata_for_Swift.String_10016c750;
  local_58 = (undefined *)0x0;
  local_68 = 0;
  local_60 = 0;
  local_90 = 0;
  local_a0 = (String)ZEXT816(0);
  _memset(auStack_c8,0,0x28);
  local_e8 = 0;
  local_e0 = 0;
  tVar13 = Swift::$_allocateUninitializedArray(4);
  pSVar10 = (String *)tVar13.1;
  SVar14 = Swift::String::init("FridaGadget",0xb,1);
  *pSVar10 = SVar14;
  SVar14 = Swift::String::init("frida",5,1);
  pSVar10[1] = SVar14;
  SVar14 = Swift::String::init("cynject",7,1);
  pSVar10[2] = SVar14;
  SVar14 = Swift::String::init("libcycript",10,1);
  pSVar10[3] = SVar14;
  Swift::$_finalizeUninitializedArray(tVar13._0_8_);
  SVar5 = Swift::Set::$init(tVar13._0_8_,puVar1,
                            PTR_$$protocol_witness_table_for_Swift.String_:_Swift.Hashable_in_Swift_ 10016d038
                           );
  SVar6 = SVar5;
  local_58 = SVar5.unknown;
  __dyld_image_count();
  local_74 = 0;
  local_78 = SUB84(SVar6.unknown,0);
  Swift::Range::init((long)&local_70,&local_74,&local_78,
                     (long)PTR_$$type_metadata_for_Swift.UInt32_10016ce88,
                     PTR_$$protocol_witness_table_for_Swift.UInt32_:_Swift.Comparable_in_Swift_10016 d030
                    );
  local_80 = local_70;
  local_7c = local_6c;
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&$$demangling_cache_variable_for_type_metadata_for_Swift.Range<Swift.UInt32>);
  Swift::Range<__int32>::$lazy_protocol_witness_table_accessor();
  (extension_Swift)::Swift::Collection::$makeIterator();
  while( true ) {
    IVar7.unknown =
         (undefined *)
         ___swift_instantiateConcreteTypeFromMangledName
                   (&
                    $$demangling_cache_variable_for_type_metadata_for_Swift.IndexingIterator<Swift.R ange<Swift.UInt32>>
                   );
    Swift::IndexingIterator::$next(IVar7);
    if ((local_84 & 1) != 0) {
      Swift::String::init("",0,1);
      _swift_bridgeObjectRelease(SVar5.unknown);
      return 1;
    }
    cString.unknown = (undefined *)(ulong)local_88;
    local_90 = local_88;
    __dyld_get_image_name();
    if (cString.unknown == (undefined *)0x0) break;
    SVar14 = Swift::String::init(cString);
    local_a0 = SVar14;
    _swift_bridgeObjectRetain(SVar5.unknown);
    Swift::Set::$makeIterator((Set)SVar5.unknown);
    _memcpy(auStack_c8,auStack_48,0x28);
    while( true ) {
      IVar8.unknown =
           (undefined *)
           ___swift_instantiateConcreteTypeFromMangledName
                     (&
                      $$demangling_cache_variable_for_type_metadata_for_Swift.Set<Swift.String>.Iter ator
                     );
      Swift::Set::Iterator::$next(IVar8);
      lVar2 = local_d0;
      local_1a0 = SVar14.bridgeObject;
      if (local_d0 == 0) break;
      local_1a8 = SVar14.str;
      local_e8 = local_d8;
      local_e0 = local_d0;
      local_f8 = local_1a8;
      local_f0 = local_1a0;
      local_108 = local_d8;
      local_100 = local_d0;
      puVar9 = Swift::String::$lazy_protocol_witness_table_accessor();
      bVar4 = (extension_Foundation)::Swift::StringProtocol::$localizedCaseInsensitiveContains
                        (&local_108,puVar1,puVar1,puVar9);
      if (bVar4) {
        uVar11 = 1;
        local_118 = Swift::DefaultStringInterpolation::init(0x1b,1);
        DVar12.unknown = (undefined *)0x1;
        local_110 = uVar11;
        SVar14 = Swift::String::init("Suspicious library loaded: ",0x1b,1);
        Swift::DefaultStringInterpolation::appendLiteral(SVar14,DVar12);
        _swift_bridgeObjectRelease(SVar14.bridgeObject);
        local_128 = local_1a8;
        local_120 = local_1a0;
        Swift::DefaultStringInterpolation::$appendInterpolation
                  (&local_128,puVar1,
                   PTR_$$protocol_witness_table_for_Swift.String_:_Swift.CustomStringConvertible_in_ Swift_10016d0a8
                   ,
                   PTR_$$protocol_witness_table_for_Swift.String_:_Swift.TextOutputStreamable_in_Swi ft_10016d5b0
                  );
        DVar12.unknown = (undefined *)0x1;
        SVar14 = Swift::String::init("",0,1);
        Swift::DefaultStringInterpolation::appendLiteral(SVar14,DVar12);
        _swift_bridgeObjectRelease(SVar14.bridgeObject);
        DVar12.unknown = local_118.unknown;
        _swift_bridgeObjectRetain();
        $$outlined_destroy_of_Swift.DefaultStringInterpolation((long)&local_118);
        Swift::String::init(DVar12);
        _swift_bridgeObjectRelease(lVar2);
        $$outlined_destroy_of_Swift.Set<>.Iterator(auStack_c8);
        _swift_bridgeObjectRelease(local_1a0);
        _swift_bridgeObjectRelease(SVar5.unknown);
        return 0;
      }
      _swift_bridgeObjectRelease(lVar2);
    }
    $$outlined_destroy_of_Swift.Set<>.Iterator(auStack_c8);
    _swift_bridgeObjectRelease(local_1a0);
  }
  Swift::_assertionFailure
            ((StaticString)0x10015770a,(StaticString)0xb,(StaticString)0x2,0x100156270,0x44);
                    /* WARNING: Does not return */
  pcVar3 = (code *)SoftwareBreakpoint(1,0x10000e054);
  (*pcVar3)();
}

checkOpenedPorts

This function checks for open ports commonly used by malicious processes.

The ports that are checked are 22, 44, 4444, and 27042.

From this list:

I am not sure what 44 is used for, but it could be another binding service listening for incoming connections.

If any of these ports are found the function will return false along with an error message: Port 22 is open.

If no open ports are found, the function will return true, indicating that the security check has passed successfully.


undefined4
$$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkOpenedPorts_in__75B14952DDFE2A78282659A 6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
          (void)

{
  undefined *puVar1;
  undefined8 uVar2;
  bool bVar3;
  undefined8 uVar4;
  IndexingIterator IVar5;
  undefined8 *puVar6;
  undefined8 uVar7;
  DefaultStringInterpolation DVar8;
  tuple2.conflict12 tVar9;
  String SVar10;
  undefined8 local_78;
  DefaultStringInterpolation local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  byte local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;

  puVar1 = PTR_$$type_metadata_for_Swift.Int_10016cdf8;
  local_30 = 0;
  local_40 = 0;
  local_38 = 0;
  local_60 = 0;
  tVar9 = Swift::$_allocateUninitializedArray(4);
  puVar6 = (undefined8 *)tVar9.1;
  *puVar6 = 0x69a2;
  puVar6[1] = 0x115c;
  puVar6[2] = 0x16;
  puVar6[3] = 0x2c;
  uVar4 = Swift::$_finalizeUninitializedArray(tVar9._0_8_);
  local_30 = uVar4;
  _swift_bridgeObjectRetain();
  local_48 = uVar4;
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&$$demangling_cache_variable_for_type_metadata_for_[Swift.Int]);
  Swift::Array<__int64>::$lazy_protocol_witness_table_accessor();
  (extension_Swift)::Swift::Collection::$makeIterator();
  do {
    IVar5.unknown =
         (undefined *)
         ___swift_instantiateConcreteTypeFromMangledName
                   (&
                    $$demangling_cache_variable_for_type_metadata_for_Swift.IndexingIterator<[Swift. Int]>
                   );
    Swift::IndexingIterator::$next(IVar5);
    uVar2 = local_58;
    if ((local_50 & 1) != 0) {
      $$outlined_destroy_of_Swift.IndexingIterator<>(&local_40);
      Swift::String::init("",0,1);
      _swift_bridgeObjectRelease(uVar4);
      return 1;
    }
    local_60 = local_58;
    bVar3 = $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(canOpenLocalConnection_in__75B14 952DDFE2A78282659A6E004BB4A)(port:_Swift.Int)_->_Swift.Bool
                      (local_58);
  } while (!bVar3);
  uVar7 = 1;
  local_70 = Swift::DefaultStringInterpolation::init(0xd,1);
  DVar8.unknown = (undefined *)0x1;
  local_68 = uVar7;
  SVar10 = Swift::String::init("Port ",5,1);
  Swift::DefaultStringInterpolation::appendLiteral(SVar10,DVar8);
  _swift_bridgeObjectRelease(SVar10.bridgeObject);
  local_78 = uVar2;
  Swift::DefaultStringInterpolation::$appendInterpolation
            (&local_78,puVar1,
             PTR_$$protocol_witness_table_for_Swift.Int_:_Swift.CustomStringConvertible_in_Swift_100 16cd78
            );
  DVar8.unknown = (undefined *)0x1;
  SVar10 = Swift::String::init(" is open",8,1);
  Swift::DefaultStringInterpolation::appendLiteral(SVar10,DVar8);
  _swift_bridgeObjectRelease(SVar10.bridgeObject);
  DVar8.unknown = local_70.unknown;
  _swift_bridgeObjectRetain();
  $$outlined_destroy_of_Swift.DefaultStringInterpolation((long)&local_70);
  Swift::String::init(DVar8);
  $$outlined_destroy_of_Swift.IndexingIterator<>(&local_40);
  _swift_bridgeObjectRelease(uVar4);
  return 0;
}

checkPSelectFlag

This function checks whether the current process is being debugged or not.

It uses sysctl system call to inspect the P_TRACED flag, which is set by the operating system, to determine this.

If the flag is set, the function will return false with an error message: Suspicious PFlag value.

If the flag is not set the function will return true indicating that the security check has passed successfully.


/* WARNING: Removing unreachable block (ram,0x00010000f0c8) */
/* WARNING: Removing unreachable block (ram,0x00010000ec50) */
/* WARNING: Heritage AFTER dead removal. Example location: x0 : 0x00010000e9d4 */
/* WARNING: Restarted to delay deadcode elimination for space: register */

bool $$static_Captain_Nohook.ReverseEngineeringToolsChecker.(checkPSelectFlag_in__75B14952DDFE2A7828 2659A6E004BB4A)()_->_(passed:_Swift.Bool,failMessage:_Swift.String)
               (void)

{
  uint uVar1;
  undefined *puVar2;
  uint uVar3;
  code *pcVar4;
  bool bVar5;
  pid_t pVar6;
  int iVar7;
  int *piVar8;
  __int64 _Var9;
  undefined *puVar10;
  undefined *puVar11;
  undefined *puVar12;
  int *piVar13;
  undefined8 uVar14;
  undefined8 uVar15;
  ContiguousArray<undefined> CVar16;
  ulong uVar17;
  long lVar18;
  long lVar19;
  undefined4 *puVar20;
  tuple2.conflict12 tVar21;
  String SVar22;
  String SVar23;
  String SVar24;
  String separator;
  int *local_3f0;
  int *local_3e8;
  int *local_308;
  __int64 local_2e0;
  u_int local_2d4;
  size_t local_2d0;
  int *local_2c8;
  undefined8 local_2b0;
  undefined8 uStack_2a8;
  undefined8 local_2a0;
  undefined8 local_298;
  uint local_290;
  undefined1 local_28c;
  undefined4 local_288;
  undefined4 local_284;
  undefined4 local_280;
  undefined8 local_278;
  undefined8 local_270;
  undefined4 local_268;
  undefined4 local_264;
  undefined4 local_260;
  undefined4 local_25c;
  undefined4 local_258;
  undefined8 local_250;
  undefined8 local_248;
  undefined4 local_240;
  undefined4 local_23c;
  undefined8 local_238;
  undefined4 local_230;
  undefined8 local_228;
  undefined4 local_220;
  undefined8 local_218;
  undefined4 local_210;
  undefined8 local_208;
  undefined8 local_200;
  undefined8 local_1f8;
  undefined4 local_1f0;
  undefined8 local_1e8;
  undefined4 local_1e0;
  undefined8 local_1d8;
  undefined4 local_1d0;
  undefined4 local_1cc;
  undefined4 local_1c8;
  undefined4 local_1c4;
  undefined1 local_1c0;
  undefined1 local_1bf;
  undefined1 local_1be;
  undefined1 local_1bd;
  undefined1 local_1bc;
  undefined1 local_1bb;
  undefined1 local_1ba;
  undefined1 local_1b9;
  undefined1 local_1b8;
  undefined1 local_1b7;
  undefined1 local_1b6;
  undefined1 local_1b5;
  undefined1 local_1b4;
  undefined1 local_1b3;
  undefined1 local_1b2;
  undefined1 local_1b1;
  undefined1 local_1b0;
  undefined1 local_1af;
  undefined1 local_1ae;
  undefined1 local_1ad;
  undefined8 local_1a8;
  undefined8 local_1a0;
  undefined2 local_198;
  undefined2 local_196;
  undefined8 local_190;
  undefined8 local_188;
  undefined8 local_180;
  undefined1 local_178;
  undefined1 local_177;
  undefined1 local_176;
  undefined1 local_175;
  undefined1 local_174;
  undefined1 local_173;
  undefined1 local_172;
  undefined1 local_171;
  undefined1 local_170;
  undefined1 local_16f;
  undefined1 local_16e;
  undefined1 local_16d;
  undefined1 local_16c;
  undefined1 local_16b;
  undefined1 local_16a;
  undefined1 local_169;
  undefined1 local_168;
  undefined1 local_167;
  undefined1 local_166;
  undefined1 local_165;
  undefined1 local_164;
  undefined1 local_163;
  undefined1 local_162;
  undefined1 local_161;
  undefined1 local_160;
  undefined1 local_15f;
  undefined1 local_15e;
  undefined1 local_15d;
  undefined1 local_15c;
  undefined1 local_15b;
  undefined1 local_15a;
  undefined1 local_159;
  undefined1 local_158;
  undefined1 local_157;
  undefined1 local_156;
  undefined1 local_155;
  undefined1 local_154;
  undefined1 local_153;
  undefined1 local_152;
  undefined1 local_151;
  undefined1 local_150;
  undefined1 local_14f;
  undefined1 local_14e;
  undefined1 local_14d;
  undefined1 local_14c;
  undefined1 local_14b;
  undefined1 local_14a;
  undefined1 local_149;
  undefined1 local_148;
  undefined1 local_147;
  undefined1 local_146;
  undefined1 local_145;
  undefined1 local_144;
  undefined1 local_143;
  undefined1 local_142;
  undefined1 local_141;
  undefined1 local_140;
  undefined1 local_13f;
  undefined1 local_13e;
  undefined1 local_13d;
  undefined1 local_13c;
  undefined1 local_13b;
  undefined1 local_13a;
  undefined1 local_139;
  undefined1 local_138;
  undefined1 local_137;
  undefined1 local_136;
  undefined1 local_135;
  undefined1 local_134;
  undefined1 local_133;
  undefined1 local_132;
  undefined1 local_131;
  undefined8 local_130;
  undefined4 local_128;
  undefined4 local_124;
  undefined4 local_120;
  undefined4 local_11c;
  undefined4 local_118;
  undefined4 local_110;
  undefined4 local_10c;
  undefined2 local_108;
  undefined4 local_104;
  undefined4 local_100;
  undefined4 local_fc;
  undefined4 local_f8;
  undefined4 local_f4;
  undefined4 local_f0;
  undefined4 local_ec;
  undefined4 local_e8;
  undefined4 local_e4;
  undefined4 local_e0;
  undefined4 local_dc;
  undefined4 local_d8;
  undefined4 local_d4;
  undefined4 local_d0;
  undefined4 local_cc;
  undefined4 local_c8;
  undefined4 local_c0;
  undefined8 local_b8;
  undefined4 local_b0;
  undefined4 local_ac;
  undefined4 local_a8;
  undefined4 local_a4;
  undefined4 local_a0;
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined4 local_80;
  undefined4 local_7c;
  undefined2 local_78;
  undefined4 local_74;
  undefined4 local_70;
  undefined8 local_68;
  undefined1 local_60;
  undefined1 local_5f;
  undefined1 local_5e;
  undefined1 local_5d;
  undefined1 local_5c;
  undefined1 local_5b;
  undefined1 local_5a;
  undefined1 local_59;
  undefined4 local_58;
  undefined2 local_54;
  undefined2 local_52;
  undefined2 local_50;
  undefined4 local_4c;
  undefined1 local_48;
  undefined1 local_47;
  undefined1 local_46;
  undefined1 local_45;
  undefined1 local_44;
  undefined1 local_43;
  undefined1 local_42;
  undefined1 local_41;
  undefined1 local_40;
  undefined1 local_3f;
  undefined1 local_3e;
  undefined1 local_3d;
  undefined4 local_3c;
  undefined4 local_38;
  undefined4 local_34;
  undefined4 local_30;
  long local_28;

  puVar2 = PTR_$$type_metadata_for_Swift.Int32_10016ce50;
  local_28 = *(long *)PTR____stack_chk_guard_10016c338;
  _bzero(&local_2b0,0x288);
  local_2c8 = (int *)0x0;
  local_2d0 = 0;
  uStack_2a8 = 0;
  local_2b0 = 0;
  local_2a0 = 0;
  local_298 = 0;
  local_290 = 0;
  local_28c = 0;
  local_288 = 0;
  local_284 = 0;
  local_280 = 0;
  local_278 = 0;
  local_270 = 0;
  local_268 = 0;
  local_264 = 0;
  local_260 = 0;
  local_25c = 0;
  local_258 = 0;
  local_250 = 0;
  local_248 = 0;
  local_240 = 0;
  local_23c = 0;
  local_238 = 0;
  local_230 = 0;
  local_228 = 0;
  local_220 = 0;
  local_218 = 0;
  local_210 = 0;
  local_208 = 0;
  local_200 = 0;
  local_1f8 = 0;
  local_1f0 = 0;
  local_1e8 = 0;
  local_1e0 = 0;
  local_1d8 = 0;
  local_1d0 = 0;
  local_1cc = 0;
  local_1c8 = 0;
  local_1c4 = 0;
  local_1c0 = 0;
  local_1bf = 0;
  local_1be = 0;
  local_1bd = 0;
  local_1bc = 0;
  local_1bb = 0;
  local_1ba = 0;
  local_1b9 = 0;
  local_1b8 = 0;
  local_1b7 = 0;
  local_1b6 = 0;
  local_1b5 = 0;
  local_1b4 = 0;
  local_1b3 = 0;
  local_1b2 = 0;
  local_1b1 = 0;
  local_1b0 = 0;
  local_1af = 0;
  local_1ae = 0;
  local_1ad = 0;
  local_1a8 = 0;
  local_1a0 = 0;
  local_198 = 0;
  local_196 = 0;
  local_190 = 0;
  local_188 = 0;
  local_180 = 0;
  local_178 = 0;
  local_177 = 0;
  local_176 = 0;
  local_175 = 0;
  local_174 = 0;
  local_173 = 0;
  local_172 = 0;
  local_171 = 0;
  local_170 = 0;
  local_16f = 0;
  local_16e = 0;
  local_16d = 0;
  local_16c = 0;
  local_16b = 0;
  local_16a = 0;
  local_169 = 0;
  local_168 = 0;
  local_167 = 0;
  local_166 = 0;
  local_165 = 0;
  local_164 = 0;
  local_163 = 0;
  local_162 = 0;
  local_161 = 0;
  local_160 = 0;
  local_15f = 0;
  local_15e = 0;
  local_15d = 0;
  local_15c = 0;
  local_15b = 0;
  local_15a = 0;
  local_159 = 0;
  local_158 = 0;
  local_157 = 0;
  local_156 = 0;
  local_155 = 0;
  local_154 = 0;
  local_153 = 0;
  local_152 = 0;
  local_151 = 0;
  local_150 = 0;
  local_14f = 0;
  local_14e = 0;
  local_14d = 0;
  local_14c = 0;
  local_14b = 0;
  local_14a = 0;
  local_149 = 0;
  local_148 = 0;
  local_147 = 0;
  local_146 = 0;
  local_145 = 0;
  local_144 = 0;
  local_143 = 0;
  local_142 = 0;
  local_141 = 0;
  local_140 = 0;
  local_13f = 0;
  local_13e = 0;
  local_13d = 0;
  local_13c = 0;
  local_13b = 0;
  local_13a = 0;
  local_139 = 0;
  local_138 = 0;
  local_137 = 0;
  local_136 = 0;
  local_135 = 0;
  local_134 = 0;
  local_133 = 0;
  local_132 = 0;
  local_131 = 0;
  local_130 = 0;
  local_128 = 0;
  local_124 = 0;
  local_120 = 0;
  local_11c = 0;
  local_118 = 0;
  local_110 = 0;
  local_10c = 0;
  local_108 = 0;
  local_104 = 0;
  local_100 = 0;
  local_fc = 0;
  local_f8 = 0;
  local_f4 = 0;
  local_f0 = 0;
  local_ec = 0;
  local_e8 = 0;
  local_e4 = 0;
  local_e0 = 0;
  local_dc = 0;
  local_d8 = 0;
  local_d4 = 0;
  local_d0 = 0;
  local_cc = 0;
  local_c8 = 0;
  local_c0 = 0;
  local_b8 = 0;
  local_b0 = 0;
  local_ac = 0;
  local_a8 = 0;
  local_a4 = 0;
  local_a0 = 0;
  local_98 = 0;
  local_90 = 0;
  local_88 = 0;
  local_80 = 0;
  local_7c = 0;
  local_78 = 0;
  local_74 = 0;
  local_70 = 0;
  local_68 = 0;
  local_60 = 0;
  local_5f = 0;
  local_5e = 0;
  local_5d = 0;
  local_5c = 0;
  local_5b = 0;
  local_5a = 0;
  local_59 = 0;
  local_58 = 0;
  local_54 = 0;
  local_52 = 0;
  local_50 = 0;
  local_4c = 0;
  local_48 = 0;
  local_47 = 0;
  local_46 = 0;
  local_45 = 0;
  local_44 = 0;
  local_43 = 0;
  local_42 = 0;
  local_41 = 0;
  local_40 = 0;
  local_3f = 0;
  local_3e = 0;
  local_3d = 0;
  local_3c = 0;
  local_38 = 0;
  local_34 = 0;
  local_30 = 0;
  tVar21 = Swift::$_allocateUninitializedArray(4);
  puVar20 = (undefined4 *)tVar21.1;
  *puVar20 = 1;
  puVar20[1] = 0xe;
  puVar20[2] = 1;
  pVar6 = _getpid();
  puVar20[3] = pVar6;
  piVar8 = (int *)Swift::$_finalizeUninitializedArray(tVar21._0_8_);
  _swift_bridgeObjectRetain();
  local_2d0 = 0x288;
  local_2c8 = piVar8;
  _Var9 = Swift::Array<undefined>::get_count(piVar8,puVar2);
  _swift_bridgeObjectRelease(piVar8);
  local_2e0 = _Var9;
  puVar10 = __int32::$lazy_protocol_witness_table_accessor();
  puVar11 = __int32::$lazy_protocol_witness_table_accessor();
  puVar12 = __int64::$lazy_protocol_witness_table_accessor();
  (extension_Swift)::Swift::UnsignedInteger::$init
            (&local_2d4,&local_2e0,PTR_$$type_metadata_for_Swift.UInt32_10016ce88,
             PTR_$$type_metadata_for_Swift.Int_10016cdf8,puVar10,puVar11,puVar12);
  ___swift_instantiateConcreteTypeFromMangledName
            ((long *)&$$demangling_cache_variable_for_type_metadata_for_[Swift.Int32]);
  Swift::Array<undefined>::reserveCapacity(0);
  piVar13 = local_2c8;
  Swift::Array<undefined>::$get__baseAddressIfContiguous(local_2c8,puVar2);
  piVar8 = local_2c8;
  if (piVar13 == (int *)0x0) {
    _swift_bridgeObjectRetain();
    Swift::Array<__int32>::$lazy_protocol_witness_table_accessor();
    bVar5 = (extension_Swift)::Swift::Collection::get_isEmpty();
    _swift_bridgeObjectRelease(piVar8);
    if (!bVar5) {
      Swift::_fatalErrorMessage
                ((StaticString)0x10015770a,(StaticString)0xb,(StaticString)0x2,0x100157090,0);
                    /* WARNING: Does not return */
      pcVar4 = (code *)SoftwareBreakpoint(1,0x10000ed80);
      (*pcVar4)();
    }
  }
  local_3f0 = local_2c8;
  local_3e8 = local_2c8;
  Swift::Array<undefined>::$get__baseAddressIfContiguous(local_2c8,puVar2);
  if (local_3e8 == (int *)0x0) {
    _swift_bridgeObjectRetain(local_3f0);
    Swift::Array<__int32>::$lazy_protocol_witness_table_accessor();
    bVar5 = (extension_Swift)::Swift::Collection::get_isEmpty();
    _swift_bridgeObjectRelease(local_3f0);
    if (!bVar5) {
      _swift_bridgeObjectRetain(local_3f0);
      lVar18 = ___swift_instantiateConcreteTypeFromMangledName
                         ((long *)&
                                  $$demangling_cache_variable_for_type_metadata_for_Swift._ArrayBuff er<Swift.Int32>
                         );
      puVar10 = Swift::_ArrayBuffer<__int32>::$lazy_protocol_witness_table_accessor();
      CVar16 = Swift::ContiguousArray::$init(puVar10,puVar2,lVar18,puVar10);
      _swift_retain();
      _swift_release(CVar16.unknown);
      local_3f0 = (int *)CVar16;
      Swift::_ContiguousArrayBuffer::$get_owner((_ContiguousArrayBuffer)CVar16.unknown);
      local_3e8 = (int *)Swift::_ContiguousArrayBuffer::get_firstElementAddress
                                   ((_ContiguousArrayBuffer)CVar16.unknown);
      _swift_release(CVar16.unknown);
      goto LAB_10000eb84;
    }
  }
  Swift::Array<undefined>::$get__owner(local_3f0,puVar2);
  if (local_3e8 == (int *)0x0) {
    local_3e8 = (int *)0x0;
  }
LAB_10000eb84:
  if (local_3e8 == (int *)0x0) {
    local_308 = (int *)0xfffffffffffffffc;
  }
  else {
    local_308 = local_3e8;
  }
  iVar7 = _sysctl(local_308,local_2d4,&local_2b0,&local_2d0,(void *)0x0,0);
  _swift_unknownObjectRelease(local_3f0);
  uVar14 = _$sSZsE8isSignedSbvgZs5Int32V_Tgmq5();
  uVar15 = _$sSZsE8isSignedSbvgZSi_Tgmq5();
  if (((uint)uVar14 & 1) == ((uint)uVar15 & 1)) {
    (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
    (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
  }
  else {
    uVar17 = _$sSZsE8isSignedSbvgZs5Int32V_Tgmq5();
    if ((uVar17 & 1) == 0) {
      (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
    }
    else {
      (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
    }
  }
  if (iVar7 != 0) {
    tVar21 = Swift::$_allocateUninitializedArray(1);
    SVar22 = Swift::String::init("Error occured when calling sysctl(). This check may not be reliabl e"
                                 ,0x43,1);
    ((String *)tVar21.1)[1].bridgeObject = PTR_$$type_metadata_for_Swift.String_10016c750;
    *(String *)tVar21.1 = SVar22;
    SVar24.str = (char *)Swift::$_finalizeUninitializedArray(tVar21._0_8_);
    SVar22 = Swift::$print();
    SVar23 = Swift::$print();
    SVar24.bridgeObject = SVar22.str;
    separator.bridgeObject = SVar23.str;
    separator.str = (char *)SVar22.bridgeObject;
    Swift::$print(SVar24,separator);
    _swift_bridgeObjectRelease(SVar23.bridgeObject);
    _swift_bridgeObjectRelease(SVar22.bridgeObject);
    _swift_bridgeObjectRelease(SVar24.str);
  }
  uVar3 = local_290;
  uVar1 = local_290 & 0x40;
  uVar14 = _$sSZsE8isSignedSbvgZs5Int32V_Tgmq5();
  uVar15 = _$sSZsE8isSignedSbvgZSi_Tgmq5();
  if (((uint)uVar14 & 1) == ((uint)uVar15 & 1)) {
    lVar18 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
    lVar19 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
    if (lVar18 < lVar19) {
      bVar5 = uVar1 == 0;
    }
    else {
      bVar5 = (uVar3 & 0x40) == 0;
    }
  }
  else {
    uVar17 = _$sSZsE8isSignedSbvgZs5Int32V_Tgmq5();
    if ((uVar17 & 1) == 0) {
      lVar18 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      lVar19 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      if (lVar18 < lVar19) {
        bVar5 = uVar1 == 0;
      }
      else {
        bVar5 = (uVar3 & 0x40) == 0;
      }
    }
    else {
      lVar18 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      lVar19 = (extension_Swift)::Swift::FixedWidthInteger::get_bitWidth();
      if (lVar19 < lVar18) {
        bVar5 = (uVar3 & 0x40) == 0;
      }
      else {
        bVar5 = uVar1 == 0;
      }
    }
  }
  if (!bVar5) {
    Swift::String::init("Suspicious PFlag value",0x16,1);
    $$outlined_destroy_of_[Swift.Int32](&local_2c8);
  }
  else {
    Swift::String::init("",0,1);
    $$outlined_destroy_of_[Swift.Int32](&local_2c8);
  }
  if (*(long *)PTR____stack_chk_guard_10016c338 != local_28) {
                    /* WARNING: Subroutine does not return */
    ___stack_chk_fail();
  }
  return bVar5;
}

Create bypass script

The quick and dirty (and unsuccessful)

My initial approach to this lab was just to hook the outer functionality which should automatically also bypass the inner calls as well, right?

Yes and no. The hooking bypass works, and you can tap the button in the app now, but you will not be able to retrieve the flag from memory!

Let’s start by hooking the is_noncompliant_device function:

// Non compliance check

var isNonCompliantDevice = Module.findExportByName(
  "Captain Nohook",
  "$s14Captain_Nohook22is_noncompliant_deviceSbyF"
);

if (isNonCompliantDevice) {
  console.log("Hooking success!");
  Interceptor.attach(isNonCompliantDevice, {
    onEnter: function (args) {
      console.log("Entered isNonCompliantDevice!");
    },
    onLeave: function (retval) {
      // Modify the return value to always return 0x0 which is false
      retval.replace(0x0);
      console.log("Exited isNonCompliantDevice, retval:" + retval);
    },
  });
} else {
  console.log("Hooking failed!");
}
frida -U -f "com.mobilehackinglab.Captain-Nohook" -l captain-nohook-bypass-jailbreak.js
Spawning `com.mobilehackinglab.Captain-Nohook`...
Hooking success!
Spawned `com.mobilehackinglab.Captain-Nohook`. Resuming main thread!
[iPhone::com.mobilehackinglab.Captain-Nohook ]->
Captain noHook frida bypass

Tapping the button does not change the UI, but we can see in the Frida logs that something is happening:

Entered isNonCompliantDevice!
Exited isNonCompliantDevice, retval:0x0

The device compliance check is run when the image is tapped. Since we hooked that to always return 0x0 (false) we have successfully bypassed the compliance check.

According to the lab description, the flag is in memory, and we should be able to access it after we bypass the hooking detection.

Let’s use Objection to see if we can search for the flag in the application’s memory:

objection -g 2352 explore
Using USB device `iPhone`
Traceback (most recent call last):
...
frida.TransportError: connection closed

Objection does not work. Something is blocking it.

I assume it might be one of the checks we identified earlier or perhaps even something else.

The long way

We need to find out why Objection does not want to work.

Upon further inspection I found some code that is likely used to confuse a debugger or even cause crashes in certain conditions:


void _disable_gdb(void)

{
  code *pcVar1;

  pcVar1 = (code *)0xfffffffffffffffd;
  _dlsym(0xfffffffffffffffd,"ptrace");
  (*pcVar1)(0x1f,0,0);
  return;
}

To verify that this is the reason for the Objection crash we can hook this function and replace its body with an empty block:

// Debugger disable check

var disableGdbOffset = 0x8000;
var disableGdb =
  Process.getModuleByName("Captain Nohook").base.add(disableGdbOffset);

var emptyBody = new NativeCallback(
  function () {
    console.log("Bypassed disableGdb!");
  },
  "void",
  []
);

Interceptor.replace(disableGdb, emptyBody);

Let’s run our hooking script again and see the result:

frida -U -f "com.mobilehackinglab.Captain-Nohook" -l captain-nohook-bypass-jailbreak.js
Spawning `com.mobilehackinglab.Captain-Nohook`...
Hooking success!
Spawned `com.mobilehackinglab.Captain-Nohook`. Resuming main thread!
[iPhone::com.mobilehackinglab.Captain-Nohook ]-> Bypassed disableGdb!

It seems to have worked, let’s test it by running Objection:

objection -g 2378 explore
Using USB device `iPhone`
Agent injected and responds ok!

     _   _         _   _
 ___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_|  _|  _| | . |   |
|___|___| |___|___|_| |_|___|_|_|
      |___|(object)inject(ion) v1.11.0

     Runtime Mobile Exploration
        by: @leonjza from @sensepost

[tab] for command suggestions
...obilehackinglab.Captain-Nohook on (iPhone: 18.5) [usb] #

Success. We are able to open Objection now and search for the flag in memory.

After tapping the image once on the device, scan the memory for the flag:

...obilehackinglab.Captain-Nohook on (iPhone: 18.5) [usb] # memory search MHL{ --string
Searching for: 4d 48 4c 7b
Unable to find the pattern in any memory region

Something is preventing the memory from being scanned.

We will now write bypasses for all the various checks and combine them all to ensure that nothing else will interfere.

The final script:

// Debugger disable

var disableGdbOffset = 0x8000;
var disableGdb =
  Process.getModuleByName("Captain Nohook").base.add(disableGdbOffset);

var emptyBody = new NativeCallback(
  function () {
    console.log("Bypassed disableGdb!");
  },
  "void",
  []
);

Interceptor.replace(disableGdb, emptyBody);

// Suspicious files check

var checkExistenceOfSuspiciousFilesOffset = 0xda78;
var checkExistenceOfSuspiciousFiles = Process.getModuleByName(
  "Captain Nohook"
).base.add(checkExistenceOfSuspiciousFilesOffset);
Interceptor.attach(checkExistenceOfSuspiciousFiles, {
  onEnter: function (args) {
    console.log("Entered checkExistenceOfSuspiciousFiles");
  },
  onLeave: function (retval) {
    retval.replace(0x1);
    console.log("Exited checkExistenceOfSuspiciousFiles, retval:" + retval);
  },
});

// DYLD check

var checkDYLDOffset = 0xddcc;
var checkDYLD =
  Process.getModuleByName("Captain Nohook").base.add(checkDYLDOffset);
Interceptor.attach(checkDYLD, {
  onEnter: function (args) {
    console.log("Entered checkDYLD");
  },
  onLeave: function (retval) {
    retval.replace(0x1);
    console.log("Exited checkDYLD, retval:" + retval);
  },
});

// Open ports check

var checkOpenedPortsOffset = 0xe300;
var checkOpenedPorts = Process.getModuleByName("Captain Nohook").base.add(
  checkOpenedPortsOffset
);
Interceptor.attach(checkOpenedPorts, {
  onEnter: function (args) {
    console.log("Entered checkOpenedPorts");
  },
  onLeave: function (retval) {
    retval.replace(0x1);
    console.log("Exited checkOpenedPorts, retval:" + retval);
  },
});

// Check PSelect

var checkPSelectFlagOffset = 0xe584;
var checkPSelectFlag = Process.getModuleByName("Captain Nohook").base.add(
  checkPSelectFlagOffset
);
Interceptor.attach(checkPSelectFlag, {
  onEnter: function (args) {
    console.log("Entered checkPSelectFlag");
  },
  onLeave: function (retval) {
    retval.replace(0x1);
    console.log("Exited checkPSelectFlag, retval:" + retval);
  },
});

Execute bypass script

We launch the application using Frida and the hooking script.

Once the application is launched, we tap the button, which will generate some output in Frida to verify that our hooking is working:

frida -U -f "com.mobilehackinglab.Captain-Nohook" -l captain-nohook-bypass-jailbreak.js
...
Spawned `com.mobilehackinglab.Captain-Nohook`. Resuming main thread!
[iPhone::com.mobilehackinglab.Captain-Nohook ]-> Bypassed disableGdb!
[iPhone::com.mobilehackinglab.Captain-Nohook ]->
[iPhone::com.mobilehackinglab.Captain-Nohook ]-> Entered checkExistenceOfSuspiciousFiles
Exited checkExistenceOfSuspiciousFiles, retval:0x1
Entered checkDYLD
Exited checkDYLD, retval:0x1
Entered checkOpenedPorts
Exited checkOpenedPorts, retval:0x1
Entered checkPSelectFlag
Exited checkPSelectFlag, retval:0x1

It looks like all our bypasses are working as intended; let’s try to grab the flag next.


Retrieve the flag

Now we launch Objection and search for the flag in memory:

objection -g 2485 explore
Using USB device `iPhone`
Agent injected and responds ok!

     _   _         _   _
 ___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_|  _|  _| | . |   |
|___|___| |___|___|_| |_|___|_|_|
      |___|(object)inject(ion) v1.11.0

     Runtime Mobile Exploration
        by: @leonjza from @sensepost

[tab] for command suggestions
...obilehackinglab.Captain-Nohook on (iPhone: 18.5) [usb] # memory search MHL{ --string
Searching for: 4d 48 4c 7b
11c017960  4d 48 4c 7b 48 30 30 6b 5f 31 6e 5f 59 30 75 72  MHL{H00k_1n_Y0ur
11c017970  5f 44 33 62 55 67 67 33 72 7d 0a 00 00 00 00 00  _D3bUgg3r}......
11c017980  cc 56 0f 85 03 6d 62 bc 80 bb 4a 00 00 00 00 00  .V...mb...J.....
11c189a71  4d 48 4c 7b 48 30 30 6b 5f 31 6e 5f 59 30 75 72  MHL{H00k_1n_Y0ur
11c189a81  5f 44 33 62 55 67 67 33 72 7d 0a 00 00 00 00 00  _D3bUgg3r}......
11c189a91  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80  ................
Pattern matched at 2 addresses
...obilehackinglab.Captain-Nohook on (iPhone: 18.5) [usb] #

We have found the flag: MHL{H00k_1n_Y0ur_D3bUgg3r}


Final thoughts

This lab contained some interesting detection mechanisms that I have not encountered before.

I found the anti-GDB trickery and the PLSelect flag check particularly interesting.

I ended up solving this challenge in reverse. Initially I only hooked the outer checks, but I soon found out that it would not be enough.

By diving into the code and also getting some LLM assistance for the more difficult security checks I was able to come up with a working solution.