作者:FloatingGuy 转载请注明出处:https://floatingguy.github.io/


Frida 支持的系统包括:

  • Android(root/非 root),
  • iOS(越狱/非越狱),
  • MacOS(关闭 SIP),
  • Linux, Windows等

Frida wiki 的这张表Hook工具兼容性对比 充分说明了frida的强大。

Frida 开发语言:JavaScript+python/C/objc

支持的系统版本:Android(<7.1)

目前主要的问题

  • 只能 hook so 中导出的函数,像 JNINativeInterface 中 函数指针就无法 hook
  • 不是很稳定

优点

  • 可以动态植入 hook 代码
  • java/native/系统库 均可以 hook

安装 & 配置

二进制frida 库: 从 realeas 下载
python模块: 从 pypi 下载

Device
$ adb push frida-server /data/local/tmp/
$ adb shell
# su
# cd /data/local/tmp/
# chmod 777 frida-server
# ./frida-server &

PC
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

python && js 开发Api

import frida
导入 python 模块

获取 USB 设备

1
2
3
4
try:
dev = frida.get_usb_device()
except:
errlog('没有设备')

返回 frida.core.Device 对象。
Device(id="emulator-5554", name="Android Emulator 5554", type='tether')

还可以获取 -R(Remote设备), -D (根据设备 ID 获取)。。

获取进程信息(Andorid)

命令

1
2
3
4
frida-ps -U 查看usb设备进程列表
frida-ps -R 远程 frida-server 进程列表
frida-ps -Ua 运行中APP 应用列表(系统 app/用户 app)
frida-ps -Uai 所有已安装的 App 应用列表(系统 app/用户 app)

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当前最前端Activity所在的进程
front_app = dev.get_frontmost_application()
手机所有的进程
processes = dev.enumerate_processes()
枚举手机所有已安装的android APP应用
apps = dev.enumerate_applications()
for app in apps:
print app

枚举某进程的模块及模块中导出的函数

  • session.enumerate_modules()
  • module.enumerate_exports()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    枚举某个进程加载的所有模块以及模块中的导出函数,dex 中没有导出函数
    session = dev.attach("com.tencent.mm")
    modules = session.enumerate_modules()
    for module in modules:
    print module
    export_funcs = module.enumerate_exports()
    print "\tfunc_name\tRVA"
    for export_func in export_funcs:
    print "\t%s\t%s"%(export_func.name,hex(export_func.relative_address))

JavaScript API [补充]

这里只介绍我用过的 API,详情请看官方文档

Java

Hook Java 虚拟机,比如 Dalvik 或者 ART

  • Java.perform(fn) 只要frida 附加到目标进程的 VM 就会执行 fn 函数 (fn 是 js 函数)
    Java.perform(function() {…}) 栗子
  • Java.enumerateLoadedClasses(callbacks) 枚举已经加载的 Classes 栗子

    1
    2
    3
    4
    Java.enumerateLoadedClasses({
    "onMatch": function (className) {...},
    "onComplete":function(){... } // 枚举完成
    })
  • overload('xxx') 当要 hook 的函数有多个重载函数时,就需要通过 overload(目标函数参数类型)指定函数的参数列表 栗子

  • Java.use("package.xxx.className") 动态获取 className 对象 栗子, 可能使用出现的Bug
  • Java.choose("className", callbacks) 枚举所有存活的 className 对象,并调用 callbacks 函数 栗子
    1
    2
    3
    4
    Java.choose("className", {
    "onMatch": function(instance) {...},
    "onComplete":function(){... } // 枚举完成
    }

Interceptor

hook so/dylib native 函数

  • Interceptor.attach(target, callbacks) 附加到目标函数,当target 执行到时自动调用 callbacks 中的函数 栗子
    1
    2
    3
    4
    Interceptor.attach(target, {
    "onEnter": function (args) {...},
    "onLeave": function(retval){...}
    })

Memory

内存操作函数,内存地址的读写。

  • Memory.readCString(addr, 栗子
  • Memory.readUtf8String() 栗子
  • Memory.writeInt(addr, value)
  • Memory.writePointer(address, ptr)
  • Memory.protect(address, size, protection)

Memory.protect(ptr("0x1234"), 4096, 'rw-');

向so 库中指定偏移地址写入

1
2
3
4
5
6
7
8
//Get base address of library
var libfoo = Module.findBaseAddress("libfoo.so");
//Calculate address of variable
var initialized = libfoo.add(ptr("0x400C"));
//Write 1 to the variable
Memory.writeInt(initialized,1);

Module

  • Module.enumerateImports(name, callbacks) 目标进程某个模块的导入表,包含导入的类型,函数地址/变量地址,模块名,函数名/变量名 栗子

    1
    2
    3
    4
    Module.enumerateImports(moduleName, {
    "onMatch":function(imp) {...},
    "onComplete":function() {...} // 枚举完成
    });
  • Module.enumerateExports(name, callbacks) 目标进程某个模块的导出表, 包含导出的类型,函数地址/变量地址,模块名,函数名/变量名 栗子

    1
    2
    3
    4
    Module.enumerateExports(moduleName, {
    "onMatch":function(imp) {...},
    "onComplete":function() {...} // 枚举完成
    });

NativeFunction(address, returnType, argTypes[, abi])

创建一个native 函数对象。 目的值为了调用 原始的native函数。 栗子

NativeCallback(func, returnType, argTypes[, abi])

使用js 函数func 实现一个函数 栗子

RPC

  • rpc.exports 我的理解是这个功能是向 目标进程添加函数,方便 python 客户端直接调用;简单点说就是在目标进程写入自己的后门。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import codecs
    import frida
    def on_message(message, data):
    if message['type'] == 'send':
    print(message['payload'])
    elif message['type'] == 'error':
    print(message['stack'])
    rdev = frida.get_remote_device()
    session = rdev.attach('hello') #/data/local/tmp/hello
    with codecs.open('./agent.js', 'r', 'utf-8') as f:
    source = f.read()
    script = session.create_script(source)
    script.on('message', on_message)
    script.load()
    print(script.exports.disassemble(0x4000100))
    print(script.exports.disassemble(0x4000100))
    session.detach()
1
2
3
4
5
6
7
'use strict';
rpc.exports = {
disassemble(address) {
return Instruction.parse(ptr(address)).toString();
}
};

我测试的时候 都失败了, 还是去看看 frida-presentations PPT

Global

  • setImmediate(fn) 远程附加到进程时防止超时,可以先执行脚本(等待进程启动)
    1
    2
    3
    4
    setImmediate({
    console.log("[*] Starting script");
    Java.perform(function () {...}
    })

测试发现 这个方法好像不起作用。。

  • hexdump(traget [, options])dump 指定地址的内存, target可以是 NativePointer 或者ArrayBuffer 数组

options 包括(都是十进制):
offset: int,
length: int,
header: boolean,
ansi: boolean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var libc = Module.findBaseAddress('libc.so');
var buf = Memory.readByteArray(libc, 64);
console.log(hexdump(buf, {
offset: 0,
length: 64,
header: true,
ansi: true
}));
-----
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
00000010 03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00 ..(.........4...
00000020 34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00 4.......4. ...(.
00000030 1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00 ........4...4...

  • 空指针 NULL: short-hand for ptr("0")

输出信息

  • send(msg) 发送msg给 python 代码 script.on('message', on_message) 注册的 on_message 函数。 栗子
  • console.log('Done:' + JSON.stringify(this.cnt)); 向终端输出信息,并发送给script.on注册的回掉函数 栗子

完整的 Demo

这里推荐 Zhiwei Li 学习 Frida 的多篇 blog.

hook Native

Interceptor.attach
Memory.readCString
总结:
frida hook native 函数(地址)非常简单, 只需要知道 地址就可以通过 NativeCallback 或者 Interceptor.attach 随意hook。

基础案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import frida
import sys
#1. 获取Device 对象
rdev = frida.get_remote_device()
#2. 附加到设备中指定的进程
session = rdev.attach("com.tencent.mm")
# 设置要 hook 的模块和 导出的函数(上一步介绍了获取方法),然后注册 2个 hook 函数:onEnter,onLeave
scr = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
onEnter: function(args) {
send("open("+Memory.readCString(args[0])+","+args[1]+")");
},
onLeave:function(retval){
}
});
"""
#3. 创建hook 脚本
script = session.create_script(scr)
def on_message(message ,data):
print message
#4. 设置回掉函数
script.on("message" , on_message)
#5. 加载 hook 脚本
script.load()
#6. 终端用户交互
sys.stdin.read()

Hook 动态库中任意native 地址

此案例 可以用来hook so 中任意地址,哪怕不知道函数名 通过偏移找到地址就可以hook.(来源)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function do_native_hooks_libfoo(){
var p_foo = Module.findBaseAddress('libfoo.so');
if (!p_foo) {
send("p_foo is null (libfoo.so). Returning now...");
return 0;
}
var p_strncmp_xor64 = p_foo.add(offset_strncmp_xor64); // 目标函数的偏移地址
send("libfoo.so @ " + p_foo.toString());
send("ptr_strncmp_xor64 @ " + p_strncmp_xor64.toString());
Interceptor.attach( p_strncmp_xor64, {
onEnter: function (args) {
send("onEnter() p_strncmp_xor64");
send("args[0]: " + args[0]);
send(hexdump(args[0], {
offset: 0,
length: 24,
header: false,
ansi: true
}));
send("args[1]: " + args[1]);
var secret = hexdump(args[1], {
offset: 0,
length: 24,
header: false,
ansi: true
})
send(secret);
},
onLeave: function (retval) {
send("onLeave() p_strncmp_xor64");
send(retval);
}
});
}


替换导入表函数

使用js函数替换pthread_create 函数实现

  1. 获取 pthread_create 函数指针
  2. 使用 1获取的指针 创建另一个native 函数
  3. 定义 NativeCallback 回调函数,重载这个方法
  4. 使用 Interceptor 的 replace 模式 去注入并替换原始的 pthread_create 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var p_pthread_create = Module.findExportByName("libc.so", "pthread_create");
var pthread_create = new NativeFunction( p_pthread_create, "int", ["pointer", "pointer", "pointer", "pointer"]); // 将原始的pthread_create 封装,方便在js中调用
send("NativeFunction pthread_create() replaced @ " + pthread_create);
Interceptor.replace( p_pthread_create, new NativeCallback(function (ptr0, ptr1, ptr2, ptr3) {
send("pthread_create() overloaded");
var ret = ptr(0);
if (ptr1.isNull() && ptr3.isNull()) { //hook 到目标
send("loading fake pthread_create because ptr1 and ptr3 are equal to 0!");
} else {
send("loading real pthread_create()");
ret = pthread_create(ptr0,ptr1,ptr2,ptr3);
}
do_native_hooks_libfoo();
send("ret: " + ret);
}, "int", ["pointer", "pointer", "pointer", "pointer"]));

hook Java

hook java 同 hook native的代码(1-6步)完全一样,只是 javascript 部分不同,下面只列出 js 部分代码。
Java.use
send

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//设置要 hook 的 class, 重新实现 hook method,保证程序正常执行 并调用hook 前的 method.
jscode = """
Java.perform(function () {
// Function to hook is defined here
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
// Whenever button is clicked
MainActivity.onClick.implementation = function (v) {
// Show a message to know that the function got called
send('onClick');
// Call the original onClick handler
this.onClick(v);
// Set our values after running the original onClick handler
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// Log to the console that it's done, and we should have the flag!
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""

其实完全可以只使用 js 代码来完成整个 hook工作。下一节介绍 通过 frida-cli 命令就可以在 pc 终端上就可以完成hook 流程。

iOS 上使用Frida

在iOS上使用 Frida-cli 工具必须要在越狱的手机上安装fridaServer,如果没有越狱的化就只能使用FridaGadget 来hook 指定的APP 操作不是很方便。推荐在越狱的机器上使用。

从cydia 安装的frida是V10.0.6,但是Mac 上之前使用的是 frida 9.2.7。所以要对 Mac 上的frida 进行升级,升级过程出现了一个pip 的bug

参考文章:在iOS应用程序中使用Frida绕过越狱检测 介绍了如下的功能:

  • 在iOS上设置frida
  • frida hook DVIA 反越狱检测的案例

案例 (待补充)

目前在iOS 上基于frida 流行的2个工具:needle 和 AppMon

Needle是一个开源的模块化框架,主要简化iOS应用程序安全评估过程,并作为一个中心点。鉴于其模块化方法,Needle很容易扩展新模块,可以以Python脚本的形式加入。
地址:https://github.com/mwrlabs/needle

AppMon是监测和修改本地macOS、iOS、Android系统API的自动化框架,并能通过web接口显示和操作
地址:https://github.com/dpnishant/appmon

使用Frida-cli Hook [推荐]

上面是通过编写 python && js 混合脚本进行 hook, 下面介绍 frida 命令(js 函数)通过 CLI hook。 这种方法更 cool, 而且使用的命令就是上面的Api

这里分2种情况:

  1. 先启动一个应用,然后再让 Frida 进行附加
  2. 使用 -f <包名> 参数,让 Frida 自动生成进程,这种方法hook 的时机较早,可以用来 hook JNI_OnLoad 函数。

help

Frida附加进程

  1. 在设备上开启进程

  2. 执行命令load 脚本(提前编写好 js 脚本)
    frida -U -l xxx.js com.tencent.mm
    -l 注入脚本

如果进程不存在会显示BUG: Failed to attach: unable to find process with name ‘com.tencent.mm’

frida -U -l onCreate.js com.tencent.mm

hook android.view.View class

Java.perform
Java.choose
console

1
2
3
4
5
6
7
8
9
10
11
12
13
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function () {
Java.choose("android.view.View", {
"onMatch":function(instance){
console.log("[*] Instance found: " + instance.toString());
},
"onComplete":function() {
console.log("[*] Finished heap search")
}
});
});
});

hook View Class

Frida启动应用

以微信为例。
frida -U --no-pause -f com.tencent.mm
目标应用已经启动, 还可以添加 -l 参数在应用启动之前注入 hook 的代码。

测试文档中的几个命令

1
2
3
4
5
[USB::LGE AOSP on HammerHead::com.tencent.mm]-> Java
{
"androidVersion": "6.0.1",
"available": true
}

Hook android.app.Activity 的 onCreate(android.os.Bundle)

  • overload(params) 当要 hook 的函数有多个重载函数时,使用 overload 指定函数参数
    overload
    1
    2
    3
    4
    5
    6
    7
    Java.perform(function () {
    var Activity = Java.use("android.app.Activity");
    Activity.onCreate.overload('android.os.Bundle').implementation = function () {
    console.log("onCreate() got called! Let's call the original implementation");
    this.onCreate(arguments[0]);
    };
    });

重写 android.app.Activity::onCreate(android.os.Bundle)函数, overload 指定具体的 onCreate 版本。

frida cli hook onCreate

Java app Classes

(警告:此处会输出很多内容,后面我会解释代码的意思。):
Java.enumerateLoadedClasses

1
2
3
4
5
6
7
Java.perform(function() {
Java.enumerateLoadedClasses({
"onMatch":function(className){
console.log(className) },
"onComplete":function(){ }
})
})

Native Modules info


通过 js 构造原型对象类型发送给 on_message(), 方便on_message 同时处理有多个 hook 消息的情况。

  1. 查看 libbinder.so模块的导入表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Module.enumerateImports('libbinder.so', {
    "onMatch":function(imp) {
    var result = imp.type+" : "+ String(imp.address) +" "+ imp.module +" : "+ imp.name
    var foo = {
    fun: 'import',
    data: result
    }
    send(foo)
    },
    "onComplete":function() {
    var foo = {
    fun: 'import',
    data: '#!over'
    }
    send(foo)
    }
    });

  1. 查看 libbinder.so 模块的导出表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Module.enumerateExports('libbinder.so', {
    "onMatch":function(exp) {
    var result = exp.type+" : "+ String(exp.address) +" "+" : "+ exp.name
    var foo = {
    fun: 'export',
    data: result
    }
    send(foo)
    },
    "onComplete":function() {
    var foo = {
    fun: 'export',
    data: '#!over'
    }
    send(foo)
    }
    });

反调试

native层

监控pthread_create函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var p_pthread_create = Module.findExportByName("libc.so","pthread_create");
Interceptor.attach(ptr(p_pthread_create), {
onEnter: function (args) {
this.thread = args[0];
this.attr = args[1];
this.start_routine = args[2];
this.arg = args[3];
this.fakeRet = Boolean(0);
send("onEnter() pthread_create(" + this.thread.toString() + ", " + this.attr.toString() + ", "
+ this.start_routine.toString() + ", " + this.arg.toString() + ");");
if (parseInt(this.attr) == 0 && parseInt(this.arg) == 0)
this.fakeRet = Boolean(1);
},
onLeave: function (retval) {
send(retval);
send("onLeave() pthread_create");
if (this.fakeRet == 1) {
var fakeRet = ptr(0);
send("pthread_create real ret: " + retval);
send("pthread_create fake ret: " + fakeRet);
return fakeRet;
}
return retval;
}
});

监控 inotify

监控ptrace

java层

java 反反调试

反调试 最终一般都会选择退出进程,java 层通过System.exit()函数退出进程。

1
2
3
4
5
6
7
8
Java.perform(function () {
send("Placing Java hooks...");
var sys = Java.use("java.lang.System");
sys.exit.overload("int").implementation = function(var_0) {
send("java.lang.System.exit(I)V // We avoid exiting the application :)");
}
});

ios反调试

练习

hook dlopen函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// void *dlopen(const char *filename, int flags);
Interceptor.attach(Module.findExportByName("libc.so", "dlopen"), {
onEnter: function (args) {
this.filename = args[0];
this.flags = parseInt(args[1]);
},
onLeave: function (retval) {
filename = Memory.readUtf8String(this.filename);
send("onEnter() dlopen(\"" + filename + "\",\"" + this.flags + "\");");
if ( filename.indexOf("foo") != -1 ) {
send("do_native_hooks_libfoo now!");
}
}
});

打印 java 调用栈

通过 hook 目标函数,让其抛出 NullPointerException, 还要再开启一个终端,监控日志输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function () {
var Activity = Java.use("c8.KSp");
var nullclass = Java.use("java.lang.NullPointerException");
Activity.getMtopApiWBSign.implementation = function () {
// console.log("c8.CPc.d() !!!");
var sign = this.getMtopApiWBSign(arguments[0], arguments[1], arguments[2]);
var nullinstall = nullclass.$new()
console.log('Printing ...')
throw nullinstall
return sign
};
});
});

这里通过反射创建了 java.lang.NullPointerException 对象。

Terminal 2:
adb logcat | grep --color=auto $pid

nullpointstack

Frida 绕过 Android SSL Pinning

Android SSL Pinning 的目的是校验Https 通信中服务端的证书,防止攻击者进行中间人攻击。为了防止攻击者将自己的恶意证书加入到系统证书中,Android 提供了 SSL Pinning 技术可以在app中绑定服务端的证书,这样其他的证书就无法伪造服务端证书。

github 中有一个自实现Android SSL Pinning 的例子使用了Retrofit(类型安全的Http 客户端),我们分析下他的实现。

  • 原理:
  1. 获取服务端的证书
    openssl s_client -showcerts -connect api.github.com:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem 可以将api.github.com 替换成希望校验的证书的服务器, 最后会生成 mycertfile.pem 证书文件
  2. 转换证书文件到 .bks 文件
    将 pem/cert 格式的证书文件,转换成keystore文件(后缀.bks)。因为pem/cert 没有对应的api 来加载,所以要转换。
    keytool -importcert -v -trustcacerts -file "mycertfile.pem" -alias ca -keystore "keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-ext-jdk15on-1.46.jar" -storetype BKS -storepass testing
    转换成 keystore 文件时,如果添加了密钥,那么java.security.KeyStore 类在load 这个keystore 文件时 需要这个密钥。
  3. 代码中pinCertificates
    • 加载Keystore 文件到java.security.KeyStore
    • 使用keystore 对象初始化 CustomTrustManager
    • 使用keystore 对象初始化 KeyManagerFactory 类对象 (我猜这个对象里还包含了系统证书的信息)
    • 使用 KMF 和 用户定义的 CustomTrustManager 初始化 SSLContext
    • 从 SSLContext 获取 SSLSocketFactory 对象
      总结一下 keystore 的引用链:
      keystore文件->Keystore 对象 -> CustomTrustManager 对象 -> SSLContext 对象 -> SSLSocketFactory 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public RetrofitClientBuilder pinCertificates(InputStream resourceStream, char[] password) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, KeyManagementException {
KeyStore keyStore = KeyStore.getInstance(HttpClientBuilder.BOUNCY_CASTLE);
keyStore.load(resourceStream, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
TrustManager[] trustManagers = {new CustomTrustManager(keyStore)};
kmf.init(keyStore, password);
SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
sslContext.init(kmf.getKeyManagers(), trustManagers, null); [1]
okHttpClient.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
return this;
}

SSL Pinning 的原理 已经清楚,下面介绍如何 绕过pinning 的检测。
注意观察上述代码 [1] 处第二个参数 就是用来 校验的TrustManagers,如果我们通过hook 替换了sslContext.ini 函数的第二个参数,那么SSL Pinning 就失效了。

  • Frida脚本的工作:
  1. 从设备加载我们的 CA证书;
  2. 创建包含我们信任的CA证书的KeyStore;
  3. 创建一个TrustManager,使它信任我们的KeyStore中的CA证书。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
Android SSL Re-pinning frida script v0.2 030417-pier
$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt
$ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause
https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/
*/
setTimeout(function(){
Java.perform(function (){
console.log("");
console.log("[.] Cert Pinning Bypass/Re-Pinning");
var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
// Load CAs from an InputStream
console.log("[+] Loading our CA...")
cf = CertificateFactory.getInstance("X.509");
try {
var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
}
catch(err) {
console.log("[o] " + err);
}
var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);
bufferedInputStream.close();
var certInfo = Java.cast(ca, X509Certificate);
console.log("[o] Our CA Info: " + certInfo.getSubjectDN());
// Create a KeyStore containing our trusted CAs
console.log("[+] Creating a KeyStore for our CA...");
var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
console.log("[+] Our TrustManager is ready...");
console.log("[+] Hijacking SSLContext methods now...")
console.log("[-] Waiting for the app to invoke SSLContext.init()...")
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
console.log("[+] SSLContext initialized with our custom TrustManager!");
}
});
},0);

mac 上尝试

在 MacOS 使用 Firda 需要关闭 SIP

没有关闭的化 无法 attach 目标进程。

1
2
3
4
5
6
7
8
9
10
11
12
frida /Applications/Calculator.app/Contents/MacOS/Calculator -l app.js --debug
____
/ _ | Frida 9.1.27 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Failed to attach: unable to access process with pid 7480 from the current user account

Bug

  1. Error: Not allowed outside Java.perform() callback
    Java.use 放错了位置

通过测试发现 Java.use、Java.cast 这2个方法不能放在 Java.perform 中使用,否则就会有这个 bug。

扩展

介绍 javascript 之前没接触过这门语言,看 Frida 之前学习了一下语法。
在知乎上搜索了一些资料,最后选择了2个学习的网站:

JavaScript语法

1.对象

  • 原型对象

一个对象就是一个属性集合,并拥有一个独立的prototype(原型)对象。这个prototype可以是一个对象或者null。

1
2
3
4
var foo = {
x: 10,
y: 20
};

我们拥有一个这样的结构,两个明显的自身属性和一个隐含的proto属性,这个属性是对foo原型对象的引用

2.变量

有如下三类基础数据类型:

  • 数值类型:比如 123,120.50 等。
  • 字符串类型:比如“This text string”。
  • 布尔类型:比如 true or false。

  • 另外两个常用类型:null 和 undefined,这两个类型均仅限定一个单一的值。

还支持上面介绍的 对象类型.
注意:JAVA语言并区分整数类型与浮点类型。JavaScript 中的数值均使用浮点值来表示。同时,按照 IEEE754 标准,JavaScript 用64位浮点格式来表示数。

在 JavaScript 编程过程中,必须先声明一个变量,这个变量才能被使用。
此外,变量是通过 “var” 来声明的,例子如下:

1
2
3
4
5
6
7
<script type="text/javascript">
<!--
var money, type;
var name;
name = "Ali";
//-->
<\/script>

JavaScript 是一种无类型语言。这就是说, JavaScript 变量可以存储任何类型的值。与其他语言不同的是,我们不需要在变量声明阶段告诉变量其要存储的数据类型是什么。

JavaScript变量作用域

  • 全局变量:全局变量具有全部整体范围的作用域,这意味着它可以在 JavaScript 代码任何地方定义。
  • 局部变量:局部变量仅在定义它的函数体内可以访问到。函数参数对于函数来说就是局部变量。
1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
<!--
var myVar = "global"; // Declare a global variable
function checkscope( ) {
var myVar = "local"; // Declare a local variable
document.write(myVar);
}
//-->
<\/script>
// 输出:
// Local

JavaScript 保留的关键字

abstract else instanceof switch
boolean enum int synchronized
break export interface this
byte extends long throw
case FALSE native throws
catch final new transient
char finally null TRUE
class float package try
const for private typeof
continue function protected var
debugger goto public void
default if return volatile
delete implements short while
do import static with
double in super

3.运算符

  • << 称为按位左移运算符。它把第一个运算数的所有二进制位向左移动第二个运算数指定的位数,而新的二进制位补0。将一个数向左移动一个二进制位相当于将该数乘以2,向左移动两个二进制位相当于将该数乘以4,以此类推。 A << 1 = 4.

  • ‘>>’ 称为按位右移运算符。它把第一个运算数的所有二进制位向右移动第二个运算数指定的位数。为了保持运算结果的符号不变,左边二进制位补0或1取决于原参数的符号位。如果第一个运算数是正的,运算结果最高位补0;如果第一个运算数是负的,运算结果最高位补1。将一个数向右移动一位相当于将该数乘以2,向右移动两位相当于将该数乘以4,以此类推。 A >> 1 = 1.

  • ‘>>>’ 称为0补最高位无符号右移运算符。这个运算符与>>运算符相像,除了位移后左边总是补0. A >>> = 1.

4.函数

1
2
3
4
5
6
7
8
9
* 定义函数:
<script type="text/javascript">
<!--
function functionname(parameter-list)
{
statements
}
//-->
<\/script>

知识的搬运工今天都当到这里,剩下的就是 Coding

Frida 公开技术

这里给出翻译稿的链接,原文请自行查找
利用FRIDA攻击Android应用程序(一)
利用FRIDA攻击Android应用程序(二)
利用FRIDA攻击Android应用程序(三)
利用FRIDA攻击Android应用程序(四)
Example tool built for an Android CTF
非 Root 条件下,如何在 Android 上使用 Frida 框架
instrumenting-android-applications-with-frida
frida-presentations
基于 Frida 框架的 Objective-C 插桩方法
使用Frida绕过Android SSL Re-Pinning
利用Frida 给ios 内核patch , 绕过KPP
基于Frida 开发的Mac上动态调试工具 CryptoShark
(frida-gadget)在Android 非root环境上使用Frida

Reference:

overload 用法