为什么要搞
- 本来之前其实就想过要搞了,但是没时间
- 最近需要写一个机器的原彩
- 精诚助手那边给了拆硬盘和越狱读两种方案,拆硬盘从多层主板之后就全是坑, 所以尽可能还是越狱读原彩
- 他越狱读原彩的原理是paramiko连上ssh然后执行ls,随后scp传文件回去
怎么搞
- 选择语言:iOS上面其实只有两种语言能用
- ObjC:写起来太麻烦了
- Go:跨平台,自带iOS支持
- 所以选择Go是很合理的事情
- 确定需求:
- 简单执行命令:ls需要能跑
- 需要scp:
- scp是个单独的协议,正常来说他的流程是scp客户端在服务器执行/usr/bin/scp启动scp服务器,通过ssh通道再沟通
- 我们没有scp,所以这个方法肯定不行
- 需要一个直接实现了scp协议的服务器
- 搜索了一下,还真有:sshdog
- 虽然我回溯完发现我完全不知道是怎么搜出来这个东西的
- 我把go/golang、ssh server、scp server、github这几个关键字组合来组合去就搜出来了
- 开始魔改!
踩坑
- posix_spawn是很好写的,因为有Cgo一切都很顺利
- 我把这个代码直接移植到了go上,效果很好:https://github.com/dweinstein/ios-stuff/blob/master/posix_spawn/main.c
- 直接把exec整了个接口出来, 万事大吉
- 但是发现这个ssh server可以执行命令(ssh XXXX ls),却拉不起来shell,而且能拉起来sh,只是拉不起来fish
- 当然这个对于解锁原彩已经够用了,但是肯定还是想可以用一点
- 在Mac上面进一步测试发现fish必须要tty才会启动交互式界面
- fish支持调试flag,我们直接拉满调试flag用一个wrapper当shell
#!/opt/homebrew/bin/fish echo 11111 fish --debug="*" -i -N echo fuck
- 分析逻辑发现没有tty就会直接die
- 垃圾fish
- 两种选择:换一个shell、硬整tty
- 换shell:
- busybox:有个ispy-shell,几年前的东西了,完全build不起来
- J的binpack:有bash,但是不给源码,我也用不上
- Go写的shell:找了一圈,没找到很好用的
- 硬整tty:
- 只要setsid + TIOCSCTTY就可以把tty设置上去然后万事大吉了
显然更简单啊
- 我选择了硬整tty,然后发现这东西并不是想象中那么简单的
- 在第一个晚上的时候我试遍了posix_spawn的所有的配置,搞出来了第一版方案
- 让sshd每次setsid,然后再TIOCSCTTY,然后再让紫禁城继承tty就可以了,
- 这个方案在mac上非常完美,除了不能用Ctrl-C结束(因为控制tty变了)以外所有东西都work,所以我开心的写了一个从stdin读Ctrl+D(EOF)的函数
- 但是到手机上发现setsid会operation not permitted (ios13、14、15都这样)
- 原因到现在也不明
- 痛定思痛,反正一个进程来回setsid是不行了,所以我们需要一个辅助进程来每次setsid之后再执行对应进程
- 尝试直接单文件方案(通过调用自身并加参数),然后发现怎么都过不去
- setsid一直失败,设置tty也一直失败,而且还没有输出
- 完全不知道原因,人都蒙了
- 事后总结原因有:
- 我意外的一直设置了POSIX_SPAWN_SETSID这个flag导致他一直setsid,自然第二次setsid就会失败
- 我没有给openpty开出来的tty设置FD_CLOEXEC,导致他没办法传到子进程里,自然就失败了
- 我没有捕捉posix_spawn的返回值,很多时间其实他都是出错的,但是我却以为他成功了
- posix_spawn出错的原因有很多其实都在file_actions,具体add_dup2是个什么原理我还没完全懂
- 重新实现:虽然不知道为什么,但是我猜测是golang的锅,毕竟golang的初始化流程很长,封装也很多,看不到很多东西
- 用C重新走了一遍流程,发现竟然连标准的fork - setsid - TIOCSCTTY流程都走不通(这是100%能走通的才对)
- 实际原因:在c里面只有出错了之后才应该去检查errno,函数正常返回的时候不会清空errno,所以我的所有命令都报错
- 被逼无奈,对着sudo开始改,把ios13的sudo直接拖出来,在ida里面看那些逻辑我少了
- 少了jailbreak helper的platformize操作:patch掉发现没影响
- 少了sudo_term_copy配置term的操作:也没影响
- 少了一些信号处理(sudo_terminated):也没影响
- 没有close掉父进程中子进程的stdio句柄:也没影响
- 没有在刚进main函数时fix_fds修复stdio:也没影响
- tmd发现sudo跟我的流程一模一样没错啊,怎么回事?
- 写了个debug helper输出了pid、ppid、pgid、pgrp、sid,发现实际上是设置上了的
- 实际上有两个错误
- setpgid(0,0)和setsid不能同时用
- errno应该只在返回-1的时候用
- fork跑通了,进一步测了posix_spawn,也跑通了
- 进一步把这个东西写成了spawn helper,用setexec flag实现execve的效果,发现提示句柄无效,为什么?
- 最后发现是FD_CLOEXEC
- 进一步用golang重写这个spawn helper,成功了 :)
存档
编译命令
CC=$(go env GOROOT)/misc/ios/clangwrap.sh CGO_ENABLED=1 GOOS=ios GOARCH=arm64 go build -ldflags "-w" && scp sshdog mobile@192.168.1.24:sshdog