securing / IOSSecuritySuite

iOS platform security & anti-tampering Swift library
https://www.securing.biz/
Other
2.39k stars 286 forks source link

What is considered a hook? #58

Closed izmcm closed 1 year ago

izmcm commented 2 years ago

Hi there, this is possibly a dumb question but I'm new with iOS security and I'm having some doubts about using the library.

I created a dumb app to test some knowledge and imported the iOSSecuritySuite library via Swift Package Manager. In my test app, I created a class with two functions as shown below (I'm trying to understand the difference between RuntimeHookChecker and MSHookChecker)


import Foundation

typealias FunctionType = @convention(thin) (OtherClass) -> () -> (Bool)

class OtherClass {
  init() { }

  func hookThisFunctionToTestMSHookDetection() -> Bool {
    return false
  }

  @objc dynamic func hookThisFunctionToTestRuntimeHookDetection() -> Bool {
    return false
  }
}

So, on my ViewController, I has two buttons that call

  func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer {
    return unsafeBitCast(function, to: UnsafeMutableRawPointer.self)
  }

  @IBAction func callMSHook(_ sender: Any) {
    let funcAddr = getSwiftFunctionAddr(OtherClass.hookThisFunctionToTestMSHookDetection)
    let amIMSHooked = IOSSecuritySuite.amIMSHooked(funcAddr)

    self.textView.text = "MSHook = \(amIMSHooked)\notherClass = \(otherClass.hookThisFunctionToTestMSHookDetection())"
  }

  @IBAction func callRuntime(_ sender: Any) {
    let amIRuntimeHooked: Bool = IOSSecuritySuite.amIRuntimeHooked(dyldWhiteList: [], detectionClass: OtherClass.self, selector: #selector(OtherClass.hookThisFunctionToTestRuntimeHookDetection), isClassMethod: false)

    self.textView.text = "RuntimeHook = \(amIRuntimeHooked)\notherClass = \(otherClass.hookThisFunctionToTestRuntimeHookDetection())"
  }

I installed this app in my jailbroken iPhone and used Frida to change the return of OtherClass' functions with the script bellow

var targetModule = 'HookDetectionPoC';
var addr = ptr(0x9530);
var moduleBase = Module.getBaseAddress(targetModule);
var targetAddress = moduleBase.add(addr);
Interceptor.attach(targetAddress, {
    onEnter: function(args) {
        this.context.x0=0x01
    },
});

addr = ptr(0x9514);
moduleBase = Module.getBaseAddress(targetModule);
targetAddress = moduleBase.add(addr);
Interceptor.attach(targetAddress, {
    onEnter: function(args) {
        this.context.x0=0x01  
    },
});

This was able to modify the result of the functions and now they are returning true instead of false. Unfortunately, the hook detections not working and are returning false always.

Someone can help me to test the hook detection?

izmcm commented 2 years ago

Also, I learned to use Theos to try to test this code. I followed this writeup: https://www.mbo42.com/2018/04/01/hooking-swift-methods/

Tweak.x

%hook OtherClass

static void (*orig_OtherClass_hookThisFunctionToTestMSHook)(void) = NULL;

BOOL hook_OtherClass_hookThisFunctionToTestMSHook() {
   orig_OtherClass_hookThisFunctionToTestMSHook();
   NSLog(@"Hooked random function");
   return YES;
}

%end

%ctor {
    %init(OtherClass = objc_getClass("HookDetectionPoC.OtherClass"));
    MSHookFunction(MSFindSymbol(NULL, "_$s16HookDetectionPoC10OtherClassC028hookThisFunctionToTestMSHookB0SbyF"),
                   (void*)hook_OtherClass_hookThisFunctionToTestMSHook,
                   (void**)&orig_OtherClass_hookThisFunctionToTestMSHook);
}

This code works as expected and hookThisFunctionToTestMSHookDetection now is returning true, but MSHookChecker keeps returning false

TannerJin commented 2 years ago

hey, @izmcm

  1. The OtherClass. hookThisFunctionToTestMSHookDetection function’s type is @convention(method) (@guaranteed OtherClass) -> Bool. (you can get by using the command line swiftc -emit-sil OtherClass.swift). In this case, you don't get the really function address by using getSwiftFunctionAddr2. maybe you can get the function address by swift v-table

  2. I'am not familiar with how Frida works, but I guess it's inlineHook instead of objcHook. so amIRuntimeHooked doesn't work

izmcm commented 2 years ago

Hi @TannerJin, hope you're well :)

  1. You are right about Frida, it should not be detected using amIRuntimeHooked but using amIReverseEngineered

  2. According my tests, RuntimeHookChecker is capable to detect the replacement of an objective-c function by a Tweak with Theos. If we don't need to have an objc func, doesn't make sense use this class, right?

  3. Now, about MSHookChecker, I still have some doubts:

The example function someFunction that appears in the README only seems to work if the function is not inside a class as usual, right? I tested putting someFunction inside OtherClass and the type changed to @convention (method) (Int, @guaranteed OtherClass) -> Bool. Whereas I can't just put this new type as FunctionType as this will result in two errors

So, should I use the virtual method table (v-table) to get function's address? I have no idea how it works 🙁, do you have some example? The only example I found on the internet about getting the address of a function in swift was a response from you on Stackoverflow haha

izmcm commented 2 years ago

Hi again @TannerJin! I saw that in the stackoverflow edits there was an example code for vtable, so my code was like this

ViewController.swift

{...}
@IBAction func callMSHook(_ sender: Any) {
  let funcAddr = getSwiftFunctionAddressFromVtable(OtherClass.self, methodIndex: 1)!

  let amIMSHooked = IOSSecuritySuite.amIMSHooked(funcAddr)

  self.textView.text = "MSHook detector = \(amIMSHooked)\nMSHook func = \(otherClass.hookThisFunctionToTestMSHookDetection())"
}

func getSwiftFunctionAddressFromVtable(_ objc_class: AnyClass,
                                     methodIndex offset: Int) -> UnsafeMutablePointer<UnsafeMutableRawPointer>? {
  let classPointer = Unmanaged.passUnretained(objc_class as AnyObject).toOpaque()

  guard let outCount = malloc(MemoryLayout<UInt32>.size)?.assumingMemoryBound(to: UInt32.self) else { return nil }
  defer {
    free(outCount)
  }

  let _ = class_copyIvarList(objc_class, outCount)

  let cachePointer = classPointer.advanced(by: 0x50 + 32*Int(outCount.pointee))
  return cachePointer.advanced(by: offset*MemoryLayout<UnsafeMutableRawPointer>.size).assumingMemoryBound(to: UnsafeMutableRawPointer.self)
}
{...}

OtherClass.swift

class OtherClass {
  init() { } // vtable 0

  // vtable 1
  func hookThisFunctionToTestMSHookDetection() -> Bool { 
    print("hookThisFunctionToTestMSHookDetection")
    return false
  }

  // vtable 2
  @objc dynamic func hookThisFunctionToTestRuntimeHookDetection() -> Bool {
    print("hookThisFunctionToTestRuntimeHookDetection")
    return false
  }
}

I noticed that the address really changes Address from function's type -> 0x00000001008dacd0 Address from vtable -> 0x000000010091ab18

Unfortunately the behavior is the same :( hookThisFunctionToTestMSHookDetection is returning true, but MSHookChecker keeps returning false.

P.S.: If it help, I noticed the MSHookFunctionChecker always return nil in translateInstruction func in both cases

TannerJin commented 2 years ago

hi, @izmcm This is just a demo of my previous Swift learning,it's not very accurate. you can use v-table to get the function address by referring to https://github.com/johnno1962/SwiftTrace#how-it-works

izmcm commented 2 years ago

Hey @TannerJin, I used the three methods of getting the function address to group all the results together for readability. My code looks like this

  @IBAction func callMSHook(_ sender: Any) {
    let funcHook = otherClass.hookThisFunctionToTestMSHookDetection()
    print("hookThisFunctionToTestMSHookDetection should return false and is returning \(funcHook)")

    let funcAddrVtable = getSwiftFunctionAddressFromVtable(OtherClass.self, methodIndex: 1)!
    let amIMSHookedVtable = IOSSecuritySuite.amIMSHooked(funcAddrVtable)

    print("Address from manual vtable: \(funcAddrVtable)")
    print("amIMSHooked with address from manual vtable: \(amIMSHookedVtable)")

    let funcAddrType = getSwiftFunctionAddr(OtherClass.hookThisFunctionToTestMSHookDetection)
    let amIMSHookedType = IOSSecuritySuite.amIMSHooked(funcAddrType)

    print("Address from FunctionType method: \(funcAddrType)")
    print("amIMSHooked with address from FunctionType method \(amIMSHookedType)")

    SwiftTrace.forEachVTableEntry(ofClass: OtherClass.self, callback: { symname, slotIndex, vtableSlot, stop in
      print("Address from SwiftTrace vtable at index \(slotIndex): \(vtableSlot)")
      let amIMSHookedSwiftTrace = IOSSecuritySuite.amIMSHooked(vtableSlot)
      print("amIMSHooked with address from SwiftTrace vtable at index \(slotIndex): \(amIMSHookedSwiftTrace)")
    })
  }

The result obtained in the console is below. As we can see, the first line shows that the function must return false, but it returns true. The second and third lines show the address captured by the stackoverflow function using vtable and the result of calling iOSSecuritySwift for that case. The fourth and fifth lines show the same thing through the method described in the README. The next six lines show the forEach of the function implemented by SwiftTrace for each of the methods described in OtherClass. It is worth noting that the address obtained by SwiftTrace is the same one obtained by the stackoverflow function. Unfortunately, in neither case amIMSHooked returns true

hookThisFunctionToTestMSHookDetection should return false and is returning true
Address from manual vtable: 0x0000000100a1eac0
amIMSHooked with address from manual vtable: false
Address from FunctionType method: 0x00000001009dfb9c
amIMSHooked with address from FunctionType method false
Address from SwiftTrace vtable at index 1: 0x0000000100a1eab8
amIMSHooked with address from SwiftTrace vtable at index 1: false
Address from SwiftTrace vtable at index 2: 0x0000000100a1eac0
amIMSHooked with address from SwiftTrace vtable at index 2: false
Address from SwiftTrace vtable at index 3: 0x0000000100a1eac8
amIMSHooked with address from SwiftTrace vtable at index 3: false
izmcm commented 2 years ago

Hey @TannerJin, is there any update about this? 😄

KeiroMidori commented 2 years ago

@izmcm were you able to prevent Frida from dynamically injecting in your app ? I've tried ISS but came to the same conclusions as you, it can't prevent it. Did you use any other tool to achieve this?

izmcm commented 2 years ago

@KeiroMidori I noticed that ISS Reverse Engineering module can detect if Frida is installed on device, but I couldn't detect more than that. I couldn't use ISS to detect hook with Frida or with Theos