浅谈ldrestart指令如何在不重启设备电源的情况下刷新用户环境

不知道各位有没有注意到一个很有一的情况,那就是越狱软件在注销桌面的时候多了一个选项,名字叫做“Reload System Daemon”。LaunchDaemon作为系统的守护者,有着神圣不可轻犯的权威。你每次kill一个守护者,作为boss代号为launchd的守护着就会负责重启他。当launchd被kill,你的设备就失去了用户环境,内核会立即panic。那怎样在不丢失用户态环境的情况下“重启设备”,并且不产生崩溃日志呢?答案也很简单,我们要让除了launchd以外的所有进程被爸爸干掉。

首先,我们需要导入头文件。能力有限,不一一叙述头文件。(我太菜了

#include <cstdio>
#include <cstdlib>
#include <errno.h>
#include <signal.h>
#include <sysexits.h>
#include <unistd.h>
#include <launch.h>
#include <sys/stat.h>

继续我们在main里面添加代码。我们要做的事情有三件。

  1. 获取进程列表
  2. 让进程自己死去
  3. 清理环境并且自己结束自己

获取进程列表

我们需要先创建一个请求,然后发送请求给launchd。在获取到数据以后,便可以释放我们创建的请求。这里没有auto_release_pool所以要额外的小心。咱们的守护者会死去,所以可能没有人来搭理善后。内核能做的非常有限。

    launch_data_t req = launch_data_new_string(LAUNCH_KEY_GETJOBS);
    launch_data_t res = launch_msg(req);
    launch_data_free(req);

接下来我们需要验证数据的合法性,检查他是否存在,并确认眼神撒

if (res == NULL || launch_data_get_type(res) != LAUNCH_DATA_DICTIONARY)
    return EPERM; // Error Permit

让我们来看看我们获取到了什么 这里的数据很有意思。

<OS_xpc_dictionary: dictionary[0x103089ca0]: { /* GREAT STUFF*/ }>

我们拿到了一份清单。接下来我们写一个自己的方法,并通过迭代器来让每一个进程都作为参数执行我们需要的代码。

void execEach(launch_data_t v1, const char *name, void *baton) 
launch_data_dict_iterate(res, &execEach, NULL); // 接力开始!

让进程自己死去

首先我们从迭代器传入的launch_data_t中提取进程pid,然后给他发送信号。

if (launch_data_get_type(v1) != LAUNCH_DATA_DICTIONARY) // 再次检查
        return;
launch_data_t _Nullable v2(launch_data_dict_lookup(v1, LAUNCH_JOBKEY_PID)); 
if (v2 == NULL || launch_data_get_type(v2) != LAUNCH_DATA_INTEGER) // 检查pid data
        return; // 并不觉得我们需要这个但是做作更健康
long long qaqPID = launch_data_get_integer(v2); // 从data获取PID
if (kill(qaqPID, 0) == -1)
        return; // 我们没有权限给他发送信号,差不多就是一些boos了。

那么问题来了,为什么我们不kill(qaqPID, 9)呢?因为这样进程就会产生一个没有被handle的sig,进而被其他守护者或kernel捕捉产生crash log。我们发送0的意义在于检查这个进程是不是真的需要被kill我们有没有能力让他kill以避免ldrestart在执行过程中崩溃。

接着我们获取进程在注册字典中的名字。

launch_data_t _Nullable v3 = launch_data_dict_lookup(v1, LAUNCH_JOBKEY_LABEL);
if (v3 == NULL || launch_data_get_type(v3) != LAUNCH_DATA_STRING)
        return; // 通用检查

接下来到了让他自己死去的环节了。我们创建一个请求并发送。

launch_data_t dadHere = launch_data_alloc(LAUNCH_DATA_DICTIONARY); // 分配一个data
// 在爸爸指令里面插入停止代码 lol 注意v3是进程标识符 他是launch_data_t
auto hi(launch_data_dict_insert(dadHere, v3, LAUNCH_KEY_STOPJOB));
auto rettt(launch_msg(dadHere));
// 好了 结束了 他已经躺好了 轮到我们善后自己了 launch_data_t 似乎都需要手动释放
launch_data_free(dadHere); launch_data_free(rettt);

这种方式同样适用于检测到越狱以后无crash无追踪退出,但是具体的实现这里就不详细叙述了。

另外,现在还有更加优秀的sbreload来加速桌面环境的刷新。一般来说越狱开发者注销桌面有四个选择,按照推荐顺序分别是【sbreload】【ldrestart】【killall backboardd】【killall SpringBoard】。sbreload用户体验应该是最好的,ldrestart更加彻底。而干掉backboardd一定程度上解决的SpringBoard死得不够彻底的问题,也能够很神奇的解决一些循环等待SpringBoard加载而锁死的问题。虽然SpringBoard有超时自动kill的机制,但是这个时间经测试大概在5分钟左右。>> iOS 11.3.1

有错误还请各位指出咯。

4 Likes

学习了 紫薯布丁 :grinning:

1 Like

牛逼,学习了。

1 Like

首先赞一个👍 整个过程大概就如你分析那样,我之前分析过ldrestart的源码以及发送LAUNCH_KEY_STOPJOB后launchd的实现。这里说下我的理解

kill(pid, 0)这里主要是为了检查进程是否存在,以及能否有权限给目标进程发送signal。比如当你用mobile用户去执行ldrestart的时候就会得到以下错误

Last login: Mon Sep  9 11:19:55 on ttys001         
ret:~ mobile$ ldrestart                            
Error stopping service:  1 - Operation not permitted                                                   
Error stopping service:  1 - Operation not permitted                                                   
Error stopping service:  1 - Operation not permitted

实际上在发送LAUNCH_KEY_STOPJOB这个xpc消息以后,launchd也会尝试用kill(pid,9)杀掉进程。

处理LAUNCH_KEY_STOPJOB的代码

 result = strcmp(v7, "StopJob");
  if ( !(_DWORD)result )
  {
    if ( !v6 )
      goto LABEL_43;
    result = xpc_get_type(v6);
    if ( (_UNKNOWN *)result != &_xpc_type_string )
      goto LABEL_43;
    v23 = xpc_string_get_string_ptr(v6);
    result = sub_10001E274(v9, v23);
    if ( result )
    {
      v24 = result;
      v25 = sub_10001EA3C(v9, 3LL, 136LL, v8, 0LL, 0LL);
      if ( (_DWORD)v25 )
      {
        v12 = v25;
        v19 = "service stop";
        goto LABEL_53;
      }
      result = sub_100015878(v24);
      v12 = result;
      if ( (_DWORD)result )
        goto LABEL_44;
      goto LABEL_88;
    }

stop目标进程的部分代码

...
 proc_terminate();
  if ( kill(*(_DWORD *)(v2 + 816), 9) )
  {
    v5 = __error();
    v3 = *v5;
    v6 = qword_100044420;
    v7 = strerror(*v5);
    sub_1000270C0("%s: could not kill trampoline: %d: %s", v6, 0, v2 + 1120, v3, v7);
    if ( (_DWORD)v3 )
    {
      if ( (_DWORD)v3 == 3 )
      {
        v9 = sub_10002931C(*(unsigned int *)(v2 + 816), &v32);
        v3 = v9;
        if ( (_DWORD)v9 )
        {
          v10 = _os_assumes_log_ctx(sub_100011FE8, v2, (signed int)v9);
          _os_avoid_tail_call(v10);
        }
      }
      else
      {
        v16 = _os_assumes_log_ctx(sub_100011FE8, v2, v3);
        _os_avoid_tail_call(v16);
        *(_DWORD *)(v2 + 844) = -(signed int)v3;
      }
      goto LABEL_31;
    }
  }
  else
  {
    *(_DWORD *)(v2 + 844) = 9;
    v12 = qword_100044420;
    v13 = strsignal(9);
    sub_1000270C0("%s: signaled trampline: %s", v12, 0, v2 + 1120, (__int64)v13);
  }
...
  v25 = qword_100044420;
  v26 = strsignal(v21);
  sub_1000270C0("%s: scheduling cleanup in %llu sec after sending %s", v25, 0, v2 + 1120, v22, v26);
...

综上我的理解在于让launchd去kill掉目标进程主要是为了能够正常、安全的回收进程,以及处理一些异常和服务相关的事情。

5 Likes

我现在拿ldrestart来解决macOS一些音频服务crash的问题贼鸡儿好用

我也是,之前XS设备越狱以后相机不能用,用ldrestart才解决的所以简单分析了一下😅

2 Likes

发错了。。。