pwn-tv
Konka TV PWN
原文作于 2023.01.27, 整理于 2023.11.21
老家的 Konka 电视是个 Android 系统,曾经 nmap 扫描过,发现 adb 是开放的,这次过年回去研究了一下,发现可以提权。
分析
使用 adb 是可以直接连接的,uid 是 shell ,不需要在 TV 上进行任何授权。
看看系统属性:
1 | [ro.adb.secure]: [0] |
内核版本:
1 | Linux version 3.18.16_s5 (wangkang@ubuntu) (gcc version 4.9.1 20140529 (prerelease) (crosstool-NG linaro-1.13.1-4.9-2014.07 - Linaro GCC 4.9-2014.06) ) #1 SMP Wed Apr 8 16:18:43 CST 2020 |
cpu 是 arm64 的,Android SDK 版本 22 .
ro.adb.secure
是 0 ,因此 adb 不需要验证即可连接。
ro.debuggable
是 1 ,因此这个系统是可调试的
ro.build.type=user
,这是个 user 构建的系统。
系统中没有 su ,尝试了一下 adb root
,执行倒是成功了,但是进入 adb shell 仍然不是 root 。此外,使用 run-as
总是报错找不到包,看来想要得到 root 甚至其他用户都没那么容易。
不过 ro.debuggable=1
还会影响别的东西,比如,app 进程的调试默认都是打开的。使用 adb jdwp
可以看到一串 pid ,其中甚至包括 system_server 的。
使用 adb forward tcp:xxxxx jdwp:
可以转发 jdwp ,利用 jdb 连接,真的可以对系统服务进行调试!
但是我们不能对 zygote 进行调试,因为 Android 在设计上就是关闭的。
注意到 system_server 的权能(Capability)是这样的:
1 | CapInh: 0000000000000000 |
其中包括了 cap_sys_ptrace ,意味着可以对任意进程进行调试——无视了 uid 等隔离。不过 CapInh 为 0 ,意味着 execve 的进程不具备这些权限。
另一方面,系统中还存在一个大洞:getenforce
返回的是 Disable
,这意味着 SELinux 的保护也不存在!
如此一来,如何提取 root 权限就有一条比较清晰的路线了:
- 系统服务暴露的 jdwp 让我们执行任意代码
- 在系统服务中加载 native lib ,ptrace 控制任意一个 root 进程
- 控制 root 进程反弹 shell
理论存在,实践开始!
第一阶段:取得 system 和 ptrace 权限
理论上来说我们可以用 am attach-agent
注入一个 agent lib ,这样就不需要和 jdwp 打交道了,但是这个 API 22 的 am 竟然没有 attach-agent 子命令。我不打算仔细研究是否能注入 agent 了,直接利用 jdwp 就行。
java - How to run custom code in JDB session? - Stack Overflow
jdwp 需要在线程挂起的时候才能执行大部分操作,比如执行任意代码。
jdb 中可以使用 eval/dump/print
执行代码,但我们首先需要挂起一个线程,
threads
列出线程信息(注意线程 id 并不是 linux 的 tid),由于是手动操作,为了避免长时间挂起线程影响系统,这里我选择挂起 watchdog
(但是实际上一直挂起它也会在某些情况下导致系统卡住,如 activity 切换的时候,这时候 resume 或退出调试器就好了)。
使用 suspend tid
挂起, resume tid
恢复(不写 tid 默认所有线程)。
使用 thread tid
进入线程,stepi
步进,这时候就可以用 eval
等命令执行任意代码了。
eval java.lang.Runtime.getRuntime().exec
可以执行任意程序,我们可以用 busybox 的 telnetd 在网络上开启 shell (可以用 Magisk busybox):
busybox telnetd -l /system/bin/shell -p 8023
但是由于 system_server 的权能无法继承,因此这个 shell 没什么用(甚至无法绑定 telnet 默认端口 23,而系统服务是有 cap_bind_net_service 权限的),最多访问一些系统目录。
编写了一个简单的 lib 测试,eval java.lang.System.load("/data/local/tmp/libpwn.so")
加载,可以发现 root 进程确实可以 ptrace 附加(此处附加了 init ,也许附加 zygote64 会更好)
第二阶段:控制 root 进程
思路:mmap rwx 内存区域,写 shellcode ,执行 fork 和 execve 系统调用启动 telnetd 。
可以利用 pwntools 生成 shellcode 。
在 android 中, SYS_fork
并不存在,libc fork 采用的是 clone
实现 fork
可以参考 AOSP:
我们使用 jdwp 加载一个 so ,在里面 ptrace init 注入 shellcode 执行。
ptrace 的源代码在这里:https://gist.github.com/5ec1cff/0cb045c79d78aa02f927edfc54be5ade#file-pt-inject-c
下面详细说说 shellcode 怎么得来的:
1 | from pwn import * |
但是使用上面生成的 shellcode 会出现 SIGBUS :
1 | 7fd4b09000-7fd4b2a000 rw-p 00000000 00:00 0 [stack] |
SIGBUS 应该不是栈溢出,查了一下是 arm64 stp, ldp 指令要求 sp 16 位对齐
How do I parse ARM64 assembly SIGBUS error? - Stack Overflow
是 pushstr_array 导致的,看起来是 pwntools 的问题,但是 pwntools 又说修了。
aarch64.linux.pushstr_array is broken · Issue #1549 · Gallopsled/pwntools
最后干脆 execve 一个跳板程序(这里就执行一个 sh /data/local/tmp/pwn.sh
),不需要 arg 也就不会用到 pushstr_array ,总算不会 SIGBUS 了。
1 | from pwn import * |
最后再完善一下 JDWP 注入,代码:
https://gist.github.com/5ec1cff/0cb045c79d78aa02f927edfc54be5ade#file-jdwp-py
参考了 jdwp-shellifier 和以下项目。
一种基于JDWP动态注入代码的方案 | Windy’s Journal
PWNED
通过 telnet 即可访问 root shell 。
总结
这一次 pwn 算是同时学习了利用 Java 的调试协议 (JDWP) 和 linux 调试 API (ptrace) 实现任意代码的执行,同时也体会到,随意开放调试是会带来巨大风险的。这台电视的系统不仅在网络上开放了 adb ,还允许调试任意进程,SELinux 被禁用,利用 system_server 的 ptrace 特权从而得到 root 权限——正是这一系列安全机制在系统的开发阶段被忽视,才有了提权的可能。但仔细一想,最恐怖的还是任意安装到电视的具有网络访问权限的 App 都可以这样获得 root 权限,掌控整台设备。假如电视还有摄像头、麦克风等外设(当然这台是没有的),必然会进一步威胁到用户的隐私。因此,作为厂商,在系统安全方面一定要做到合规。