sshdog_ios - TrollStore SSH

大类
iOS
Util
技术标签
逆向-iOS
开发-HookPatch-iOS
原理研究-iOS
优先级
Medium
开始日期
Nov 10, 2022
状态
Maintaining
Public
Public
最后更新
Nov 13, 2022

为什么要搞

  1. 本来之前其实就想过要搞了,但是没时间
  1. 最近需要写一个机器的原彩
    1. 精诚助手那边给了拆硬盘和越狱读两种方案,拆硬盘从多层主板之后就全是坑, 所以尽可能还是越狱读原彩
    2. 他越狱读原彩的原理是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这几个关键字组合来组合去就搜出来了
  • 开始魔改!
 

踩坑

  • 但是发现这个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
    • 这个时候发现fish执行完exec-job-exec: Executed job 0 from command 'fish_default_key_bindings’就退出了
    • 查看源码就知道这个函数后面就是配置tty
      • 分析逻辑发现没有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
          • notion image
    • 进一步用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