转载自Sakura的博客:https://eternalsakura13.com/2020/07/04/fridajava
致谢
本篇文章学到的内容来自且彻底来自r0ysue的知识星球,推荐一下(这个男人啥都会,还能陪你在线撩骚)。
python

Frida环境
https://github.com/frida/fridaandroid
pyenv
python全版本随机切换,这里提供macOS上的配置方法c++
1brew update
2brew install pyenv
3echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bash_profile
1下载一个3.8.2,下载真的很慢,要慢慢等
2pyenv install 3.8.2
3
4pyenv versions
5sakura@sakuradeMacBook-Pro:~$ pyenv versions
6 system
7* 3.8.2 (set by /Users/sakura/.python-version)
8切换到咱们装的
9pyenv local 3.8.2
10python -V
11pip -V
12本来系统自带的
13python local system
14python -V
另外当你须要临时禁用pyenv的时候
git

把这个注释了而后另开终端就行了。github
关于卸载某个python版本web
1Uninstalling Python Versions
2As time goes on, you will accumulate Python versions in your $(pyenv root)/versions directory.
3
4To remove old Python versions, pyenv uninstall command to automate the removal process.
5
6Alternatively, simply rm -rf the directory of the version you want to remove. You can find the directory of a particular Python version with the pyenv prefix command, e.g. pyenv prefix 2.6.8.
frida安装
若是直接按下述安装则会直接安装frida和frida-tools的最新版本。sql
1pip install frida-tools
2frida --version
3frida-ps --version
咱们也能够自由安装旧版本的frida,例如12.8.0shell
1pyenv install 3.7.7
2pyenv local 3.7.7
3pip install frida==12.8.0
4pip install frida-tools==5.3.0
老版本frida和对应关系
对应关系很好找
数据库

安装objection
1pyenv local 3.8.2
2pip install objection
3objection -h
1pyenv local 3.7.7
2pip install objection==1.8.4
3objection -h
frida使用
下载frida-server并解压,在这里下载frida-server-12.8.0
先adb shell,而后切换到root权限,把以前push进来的frida server改个名字叫fs
而后运行frida
1adb push /Users/sakura/Desktop/lab/alpha/tools/android/frida-server-12.8.0-android-arm64 /data/local/tmp
2chmod +x fs
3./fs
若是要监听端口,就
1./fs -l 0.0.0.0:8888
frida开发环境搭建
安装
1git clone https://github.com/oleavr/frida-agent-example.git
2cd frida-agent-example/
3npm install
使用vscode打开此工程,在agent文件夹下编写js,会有智能提示。
npm run watch
会监控代码修改自动编译生成js文件python脚本或者cli加载_agent.js
frida -U -f com.example.android --no-pause -l _agent.js
下面是测试脚本
s1.js
1function main() {
2 Java.perform(function x() {
3 console.log("sakura")
4 })
5}
6setImmediate(main)
loader.py
1import time
2import frida
3
4device8 = frida.get_device_manager().add_remote_device("192.168.0.9:8888")
5pid = device8.spawn("com.android.settings")
6device8.resume(pid)
7time.sleep(1)
8session = device8.attach(pid)
9with open("si.js") as f:
10 script = session.create_script(f.read())
11script.load()
12input() #等待输入
解释一下,这个脚本就是先经过frida.get_device_manager().add_remote_device
来找到device,而后spawn方式启动settings,而后attach到上面,并执行frida脚本。
FRIDA基础
frida查看当前存在的进程
frida-ps -U
查看经过usb链接的android手机上的进程。
1sakura@sakuradeMacBook-Pro:~$ frida-ps --help
2Usage: frida-ps [options]
3
4Options:
5 --version show program's version number and exit
6 -h, --help show this help message and exit
7 -D ID, --device=ID connect to device with the given ID
8 -U, --usb connect to USB device
9 -R, --remote connect to remote frida-server
10 -H HOST, --host=HOST connect to remote frida-server on HOST
11 -a, --applications list only applications
12 -i, --installed include all installed applications
1sakura@sakuradeMacBook-Pro:~$ frida-ps -U
2 PID Name
3----- ---------------------------------------------------
4 3640 ATFWD-daemon
5 707 adbd
6 728 adsprpcd
726041 android.hardware.audio@2.0-service
8 741 android.hardware.biometrics.fingerprint@
经过grep过滤就能够找到咱们想要的包名。
frida打印参数和修改返回值
1package myapplication.example.com.frida_demo;
2
3import android.support.v7.app.AppCompatActivity;
4import android.os.Bundle;
5import android.util.Log;
6
7public class MainActivity extends AppCompatActivity {
8
9 private String total = "@@@###@@@";
10
11 @Override
12 protected void onCreate(Bundle savedInstanceState) {
13 super.onCreate(savedInstanceState);
14 setContentView(R.layout.activity_main);
15
16 while (true){
17
18 try {
19 Thread.sleep(1000);
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23
24 fun(50,30);
25 Log.d("sakura.string" , fun("LoWeRcAsE Me!!!!!!!!!"));
26 }
27 }
28
29 void fun(int x , int y ){
30 Log.d("sakura.Sum" , String.valueOf(x+y));
31 }
32
33 String fun(String x){
34 total +=x;
35 return x.toLowerCase();
36 }
37
38 String secret(){
39 return total;
40 }
41}
1function main() {
2 console.log("Enter the Script!");
3 Java.perform(function x() {
4 console.log("Inside Java perform");
5 var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");
6 // 重载找到指定的函数
7 MainActivity.fun.overload('java.lang.String').implementation = function (str) {
8 //打印参数
9 console.log("original call : str:" + str);
10 //修改结果
11 var ret_value = "sakura";
12 return ret_value;
13 };
14 })
15}
16setImmediate(main);
1sakura@sakuradeMacBook-Pro:~$ frida-ps -U | grep frida
28738 frida-helper-32
38897 myapplication.example.com.frida_demo
4
5// -f是经过spawn,也就是重启apk注入js
6sakura@sakuradeMacBook-Pro:~$ frida -U -f myapplication.example.com.frida_demo -l frida_demo.js
7...
8original call : str:LoWeRcAsE Me!!!!!!!!!
912-21 04:46:49.875 9594-9594/myapplication.example.com.frida_demo D/sakura.string: sakura
frida寻找instance,主动调用。
1function main() {
2 console.log("Enter the Script!");
3 Java.perform(function x() {
4 console.log("Inside Java perform");
5 var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");
6 //overload 选择被重载的对象
7 MainActivity.fun.overload('java.lang.String').implementation = function (str) {
8 //打印参数
9 console.log("original call : str:" + str);
10 //修改结果
11 var ret_value = "sakura";
12 return ret_value;
13 };
14 // 寻找类型为classname的实例
15 Java.choose("myapplication.example.com.frida_demo.MainActivity", {
16 onMatch: function (x) {
17 console.log("find instance :" + x);
18 console.log("result of secret func:" + x.secret());
19 },
20 onComplete: function () {
21 console.log("end");
22 }
23 });
24 });
25}
26setImmediate(main);
frida rpc
1function callFun() {
2 Java.perform(function fn() {
3 console.log("begin");
4 Java.choose("myapplication.example.com.frida_demo.MainActivity", {
5 onMatch: function (x) {
6 console.log("find instance :" + x);
7 console.log("result of fun(string) func:" + x.fun(Java.use("java.lang.String").$new("sakura")));
8 },
9 onComplete: function () {
10 console.log("end");
11 }
12 })
13 })
14}
15rpc.exports = {
16 callfun: callFun
17};
1import time
2import frida
3
4device = frida.get_usb_device()
5pid = device.spawn(["myapplication.example.com.frida_demo"])
6device.resume(pid)
7time.sleep(1)
8session = device.attach(pid)
9with open("frida_demo_rpc_call.js") as f:
10 script = session.create_script(f.read())
11
12def my_message_handler(message, payload):
13 print(message)
14 print(payload)
15
16script.on("message", my_message_handler)
17script.load()
18
19script.exports.callfun()
1sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ python frida_demo_rpc_loader.py
2begin
3find instance :myapplication.example.com.frida_demo.MainActivity@1d4b09d
4result of fun(string):sakura
5end
frida动态修改
即将手机上的app的内容发送到PC上的frida python程序,而后处理后返回给app,而后app再作后续的流程,核心是理解send/recv
函数
1<TextView
2 android:id="@+id/textView"
3 android:layout_width="wrap_content"
4 android:layout_height="wrap_content"
5 android:text="please input username and password"
6 app:layout_constraintBottom_toBottomOf="parent"
7 app:layout_constraintLeft_toLeftOf="parent"
8 app:layout_constraintRight_toRightOf="parent"
9 app:layout_constraintTop_toTopOf="parent" />
10
11
12 <EditText
13 android:id="@+id/editText"
14 android:layout_width="fill_parent"
15 android:layout_height="40dp"
16 android:hint="username"
17 android:maxLength="20"
18 app:layout_constraintBottom_toBottomOf="parent"
19 app:layout_constraintEnd_toEndOf="parent"
20 app:layout_constraintHorizontal_bias="1.0"
21 app:layout_constraintStart_toStartOf="parent"
22 app:layout_constraintTop_toTopOf="parent"
23 app:layout_constraintVertical_bias="0.095" />
24
25 <EditText
26 android:id="@+id/editText2"
27 android:layout_width="fill_parent"
28 android:layout_height="40dp"
29 android:hint="password"
30 android:maxLength="20"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintTop_toTopOf="parent"
33 app:layout_constraintVertical_bias="0.239" />
34
35 <Button
36 android:id="@+id/button"
37 android:layout_width="100dp"
38 android:layout_height="35dp"
39 android:layout_gravity="right|center_horizontal"
40 android:text="提交"
41 android:visibility="visible"
42 app:layout_constraintBottom_toBottomOf="parent"
43 app:layout_constraintEnd_toEndOf="parent"
44 app:layout_constraintStart_toStartOf="parent"
45 app:layout_constraintTop_toTopOf="parent"
46 app:layout_constraintVertical_bias="0.745" />
1public class MainActivity extends AppCompatActivity {
2
3 EditText username_et;
4 EditText password_et;
5 TextView message_tv;
6
7 @Override
8 protected void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.activity_main);
11
12 password_et = (EditText) this.findViewById(R.id.editText2);
13 username_et = (EditText) this.findViewById(R.id.editText);
14 message_tv = ((TextView) findViewById(R.id.textView));
15
16 this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
17 @Override
18 public void onClick(View v) {
19
20 if (username_et.getText().toString().compareTo("admin") == 0) {
21 message_tv.setText("You cannot login as admin");
22 return;
23 }
24 //hook target
25 message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));
26
27 }
28 });
29
30 }
31}
先分析问题,个人最终目标是让message_tv.setText能够"发送"username为admin的base64字符串。
那确定是hook TextView.setText这个函数。
1console.log("Script loaded successfully ");
2Java.perform(function () {
3 var tv_class = Java.use("android.widget.TextView");
4 tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {
5 var string_to_send = x.toString();
6 var string_to_recv;
7 send(string_to_send); // send data to python code
8 recv(function (received_json_object) {
9 string_to_recv = received_json_object.my_data
10 console.log("string_to_recv: " + string_to_recv);
11 }).wait(); //block execution till the message is received
12 var my_string = Java.use("java.lang.String").$new(string_to_recv);
13 this.setText(my_string);
14 }
15});
1import time
2import frida
3import base64
4
5def my_message_handler(message, payload):
6 print(message)
7 print(payload)
8 if message["type"] == "send":
9 print(message["payload"])
10 data = message["payload"].split(":")[1].strip()
11 print( 'message:', message)
12 #data = data.decode("base64")
13 #data = data
14 data = str(base64.b64decode(data))
15 print( 'data:',data)
16 user, pw = data.split(":")
17 print( 'pw:',pw)
18 #data = ("admin" + ":" + pw).encode("base64")
19 data = str(base64.b64encode(("admin" + ":" + pw).encode()))
20 print( "encoded data:", data)
21 script.post({"my_data": data}) # send JSON object
22 print( "Modified data sent")
23
24device = frida.get_usb_device()
25pid = device.spawn(["myapplication.example.com.frida_demo"])
26device.resume(pid)
27time.sleep(1)
28session = device.attach(pid)
29with open("frida_demo2.js") as f:
30 script = session.create_script(f.read())
31script.on("message", my_message_handler)
32script.load()
33input()
1sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ python frida_demo_rpc_loader2.py
2Script loaded successfully
3{'type': 'send', 'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'}
4None
5Sending to the server :c2FrdXJhOjEyMzQ1Ng==
6
7message: {'type': 'send', 'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'}
8data: b'sakura:123456'
9pw: 123456'
10encoded data: b'YWRtaW46MTIzNDU2Jw=='
11Modified data sent
12string_to_recv: b'YWRtaW46MTIzNDU2Jw=='
13
参考连接:https://github.com/Mind0xP/Frida-Python-Binding
API List
Java.choose(className: string, callbacks: Java.ChooseCallbacks): void
经过扫描Java VM的堆来枚举className类的live instance。Java.use(className: string): Java.Wrapper<{}>
动态为className生成JavaScript Wrapper,能够经过调用$new()
来调用构造函数来实例化对象。
在实例上调用$dispose()
以对其进行显式清理,或者等待JavaScript对象被gc。Java.perform(fn: () => void): void
Function to run while attached to the VM.
Ensures that the current thread is attached to the VM and calls fn. (This isn't necessary in callbacks from Java.)
Will defer calling fn if the app's class loader is not available yet. Use Java.performNow() if access to the app's classes is not needed.send(message: any, data?: ArrayBuffer | number[]): void
任何JSON可序列化的值。
将JSON序列化后的message发送到您的基于Frida的应用程序,并包含(可选)一些原始二进制数据。
The latter is useful if you e.g. dumped some memory using NativePointer#readByteArray().recv(callback: MessageCallback): MessageRecvOperation
Requests callback to be called on the next message received from your Frida-based application.
This will only give you one message, so you need to call recv() again to receive the next one.wait(): void
堵塞,直到message已经receive而且callback已经执行完毕并返回
Frida动静态结合分析
Objection
参考这篇文章
实用FRIDA进阶:内存漫游、hook anywhere、抓包objection
https://pypi.org/project/objection/
objection启动并注入内存
objection -d -g package_name explore
1sakura@sakuradeMacBook-Pro:~$ objection -d -g com.android.settings explore
2[debug] Agent path is: /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages/objection/agent.js
3[debug] Injecting agent...
4Using USB device `Google Pixel`
5[debug] Attempting to attach to process: `com.android.settings`
6[debug] Process attached!
7Agent injected and responds ok!
8
9 _ _ _ _
10 ___| |_|_|___ ___| |_|_|___ ___
11| . | . | | -_| _| _| | . | |
12|___|___| |___|___|_| |_|___|_|_|
13 |___|(object)inject(ion) v1.8.4
14
15 Runtime Mobile Exploration
16 by: @leonjza from @sensepost
17
18[tab] for command suggestions
19com.android.settings on (google: 8.1.0) [usb] #
objection memory
查看内存中加载的module `memory list modules`
1com.android.settings on (google: 8.1.0) [usb] # memory list modules
2Save the output by adding `--json modules.json` to this command
3Name Base Size Path
4----------------------------------------------- ------------ -------------------- ---------------------------------------------------------------
5app_process64 0x64ce143000 32768 (32.0 KiB) /system/bin/app_process64
6libandroid_runtime.so 0x7a90bc3000 1990656 (1.9 MiB) /system/lib64/libandroid_runtime.so
7libbinder.so 0x7a9379f000 557056 (544.0 KiB) /system/lib64/libbinder.so
查看库的导出函数 `memory list exports libssl.so`
1com.android.settings on (google: 8.1.0) [usb] # memory list exports libssl.so
2Save the output by adding `--json exports.json` to this command
3Type Name Address
4-------- ----------------------------------------------------- ------------
5function SSL_use_certificate_ASN1 0x7c8ff006f8
6function SSL_CTX_set_dos_protection_cb 0x7c8ff077b8
7function SSL_SESSION_set_ex_data 0x7c8ff098f4
8function SSL_CTX_set_session_psk_dhe_timeout 0x7c8ff0a754
9function SSL_CTX_sess_accept 0x7c8ff063b8
10function SSL_select_next_proto 0x7c8ff06a74
dump内存空间
memory dump all 文件名
memory dump from_base 起始地址 字节数 文件名
搜索内存空间
Usage: memory search "
objection android
内存堆搜索实例 `android heap search instances 类名`
在堆上搜索类的实例
1sakura@sakuradeMacBook-Pro:~$ objection -g myapplication.example.com.frida_demo explore
2Using USB device `Google Pixel`
3Agent injected and responds ok!
4
5[usb] # android heap search instances myapplication.example.com.frida_demo
6.MainActivity
7Class instance enumeration complete for myapplication.example.com.frida_demo.MainActivity
8Handle Class toString()
9-------- ------------------------------------------------- ---------------------------------------------------------
100x2102 myapplication.example.com.frida_demo.MainActivity myapplication.example.com.frida_demo.MainActivity@5b1b0af
调用实例的方法 `android heap execute 实例ID 实例方法`
查看当前可用的activity或者service `android hooking list activities/services`
直接启动activity或者服务 `android intent launch_activity/launch_service activity/服务`
android intent launch_activity com.android.settings.DisplaySettings
这个命令比较有趣的是用在若是有些设计的很差,可能就直接绕过了密码锁屏等直接进去。
1com.android.settings on (google: 8.1.0) [usb] # android hooking list services
2com.android.settings.SettingsDumpService
3com.android.settings.TetherService
4com.android.settings.bluetooth.BluetoothPairingService
列出内存中全部的类 `android hooking list classes`
在内存中全部已加载的类中搜索包含特定关键词的类。`android hooking search classes display`
1com.android.settings on (google: 8.1.0) [usb] # android hooking search classes display
2[Landroid.icu.text.DisplayContext$Type;
3[Landroid.icu.text.DisplayContext;
4[Landroid.view.Display$Mode;
5android.hardware.display.DisplayManager
6android.hardware.display.DisplayManager$DisplayListener
7android.hardware.display.DisplayManagerGlobal
内存中搜索指定类的全部方法 `android hooking list class_methods 类名`
1com.android.settings on (google: 8.1.0) [usb] # android hooking list class_methods java.nio.charset.Charset
2private static java.nio.charset.Charset java.nio.charset.Charset.lookup(java.lang.String)
3private static java.nio.charset.Charset java.nio.charset.Charset.lookup2(java.lang.String)
4private static java.nio.charset.Charset java.nio.charset.Charset.lookupViaProviders(java.lang.String)
在内存中全部已加载的类的方法中搜索包含特定关键词的方法 `android hooking search methods display`
知道名字开始在内存里搜就颇有用
1com.android.settings on (google: 8.1.0) [usb] # android hooking search methods display
2Warning, searching all classes may take some time and in some cases, crash the target application.
3Continue? [y/N]: y
4Found 5529 classes, searching methods (this may take some time)...
5android.app.ActionBar.getDisplayOptions
6android.app.ActionBar.setDefaultDisplayHomeAsUpEnabled
7android.app.ActionBar.setDisplayHomeAsUpEnabled
hook类的方法(hook类里的全部方法/具体某个方法)
android hooking watch class 类名
这样就能够hook这个类里面的全部方法,每次调用都会被log出来。android hooking watch class 类名 --dump-args --dump-backtrace --dump-return
在上面的基础上,额外dump参数,栈回溯,返回值
1android hooking watch class xxx.MainActivity --dump-args --dump-backtrace --dump-return
android hooking watch class_method 方法名
1//能够直接hook到全部重载
2android hooking watch class_method xxx.MainActivity.fun --dump-args --dump-backtrace --dump-return
grep trick和文件保存
objection log默认是不能用grep过滤的,可是能够经过objection run xxx | grep yyy的
方式,从终端经过管道来过滤。
用法以下
1sakura@sakuradeMacBook-Pro:~$ objection -g com.android.settings run memory list modules | grep libc
2Warning: Output is not to a terminal (fd=1).
3libcutils.so 0x7a94a1c000 81920 (80.0 KiB) /system/lib64/libcutils.so
4libc++.so 0x7a9114e000 983040 (960.0 KiB) /system/lib64/libc++.so
5libc.so 0x7a9249d000 892928 (872.0 KiB) /system/lib64/libc.so
6libcrypto.so 0x7a92283000 1155072 (1.1 MiB) /system/lib64/libcrypto.so
有的命令后面能够经过--json logfile
来直接保存结果到文件里。
有的能够经过查看.objection
文件里的输出log来查看结果。
1sakura@sakuradeMacBook-Pro:~/.objection$ cat *log | grep -i display
2android.hardware.display.DisplayManager
3android.hardware.display.DisplayManager$DisplayListener
4android.hardware.display.DisplayManagerGlobal
案例学习
案例学习case1:《仿VX数据库原型取证逆向分析》
附件连接
android-backup-extractor工具连接
1sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn$ java -version
2java version "1.8.0_141"
3
4sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn$ java -jar abe-all.jar unpack 1.ab 1.tar
50% 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% 65% 66% 67% 68% 69% 70% 71% 72% 73% 74% 75% 76% 77% 78% 79% 80% 81% 82% 83% 84% 85% 86% 87% 88% 89% 90% 91% 92% 93% 94% 95% 96% 97% 98% 99% 100%
69097216 bytes written to 1.tar.
7
8...
9sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn/apps/com.example.yaphetshan.tencentwelcome$ ls
10Encryto.db _manifest a db
装个夜神模拟器玩
1sakura@sakuradeMacBook-Pro:/Applications/NoxAppPlayer.app/Contents/MacOS$ ./adb connect 127.0.0.1:62001
2* daemon not running. starting it now on port 5037 *
3adb E 5139 141210 usb_osx.cpp:138] Unable to create an interface plug-in (e00002be)
4* daemon started successfully *
5connected to 127.0.0.1:62001
6sakura@sakuradeMacBook-Pro:/Applications/NoxAppPlayer.app/Contents/MacOS$ ./adb shell
7dream2qltechn:/ # whoami
8root
9dream2qltechn:/ # uname -a
10Linux localhost 4.0.9+ #222 SMP PREEMPT Sat Mar 14 18:24:36 HKT 2020 i686


确定仍是先定位目标字符串Wait a Minute,What was happend?
jadx搜索字符串

重点在a()代码里,实际上是根据明文的name和password,而后aVar.a(a2 + aVar.b(a2, contentValues.getAsString("password"))).substring(0, 7)
再作一遍复杂的计算并截取7位当作密码,传入getWritableDatabase去解密demo.db数据库。
因此咱们hook一下getWritableDatabase便可。
1frida-ps -U
2...
35662 com.example.yaphetshan.tencentwelcome
4
5
6objection -d -g com.example.yaphetshan.tencentwelcome explore
看一下源码
1package net.sqlcipher.database;
2...
3public abstract class SQLiteOpenHelper {
4 ...
5 public synchronized SQLiteDatabase getWritableDatabase(char[] cArr) {
也能够objection search一下这个method
1...mple.yaphetshan.tencentwelcome on (samsung: 7.1.2) [usb] # android hooking search methods getWritableDatabase
2Warning, searching all classes may take some time and in some cases, crash the target application.
3Continue? [y/N]: y
4Found 4650 classes, searching methods (this may take some time)...
5
6android.database.sqlite.SQLiteOpenHelper.getWritableDatabase
7...
8net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase
hook一下这个method
1[usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return
2- [incoming message] ------------------
3{
4 "payload": "Attempting to watch class \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m and method \u001b[32mgetWritableDatabase\u001b[39m.",
5 "type": "send"
6}
7- [./incoming message] ----------------
8(agent) Attempting to watch class net.sqlcipher.database.SQLiteOpenHelper and method getWritableDatabase.
9- [incoming message] ------------------
10{
11 "payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31mjava.lang.String\u001b[39m)",
12 "type": "send"
13}
14- [./incoming message] ----------------
15(agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String)
16- [incoming message] ------------------
17{
18 "payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31m[C\u001b[39m)",
19 "type": "send"
20}
21- [./incoming message] ----------------
22(agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase([C)
23- [incoming message] ------------------
24{
25 "payload": "Registering job \u001b[94mjytq1qeyllq\u001b[39m. Type: \u001b[92mwatch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase\u001b[39m",
26 "type": "send"
27}
28- [./incoming message] ----------------
29(agent) Registering job jytq1qeyllq. Type: watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase
30...mple.yaphetshan.tencentwelcome on (samsung: 7.1.2) [usb] #
hook好以后再打开这个apk


1(agent) [1v488x28gcs] Called net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String)
2...
3(agent) [1v488x28gcs] Backtrace:
4 net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(Native Method)
5 com.example.yaphetshan.tencentwelcome.MainActivity.a(MainActivity.java:55)
6 com.example.yaphetshan.tencentwelcome.MainActivity.onCreate(MainActivity.java:42)
7 android.app.Activity.performCreate(Activity.java:6692)
8...
9(agent) [1v488x28gcs] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99)
10
11...
12...mple.yaphetshan.tencentwelcome on (samsung: 7.1.2) [usb] # jobs list
13Job ID Hooks Type
14----------- ------- -----------------------------------------------------------------------------
151v488x28gcs 2 watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase
找到参数ae56f99
剩下的就是用这个密码去打开加密的db。

而后base64解密一下就行了。
还有一种策略是主动调用,基于数据流的主动调用分析是很是有意思的。
即本身去调用a函数以触发getWritableDatabase的数据库解密。
先寻找a所在类的实例,而后hook getWritableDatabase,最终主动调用a。
这里幸运的是a没有什么奇奇怪怪的参数须要咱们传入,主动调用这种策略在循环注册等地方可能就会有需求8.
1 [usb] # android heap search instances com.example.yaphetshan.tencentwelcome.MainActivity
2Class instance enumeration complete for com.example.yaphetshan.tencentwelcome.MainActivity
3Handle Class toString()
4-------- -------------------------------------------------- ----------------------------------------------------------
50x20078a com.example.yaphetshan.tencentwelcome.MainActivity com.example.yaphetshan.tencentwelcome.MainActivity@1528f80
6
7 [usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return
8
9[usb] # android heap execute 0x20078a a
10
11(agent) [taupgwkum4h] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99)
案例学习case2:主动调用爆破密码
附件连接

由于直接找Unfortunately,note the right PIN :(
找不到,多是把字符串藏在什么资源文件里了。
review代码以后找到校验的核心函数,逻辑就是将input编码一下以后和密码比较,这确定是什么不可逆的加密。
1 public static boolean verifyPassword(Context context, String input) {
2 if (input.length() != 4) {
3 return false;
4 }
5 byte[] v = encodePassword(input);
6 byte[] p = "09042ec2c2c08c4cbece042681caf1d13984f24a".getBytes();
7 if (v.length != p.length) {
8 return false;
9 }
10 for (int i = 0; i < v.length; i++) {
11 if (v[i] != p[i]) {
12 return false;
13 }
14 }
15 return true;
16 }
这里就爆破一下密码。
1frida-ps -U | grep qualification
27660 org.teamsik.ahe17.qualification.easy
3
4frida -U org.teamsik.ahe17.qualification.easy -l force.js
1function main() {
2 Java.perform(function x() {
3 console.log("In Java perform")
4 var verify = Java.use("org.teamsik.ahe17.qualification.Verifier")
5 var stringClass = Java.use("java.lang.String")
6 var p = stringClass.$new("09042ec2c2c08c4cbece042681caf1d13984f24a")
7 var pSign = p.getBytes()
8 // var pStr = stringClass.$new(pSign)
9 // console.log(parseInt(pStr))
10 for (var i = 999; i < 10000; i++){
11 var v = stringClass.$new(String(i))
12 var vSign = verify.encodePassword(v)
13 if (parseInt(stringClass.$new(pSign)) == parseInt(stringClass.$new(vSign))) {
14 console.log("yes: " + v)
15 break
16 }
17 console.log("not :" + v)
18 }
19 })
20}
21setImmediate(main)
1...
2not :9080
3not :9081
4not :9082
5yes: 9083
这里注意parseInt

关注小白技术社,开启爬虫工程师的app逆向之路。
恭喜你完成Frida三部曲的第一部,点个在看告诉你们吧
本文分享自微信公众号 - 小白技术社(xbjss123)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。