破解旧机顶盒,随手挖漏洞 #
1. 机顶盒信息 #
家里换了宽带,闲置了一个IPTV的安卓机顶盒。
淘宝全新的 8+64G 30块,这个 1+8G 估计10块钱都卖不上?!
1.1 机顶盒的硬件信息 #
- 生产厂家:九州电子
- 产品名称:PTV-8608 4K超高清IP机顶盒
- 产品概述:采用国科微GK6323主芯片,该芯片集成四核64位Cortex A53处理器及四核高性能3D GPU;支持4KP60 HDR解码,支持HDMI 2.0 ,支持1个FE内置PHY,支持1路USB,支持1路AV等。
1.2 技术规格 #
- 国科微GK6323V100A主芯片。
- 芯片集成四核64位Cortex A53处理器及四核高性能3D GPU。
- 支持Android4.4智能操作系统。
- 支持DLNA和Miracast多屏互动相关协议。
- 内置2.4G WiFi软路由,支持IEEE 802.11 b/g/n。
- 支持全4K功能。
- 支持HDR解码。
- 支持4KP60,支持各种主流音视频格式,支持4KP60,超高清音视频解码播放。
- USB2.0接口,支持移动硬盘,鼠标和键盘。
- 支持Android4.4智能操作系统。
- 支持全4K业务部署,满足视频通信、卡拉OK、云游戏、多屏互动等增值业务需求。
- 支持H.265视频编码。
- 支持HDMI2.0。
- 内置标准蓝牙模块,支持蓝牙遥控器。
现在的情况是能正常开机进入桌面,但是帐号已经注销了,IPTV的业务用不了:
2. 获取系统控制权 #
机顶盒的系统是深度定制的安卓系统,安卓原生的设置调不出来,默认给的设置能做的非常有限:
插上U盘没有显示,也不能自由的安装apk。
我们首先要做的就是获取系统控制权,包括但不限于:
- adb调试
- root shell
- TTL调试
- 其它
2.1 获取adb #
信息收集:
- 在网上找了一下,没有找到可以用的卡刷固件。
- 一些唤出隐藏设置的后门,比如在遥控器上输入特定密码,或者连续按键,都没有效果。
- 插上U盘,也不识别U盘里面的apk文件。
检索关键字GK6323V100A,全网只找到一篇帖子: https://dmm.ink/2024/12/01/apg0100gk6323v100a/
- 帖子里面的主板跟我的主板型号相同,板子的细节有出入,可能产品出厂又做了调整。
- 红框里面的TTL调试针脚,帖子的主板没有,我的主板上是全的。
TTL调试模块:
- 下单了一个TTL转USB的模块,由于我的主板TTL针脚都在,电烙铁就下次再买。
AI辅助:
- 我自号C/C++程序员,搞系统开发/安全开发,软件写得多,搞硬件还是第一次。
- 全程用DeepSeek做信息检索。DeepSeek访问人太多了,服务经常报错。
- 主观用下来的感受,DeepSeek的中文交互理解能力比chatGBT强一些。
接入TTL串口调试:
-
TTL调试中RX是接收数据,TX是发送数据,GND是共地,接入方法是交叉连接RX和TX线,即模块TX接主板的RX,模块RX接主板的TX,剩下的GND对接。
-
我这里用的终端工具是MobaXterm,选择串口,设置波特率为115200。
-
加电开机直接跑码成功,获取的就是root shell。
开启adb调试端口: 直接在终端里面执行:adbd &
- adbd:注意这里运行是adb服务,所以是adbd。后面的d表示是一个系统服务(daemon),属于linux系统下约定熟成的一个命名方式,比如sshd、ntpd。
- &:表示后台运行。
执行后续的操作之前,先把系统目录重新挂载为可读可写(非常关键):
adb mount -o remount,rw / adb mount -o remount,rw /system
备注:某些命令执行失败,可以先执行一下su。
2.2 adb操作 #
查看系统信息:
adb shell getprop
adb shell getprop ro.build.version.release
连接/查看/屏幕/日志/重启/shell:
# 连接到设备
adb connect ip:port
# 断开连接
adb disconnect ip:port
# 查看设备
adb device
# 截屏(保存到设备)
adb shell screencap /sdcard/screenshot.png
# 录屏(需 Android 4.4+)
adb shell screenrecord /sdcard/video.mp4
# 实时查看日志
adb logcat
# 过滤特定标签的日志(如 "ActivityManager")
adb logcat -s ActivityManager
# 普通重启
adb reboot
# 进入 Bootloader 模式
adb reboot bootloader
# 进入 recovery 模式
adb reboot recovery
# 进入 adb shell
adb shell
文件上传/下载:
# 上传
adb push [本地文件路径] [设备目标路径]
# 下载
adb pull [设备文件路径] [电脑本地路径]
应用安装/卸载/禁用:
# 安装 APK
adb install app.apk
# 卸载应用(需包名,如 com.example.app)
adb uninstall com.example.app
# 列出已安装包名
adb shell pm list package
# 仅显示第三方应用(用户安装的APP)
adb shell pm list packages -3
# 仅显示系统应用
adb shell pm list packages -s
# 显示已启用的应用
adb shell pm list packages -e
# 显示被禁用的应用
adb shell pm list packages -d
# 显示包名及关联的APK路径
adb shell pm list packages -f
# 禁用用户应用
adb shell pm disable-user com.android.browser
# 禁用系统核心组件(谨慎操作)
adb shell pm disable com.google.android.gms/.update.SystemUpdateService
# 重新启用应用
adb shell pm enable <包名>
应用启动/停止: 通过包名启动主Activity,需要首先获取主Activity的名称:
# 手动启动一次应用,观察日志中的 Activity 名
adb logcat | grep "ActivityTaskManager: START"
# 使用 dumpsys 查看已安装包信息
adb shell dumpsys package <包名> | grep "MAIN"
启动应用:
# 示例:启动微信
adb shell am start -n com.tencent.mm/.ui.LauncherUI
强制停止应用:
adb shell am force-stop <包名>
终止进程(需谨慎),可能需要 root 权限:
adb shell am kill <包名>
运行桌面启动器:
adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME
2.3 添加sshserver #
- 成功获取root shell,单纯启动adb服务还不够,还要再添加一个sshserver。
- 这里机顶盒的CPU架构是ARMv7,用C/C++搭一个交叉编译环境太费时间了,这里选择用Golang。
- Golang的sshserver开源项目就用这个:reverse-ssh
项目直接用会有bug,需要针对安卓平台修改一下:
- 安卓的环境变量跟其它linux发行版不太一样,这里需要添加一个环境,指向命令的路径:
- Makefile文件修改一下,一个是ssh连接密码,它这里原本是动态随机的,这里写死为root;另外一个是交叉编译命令,添加一条编译ARMv7架构的命令。
利用adb上传编译后的文件到/system/bin/目录,chmod赋予执行权限:
运行sshserver,-s指定shell的路径,&后台运行:
reverse-ssh-armv7 -s /system/bin/sh &
ssh客户端登录,默认端口是31337,密码是root:
2.4 添加开机启动 #
- 上面所有配置开机之后都会重置,需要添加开机启动。
- 可以修改的点:/system/bin/init.kunlun.sh,添加三条开机启动命令:
- 启动adbd
- 启动sshserver
- 重新挂载系统目录
- 系统bin目录没有vi命令,busybox有vi参数,执行busybox vi /system/bin/init.kunlun.sh修改文件:
3. 系统改造 #
经过上面的步骤,至此我们已经完全拥有了系统的控制权。
改造目标:
- 使用dd命令备份系统
- 更换系统启动器(Launcher)
- 精简系统组件,禁用系统更新,删除一些不需要的应用
- 机器是有wifi模块,系统只开放了无线热点共享,没有主动连接的功能。尝试修复一下
- 机顶盒作为瘦终端串流游戏主机
3.1 备份系统 #
开搞之前先把系统备份一下,这篇帖子 https://dmm.ink/2024/12/01/apg0100gk6323v100a/ 说没找到dd命令,实际上dd命令是有的,由busybox实现:
查看存储信息:
busybox df -h
cat /proc/partitions # 安卓设备查看块设备(如 /dev/block/mmcblk0)
常见分区参考:
分区名 | 路径示例 | 作用 |
---|---|---|
boot | /dev/block/mmcblk0p5 | 内核和初始化程序 |
system | /dev/block/mmcblk0p16 | 系统主文件 |
vendor | /dev/block/mmcblk0p17 | 厂商驱动和配置 |
recovery | /dev/block/mmcblk0p6 | 恢复模式分区 |
userdata | /dev/block/mmcblk0p20 | 用户数据(可选择性备份) |
dd备份系统分区:
busybox dd if=/dev/block/platform/goke_mci.0/by-name/system of=/mnt/sda/sda1/system.img bs=4096
- if: 输入文件(分区路径)。
- of: 输出文件(备份路径)。
- bs: 块大小(可选,默认 512 字节)。
dd还原操作:
busybox dd if=/mnt/sda/sda1/system.img of=/dev/block/platform/goke_mci.0/by-name/system
3.2 更换系统桌面 #
原本想装Emotn UI启动器,Emotn UI最低需要安卓5.0,机顶盒系统版本是安卓4.4.2,这里装不上:
支持安卓4.4.2的Emotn UI旧版本安装包没有找到,我们换一下安装当贝桌面:
设置桌面启动器,在弹出来的选择框里设置始终以当贝桌面运行:
adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME
3.3 精简系统服务,冻结应用 #
- 冻结CATV_Auth默认桌面,当贝桌面就不需要切换了
- 精简其它系统应用:系统更新、其它杂项
3.4 设置桌面开机自启动 #
设置开机启动当贝桌面,在/system/etc/dtvlib_init.sh的最后添加两条命令:
sleep 10 # 等待系统初始化
adb shell am start com.dangbei.tvlauncher # 运行当贝桌面
完成的效果:
天气插件识别的地点有误,就不打码了,我在湖南但是并不在怀化:
3.5 解决wifi连接问题 #
重新安装一个WiFi连接管理器
4. 机顶盒作为瘦终端串流游戏主机 #
机顶盒设计的初衷就是用来进行直播串流的,原本是从直播服务器拉取视频流,现在是从本地局域网的游戏主机拉取视频流,作为瘦终端运行串流程序正当其用。
我这里用的串流工具是Moonlight,最低支持安卓4.1,机顶盒是安卓4.4,完全适用:
4.1 外设支持 #
作为瘦终端需要插外设,比如键盘、鼠标、手柄等等。
查看已连接的输入设备列表:
cat /proc/bus/input/devices
北通的手柄正常:
罗技的鼠标正常:
绿联拓展坞正常使用,普通的薄膜键盘正常:
备注:拓展坞即便接上辅助供电,机械键盘接上用不了。
4.2 游戏实测 #
在机顶盒安装Moonlight:
发现局域网的串流主机:
steam启动:
插上鼠标以后,鼠标指针变成这种大指针:
魂like/密药猎人/除客刀,怪猎肝不动了,dota排位好久不玩了,现在偶尔玩点战棋类的地图,比如自走棋:
平常用笔记本玩游戏,看的是小屏幕。在客厅的电视上串流,游戏体验又不一样了。
1080p 60帧的串流效果:
5. 网络安全/逆向分析 #
回归主线,搞点网络安全相关的东西。
5.1 获取恢复出厂设置密码 #
系统设置里面可以恢复出厂设置,密码在网上没有搜索到:
把文件/system/app/Hunan_Setting_IPTVsettings.apk提取出来:
jadx-gui解包,关键字定位,恢复出厂设置密码:96531
5.2 调出隐藏设置界面 #
这种机顶盒设备好像都有隐藏设置界面,通过特定的按键顺序唤出。
在系统设置界面显示隐藏界面
原本系统设置:
显示隐藏界面的按键顺序:上 → 下 → 左 → 右 → 音量下 → 音量下:
相关代码如下:
@Override // android.app.Activity, android.view.KeyEvent.Callback
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
Log.d("SystemInfoActivity", "keyCode == " + keyCode + "--count_console==" + this.count_console + "keyStr==" + ((Object) this.keyStr) + "Factory_count=" + this.Factory_count);
if (keyCode == 19) {
this.keyStr.setLength(0);
this.keyStr.append("1");
}
if (keyCode == 20) {
this.keyStr.append("2");
}
if (keyCode == 21) {
this.keyStr.append(SettingsData.NETWORKTYPE_STATIC);
if (this.count_console == 4 || this.count_console == 5) {
this.count_console++;
if (this.count_console == 6) {
startconsole();
this.count_console = 0;
}
} else {
this.count_console = 0;
}
}
if (keyCode == 22) {
this.keyStr.append(SettingsData.NETWORKTYPE_DOUBLESTACK);
if (this.count_console == 2 || this.count_console == 3) {
this.count_console++;
} else {
this.count_console = 0;
}
}
if (keyCode == 24) {
this.keyStr.append("5");
if (this.count_console == 1) {
this.count_console++;
} else {
this.count_console = 0;
}
}
if (keyCode == 25) {
Log.v("SystemInfoActivity", "keyStr.toString()=" + this.keyStr.toString());
this.keyStr.append("6");
if (this.keyStr.toString().equals(this.keyStrEquals)) {
Intent i = new Intent(this, HindingSetting.class);
startActivity(i);
this.keyStr.setLength(0);
}
if (this.count_console == 0) {
this.count_console++;
} else {
this.count_console = 0;
}
}
if (keyCode == 16) {
if (this.Factory_count == 0) {
this.Factory_count++;
} else {
this.Factory_count = 0;
}
} else if (keyCode == 13) {
if (this.Factory_count == 1) {
this.Factory_count++;
} else {
this.Factory_count = 0;
}
} else if (keyCode == 12) {
if (this.Factory_count == 2) {
this.Factory_count++;
} else {
this.Factory_count = 0;
}
} else if (keyCode == 10) {
if (this.Factory_count == 3) {
this.Factory_count++;
} else {
this.Factory_count = 0;
}
} else if (keyCode == 8) {
if (this.Factory_count == 4) {
this.Factory_count = 0;
} else {
this.Factory_count = 0;
}
} else {
this.Factory_count = 0;
}
return super.onKeyDown(keyCode, keyEvent);
}
5.3 再随手挖一个命令注入漏洞 #
漏洞挖掘的突破口一般是找用户输入点,这个设备有两个用户输入点:
- Ping
- Traceroute
命令注入生效的只有一个,就是Ping。
Traceroute会把输入的域名先解析成ip地址,所以Traceroute这里注入不了。
Ping功能命令注入的poc非常简单,就一条命令:
执行效果:
命令注入调用栈:
接收输入(Java层)-> 拼接字符串pingHost_start(Java层)-> 调用executeCommand1(Java层)-> 调用executeCommand(C/C++层)-> 发送到系统服务RootShellServer执行命令
(1)NetworkPingActivity:接收输入域名
(2)NetworkPingActivity:拼接命令
(3)CommonUtils:调用Java方法executeCommand1
(4)CommonUtils:调用libRootShell_jni.so导出函数executeCommand
(5)libRootShellService.so导出函数android::RootShellService::executeCommand
6. 参考文档 #
- 全程使用DeepSeek作为辅助搜索
- (湖南广电)艾米格_蜗牛TV-APG0100_GK6323V100A_1+8_RTL8723DU