惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Privacy International News Feed
T
Threatpost
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
NISL@THU
NISL@THU
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Jina AI
Jina AI
雷峰网
雷峰网
Know Your Adversary
Know Your Adversary
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
Intezer
博客园 - Franky
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
The Hacker News
The Hacker News
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tailwind CSS Blog
Project Zero
Project Zero
T
Tor Project blog
B
Blog RSS Feed
Recorded Future
Recorded Future
Scott Helme
Scott Helme
美团技术团队
V
V2EX
V
Visual Studio Blog
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
D
DataBreaches.Net
The Register - Security
The Register - Security
M
MIT News - Artificial intelligence
L
LangChain Blog
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园_首页
P
Privacy & Cybersecurity Law Blog

lazy_forever's Blog

GeekCTF 2024 Web WriteUp(全) Java反序列化CC链 2023 DataCon大数据安全分析竞赛 WriteUp NewStarCTF 2023-WEEK4 Web WriteUp NewStarCTF 2023-WEEK3 Web WriteUp NewStarCTF 2023-WEEK2 Web WriteUp NewStarCTF 2023-WEEK1 WriteUp 天津市大学生信息安全网络攻防大赛 [DASCTF 2023 & 0X401七月暑期挑战赛] MyPicDisk 使用hexo框架搭建github静态博客 My First Blog
记一次零基础IOT设备与app交互0day漏洞挖掘学习经历
lazy_forever · 2023-12-12 · via lazy_forever's Blog

基础知识

apk结构

apk是对一个安卓应用程序所需要的文件进行打包,本质上是一个被签名的压缩包。

通常情况下,apk会含有以下文件:

  • asset文件夹:是不需编译的原始资源目录,包含各种静态的资源,如各种配置文件、JavaScript、字体文件、图片文件等。
  • lib文件夹:动态链接库存放的位置,通常情况下,这个文件夹内部以不同处理器版本还会划分成多个文件夹,如armeabi、armeabi-v7a、x86等,其存放的文件通常为Android Native的代码。
  • META-INF文件夹:用来存放签名信息,通常会有CERT.RSA、CERT.SF和MANIFEST.MF三个文件。是用来保护apk的所有权和防止apk被恶意篡改。
  • r文件夹/res文件夹:存放编译资源文件,与asset文件夹相似,区别是其存放的文件是编译后的。通常会包含drawable 文件夹(图片资源文件)、layout 文件夹(布局文件)和values 文件夹(值资源文件)等。r文件夹通常是res文件夹进行混淆后的结果。
  • AndroidManifest.xml文件:apk的整体配置文件,其中包含了一个apk的各种配置信息,包括包名、应用名、权限、安卓四大组件、版本等重要信息。
  • dex文件:存放字节码的文件,其反汇编后为smali语言,可转化为Java代码,通常dex文件包含一个apk的主要逻辑。
  • resources.arsc文件:用来存放应用程序的资源表,包含了应用程序的资源 ID 和资源类型的映射关系。

使用工具

jadx/jeb:均为Android程序Java层反编译软件,相对来说jeb反编译能力相对较强,可以看到smali层的代码,而且也有一些混淆对抗的能力

frida:Android程序二进制动态插桩工具,用来动态调试Android程序

xposed:动态调试插件,相对于frida更加稳定

mitmproxy:中间人工具,用来监控并解密app端TLS流量

wireshark:对网卡进行抓包,配合mitmproxy使用可以获取tls解密后的流量信息

ida:用来反编译Android native层代码

iptables:配合mitmproxy透明模式,避免app端流量不经过代理导致tls流量无法被解密

手机:已ROOT

adb:用来连接手机的shell

环境搭建

获取手机Root权限

本次使用的手机是一台Pixel 6,首先需要在电脑上安装adb,过程省略

进入手机开发者模式,打开USB调试开关,连接电脑adb,可以使用adb devices查看是否连接成功,连接成功后开始解BL,输入

1
adb reboot bootloader

将手机进入Bootloader界面,此时手机处于fastboot模式下,输入fastboot devices查看是否可以正常连接。输入

1
fastboot flashing unlock

成功解锁BL。

注:Pixel手机如果发现进入fastboot模式adb断开的情况,请检查是否是数据线的原因,可以换个数据线试试,这个坑卡了我好久-_-

解锁BL后,一般都会向手机中安装Magisk,它是用来管理Root权限的工具。来源:topjohnwu/Magisk: The Magic Mask for Android (github.com)

下载好安装包后,可以使用adb install命令来安装Magisk,装好Magisk后,接下来进行镜像修补。

Nexus 和 Pixel 设备的出厂映像 | Google Play services | Google for Developers中下载手机对应的镜像文件,在手机设置 - 关于手机页面的最底端可以看到当前的版本号,在页面中找到对应的镜像下载,下载后将其中的boot.img用adb传输到手机,最后使用Magisk软件进行镜像修补,最后将修补完的镜像文件adb pull出来。

接下来进入最后的刷机步骤,使用adb reboot bootloader再次进入fastboot模式中,使用fastboot boot img地址指令将手机从修补过的img启动,重启后进入Magisk按照步骤安装好即可。

mitmproxy+iptables搭建中间人代理

mitmproxy是一个强大的中间人代理工具,与其他中间人代理工具相比,mitmproxy不仅可以转发http/https流量,还可以转发非http流量,如MQTT等。

在Android设备中抓取https流量,我们需要安装mitmproxy的CA证书,由于Android 7开始,应用会默认忽略用户级别的证书,因此,我们需要将CA证书放入系统级别中。

一般情况下,将证书格式先转化为pem格式,然后通过openssl x509 -subject_hash_old -in certificate.pem|head -1命令读取哈希值,将pem证书名字改为刚刚提取的哈希值加.0,如9a5ba575.0,其中.0是为了防止证书哈希值重复,如果两个证书哈希值重复,那么后面的证书就会被重命名为.1、.2等,最后将/system/etc/security/cacerts/目录可写权限打开,将重命名后的证书放进去即可。

我的手机版本为Android 10以上,无法直接通过更改文件夹写入权限来导入证书(也可能是我没有搞好),我使用了Magisk的Always Trust User Certificates模块,直接将证书装在用户目录下,重启后即可导入到系统证书中。

装好证书后,正常情况下手机端连wifi时配置代理后应该是可以解密https流量了,但是,如果app拒绝代理或想要捕获其他tcp流量时,就需要使用mitmproxy透明模式,透明模式的启动命令为mitmproxy --mode transparent --showhost,开启透明模式后,工作原理如下图:

此时,对于手机来说,mitmproxy相当于一个服务器,对于原服务器来说,mitmproxy相当于设备。

由于透明模式需要对网络层进行转发,因此还需要配置iptables,关于iptables的知识可以看这篇博客iptables-朱双印博客 (zsythink.net),在此贴一张iptables的原理图。

我的iptables规则参考了fwx学长的博客基于mitmproxy+iptables+SSL pinning绕过技术+wireshark的安卓APP流量(包括HTTP、HTTPS和非HTTP)捕获 | 代码鬼才的Blog (fwx2233.github.io),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# 写监听的无线网卡的名称
WIRELESS_CARD="wlxc01c30151c62"

# 开启相关的转发服务
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv4.conf.all.send_redirects=0

# 设置iptables的规则
# 将之前的规则清空
sudo iptables -F PREROUTING -t nat -i $WIRELESS_CARD
# 设置端口转发(MQTT: 8883, HTTP: 80, HTTS: 443)
sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 443 -j REDIRECT --to-port 8080
sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 8883 -j REDIRECT --to-port 8080

最开始我一直想要拿win+mitmproxy透明模式进行抓包,想要通过netsh来代替iptables进行流量转发,然而一直没有成功,如果读者有配置成功的经历麻烦评论区分享一下

接下来配置wireshark,通过捕获mitmproxy密钥交换过程中生成的随机数来进行TLS解密,操作方式也可以直接看官方文档Wireshark and SSL/TLS (mitmproxy.org)

如果时间过长或多次抓包导致生成的随机数文件过大,可能会导致wireshark解密失败,可以定时清空生成的随机数文件。

至此基本环境配置完毕,给出我的最终网络拓扑图。

原理

frida进行hook

frida安装过程省略,网上有很多教程可以参考。

frida中有两种操作模式,分别是CLI模式和RPC模式

  • CLI(命令行)模式:通过命令行直接将JavaScript脚本注入进程中,对进程进行操作
  • RPC模式:使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本,可以通过RPC传输给Python脚本来进行复杂数据的处理

frida有两种注入模式,分别是Spawn和Attach

  • Spawn模式:将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App。在命令行模式中需要加入参数-f,可以对从启动就开始对App进行监控。
  • Attach模式:在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作。如果只关心一个功能时通常会用这种模式。

相关的api可以在官网上查看官方文档Welcome | Frida • A world-class dynamic instrumentation toolkit

给出一个python的框架代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys,frida

jscode = """
这里输入你的js代码
"""

def on_message(message,data):
if message["type"] == "send":
print(message["payload"])
else:
print(message)

# process = frida.get_usb_device().spawn("app"))
process = frida.get_usb_device().attach("app")
script = process.create_script(jscode)
script.on("message",on_message)
script.load()
sys.stdin.read()

Java层hook

Java层hook示例代码:

1
2
3
4
5
6
7
8
9
10
11
setImmediate(
Java.perform(function () {
var targetClass = Java.use(className); // 替换为您的类名
targetClass.examplefunction.implementation = function(a,b,c...){//替换为参数
//调用原始方法
var result = this.examplefunction(a, b, c...);
//可以在这里进行各种操作
return result;
}
});
);

hook重载参数:

1
2
3
4
5
6
7
8
9
function hook(){
var utils = Java.use(className);
//overload定义重载函数,根据函数的参数类型填
utils.expfunc.overload('com.example.Demo$Class','java.lang.String').implementation = function(a,b){
b = "aaaaaaaaaa";
this.expfunc(a,b);
console.log(b);
}
}

hook字段修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hook(){
//静态字段修改
var utils = Java.use(className);
//修改类的静态字段"flag"的值
utils.staticField.value = "我是被修改的静态变量";
console.log(utils.staticField.value);
//非静态字段的修改
//使用`Java.choose()`枚举类的所有实例
Java.choose("com.example.Demo", {
onMatch: function(obj){
//修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。
obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线
obj.privateInt.value = 9999;
},
onComplete: function(){
}
});
}

对内部类进行hook

1
2
3
4
5
6
7
8
function hook(){
//内部类
var innerClass = Java.use("com.example.Demo$innerClass");//如果是匿名类需要反编译查看具体标号
console.log(innerClass);
innerClass.$init.implementation = function(){
console.log("hook");
}
}

静态方法主动调用

1
2
3
4
function hook(){
var ClassName=Java.use("com.example.Demo");
ClassName.privateFunc("传参");
}

非静态方法主动调用

1
2
3
4
5
6
7
8
9
10
11
var ret;
function hook() {
Java.choose("com.example.Demo",{ //要hook的类
onMatch:function(instance){
ret=instance.privateFunc("aaaaaaa"); //要hook的方法
},
onComplete:function(){
console.log("result: " + ret);
}
});
}

Native层hook

枚举so库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hook(){
Process.enumerateModules({
onMatch: function (module) {
send(module.name + " : " + module.base.toString());
if (module.name == "example.so") {
send("example.so found !");
send("hooking...");
send(module.name + " : " + module.base.toString() + " : " + module.size.toString() + " : " + module.path);
}
},
onComplete: function () {
send("end");
}
});
}

hook函数

1
2
3
4
5
6
7
8
9
10
11
12
function hook(){
var base_addr = Module.findBaseAddress("example.so");
Interceptor.attach(base_addr.add(0xabcd), {//偏移值
onEnter: function (args) {
//args是参数数组
console.log(args[0]);
},
onLeave: function (retval) {
console.log(retval);
}
});
}

这里有一个偏移值的计算,安卓里一般32 位的 so 中都是thumb指令,64 位的 so 中都是arm指令,通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)

  • thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1
  • arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移

IDA对so库逆向

ida的话就是纯看代码环节了,说几个小技巧吧。

首先就是尽量不要从导出表中反过来找函数,因为有一部分导出表对应的函数是fastcall类型,只观察函数内部有时ida无法将参数识别出来,导致后来再去找函数的调用处时代码都是乱的。

ida有一键生成frida的插件,P4nda0s/IDAFrida: IDA Frida Plugin for tracing something interesting. (github.com)AnxiangLemon/MyIdaFrida: Generate Frida Script (github.com),可以直接从ida中生成frida的hook脚本,比较方便(虽然我没用过几次)

后记

关于漏洞细节就不发出来了。历时2个多月的零基础从入门到入土,确实学到了不少东西,也踩了一大堆坑,有的坑也卡的比较久,在此感谢w学长给我一步步梳理思路。