openwrt路由器启动流程

前言

只做了大致流程的分析

总结

通过/etc/rc.d下的链接执行**/etc/init.d**下的初始化服务,不过有的固件并没有init.d目录,因此还是得特殊情况特殊看待

流程

/sbin/init->第一次/sbin/procd->/sbin/preinit->第二次/sbin/procd

分析

/sbin/init

首先第一个启动的程序便是/sbin/init初始化程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // r0
int v4; // r0
int v5; // r0
int v6; // r0
int v7; // r0

sigaction(15, &asc_1244C, 0);
sigaction(10, &asc_1244C, 0);
v3 = sigaction(12, &asc_1244C, 0);
v4 = sub_9110(v3);
sub_903C(v4);
v5 = sub_9C04(1);
v6 = uloop_init(v5);
v7 = sub_94D0(v6);
uloop_run(v7);
return 0;
}

sub_9110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int sub_9110()
{
int result; // r0
int v1; // r4
struct stat v2; // [sp+8h] [bp-68h] BYREF

result = getpid();
if ( result == 1 )
{
mount("proc", "/proc", "proc", 0x400u, 0);
mount("sysfs", "/sys", "sysfs", 0x400u, 0);
mount("tmpfs", "/tmp", "tmpfs", 0x406u, 0);
mkdir("/tmp/run", 0x1FFu);
mkdir("/tmp/lock", 0x1FFu);
mkdir("/tmp/state", 0x1FFu);
symlink("/tmp", "/var");
mount("tmpfs", "/dev", "tmpfs", 0x400u, "mode=0755,size=512K");
mkdir("/dev/shm", 0x1EDu);
mkdir("/dev/pts", 0x1EDu);
mount("devpts", "/dev/pts", "devpts", 0x400u, "mode=600");
sub_98F0("*", 384);
mknod("/dev/null", 0x1B6u, 0x103uLL);
setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1);
if ( stat("/dev/console", &v2) )
{
syslog(0, "Failed to stat %s\n", "/dev/console");
fprintf((FILE *)stderr, "procd: Failed to stat %s\n", "/dev/console");
}
else
{
v1 = open("/dev/console", 2);
if ( v1 < 0 )
v1 = open("/dev/null", 2);
dup2(v1, 0);
dup2(v1, 1);
dup2(v1, 2);
if ( (unsigned int)v1 > 2 )
close(v1);
}
syslog(0, "Console is alive\n");
return fputs("procd: Console is alive\n", (FILE *)stderr);
}
return result;
}

可以看到要求当前程序的pid为1,在仿真时,或许可以直接把其patch掉,挂载多个文件系统,创建目录和符号链接,设置模式,大小和权限,之后调用sub_98F0函数

sub_98F0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __fastcall sub_98F0(const char *a1, int a2, int a3)
{
size_t v6; // r0
_BYTE *v7; // r4
_DWORD v8[7]; // [sp+4h] [bp-1Ch] BYREF

v8[0] = a2; // 0x180
v8[1] = a3;
if ( chdir("/dev") ) // 设置当前工作目录为/dev,失败返回1
return 1;
v6 = strlen(a1); // *
v7 = malloc(v6 + 2);
*v7 = '*';
strcpy(v7 + 1, a1);
v8[0] = v7; // &(**)
addrxx = (int)v8;
dword_144A4 = 1;
dword_12460 = a2;
sub_9688(1);
sub_9688(0);
chdir("/");
return 0;
}

其中的sub_9688函数,是为了遍历指定目录,读取符号链接,匹配特定模式,并创建相应的设备节点。

  1. 参数为 0:
    • 路径为 /sys/dev/char
    • 创建字符设备节点(v14 = 0x2000)。
  2. 参数为 1:
    • 路径为 "/sys/dev/block"
    • 创建块设备节点(v14 = 24576,即 0x6000)。

sub_903C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void sub_903C()
{
int v0; // r0
int v1; // r5
char buf[1016]; // [sp+8h] [bp-440h] BYREF
regex_t v3; // [sp+408h] [bp-40h] BYREF
regmatch_t v4; // [sp+428h] [bp-20h] BYREF
int v5; // [sp+430h] [bp-18h]
int v6; // [sp+434h] [bp-14h]

v0 = open("/proc/cmdline", 0);
v1 = v0;
if ( v0 >= 0 )
{
buf[read(v0, buf, 0x3FFu)] = 0;
close(v1);
regcomp(&v3, "init_debug=([0-9]+)", 1);
if ( !regexec(&v3, buf, 2u, &v4, 0) )
{
buf[v6] = 0;
dword_12474 = atoi(&buf[v5]);
}
regfree(&v3);
}
}

此函数的作用是解析内核命令行参数,提取 init_debug 的值,并将其存储在全局变量 dword_12474

sub_9C04

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const char *__fastcall sub_9C04(int a1)
{
const char *result; // r0
const char *v3; // r4
int v4; // r4
int v5; // r0
int v6; // r0
FILE *v7; // r4
int v8; // r0

result = getenv("WDTFD");
v3 = result;
if ( dword_12464 < 0 )
{
dword_144B4 = (int)sub_9980;
if ( result )
{
if ( (unsigned int)dword_12474 > 1 )
{
syslog(0, "Watchdog handover: fd=%s\n", result);
fprintf((FILE *)stderr, "procd: %s(%d): Watchdog handover: fd=%s\n", "watchdog_init", 103, v3);
}
dword_12464 = atoi(v3);
result = (const char *)unsetenv("WDTFD");
}
else
{
dword_12464 = -1;
}
v4 = dword_12464;
if ( dword_12464 >= 0 )
{
if ( !a1 )
{
v5 = fcntl(dword_12464, 1);
fcntl(v4, 2, v5 | 1);
}
syslog(0, "- watchdog -\n");
fputs("procd: - watchdog -\n", (FILE *)stderr);
sub_9A84(30);
result = (const char *)sub_9980(&unk_144A8);
if ( (unsigned int)dword_12474 > 3 )
{
v6 = sub_9A84(0);
syslog(0, "Opened watchdog with timeout %ds\n", v6);
v7 = (FILE *)stderr;
v8 = sub_9A84(0);
return (const char *)fprintf(v7, "procd: %s(%d): Opened watchdog with timeout %ds\n", "watchdog_init", 120, v8);
}
}
}
return result;
}

初始化看门狗计时器

sub_94D0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
int sub_94D0()
{
__pid_t v0; // r0
const char *v1; // r0
const char *v2; // r0
__pid_t v3; // r0
int result; // r0
char *file[4]; // [sp+Ch] [bp-3Ch] BYREF
char *v6[11]; // [sp+1Ch] [bp-2Ch] BYREF

v6[0] = "/bin/sh";
v6[1] = "/etc/preinit";
v6[2] = 0;
file[0] = "/sbin/procd";
file[1] = "-h";
file[2] = "/etc/hotplug-preinit.json";
file[3] = 0;
syslog(0, "- preinit -\n");
fputs("procd: - preinit -\n", (FILE *)stderr);
dword_12484 = (int)sub_9344;
v0 = fork();
dword_12488 = v0;
if ( !v0 ) // 子
{
execvp(file[0], file);
syslog(0, "Failed to start plugd\n");
v1 = &byte_9F8F;
goto LABEL_8;
}
if ( v0 <= 0 )
{
syslog(0, "Failed to start new plugd instance\n");
v2 = byte_9FAD;
return fputs(v2, (FILE *)stderr);
}
uloop_process_add(&unk_12478);
setenv("PREINIT", "1", 1);
dword_12498 = (int)sub_9350;
v3 = fork();
dword_1249C = v3;
if ( !v3 )
{
execvp(v6[0], v6);
syslog(0, "Failed to start preinit\n");
v1 = "procd: Failed to start preinit\n";
LABEL_8:
fputs(v1, (FILE *)stderr);
exit(-1);
}
if ( v3 <= 0 )
{
syslog(0, "Failed to start new preinit instance\n");
v2 = "procd: Failed to start new preinit instance\n";
return fputs(v2, (FILE *)stderr);
}
result = uloop_process_add(&unk_1248C);
if ( (unsigned int)dword_12474 > 3 )
{
syslog(0, "Launched preinit instance, pid=%d\n", dword_1249C);
return fprintf((FILE *)stderr, "procd: %s(%d): Launched preinit instance, pid=%d\n", "preinit", 120, dword_1249C);
}
return result;
}

首先fork运行/sbin/procd,以-h /etc/hotplug-preinit.json作为参数,之后uloop_process_add(&unk_12478);, setenv(“PREINIT”, “1”, 1);设置preinit为1作为flag,之后fork运行/bin/sh,以/etc/preinit作为参数

execvp 会加载新的程序到当前进程的地址空间,覆盖原有的代码、数据和堆栈,因此紧跟execvp后面的便是错误的输出

uloop_process 是 Libubox 提供的一个进程管理工具,它并不会帮你创建进程,它主要用来等待子进程工作的结束,然后调用自定义的回调函数,所以一般需要配合 fork一起使用。

这里注意观察两个地方

uloop_process_add(&dword_12478);和result = uloop_process_add(&dword_1248C);,其实这个add的是结构体

1
2
3
4
5
6
7
struct uloop_process
{
struct list_head list;
bool pending;
uloop_process_handler cb;
pid_t pid;
};

实际调用的函数是cb,而list_head一般包括pre和next两个指针占用8个字节,pending由于对齐占用4个字节

因此之后main函数运行uloop_run(v1);这个时,调用的分别是sub_9344和sub_9350函数,但是首先来看之后运行的/sbin/procd和/etc/preinit

/sbin/procd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v5; // r0
const char *v7; // r4
int v8; // r0
int v9; // r0
int v10; // r0
int v11; // r0

v5 = getenv("DBGLVL");
if ( v5 )
{
dword_1CEE4 = atoi(v5);
unsetenv("DBGLVL");
}
while ( 1 )
{
v8 = getopt(argc, (char *const *)argv, "d:s:h:");
if ( v8 == -1 )
break;
switch ( v8 )
{
case 'h':
return sub_10E20(optarg);
case 's':
dword_1AD68 = optarg;
break;
case 'd':
.......
}

因为是-h参数,因此看前面即可,DBGLVL估计是测试debug用的

sub_10E20

1
2
3
4
5
6
7
8
9
int __fastcall sub_10E20(int a1)
{
int v2; // r0

uloop_init();
v2 = sub_10C5C((const char *)a1);
uloop_run(v2);
return 0;
}

sub_10C5C函数功能如下

  1. 设置热插拔套接字:(类似usb等)
    • 创建并绑定一个套接字,用于处理热插拔事件。
  2. 错误处理:
    • 如果套接字创建或绑定失败,记录错误信息并退出程序。
  3. 配置套接字选项:
    • 设置接收缓冲区的大小。
  4. 初始化 JSON 脚本:
    • 调用初始化函数,准备处理 JSON 脚本。
  5. 添加到事件循环:
    • 将套接字添加到事件循环中,以便异步处理事件。

/etc/preinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

[ -z "$PREINIT" ] && exec /sbin/init

export PATH=/bin:/sbin:/usr/bin:/usr/sbin

pi_ifname=
pi_ip=192.168.1.1
pi_broadcast=192.168.1.255
pi_netmask=255.255.255.0

fs_failsafe_ifname=
fs_failsafe_ip=192.168.1.1
fs_failsafe_broadcast=192.168.1.255
fs_failsafe_netmask=255.255.255.0

fs_failsafe_wait_timeout=2

pi_suppress_stderr="y"
pi_init_suppress_stderr="y"
pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
pi_init_cmd="/sbin/init"

. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
. /lib/functions/userconfig.sh

boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root

for pi_source_file in /lib/preinit/*; do
. $pi_source_file
done

boot_run_hook preinit_essential

pi_mount_skip_next=false
pi_jffs2_mount_success=false
pi_failsafe_net_message=false

boot_run_hook preinit_main
1
2
3
4
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
. /lib/functions/userconfig.sh

function.sh定义了一系列配置函数,

/lib/functions/preinit.sh中定义了boot_hook_init函数

1
2
3
4
5
boot_hook_init() {
local hook="${1}_hook"
export -n "PI_STACK_LIST=${PI_STACK_LIST:+$PI_STACK_LIST }$hook"
export -n "$hook="
}

类似于boot_hook_init preinit_essential,便有preinit_essential_hook= 之类的环境变量

之后初始化一系列的hook,然后调用/lib/preinit下的函数

调用了如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/bin/sh

define_default_set_state() {
. /etc/diag.sh
}

boot_hook_add preinit_main define_default_set_state
===============================================================================================
#!/bin/sh
#
# Copyright (c) 2013 The Linux Foundation. All rights reserved.
#
do_ipq806x() {
. /lib/ipq806x.sh

ipq806x_board_detect
}

boot_hook_add preinit_main do_ipq806x
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

# commands for emitting messages to network in failsafe mode

indicate_failsafe_led () {
set_state failsafe
}

indicate_failsafe() {
echo "- failsafe -"
preinit_net_echo "Entering Failsafe!\n"
indicate_failsafe_led
}

boot_hook_add failsafe indicate_failsafe
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

preinit_ip() {
# if the preinit interface isn't specified and ifname is set in
# preinit.arch use that interface
if [ -z "$pi_ifname" ]; then
pi_ifname=$ifname
fi

[ -n "$pi_ifname" ] && grep -q "$pi_ifname" /proc/net/dev && {
ifconfig $pi_ifname $pi_ip netmask $pi_netmask broadcast $pi_broadcast up
}
}

preinit_ip_deconfig() {
[ -n "$pi_ifname" ] && grep -q "$pi_ifname" /proc/net/dev && {
ifconfig $pi_ifname 0.0.0.0 down
}
}

preinit_net_echo() {
[ -n "$pi_ifname" ] && grep -q "$pi_ifname" /proc/net/dev && {
{
[ "$pi_preinit_net_messages" = "y" ] || {
[ "$pi_failsafe_net_message" = "true" ] &&
[ "$pi_preinit_no_failsafe_netmsg" != "y" ]
}
} && netmsg $pi_broadcast "$1"
}
}

preinit_echo() {
preinit_net_echo $1
echo $1
}

pi_indicate_led() {
set_state preinit
}

pi_indicate_preinit() {
preinit_net_echo "Doing OpenWRT Preinit\n"
pi_indicate_led
}

boot_hook_add preinit_main preinit_ip
boot_hook_add preinit_main pi_indicate_preinit
===============================================================================================
#!/bin/sh
# Copyright (C) 2006-2010 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

fs_wait_for_key () {
local timeout=$3
local timer
local do_keypress
local keypress_true="$(mktemp)"
local keypress_wait="$(mktemp)"
local keypress_sec="$(mktemp)"
if [ -z "$keypress_wait" ]; then
keypress_wait=/tmp/.keypress_wait
touch $keypress_wait
fi
if [ -z "$keypress_true" ]; then
keypress_true=/tmp/.keypress_true
touch $keypress_true
fi
if [ -z "$keypress_sec" ]; then
keypress_sec=/tmp/.keypress_sec
touch $keypress_sec
fi

trap "echo 'true' >$keypress_true; lock -u $keypress_wait ; rm -f $keypress_wait" INT
trap "echo 'true' >$keypress_true; lock -u $keypress_wait ; rm -f $keypress_wait" USR1

[ -n "$timeout" ] || timeout=1
[ $timeout -ge 1 ] || timeout=1
timer=$timeout
lock $keypress_wait
{
while [ $timer -gt 0 ]; do
echo "$timer" >$keypress_sec
timer=$(($timer - 1))
sleep 1
done
lock -u $keypress_wait
rm -f $keypress_wait
} &

echo "Press the [$1] key and hit [enter] $2"
echo "Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level"
# if we're on the console we wait for input
{
while [ -r $keypress_wait ]; do
timer="$(cat $keypress_sec)"

[ -n "$timer" ] || timer=1
timer="${timer%%\ *}"
[ $timer -ge 1 ] || timer=1
do_keypress=""
{
read -t "$timer" do_keypress
case "$do_keypress" in
$1)
echo "true" >$keypress_true
;;
1 | 2 | 3 | 4)
echo "$do_keypress" >/tmp/debug_level
;;
*)
continue;
;;
esac
lock -u $keypress_wait
rm -f $keypress_wait
}
done
}
lock -w $keypress_wait

keypressed=1
[ "$(cat $keypress_true)" = "true" ] && keypressed=0

rm -f $keypress_true
rm -f $keypress_wait
rm -f $keypress_sec

return $keypressed
}

failsafe_wait() {
local is_failsafe=$(uci get system.@system[0].failsafe)
echo "is_failsafe is $is_failsafe" > /dev/console
if [ $is_failsafe = "true" ]; then
FAILSAFE=
grep -q 'failsafe=' /proc/cmdline && FAILSAFE=true && export FAILSAFE
if [ "$FAILSAFE" != "true" ]; then
pi_failsafe_net_message=true
preinit_net_echo "Please press button now to enter failsafe"
pi_failsafe_net_message=false
fs_wait_for_key f 'to enter failsafe mode' $fs_failsafe_wait_timeout && FAILSAFE=true
[ -f "/tmp/failsafe_button" ] && FAILSAFE=true && echo "- failsafe button "`cat /tmp/failsafe_button`" was pressed -"
[ "$FAILSAFE" = "true" ] && export FAILSAFE && touch /tmp/failsafe
fi

fi
}

boot_hook_add preinit_main failsafe_wait
===============================================================================================
#!/bin/sh
# Copyright (C) 2006-2010 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

run_failsafe_hook() {
if [ "$FAILSAFE" = "true" ]; then
boot_run_hook failsafe
lock -w /tmp/.failsafe
fi
}

boot_hook_add preinit_main run_failsafe_hook
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

indicate_regular_preinit() {
preinit_net_echo "Continuing with Regular Preinit\n"
pi_indicate_led
}

boot_hook_add preinit_main indicate_regular_preinit
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

initramfs_test() {
if [ -n "$INITRAMFS" ]; then
boot_run_hook initramfs
preinit_ip_deconfig
break
fi
}

boot_hook_add preinit_main initramfs_test
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

do_mount_root() {
ramoverlay
find_mount_jffs2
rootfs_init_etc_ramfs
boot_run_hook preinit_mount_root

defFlag=`grep -o Default /tmp/userconfig/userconfig_default.tmp`
if [ -n "$defFlag" ] && [ $defFlag = 'Default' ];
then
#set dftmac from tddp flash
/usr/bin/tddpd dftmac
/lib/cfg_save/config.sh save
else
echo "no"
fi

}

[ "$INITRAMFS" = "1" ] || boot_hook_add preinit_main do_mount_root
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

failsafe_netlogin () {
telnetd -l /bin/login.sh <> /dev/null 2>&1
}

failsafe_shell() {
lock /tmp/.failsafe
ash --login
echo "Please reboot system when done with failsafe network logins"
}

boot_hook_add failsafe failsafe_netlogin
boot_hook_add failsafe failsafe_shell
===============================================================================================
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

run_init() {
preinit_ip_deconfig
}

boot_hook_add preinit_main run_init

执行preinit_essential,而preinit_essential中啥也没有,因此什么都不执行??

之后执行preinit_main,preinit_main中有define_default_set_statedo_ipq806xpreinit_ippi_indicate_preinitfailsafe_waitrun_failsafe_hookindicate_regular_preinitinitramfs_testdo_mount_rootrun_init函数,分别执行如下

1
2
3
define_default_set_state() {
. /etc/diag.sh
}

执行/etc/diag.sh,set_state() { :; }default set

1
2
3
4
5
do_ipq806x() {
. /lib/ipq806x.sh

ipq806x_board_detect
}

执行ipq806x.sh脚本中的**ipq806x_board_detect**函数,作用:读取板子的信息并记录

preinit_ip 函数

  • 接口选择
    • 如果 pi_ifname 未设置,则使用 ifname
  • 配置接口
    • 检查接口是否存在于 /proc/net/dev
    • 使用 ifconfig 命令配置接口的 IP 地址、子网掩码和广播地址,并激活接口。

**pi_indicate_preinit**:输出并亮灯预示初始化的状态,想来启动路由器的时候的亮灯由此而来

failsafe_wait 函数:检测系统上是否应该进入故障安全模式(在给定的超时时间内等待特定按键输入,以触发故障安全模式或设置调试级别)

**run_failsafe_hook **函数:检查系统是否进入故障安全模式,如果进入则执行failsafe,并创建一个锁lock -w /tmp/.failsafe

**indicate_regular_preinit**函数:输出”Continuing with Regular Preinit\n”,并再次亮灯

**initramfs_test**函数:检查系统是否在 initramfs 模式下运行,并执行相关操作

Initramfs 模式是一种用于引导 Linux 系统的初始文件系统模式。它通常用于在内核启动时提供一个临时的根文件系统,以便加载必要的驱动程序和初始化脚本,直到真正的根文件系统可以挂载为止。(或许解加密脚本便是从这里开始的??)

**do_mount_root **函数:负责挂载根文件系统并进行相关初始化。

**run_init**函数:运行preinit_ip_deconfig,负责卸载特定网络接口的 IP 配置。(对于重置网络环境十分重要)

至此preinit便运行完成

之后便回到/sbin/init中执行uloop_run(v1);

sub_9344

1
2
3
4
5
int __fastcall sub_9344(int result)
{
*(_DWORD *)(result + 16) = 0;
return result;
}

或许是对下一个的赋值??要求下一个函数必须在子进程运行完毕才可运行?(感觉有道理

sub_9350

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int sub_9350()
{
const char *v0; // r4
FILE *v1; // r0
FILE *v2; // r4
struct stat v4; // [sp+0h] [bp-70h] BYREF
char *argv[2]; // [sp+58h] [bp-18h] BYREF
int v6; // [sp+60h] [bp-10h] BYREF
char s[12]; // [sp+64h] [bp-Ch] BYREF

argv[0] = "/sbin/procd";
argv[1] = 0;
v0 = (const char *)sub_9BC4();
if ( dword_12488 > 0 )
kill(dword_12488, 9);
if ( !stat("/tmp/sysupgrade", &v4) )
{
while ( 1 )
sleep(1u);
}
unsetenv("INITRAMFS");
unsetenv("PREINIT");
if ( (unsigned int)dword_12474 > 1 )
{
syslog(0, "Exec to real procd now\n");
fprintf((FILE *)stderr, "procd: %s(%d): Exec to real procd now\n", "spawn_procd", 66);
}
if ( v0 )
setenv("WDTFD", v0, 1);
v1 = fopen("/tmp/debug_level", "r");
v6 = 0;
v2 = v1;
if ( v1 )
{
fscanf(v1, "%d", &v6);
fclose(v2);
unlink("/tmp/debug_level");
if ( (unsigned int)(v6 - 1) <= 3 )
dword_12474 = v6;
}
if ( dword_12474 )
{
snprintf(s, 2u, "%d", dword_12474);
setenv("DBGLVL", s, 1);
}
return execvp(argv[0], argv);
}

该函数用于清理环境、设置调试级别,并最终再次执行 /sbin/procd

/sbin/procd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v5; // r0
const char *v7; // r4
int v8; // r0
int v9; // r0
int v10; // r0
int v11; // r0

v5 = getenv("DBGLVL");
if ( v5 )
{
debug_level = atoi(v5);
unsetenv("DBGLVL");
}
while ( 1 )
{
v8 = getopt(argc, (char *const *)argv, "d:s:h:");
if ( v8 == -1 )
break;
......
}
v9 = uloop_init();
v10 = sub_A798(v9);
sub_F594(v10);
if ( getpid() == 1 )
v11 = sub_AE4C();
else
v11 = sub_BD60();
uloop_run(v11);
return 0;
}

由于没有参数,便直接break了

sub_A798

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int sub_A798()
{
int result; // r0

signal(13, (__sighandler_t)1);
result = getpid();
if ( result == 1 )
{
sigaction(15, &act, 0);
sigaction(10, &act, 0);
sigaction(12, &act, 0);
sigaction(11, &off_1AA20, 0);
sigaction(7, &off_1AA20, 0);
sigaction(1, &off_1AA34, 0);
sigaction(9, &off_1AA34, 0);
return sigaction(19, &off_1AA34, 0);
}
return result;
}

设置系统调用的处理方式,act是shutdown,1AA20是reboot,1AA34是无效调用

由于是execvp替换进程而pid是不会改变的,因此pid还是1,调用sub_AE4C函数

sub_AE4C

1
2
3
4
5
6
7
8
9
10
int __fastcall sub_AE4C(int a1)
{
if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Change state %d -> %d\n", choi, choi + 1);
a1 = fprintf((FILE *)stderr, "procd: %s(%d): Change state %d -> %d\n", "procd_state_next", 90, choi, choi + 1);
}
++choi;
return sub_AC7C(a1);
}

sub_AC7C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void sub_AC7C()
{
int v0; // r0
int v1; // r0
int v2; // r0
char v3[28]; // [sp+4h] [bp-1Ch] BYREF

strcpy(v3, "/sbin/ubusd");
switch ( choi )
{
case 1:
syslog(0, "- early -\n");
fputs("procd: - early -\n", (FILE *)stderr);
sub_AACC(0);
v0 = sub_10C5C("/etc/hotplug.json");
sub_FAF8(v0);
break;
case 2:
sub_AACC(0);
syslog(0, "- ubus -\n");
v1 = fputs("procd: - ubus -\n", (FILE *)stderr);
sub_BD60(v1);
syslog(0, "- init -\n");
v2 = fputs("procd: - init -\n", (FILE *)stderr);
sub_D628(v2);
sub_D4CC("ubus", v3);
sub_B458();
sub_B3F4("respawn");
sub_B3F4("askconsole");
sub_B3F4("askfirst");
sub_B3F4("sysinit");
break;
case 3:
syslog(0, "- init complete -\n");
fputs("procd: - init complete -\n", (FILE *)stderr);
break;
case 4:
syslog(0, "- shutdown -\n");
fputs("procd: - shutdown -\n", (FILE *)stderr);
sub_B3F4("shutdown");
sync();
break;
case 5:
syslog(0, "- reboot -\n");
fputs("procd: - reboot -\n", (FILE *)stderr);
reboot(dword_1ACCC);
break;
default:
syslog(0, "Unhandled state %d\n", choi);
fprintf((FILE *)stderr, "procd: Unhandled state %d\n", choi);
break;
}
}

状态处理

  1. case 1 - Early
    • 记录日志 “- early -“
    • 调用 sub_AACC(0) 执行早期初始化。
    • 调用 sub_10C5C("/etc/hotplug.json") 处理配置文件。
    • 调用 sub_FAF8 可能用于进一步初始化。
  2. case 2 - Ubus Initialization
    • 调用 sub_AACC(0)
    • 记录日志 “- ubus -“
    • 调用 sub_BD60 进行 Ubus 初始化。
    • 记录日志 “- init -“
    • 调用 sub_D628 进行进一步初始化。
    • 调用 sub_D4CC("ubus", v3) 启动 /sbin/ubusd
    • 调用 sub_B458() 执行其他初始化步骤。
    • 调用 sub_B3F4 处理一系列状态(”respawn”, “askconsole”, “askfirst”, “sysinit”)。
  3. case 3 - Init Complete
    • 记录日志 “- init complete -“
  4. case 4 - Shutdown
    • 记录日志 “- shutdown -“
    • 调用 sub_B3F4("shutdown") 处理关闭操作。
    • 调用 sync() 确保数据完整性。
  5. case 5 - Reboot
    • 记录日志 “- reboot -“
    • 调用 reboot(dword_1ACCC) 执行重启。
  6. default - Unhandled State
    • 记录未处理状态的日志。

case1

sub_AACC函数确保在系统启动时正确初始化看门狗,以便在系统无响应时采取适当措施。

sub_10C5C函数用于设置和配置一个处理热插拔事件的套接字,并将其集成到事件循环中(或许第一次是初始化,这次才是配置?)

sub_FAF8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void *sub_FAF8()
{
__pid_t v0; // r0
void *data; // [sp+0h] [bp-20h]
char *file[6]; // [sp+8h] [bp-18h] BYREF

file[1] = 0;
file[0] = "udevtrigger";
umount2("/dev/pts", 2);
umount2("/dev/", 2);
mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755,size=512K");
mkdir("/dev/shm", 0x1EDu);
mkdir("/dev/pts", 0x1EDu);
mount("devpts", "/dev/pts", "devpts", 0, 0);
dword_1AE8C = (int)sub_FA40;
v0 = fork();
dword_1AE90 = v0;
if ( !v0 )
{
execvp(file[0], file);
syslog(0, "Failed to start coldplug\n");
fputs("procd: Failed to start coldplug\n", (FILE *)stderr);
exit(-1);
}
if ( v0 <= 0 )
{
syslog(0, "Failed to start new coldplug instance\n");
fputs("procd: Failed to start new coldplug instance\n", (FILE *)stderr);
}
else
{
uloop_process_add(&unk_1AE80);
if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Launched coldplug instance, pid=%d\n", dword_1AE90);
fprintf((FILE *)stderr, "procd: %s(%d): Launched coldplug instance, pid=%d\n", "procd_coldplug", 65, dword_1AE90);
}
}
return data;
}

整个函数的主要作用便是在系统启动时检测和初始化所有已连接的硬件设备,并add一个process sub_FA40

sub_FA40

1
2
3
4
5
6
7
8
9
int sub_FA40()
{
if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Finished udevtrigger\n");
fprintf((FILE *)stderr, "procd: %s(%d): Finished udevtrigger\n", "udevtrigger_complete", 36);
}
return sub_10C38(sub_FA9C);
}

大致的还是一个log的记录,主要还是

1
2
3
4
5
6
7
8
9
10
11
12
int sub_FA9C()
{
int v0; // r0

if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Coldplug complete\n");
fprintf((FILE *)stderr, "procd: %s(%d): Coldplug complete\n", "coldplug_complete", 29);
}
v0 = sub_10C38(0);
return sub_AE4C(v0);
}

又运行一次sub_AE4C函数

此时就是case2了

case2

sub_8D60

1
2
3
4
5
int sub_BD60()
{
dword_1AD5C = (int)sub_BC18;
return uloop_timeout_set(&unk_1AD50, 1000);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int sub_BC18()
{
int v0; // r0

v0 = ubus_connect(dword_1AD68);
dword_1AD6C = v0;
if ( v0 )
{
*(_DWORD *)(v0 + 92) = sub_BBFC;
sub_D610();
sub_C79C(dword_1AD6C);
sub_F99C(dword_1AD6C);
if ( (unsigned int)debug_level > 1 )
{
syslog(0, "Connected to ubus, id=%08x\n", *(_DWORD *)(dword_1AD6C + 80));
fprintf(
(FILE *)stderr,
"procd: %s(%d): Connected to ubus, id=%08x\n",
"ubus_connect_cb",
58,
*(_DWORD *)(dword_1AD6C + 80));
}
return uloop_fd_add(dword_1AD6C + 44, 9);
}
else
{
if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Connection to ubus failed\n");
fprintf((FILE *)stderr, "procd: %s(%d): Connection to ubus failed\n", "ubus_connect_cb", 48);
}
return uloop_timeout_set(&unk_1AD50, 1000);
}
}

尝试连接到 ubus,并根据连接结果执行相应的初始化和事件处理设置。成功时,配置回调并将连接添加到事件循环;失败时,设置一个定时器以便稍后重试。

sub_D4CC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int __fastcall sub_D4CC(char *a1, char *a2)
{
int v4; // r8
int v5; // r7
int v6; // r6
char *i; // r0
char *v8; // r4
int v9; // r6

blob_buf_init(&dword_1AD8C, 0);
sub_CE04((int)"name", a1);
v4 = blobmsg_open_nested(&dword_1AD8C, "instances", 0);
v5 = blobmsg_open_nested(&dword_1AD8C, "instance1", 0);
v6 = blobmsg_open_nested(&dword_1AD8C, "command", 1);
for ( i = a2; ; i = 0 )
{
v8 = strtok(i, " ");
if ( !v8 )
break;
sub_CE04(0, v8);
}
blob_nest_end(&dword_1AD8C, v6);
v9 = blobmsg_open_nested(&dword_1AD8C, "respawn", 1);
sub_CE04(0, "3600");
sub_CE04(0, "1");
sub_CE04(0, (char *)"0");
blob_nest_end(&dword_1AD8C, v9);
blob_nest_end(&dword_1AD8C, v5);
blob_nest_end(&dword_1AD8C, v4);
return sub_D304(0, 0, 0, (char *)"add", dword_1AD8C);
}

通过构建 blobmsg 消息来组织和传递数据。它创建一个包含实例和命令的结构,适用于需要复杂数据传递的场景。最终调用 sub_D304 处理该消息。

函数 sub_D304 处理服务请求,根据输入参数更新现有服务或创建新服务。

sub_B458函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
void sub_B458()
{
char *v0; // r4
_DWORD *v1; // r6
size_t v2; // r0
char *v3; // r2
int v4; // t1
int v5; // r3
regoff_t *p_rm_eo; // r2
regoff_t v7; // r0
regoff_t v8; // r12
_DWORD *v9; // r7
int v10; // r5
char *i; // r0
int v12; // r3
int v13; // r2
const char *v14; // r10
int v15; // r7
int v16; // r5
int v17; // r0
_DWORD *v18; // r3
FILE *stream; // [sp+8h] [bp-88h]
regmatch_t v20[5]; // [sp+10h] [bp-80h] BYREF
regex_t preg; // [sp+38h] [bp-58h] BYREF
int v22[14]; // [sp+58h] [bp-38h] BYREF

stream = fopen("/etc/inittab", "r");
if ( stream )
{
regcomp(&preg, "([a-zA-Z0-9]*):([a-zA-Z0-9]*):([a-zA-Z0-9]*):(.*)", 1);// 编辑一个正则表达式
v0 = (char *)malloc(0x80u);
v1 = malloc(0x64u);
memset(v1, 0, 0x64u);
LABEL_26:
while ( fgets(v0, 128, stream) ) // 读取
{
v2 = strlen(v0);
v3 = &v0[v2];
while ( 1 )
{
v4 = (unsigned __int8)*--v3;
if ( (*(_WORD *)(_ctype_b + 2 * v4) & 0x20) == 0 )
break;
--v2;
}
v0[v2] = 0;
if ( *v0 != '#' && !regexec(&preg, v0, 5u, v20, 0) )// 忽略行首为#
{
if ( (unsigned int)debug_level > 3 )
{
syslog(0, "Parsing inittab - %s", v0);
fprintf((FILE *)stderr, "procd: %s(%d): Parsing inittab - %s", "procd_inittab", 274, v0);
}
v5 = 0;
p_rm_eo = &v20[0].rm_eo;
do
{
v7 = *p_rm_eo;
v8 = p_rm_eo[1];
p_rm_eo += 2;
v0[v7] = 0;
v22[v5++] = (int)&v0[v8]; // 提取字段
}
while ( v5 != 4 );
v9 = v1;
v10 = 0;
for ( i = strtok((char *)v22[3], " "); ; i = strtok(0, " ") )
{
v12 = (int)i;
++v9;
if ( i )
v12 = 1;
if ( v10 > 6 )
v12 = 0;
if ( !v12 )
break;
v9[2] = i;
++v10;
}
v13 = v22[0];
v14 = (const char *)v22[2];
v15 = 0;
v1[v10 + 3] = 0;
v16 = 0;
v1[2] = v13;
v1[11] = v0;
while ( v15 != 5 )
{
v17 = strcmp((&off_1AA58)[v16], v14); // 匹配字段
v16 += 3;
if ( !v17 )
{
*v1 = &off_1AA50;
v18 = off_1AA54;
off_1AA54 = v1;
v1[1] = v18;
v1[12] = &(&off_1AA58)[3 * v15];
*v18 = v1;
v0 = (char *)malloc(0x80u);
v1 = malloc(0x64u);
memset(v1, 0, 0x64u);
goto LABEL_26;
}
++v15;
}
syslog(0, "Unknown init handler %s\n", v14);
fprintf((FILE *)stderr, "procd: Unknown init handler %s\n", v14);
}
}
fclose(stream);
free(v0);
free(v1);
regfree(&preg);
}
else
{
syslog(0, "Failed to open %s\n", "/etc/inittab");
fprintf((FILE *)stderr, "procd: Failed to open %s\n", "/etc/inittab");
}
}

读取/etc/inittab文件,匹配格式为 identifier:runlevel:action:process 的行,以处理服务初始化数据

/etc/inittab
1
2
3
4
# Copyright (c) 2013 The Linux Foundation. All rights reserved.
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
ttyHSL1::askfirst:/bin/login
1
2
3
4
5
6
7
8
9
10
*v1 = &off_1AA50;
v18 = off_1AA54;
off_1AA54 = v1;
v1[1] = v18;
v1[12] = &(&off_1AA58)[3 * v15];
*v18 = v1;
v0 = (char *)malloc(0x80u);
v1 = malloc(0x64u);
memset(v1, 0, 0x64u);
goto LABEL_26;

可以看到这部分1AA50已然被修改了

之后执行

1
2
3
4
sub_B3F4("respawn");
sub_B3F4("askconsole");
sub_B3F4("askfirst");
sub_B3F4("sysinit");

想来算是只会执行最后一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const char *__fastcall sub_B3F4(const char *s2)
{
const char *v1; // r6
void **i; // r4
const char **v3; // r5
const char *v4; // r3

v1 = s2;
for ( i = (void **)off_1AA50; i != &off_1AA50; i = (void **)*i )
{
v3 = (const char **)i[12];
s2 = (const char *)strcmp(*v3, v1);
if ( !s2 )
{
v4 = v3[1];
if ( !v3[2] )
return (const char *)((int (__fastcall *)(void **))v4)(i);
s2 = (const char *)((int (__fastcall *)(void **))v4)(i);
}
}
return s2;
}

执行i[12]即sub_B398(sysinit对应的函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall sub_B398(int a1, int a2, int a3, int a4)
{
int v4; // r1

if ( *(_DWORD *)(a1 + 16) )
{
v4 = *(_DWORD *)(a1 + 20);
if ( v4 )
return sub_BB80(*(_DWORD *)(a1 + 16), v4, sub_B394, a4);
}
syslog(0, "valid format is rcS <S|K> <param>\n", a3);
return fputs((const char *)&word_1162A, (FILE *)stderr);
}
1
2
3
4
5
6
7
int __fastcall sub_BB80(int a1, int a2, int a3)
{
runqueue_init((int)&unk_1ACD0);
dword_1AD04 = 1;
dword_1AD0C = a3;
return sub_B730((int)&unk_1ACD0, "/etc/rc.d", a1, (int)"*", a2);
}

大致的作用差不多是添加/etc/rc.d/*下的脚本作为task并执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
~/fr/t/_TL-WAR2/squashfs-root/etc ------------------------------------------------------------------------------------------------------------------
> ls -all ./rc.d
总用量 8
drwxr-xr-x 2 s1nec-1o s1nec-1o 4096 626 2019 .
drwxr-xr-x 39 s1nec-1o s1nec-1o 4096 1225 15:44 ..
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 K20miniupnpd -> ../init.d/miniupnpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 K26pppox -> ../init.d/pppox
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 K50dropbear -> ../init.d/dropbear
lrwxrwxrwx 1 s1nec-1o s1nec-1o 13 626 2019 K80uac -> ../init.d/uac
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 K81apdb_check -> ../init.d/apdb_check
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 K90wireless -> ../init.d/wireless
lrwxrwxrwx 1 s1nec-1o s1nec-1o 14 626 2019 K98boot -> ../init.d/boot
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 K99luci_monitor -> ../init.d/luci_monitor
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 K99sys_ha -> ../init.d/sys_ha
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 K99umount -> ../init.d/umount
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S00sysfixtime -> ../init.d/sysfixtime
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S01led_early -> ../init.d/led_early
lrwxrwxrwx 1 s1nec-1o s1nec-1o 14 626 2019 S10boot -> ../init.d/boot
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S10system -> ../init.d/system
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S14tddpd -> ../init.d/tddpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S15loggerd -> ../init.d/loggerd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S15rsa_check -> ../init.d/rsa_check
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S20network -> ../init.d/network
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S21switch -> ../init.d/switch
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S25sysctl -> ../init.d/sysctl
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S26time_setting -> ../init.d/time_setting
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S31tmngtd -> ../init.d/tmngtd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S40fstab -> ../init.d/fstab
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S40watchdog -> ../init.d/watchdog
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S42ipgroup -> ../init.d/ipgroup
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S42ippool -> ../init.d/ippool
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S45firewall -> ../init.d/firewall
lrwxrwxrwx 1 s1nec-1o s1nec-1o 13 626 2019 S46nat -> ../init.d/nat
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S47access_ctl -> ../init.d/access_ctl
lrwxrwxrwx 1 s1nec-1o s1nec-1o 24 626 2019 S47administration -> ../init.d/administration
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S47dos_defense -> ../init.d/dos_defense
lrwxrwxrwx 1 s1nec-1o s1nec-1o 23 626 2019 S47flood_defense -> ../init.d/flood_defense
lrwxrwxrwx 1 s1nec-1o s1nec-1o 13 626 2019 S47imb -> ../init.d/imb
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S47mac_filter -> ../init.d/mac_filter
lrwxrwxrwx 1 s1nec-1o s1nec-1o 14 626 2019 S50cron -> ../init.d/cron
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S50dropbear -> ../init.d/dropbear
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S50pppox -> ../init.d/pppox
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S50pure-ftpd -> ../init.d/pure-ftpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S50queueventd -> ../init.d/queueventd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S50telnet -> ../init.d/telnet
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S50wireless -> ../init.d/wireless
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S52qos-tplink -> ../init.d/qos-tplink
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S60collectd -> ../init.d/collectd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S60dnsmasq -> ../init.d/dnsmasq
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S60pptpd -> ../init.d/pptpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S60url_filter -> ../init.d/url_filter
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S60xl2tpd -> ../init.d/xl2tpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S62vpn_wan -> ../init.d/vpn_wan
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S65wifidog -> ../init.d/wifidog
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S69ipstat -> ../init.d/ipstat
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S69online -> ../init.d/online
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S70freeStrategy -> ../init.d/freeStrategy
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S70init_iface -> ../init.d/init_iface
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S70qca-nss-drv -> ../init.d/qca-nss-drv
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S70qca-nss-ecm -> ../init.d/qca-nss-ecm
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S72shortcut-fe -> ../init.d/shortcut-fe
lrwxrwxrwx 1 s1nec-1o s1nec-1o 24 626 2019 S76session_limits -> ../init.d/session_limits
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S80websort -> ../init.d/websort
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S83web_security -> ../init.d/web_security
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S85webfilter -> ../init.d/webfilter
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S89remote_mngt -> ../init.d/remote_mngt
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S90dnsproxy -> ../init.d/dnsproxy
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S93appdist -> ../init.d/appdist
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S93bwlist_qq -> ../init.d/bwlist_qq
lrwxrwxrwx 1 s1nec-1o s1nec-1o 14 626 2019 S93done -> ../init.d/done
lrwxrwxrwx 1 s1nec-1o s1nec-1o 14 626 2019 S93l2tp -> ../init.d/l2tp
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S93mwan3 -> ../init.d/mwan3
lrwxrwxrwx 1 s1nec-1o s1nec-1o 25 626 2019 S94default_balance -> ../init.d/default_balance
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S94isp_route -> ../init.d/isp_route
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S94load_balance -> ../init.d/load_balance
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S94miniupnpd -> ../init.d/miniupnpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S94policy_route -> ../init.d/policy_route
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S94static_route -> ../init.d/static_route
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S94sysntpd -> ../init.d/sysntpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 15 626 2019 S95ipsec -> ../init.d/ipsec
lrwxrwxrwx 1 s1nec-1o s1nec-1o 19 626 2019 S96cloud_sdk -> ../init.d/cloud_sdk
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S97cloud_client -> ../init.d/cloud_client
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S97cmxddns -> ../init.d/cmxddns
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S97dev_discover -> ../init.d/dev_discover
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S97dyn3322ddns -> ../init.d/dyn3322ddns
lrwxrwxrwx 1 s1nec-1o s1nec-1o 22 626 2019 S97luci_monitor -> ../init.d/luci_monitor
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S97phddns -> ../init.d/phddns
lrwxrwxrwx 1 s1nec-1o s1nec-1o 23 626 2019 S97storage_share -> ../init.d/storage_share
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S97sys_ha -> ../init.d/sys_ha
lrwxrwxrwx 1 s1nec-1o s1nec-1o 21 626 2019 S97sys_monitor -> ../init.d/sys_monitor
lrwxrwxrwx 1 s1nec-1o s1nec-1o 23 626 2019 S97system_params -> ../init.d/system_params
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S97tpddnsd -> ../init.d/tpddnsd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 16 626 2019 S97uhttpd -> ../init.d/uhttpd
lrwxrwxrwx 1 s1nec-1o s1nec-1o 20 626 2019 S98apdb_check -> ../init.d/apdb_check
lrwxrwxrwx 1 s1nec-1o s1nec-1o 13 626 2019 S98led -> ../init.d/led
lrwxrwxrwx 1 s1nec-1o s1nec-1o 13 626 2019 S98uac -> ../init.d/uac
lrwxrwxrwx 1 s1nec-1o s1nec-1o 24 626 2019 S98zero_boot_done -> ../init.d/zero_boot_done
lrwxrwxrwx 1 s1nec-1o s1nec-1o 27 626 2019 S99commit_sysupgrade -> ../init.d/commit_sysupgrade
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S99devmngr -> ../init.d/devmngr
lrwxrwxrwx 1 s1nec-1o s1nec-1o 18 626 2019 S99powerctl -> ../init.d/powerctl
lrwxrwxrwx 1 s1nec-1o s1nec-1o 17 626 2019 S99thermal -> ../init.d/thermal

即执行/etc/init.d/下的所有服务

至此启动流程分析完毕