第一次尝试对无人机App分析,这个是纯对App进行分析,参考度较低,建议结合无人机漏洞挖掘学习食用。
1. 环境配置
无人机型号:F7 4K PRO
App:SJ F PRO v2.4.5
安卓手机要求:
- Android系统4.4及以上,运行内存1G及以上
- 拥有WIFI,GPS定位模块
- 使用过程中不能开启VPN等网络管理工具,否则会导致手机与无人机之间不能通讯
两器一机开机步骤:
- 点按遥控器电源开关按键查看当前电量,再次点按开机。遥控器显示“CONNECTING_”,发出“嘀-,嘀嘀”声。
- 长按飞行器电源3s,发出“嘚嘚嘚,嘚-,嘚-”声,前后指示灯快闪 -> 红灯慢闪,进入对频状态。
- 遥控器发出“嘀”声,显示“GPS MODE”,后绿前白交替慢闪,对频成功。
- 开启手机WIFI功能,选择WIFI“SJ-F-PRO-*-BRG”,可能会显示“已连接,但无法访问互联网”,不用管。打开App,进入“控制”界面。
- 遥控器两边手杆推向内上,后绿前白快闪。App显示“进入罗盘校准”。
- 水平拿起飞行器转一圈,遥控器发出“嘀”声,后绿常亮。
- 拿起飞行器头朝上转一圈,遥控器发出“嘀”声,前红白灯慢闪。App显示“罗盘校准成功”。
- 将飞行器放置水平面,后绿前白慢闪,进入搜星状态。App显示“正在等待GPS信号”。后绿前白常亮,GPS搜星成功。
- 遥控器两边手杆推向外上,后蓝前白快闪,App显示“陀螺仪校准完成”。
- 遥控器两边手杆推向内下,电机自动解锁启动,直接推油门杆起飞。(取消电机解锁:两边手杆推向内下,电机停止工作。或解锁后无操作20s自动停止工作。)
- 后绿前白常亮,App显示“可以起飞”。
若GPS信号较差,在步骤8时一直处于搜星状态,此时如果想要起飞,长按遥控器上的“速度切换”功能键(左一)5s,关闭GPS功能。后绿闪烁前白常亮,无人机切换到姿态模式,可以起飞,但GPS所有功能关闭。
两器一机关机步骤:
- 长按飞行器电源3s关机。
- 点按遥控器电源,再长按3s关机。
2. 查看基本信息
首先来查看关于这个APK的基本信息。将该APK载入jadx查看它的包名与入口Activity。
启动frida-server,打开App,对该App进行objection。
1 | objection -g com.vison.macrochip.sj.f.pro explore |
查看该App所有的Activities:
1 | android hooking list activities |
已知入口Activity为com.vison.macrochip.sj.f.pro.activity.WelcomeActivity
,我们可以尝试用intent进入其它Activity:
经过intent后大致知道了哪个Activity对应哪个界面,我们重点关注com.vison.macrochip.sj.gps.pro.activity.ControlFActivity
和com.vison.macrochip.sj.gps.pro.activity.ControlHyActivity
,因为它们是飞行器的两种飞行控制模式,一种是GPS模式,另一种是姿态模式。
可以通过hooking找到对应关系:
1 | android hooking watch class com.vison.macrochip.sj.gps.pro.activity.ControlHyActivity --dump-args --dump-backtrace --dump-return |
点击“控制”按钮,界面左上方显示“搜星中…”,表示GPS模式。使用ControlHyActivity
成功hook上。
此时可以知道com.vison.macrochip.sj.gps.pro.activity.ControlHyActivity
是GPS模式,而com.vison.macrochip.sj.gps.pro.activity.ControlFActivity
对应的是姿态模式。
3. WelcomeActivity.onCreate()
1 | public void onCreate(Bundle bundle) { |
ProtocolEnum
数据类型是一个枚举类型。
进入V()
方法:
1 | public void V() { |
Android开发中经常会在setContentView(R.layout.XXX);
前设置requestWindowFeature(XXX);
。
ButterKnife.a()
方法进去,大概了解它应该是一个主动调用自身Activity的ViewBinding类。
c.g.a.h.f.B
存储的是无人机类型常量,初始值为0。由于它是静态的,所以在App界面选定其它类型常量后,重启App还是上一次选定其它无人机类型常量。
如果无人机类型常量为2,表示型号是F11s;常量为3,表示型号是F7;常量为5,表示型号为F7s;否则型号默认为F11。如果型号为F11,枚举协议为SJ;其余型号的枚举协议为HACK_FLY。
接下来进入f0()
。
f0()
从内容布局中设置了一个监听长按事件。 setOnLongClickListener()
中return值决定是否在长按后再加一个短按动作。true为不加短按,false为加入短按。
所以这里的意思是,在WelcomeActivity的任何地方(除了按钮),长按后会开启一个LogListActivity,返回值为false,即加入短按事件OnClick()
。这个短按事件是什么意思呢?
f0()
中的下面这条语句是跟“控制”按钮有关的。
1 | c.d.a.b.a.a(this.controlBtn).y(1L, TimeUnit.SECONDS).u(new g()); |
进入c.d.a.b.a.a()
方法,继续进入c.d.a.a.b.b()
方法,发现它调用了requireNonNull()
方法,如果对象为空则抛出空指针异常,否则返回该对象。
查看WelcomeActivity.g.g
类发现一些android:permission
属性,可能跟授予权限有关。
第一次点击控制按钮后,会出现需要授予权限的弹窗。
sendEmptyMessageDelayed()
的意思是指定多少毫秒后发送空消息,一般做延时操作的时候会使用到。呃这个不知道什么意思,0.5s后发送空消息?
4. WelcomeActivity.onClick()
经过2的查看基本信息,可以将Button和TextView一一对应上。
view.getId()
是取得R.id.xxx
中的内容。R.id.xxx
与Button、TextView的对应关系在WelcomeActivity_ViewBinding.class
中。
在switch…case…语句中:
R.id.album_btn
这个按钮并没有出现在WelcomeActivity界面,但如果初次触发了就会要求取得相应权限。但是我们在3中点击“控制”时就将读写权限允许了,所以就算触发了R.id.album_btn
它也不会弹窗需要权限获取。
R.id.learn_btn
是WelcomeActivity界面上的“视频宣传”,如果型号为F7s并且地区语言不是汉语则返回。
如果地区语言为汉语,不同型号返回不同设备型号的宣传视频,视频在youku平台上发布。如果地区语言不是汉语且无人机型号不是F7s,不同型号返回不同设备型号的宣传视频,视频在youtube平台上发布。
如果链接失效则返回。
1 | com.vison.baselibrary.utils.g.g("http", str); |
在App中启动一个Activity,这个Activity将打开对应的链接。
R.id.product_item
乍一眼看以为也不在WelcomeActivity界面中,但从它字面意思就可以知道它是产品项目,结合语句中的PopupWindow可以知道它是悬浮框,也就是选择无人机型号的地方。后面语句无非是给c.g.a.h.f.B
和c.g.a.h.f.Q
赋值,在3时也说过。如果型号为F11,c.g.a.h.f.B
赋值为1。只有型号为F11s时c.g.a.h.f.R
的值才为true。
提交无人机型号,其它按钮点击后会显示对应型号的内容。
R.id.product_name_tv
是设计悬浮框属性的。
LinearLayout可以控制组件横向排列或者纵向排列,内容不会换行,超出屏幕部分将不会显示出来等等属性。
我猜在WelcomeActivity界面中,点击下拉按钮是触发的R.id.product_name_tv
,而选择型号是触发R.id.product_item
。
R.id.support_btn
,点击它会去到SupportItemActivity.class
。
R.id.video_editor_btn
是WelcomeActivity界面上的“视频编辑”,初次点击需要权限。同R.id.album_btn
。猜测触发R.id.video_editor_btn
后才能在相册中选择照片或视频,即而后才触发R.id.album_btn
。
5. ControlFActivity和ControlHyActivity
分析完WelcomeActivity.onClick()
发现没有id为R.id.control_btn
的case,回到WelcomeActivity.onCreate()
发现并没有直接设置controlBtn的点击事件,当然不会在onClick()
中出现。
我们记得在3时,点击“控制”按钮会出现授予权限的弹窗,说明点击“控制”按钮一定会经过f0()
,所以controlBtn的点击事件监听器很有可能在f0()
中。上次分析到requireNonNull()
方法就结束了,但c.d.a.b.a.a()
最后还调用了一个c.d.a.b.b.b()
方法,controlBtn作为参数传入。
1 | private void f0() { |
y()
方法传入了两个参数,一个是长整型,一个是时间单位。可以查到很有可能是boolean await(long time, TimeUnit unit)
方法,使线程进入等待状态,直到被唤醒或中断,或到截止时间。这是有关Java线程的等待与唤醒机制的知识。
u()
方法传入了一个g对象的实例,进去发现有onNext、onError等字眼,一查发现是在RxJava里的东西。
RxJava是 ReactiveX 在 Java 上的开源的实现。RxJava可以轻松处理不同运行环境下的后台线程或UI线程任务的框架。RxJava的异步实现,是通过一种扩展的观察者模式来实现的。
WelcomeActivity.g.g
在WelcomeActivity.g
中,发现有个WelcomeActivity.g.a.accept()
方法,就是用来跳转到ControlFActivity.class
或ControlHyActivity.class
页面的。
如果枚举协议为HACK_FLY,则跳转到搜星模式;否则跳转到姿态模式。c.g.a.h.f.Q
的值默认为SJ。
那我们点击“控制”按钮的时候,是不是就经过了这个方法呢?又是怎么经过这个方法的呢?
先在WelcomeActivity中找到内部类WelcomeActivity.g.a
。
1 | android hooking list classes com.vison.macrochip.sj.gps.pro.activity.WelcomeActivity |
再hook这个类。
1 | android hooking watch class com.vison.macrochip.sj.gps.pro.activity.WelcomeActivity$g$a |
发现点击“控制”按钮时确实调用到了这个类的a()
和accept()
方法。
hookaccept()
方法查看它的参数、返回值和调用栈。
1 | android hooking watch class_method com.vison.macrochip.sj.gps.pro.activity.WelcomeActivity$g$a.accept --dump-args --dump-backtrace --dump-return |
什么意思,不在onCreate()
那里来的吗?还是我不会看调用栈?有点迷茫。先不管它怎么进来的吧,主要还是分析ControlFActivity.class
和ControlHyActivity.class
飞控相关的内容。
5.1 ControlHyActivity
在无设备连接的情况下,点击“控制”按钮会默认进入ControlHyActivity(GPS模式)。使用objection hook整个ControlHyActivity,点击其界面中的任何地方都可以清楚地看到哪个函数被调用到。
5.1.1 onCreate()
上面这些hook的方法都在onCreate()
方法中被调用到了:
1 | public void onCreate(Bundle bundle) { |
i1()
是设置界面按钮与id之间的对应关系;h1()
是监听各类点击事件;r1()
是通过读取APK包信息设置下面那一栏的单位。
我在想能不能通过改变i的值从而改变单位呢?对h()
进行一个简单的hook:
1 | function main(){ |
5.1.2 onClick()
在点击这些按钮时,会触发onClick()
事件,由于没有连接无人机,会提示“设备未连接”。
那我们进入onClick()
方法看看它做了什么。
R.id.album_btn
是一个相册按钮,点击后会开启MediaActivity.class
。可以hook一下查看是哪个按钮所为。
1 | android hooking watch class com.photoalbum.activity.MediaActivity |
R.id.audio_btn
音频,显而易见,默认关闭。R.drawable.ic_audio_off
是图标。
R.id.back_btn
,返回按钮就是左上角的图标。
R.id.camera_shut
是切换拍照和录像的按钮,根据选择的“拍照”或“录像”图标来选择资源。
R.id.function_btn
是左侧操纵杆图标的按钮。t()
方法点进去定义的是一个悬浮框。
1 | private final PopupWindow f5666a; |
R.id.go_home_btn
是返航键,点击它如果检测不到设备会显示“设备未连接”。
1 | case R.id.go_home_btn /* 2131230958 */: |
检测到设备,如果想取消返航可再次点击返航键,否则会创建一个新的按钮和滑动解锁框,监听点击与解锁事件,向右滑动按钮确认返航。
R.id.more
是右上角“…”按钮,具体再看吧。
R.id.ptz_down_btn
和R.id.ptz_up_btn
是在开启角度调节功能后,界面会出现垂直滚动条,用来调节摄影角度。
R.id.sd_stream_tv
,点进去J0()
方法,发现与R.id.go_home_btn
部分代码几乎一样,在/res/value-zh/strings.xml
中果然能找到j_res_0x7f0e006b的内容。
这个就是界面上SD卡图标的按钮。
R.id.s1
,进去K0()
方法:
1 | public void K0() { |
R.id.show_angle_btn
,展示角度的按钮?
1 | case R.id.show_angle_btn /* 2131231207 */: |
R.id.shut_btn
是快门键。
R.id.to_fly_btn
是“自动起飞”按钮。
1 | public void I0() { |
R.id.windows_btn
这个是什么按钮,怎么它的语句这么多?
5.2 ControlFActivity
也同样分析一下姿态模式的onCreate()
和onClick()
。由于姿态模式与GPS模式差不多,只不过多了几个功能,所以只讲多的那些功能。
5.2.1 onCreate()
1 | public void onCreate(Bundle bundle) { |
5.2.2 onClick()
这个也几乎一样。
6. 另一种思路
直接分析App可能不好找入手的地方,可以结合无人机上面的固件和App一起分析,看有没有telnet和ftp的空口令,比如捕获App发送起飞、降落、拍照命令时的数据包,通常是udp,看重放有没有效果。
如果分析无人机飞控相关,使用App代替遥控器进行操作,说明进行了远程控制,有可能是利用telnet协议进行的。如果分析无人机图传相关,飞行器拍下来的照片或录像保存到了手机上,有可能是利用ftp协议进行文件传输。
没有WiFi的情况下如何进行Android抓包?没有WiFi意味着Charles和fiddler都不能用了,因为它们必须要求手机与电脑在同一个局域网内。
但遇到物联网时,比如无人机,需要手机连接无人机设备的WiFi,此时电脑要想和手机处在一个局域网内,就必须要连接无人机设备的WiFi,但该WiFi不能联网,不能联网不可以使用Charles或fiddler抓包。
我目前试过可行的方法就是使用一台root好的手机,使用Android tcpdump工具将手机内的数据包dump下来。
通过USB连接手机与电脑,下载好Android tcpdump后将它push到手机中,授予777权限。将手机连接到无人机设备的WiFi,在手机终端界面执行以下命令进行抓包:
1 | ./tcpdump -i any -p -s 0 -w /sdcard/sjf/capture.pcap |
Ctrl + C停止抓包,回到Kali终端执行以下命令将手机内的capture.pcap
pull出来:
1 | adb pull /sdcard/sjf/capture.pcap |
一个简单的抓包过程:
1 | ┌──(root㉿kali)-[~] |
抓包后再用Wireshark查看,发现捕获到大量的UDP和TCP等协议。
然而并分析不出什么,因为是厂商自定义的协议,Wireshark解析不了。看能不能定位到App发送TCP/UDP给无人机的那部分代码,或无人机里固件对应的代码。
使用网络通信协议分析这篇文章的hook脚本,可以知道该自定义协议本质上是用了Java层的socketRead0()
和socketWrite0()
(TCP),还有sendtoBytes()
和recvfromBytes()
(UDP)进行通信,查看调用栈定位到App中的代码。
TCP:
UDP:
6.1 TCP
经过分析Java代码发现并没有进行加解密算法,而是直接将数据进行发送和接收。由于主要分析App向无人机发送命令,可以以TCP的write()
为例:
1 | private final ArrayBlockingQueue<byte[]> f3987d = new ArrayBlockingQueue<>(f3985b); |
write()
把字节数组通过TCP协议发了出去,所以这个字节数组是什么呢?ArrayBlockingQueue详解,它里面有几个重要的方法,比如poll()
和offer()
。offer()
的功能是将数据加到 BlockingQueue 里,如果 BlockingQueue 可以容纳,则返回 true,否则返回 false。搜索一下这个阻塞式的队列offer()
方法,果然是有的:
查看哪里调用了o()
方法,有以下两处:
其中第二处的参数固定,并且有定时器,很有可能是充当心跳包。所以还是主要分析第一处。
第一处是被I()
调用了,并且数据是I()
的参数,所以继续查看哪里调用了I()
方法:
6.1.1 当前经纬度数据
第一个,应该是控制无人机在设定好的经纬度飞行。
6.1.2 云台角度调节
第二个,看不出来什么。
查看哪里调用了d0()
,发现恰好是控制界面中的onClick()
调用了它:
点击其中一个,发现它是跟云台的角度调节有关的,所以当我们使用App调节云台角度时,它是通过这些代码来发送TCP数据包,让云台执行命令。
分析到这里,我们是不是可以使用hook d0()
方法,先查看它的参数与返回值,在此基础上修改它的参数与返回值,从而达到不点击App上的按钮,就可以实现调节云台角度的功能呢?
查看参数与返回值,由于d0()
方法没有返回值,所以只看参数就好了:
1 | function hookd0(){ |
经过实验,每按一次减号键参数递增5,每按一次加号键参数递减5,并且云台角度确实上下变换。
接下来直接在js代码中修改参数:
1 | function hookd0(){ |
发现不能自行改变云台角度,需要按加或减按钮一下才能执行到我们修改的参数的角度,再次按加或减按钮不改变角度,固定在了80。因为上面这个写法,是需要触发条件的,而触发条件就是点击按钮。要想绕过触发条件,就必须进行主动调用。
1 | function hookd0(){ |
1 | frida -FU -l d0.js |
我主动调用了d0()
,为什么打印不了上面的参数信息?疑惑。
主动调用还可以这样写:
1 | function invoked0(){ |
1 | frida -FU -l d0.js |
这样就可以实现无需点击按钮,发送云台调节命令的TCP数据包。
但是它发送的数据包具体内容是什么呢?仔细看d0()
代码,发现要发送的数据包会对bArr字节数组的某些元素进行异或运算。再进入I()
方法,里面有一个判断,如果i.m().p()
为true则进入i.m().z(bArr)
方法;否则直接将bArr这5或6字节数组通过TCP发送。
我们可以抓一下云台角度调节的正常包,验证我们的猜想。搜索d0()
方法中传过去的字节数组,开头3字节固定为68 07 01
。
1 | 68 07 01 05 03 |
第4字节从代码中就能看出是传入的参数,也就是递增或递减5。最后发现第5字节是第2~4字节的异或运算,原来bArr字节数组就是传过去的数据本身。
进而尝试使用Python构造TCP包,重放看是否能调节云台角度。
1 | import socket |
没得问题,成功!重放的话,即使不是递增递减5,只要符合规则,也是可以调节角度的。
6.1.3 设备升级
第三第四个,查看哪里调用了b()
和c()
。
从文字分析应该是有关设备升级代码,可以不管。可以通过调用这个下载病毒或木马不?(bushi
6.1.4 设置中的参数栏
第五第六个非常相似,都是跟“设置”相关的。
从第六个调用地的变量j、a2、c2、e2可以知道,它们分别是“设置”中参数栏下的新手模式、总飞行距离、飞行高度、返航高度。
要想修改其中的数值,直接修改它们的返回值即可。要想知道数据包的内容,与6.1.2同样方法求得。(或许吧)
同样可以继续深入查看调用,其中发现姿态模式的控制页面调用了它。
而第五个应该是与初始化相关?
6.1.5 时间
第七第八个是发送与时间相关数据包:
顺着一路往上追可以追到快门键,感兴趣可以追一下。
6.1.6 航点
最后一个:
对列表中的每个元素进行遍历,每个循环中创建一个长度为16的字节数组,前3个字节固定值,转换成十六进制为68 04 0C
。定义了一个LGFlyLineBean
类,里面存的是每个元素的经纬度、元素下标、高度、速度和时间。再将这个LGFlyLineBean
类通过convertFlyLine()
这个Native方法转换为字节数组。arraycopy()
方法将convertFlyLine字节数组从下标为0开始复制到bArr数组下标为3的地址,复制长度为convertFlyLine字节数组的长度,结合下面的异或运算可以猜测长度为12,bArr字节数组下标为15的元素是下标为1~14的异或结果。最后将bArr字节数组通过TCP发送到无人机。
继续不停往上查看调用,最后发现是R.id.s1
按钮调用的它。
R.id.s1
的解析可以看5.1.2,它应该是进入航线规划后的“GO”按钮。其中列表的每个元素就是每个设置好的航点。
6.2 UDP
UDP也是用阻塞式的队列offer()
方法来将数据加入队列。这里面的参数非常明显了,字节数组、字节数组的长度、对端IP、对端端口。发现有两个调用:
先来看第一个。
这个应该是设置中继的IP和端口??
中继器(RP repeater)工作于OSI的物理层,是局域网上所有节点的中心,它的作用是放大信号,补偿信号衰减,支持远距离的通信。
继续看第二个,默认以端口8080发送UDP数据。
可以发现发送的数据很多都是以ff 53 54
开头的,抓的包也有很多这些数据。我们先看。
6.2.1 c.g.a.h.f
在c.g.a.h.f
类中的,往上跟会发现大多是在进行初始化操作。
再看这个类中的N()
、U()
、e0()
。N()
和U()
在同一个方法中被调用,只是所需的前提条件不一样,而且它们内部执行流程也不一样。
暂时还不知道有什么用,看不懂。e0()
也看不出什么,真是醉了。
继续看这个类中的g0()
,发现终于有点提示了。
查看g0()
有两处调用,o.f5360a=128,o.f5361b=5,即每按一次左或右,在初始值128的基础上递增或递减5,区间范围在0~255之间。
抓包发现App向无人机发送数据,无人机返回同样的数据给App。
再看h0()
,结合App中的信息,可以知道这个是“设置”中图像栏的SD卡分辨率,默认为4K,也就是常量3,常量1表示为2.7K。
6.2.2 c.g.a.l
c.g.a.l.d
c.g.a.l.e
c.g.a.l.f
c.g.a.l.g
c.g.a.l.h
c.g.a.l.i
6.2.3 c.g.a.m.q
这个类中的a()
方法是删除SD卡中所有内容。
b()
方法是获取SD卡的状态和容量。
6.2.4 c.i.a.f.b
B()
方法,包中的数据以“D”开头:
1 | public void B(String str) { |
追踪到最后发现这个方法是设置国家码之类的。
而F()
、G()
、H()
是使用UDP发的来表示时间的方法。
而g.run()
方法,只发送了一个字节42
(“B”),抓包时可以抓到这个包,但是使用jadx查看用例却溯源不了。暂时放弃。
1 | public class g extends Thread { |
k()
方法也同样溯源不了,只发了一个字节47
(“G”)。
l()
方法,如果设备型号和设备ID都为空,发送一个字节0f
。
1 | public void l() { |
o()
方法发送一个字节28
。
p()
方法发送一个字节2c
。
x()
方法发送当前日期和时间。
1 | public void x() { |
以上这些方法都追不到调用地,但抓包时都抓到了,很可能是初始化时发送的。
6.2.5 c.i.a.g.a
b.run()
在判断数据流是JPEG格式还是H264格式。如果是H264格式需要解码。其中getOneFrame()
和decodeH264()
都是JNI函数。发送一个字节27
。
6.2.6 c.i.a.g.e
a.run()
跟进去发现是跟定时器有关的,而e.f3977a是一个没有初始化的字节数组,所以传的是什么数据?
6.2.7 c.i.a.k.a
C0121a.run()
方法传入的也是5字节数组,这里可以很清晰看出是获得当前IP地址并加上某些数据得到的数据包。这里也是充当一个定时器。
6.2.8 com.sj.baselibrary.view.c
a.run()
,很容易看出这个是在机器校准时进行的代码。
6.2.9 com.sj.baselibrary.view.g
g()
、h()
、i()
中参数初始化值都为128。
看不懂这是哪里的拖动条,又是什么意思。
6.2.10 com.vison.macrochip.sj.gps.pro.activity.b
k()
方法很容易知道是环绕飞行的操作。
7. 第三种思路
由于我们已经知道在App中进行操作所发送的TCP/UDP包是自定义协议,并且自定义协议是通过Java代码中一些字节数组实现,那我们是不是可以通过抓到的TCP/UDP包,来实现Java代码的定位呢?通过分析数据包发现,一般如果数据多于3个字节,那么数据的前3个字节是固定的。
依照这个思路,可以抓相关飞控、图传之类的数据包。
7.1 飞控
航线规划
兴趣点环绕(环绕半径)
GPS跟随
图像跟随
自动起飞
自动返航
7.2 图传
7.2.1 拍照
进行了两次拍照,一次使用App,一次使用遥控器。
1 | public static void b() { |
这只是在拍照前发送获取状态和容量的数据包,然而拍照却不是这里。
可以尝试hook b()
方法进行验证:
1 | function hookb(){ |
1 | hookb() |
包抓到了,但是没进行拍照,可以确定这个只是单纯获取状态和容量的。
可以看到在快门键中有一个条件判断。查看变量B0
的赋值:
在A0()
方法中,如果现在的状态是“拍照”,则返回true;如果是“录像”,则返回false。如果是“拍照”,执行D0()
方法。
1 | public void D0() { |
尝试hook D0()
方法,看是否经过了它:
1 | function main(){ |
实验证明确实是经过了。将this.D0()
这一句注释掉后,发现按下快门键不拍照了,说明获取图片信息就在D0()
方法中。D0()
只调用了一个方法:c.i.a.i.a.b()
,可以先看一下它的参数是什么。
1 | function main(){ |
new l()
-> c.g.a.m.p.f()
:
1 | public class l implements c.i.a.j.c { |
在AK中可以知道这个资源ID是照片。
网上对Context的理解:当前对象在程序中所处的一个环境,一个与系统交互的过程。 比如QQ和朋友聊天时,此时的Context是指的聊天界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。
c.g.a.m.p.f()
-> c()
:
c()
是SoundPool对象的一个实例,其中它的load()
方法声明如下:
1 | //通过resID从APK资源中载入 |
D0()
-> c.i.a.i.a.b()
-> c.i.a.i.a.c()
:
c.i.a.i.b.a()
方法的功能是根据拍照质量选择对应的像素。
c.i.a.g.j.d.k().q()
方法是设置水印。
c.i.a.g.j.d.k().n()
方法传入l()
的一个实例。
c.i.a.f.b.f3940c()
是String型,查看哪里给它赋值了。
很明显就是在特定路径下创建了一个目录,并取绝对文件路径。
将这个文件路径作为参数传入com.vison.baselibrary.utils.c.a()
方法:
1 | public static File a(String str) { |
在该目录下创建了一个名字以“IMG_”开头,“.jpg”结尾,中间是当前日期时间的文件。尝试hook这个a()
方法可以得到照片在手机中的路径,最终返回一个File类的实例。
并将该实例作为参数传入e.g().o()
方法。
1 | public void o(File file) { |
而o()
只调用了一个a()
方法,所以直接hook a()
方法看是否经过它。
1 | function main(){ |
是经过了,但a()
方法构造如下:
1 | public void a(File file) { |
查看this.f
在哪里被调用到:
那就直接hook l()
方法:
1 | function main(){ |
四个参数依次是File类引用、屏幕的分辨率17941080(把手机侧边的虚拟按键去掉就可以1920\1080)、true。true是什么意思呢?将它改为false发现照片上下翻转了,就是实现照片上下翻转功能。
l()
方法最终调用m()
方法:
直接hook f()
方法试试吧。
1 | function main(){ |
同样注释掉this.f()
拍不了照,继续看发现最终调用i()
方法创建了一个新线程。
1 | public void f() { |
那照片是如何存到SD卡中呢?我们发现它不仅有.jpg
格式的文件,还有同名的.thm
文件,有些视频播放器除了需要MP4或者MPG格式的视频文件外,还需要一个THM格式的索引文件才能播放(所以关JPG格式什么事)。在jadx中搜索“thm”果然有相关信息。
根据上下文分析,这个run()
方法的主要功能是将指定目录下的所有.jpg
文件放入一个列表,再从该列表中依次取出元素,将每个元素的后缀名改为.thm
,按指定格式写入当前目录。
(我在干嘛…不想做了)
拍照、录像、手势拍照、手势录像。
上面说到,如果把tcpdump放入手机中,只能抓手机与无人机通信的包,而将tcpdump放在无人机中,就可以同时抓到无人机与遥控器、无人机与手机通信的包了。如何将tcpdump放进去呢?使用tftp工具或SD卡都可以。tcpdump 4.99.1 / 1.10.1 版本太新,无人机系统不兼容,所以选择tcpdump 4.9.2 / 1.9.0。
在抓包前,查看当前环境:
- 无人机:172.16.10.1
- 遥控器:172.16.10.10
- 手机:172.16.10.20
- 物理机:172.16.10.21
- 虚拟机:172.16.10.22
执行抓包命令就可以知道无人机在跟哪些IP通信。