本文,主要是iot新手的第一次漏洞复现,相较于前辈的参考文章多了一些解释,更易食用
文章参考[原创]Tenda 路由器栈溢出详细分析(CVE-2018-18708)-智能设备-看雪-安全社区|安全招聘|kanxue.com
cve-list中的报告
CVE - CVE-2018-18708 (mitre.org)

e7eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8%4N6F1i4K6u0W2N6r3g2F1k6r3q4Q4x3X3g2U0L8$3#2Q4x3X3g2U0L8W2)9J5c8Y4g2H3L8r3!0S2k6r3k6A6L8r3g2Q4x3V1k6m8b7K6p5#2i4K6u0r3g2g2y4Q4y4h3k6m8b7K6p5#2g2U0q4Q4x3X3f1H3b7W2u0Q4y4h3k6h3x3e0g2Q4x3X3f1H3x3#2)9J5k6e0l9#2i4K6u0W2x3e0W2Q4y4h3k6E0N6h3I4@1K9g2)9#2k6W2c8p5x3o6q4Q4x3X3g2*7K9i4l9`.
嵌入式系统通常运行在资源受限的环境中,所以需要高效的存储利用和只读特性
高压缩比:
嵌入式设备通常具有有限的存储空间
SquashFS通过高效的压缩算法(如gzip、LZMA、XZ等)
只读文件系统:
快速启动:
一致性和完整性:
固件分发和升级:
只读根文件系统:
Live系统和恢复系统:
嵌入式Linux发行版:
首先,准备要打包的文件系统目录,例如/rootfs,然后使用mksquashfs工具创建SquashFS镜像:
将生成的/rootfs.img烧录到嵌入式设备的存储中,例如,通过TFTP或其他方法将其传输到设备上。
在设备启动时,通过启动脚本挂载SquashFS镜像:
readelf 工具查看名为 busybox 的可执行文件的 ELF 文件头信息
readelf 是一个分析 ELF (Executable and Linkable Format) 格式文件的工具
-h 参数表示仅显示 ELF 文件的头部信息

==32 位的小端序 ARM 可执行文件,采用ida反编译==
这个时候要注意命令执行文件夹位置,一定要在前面提取出来的地方运行程序
下载虚拟化套件
复制到当前目录下
解释一下这里运行的指令:
chroot ./: 这个命令改变了新进程的根目录为当前目录 (./)
新的根目录下的文件和目录会对于chroot环境中的程序来说,就如同是在一个全新的系统中
./qemu-arm-static,传递给chroot环境运行的第一个程序
即是运行了 httpd 程序
运行效果:

但是这里我们的程序进入后就没法运行了,要进入ida分析一下问题
网上看到了这个报错
<img src="de0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4x3i4u0X3k6X3g2Q4x3X3g2G2M7%4y4Q4x3X3c8U0L8W2)9J5k6r3y4Z5k6h3&6Y4k6s2g2Q4x3X3g2S2L8r3W2&6N6h3&6U0M7#2)9J5k6h3y4G2L8g2)9J5c8Y4c8&6M7r3!0J5j5g2)9J5c8X3W2E0j5h3N6W2i4K6u0V1x3U0l9J5y4o6l9#2x3U0M7I4z5e0x3^5x3U0l9#2z5o6N6Q4x3X3g2H3L8X3M7`." alt="image-20240527193820587" style="zoom:150%;" />
表示不存在/proc/sys/kernel/core_pattern这样一个文件夹
接直接再创建一个这样的文件就行了
==(这个不是漏洞文件,只是分析一下固件的一些逻辑)==
地址:
(busybox运行iot固件的关键逻辑)

使用finger恢复没什么用,算了影响不大(就是想试试,八成是没啥用的)





没什么意思,但是整理一下:
密码长度检查:
检查密码长度是否大于 5 个字符
密码与用户名相似性检查:
使用 sub_5125C 函数检查密码是否与用户名相似
密码与 gecos 字段相似性检查:
使用 sub_5125C 函数检查密码是否与 gecos 字段(一般存储用户的个人信息)相似
密码与主机名相似性检查:
使用 sub_5125C 函数检查密码是否与主机名相似
密码字符组成检查:
通过遍历密码中的每个字符,检查密码是否包含小写字母、大写字母和数字,并将结果存储在 v9 中
相似字符检查:
检查密码中是否有过多的重复字符
密码强度检查:
根据 v9 的值计算密码的强度,并检查密码是否足够强
与旧密码相似性检查:
使用 sub_5125C 函数检查密码是否与旧密码相似

看看有没有提取时候对他进行混淆,很遗憾没有找到,难道没有?
只找到这个地方对文件中的密码进行检索修改
这里主要就是一个登陆界面的密码检查和修改相关程序
修改后的idb分析文件11cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3N6A6M7X3q4X3k6X3g2^5K9i4g2Q4x3X3g2D9L8%4k6W2i4K6u0r3N6%4m8Q4x3X3c8U0L8$3&6@1k6h3&6@1i4K6u0r3N6i4m8D9L8$3q4V1M7#2)9J5c8U0t1H3x3U0c8Q4x3V1j5H3y4g2)9J5c8X3u0#2M7%4W2T1L8%4S2Q4x3X3g2*7K9i4l9`.
还是一样的地址


==这里实现了网络连接检查、初始化网络设置、与内核通信、信号处理和主循环的执行==
==这个地方调用了check_network函数,如果检查不过就会跳转到蓝线指向的流程进行,然后再次无条件跳转回去到loc_2E504再次指令流程==
进入一个死循环导致无法进行后续的流程

这里的绕过很简单,我们这里因为如果检查不通过就会给R0返回0 ,然后将R0赋给R3,然后比较如果R3等于0,就会进入死循环
这里我们尝试使用patch bytes将R3直接设置为1,永真就不会进入循环了

要将这里的数据改成MOV R3, #1

注意这里的虚拟环境运行

keystone-engine/keypatch: Multi-architecture assembler for IDA Pro. Powered by Keystone Engine. (github.com)

可以看到这里调用了Cfm函数检查网络的连接状况,跟前面逻辑类似,如果返回的R0为0则跳转到红线的函数

这里跳转后你会直接无条件跳转到函数结束的地方

和上面一样将mov的R0换成#1,变成永真后保存就可以了


5.替换文件
将原本文件夹内的文件替换为修改后的文件


==ip地址一看不对没法访问,分析调用逻辑==
==字符串定位找到关键调用函数==

==创建并配置一个网络套接字,以在指定的 IP 地址和端口上监听连接请求==
第一次用gdb调试异架构程序顺便详细记录一下



交叉引用这里的inet_ntoa回调函数进行了对于ip的处理
<img src="5bfK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4x3i4u0X3k6X3g2Q4x3X3g2G2M7%4y4Q4x3X3c8U0L8W2)9J5k6r3y4Z5k6h3&6Y4k6s2g2Q4x3X3g2S2L8r3W2&6N6h3&6U0M7#2)9J5k6h3y4G2L8g2)9J5c8Y4c8&6M7r3!0J5j5g2)9J5c8X3W2E0j5h3N6W2i4K6u0V1x3U0l9J5y4o6l9#2x3U0V1I4y4o6x3%4x3K6R3@1y4o6g2Q4x3X3g2H3L8X3M7`." alt="image-20240529143738445" style="zoom:150%;" />

打开一个SSL套接字,用于处理HTTPS请求
这个函数主要是处理SSL的socket,处理http请求,跟ip的生成没有太大的关系
==调用sub_1B84C作用是指定端口上创建一个 SSL 套接字==
成功,则返回一个非负的套接字标识符
失败,则返回 -1 并打印错误信息
==一个网络套接字(Socket)的函数==
解释一下这个地方的意思:
端口号:端口号是一个 16 位的数字,用来标识网络上的特定服务。比如,HTTP 通常使用端口 80,HTTPS 使用端口 443。
套接字:套接字是一个网络编程接口,用于在不同设备之间进行数据传输。它可以看作是通信的终点,包括一个 IP 地址和一个端口号。
创建套接字:在指定端口 a1 上创建一个套接字意味着在这个端口上为应用程序配置一个通道,通过这个通道可以接收和发送数据。这个过程通常包括以下步骤:
==sub_29818 尝试在指定端口 a1 上创建并配置一个套接字,如果成功,服务器就可以通过这个套接字接收客户端的 HTTP 请求==
初始化:清空一个临时字符数组 s,并将全局局域网 IP 地址赋给局部变量 v8
尝试打开套接字:在一个循环中调用 sub_1B84C 函数,尝试在指定的 IP 地址和端口 a1 上创建一个套接字。如果成功(返回值大于等于0),跳出循环
成功处理:
失败处理:
这个函数调用sub_1B84C函数作用:
==创建一个网络套接字,并在指定的 IP 地址 v8 和端口 a1 上监听 HTTP 请求==


发现主要是四个地方在调用


==确保设备在局域网中的 IP 地址能够被正确地获取并存储在全局变量中,以便后续的网络操作和通信==
在调用 inet_aton 函数时,将全局变量 g_lan_ip 的值作为参数传递给了该函数的第一个参数,即将设备在局域网中的 IP 地址转换为了一个 struct in_addr 结构体中的值 inp
==交叉引用这个函数,发现在主函数初始化等这个g_lan_ip参数,再调用这个函数进行处理g_lan_ip==





来仔细看一下这个地方的inet_addr(a1)的调用
==将提供的IP地址字符串(如果有的话)转换为二进制形式,并存储在 sockaddr 结构体的 sa_data 字段中==
==如果没有提供IP地址字符串,则将 sa_data 的对应部分设置为0==
if (a1): 检查 a1 是否为非空指针
如果 a1 非空,说明它指向一个有效的字符串
*(_DWORD *)&s.sa_data[2] = inet_addr(a1);:如果 a1 非空,使用 inet_addr 函数将 a1 指向的字符串转换为网络字节序的二进制IP地址,并将该值存储到 s.sa_data[2] 中
具体来说,(_DWORD *)&s.sa_data[2] 是将 sa_data 中从偏移量 2 开始的4个字节解释为一个32位的无符号整数(DWORD),并将转换后的IP地址存储到该位置
else: 如果 a1 是空指针
说明没有提供有效的IP地址字符串
*(_DWORD *)&s.sa_data[2] = 0;:在这种情况下,将 s.sa_data[2] 的值设置为0
也就是说,将 s.sa_data 的从偏移量 2 开始的4个字节都设置为0
这表示使用默认的IP地址(通常为 INADDR_ANY,表示绑定到所有可用接口)
可以看到这里的v8对应前面函数传入的a1
v8就是 g_lan_ip

这里的v17赋值给 g_lan_ip

这里的函数综合处理了s和v17
==推测应该是getIfIp处理的ip生成==
<img src="c48K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4x3i4u0X3k6X3g2Q4x3X3g2G2M7%4y4Q4x3X3c8U0L8W2)9J5k6r3y4Z5k6h3&6Y4k6s2g2Q4x3X3g2S2L8r3W2&6N6h3&6U0M7#2)9J5k6h3y4G2L8g2)9J5c8Y4c8&6M7r3!0J5j5g2)9J5c8X3W2E0j5h3N6W2i4K6u0V1x3U0l9J5y4o6l9#2x3U0V1J5x3e0l9K6x3o6t1^5y4o6g2Q4x3X3g2H3L8X3M7`." alt="image-20240529210302845" style="zoom:150%;" />


找到这里的链接库

路径在截图的上方


这里可以看见传入的两个参数分别是g_lan_ip和v17
可以发现这里的关键是申请ip的函数ioctl
<img src="a21K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4x3i4u0X3k6X3g2Q4x3X3g2G2M7%4y4Q4x3X3c8U0L8W2)9J5k6r3y4Z5k6h3&6Y4k6s2g2Q4x3X3g2S2L8r3W2&6N6h3&6U0M7#2)9J5k6h3y4G2L8g2)9J5c8Y4c8&6M7r3!0J5j5g2)9J5c8X3W2E0j5h3N6W2i4K6u0V1x3U0l9J5y4o6l9#2x3U0V1J5x3e0t1^5x3e0x3$3x3o6y4Q4x3X3g2H3L8X3M7`." alt="image-20240529212813603" style="zoom:200%;" />
ioctl 用于与设备驱动程序进行交互,可以用来获取或设置接口的各种参数
0x8915u 是一个具体的命令,用于获取接口的 IP 地址
SIOCGIFADDR 是一个 ioctl 命令,用于获取指定网络接口的 IP 地址
定义在 <sys/ioctl.h> 中,通常表示为 0x8915u
ifreq 结构定义在 <net/if.h> 中,用于传递网络接口相关信息:
ifr_name 用于指定网络接口名称,ifr_addr 用于存储获取到的 IP 地址

调用getLanIfName函数


进入查看


这里看到传入的参数是0

对应的 v1 = "br0";,所以这里要设置的第三个参数为网卡的名称为br0
==设置一个第三个参数为网卡的名称为br0==
操作:
在ubuntu的浏览器上直接访问ip就可以发现,可以成功连接上路由了

根据官方文档在httpd文件内的sub_C24C0函数存在栈溢出漏洞


==这个地方strcpy不会检查字符串的大小,a1直接没有判断长度拷贝到了a2+32,从而可以实现栈溢出,更改函数的流程==


交叉引用sub_C24C0,得到a2的大小
可以看到==a2的大小是176个字节==






这个函数相当于是在给每个网络相关的处理函数分配名字








==HTTP请求中deviceList的值,并一路传递到sub_c24C0函数的漏洞点==
这里详细解释一下:
这个地方很关键的一个理解
这两个函数共同解释注册表,这个地方就是goform/setMacFilterCfg地址的调用流程





==a1的参数是black或者white就会将相应的值设置为macfilter.mode,并且让a1返回0==
当a2指针指向的字符串不为0时,将循环调用sub_C17A0

这个地方的a2就是前面函数的v17(已经判断过他的生成逻辑,肯定不为空)

没有判定条件,直接执行

抓个damn

结合前面的对于httpd的调用链的拼接和抓包的格式和字符串的溢出长度

可以看见这里开启了NX保护,但是其他保护都没有打开,所以很好泄露


连接成功


<img src="77bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4x3i4u0X3k6X3g2Q4x3X3g2G2M7%4y4Q4x3X3c8U0L8W2)9J5k6r3y4Z5k6h3&6Y4k6s2g2Q4x3X3g2S2L8r3W2&6N6h3&6U0M7#2)9J5k6h3y4G2L8g2)9J5c8Y4c8&6M7r3!0J5j5g2)9J5c8X3W2E0j5h3N6W2i4K6u0V1x3U0l9J5y4o6l9$3x3o6t1I4z5o6x3I4x3e0j5H3y4K6W2Q4x3X3g2H3L8X3M7`." alt="image-20240602183116079" style="zoom:250%;" />
可以发现这个地方已经将连续的A字符串压入栈中,实现溢出,覆盖了pc寄存器和返回地址的数据

==这里很悲伤的是qemu-user模拟不支持vmmap指令打印内存信息==
==qemu是默认关闭ASLR的,每次调试地址是一样的==
我们把断点下再这个地方的main函数puts的调用


这个地方就是puts函数
IDA中查看puts函数的地址为0x35cd4,得到偏移量为:0xff5c1cd4 - 0x35cd4 = 0xff58c000

找到一个能用的就行

找一个简单点的pop链

引发一个缓冲区溢出,控制溢出处函数的返回地址
将这个地址更改为第一个gadget(pop {r3, pc})的地址(当函数返回时,它就会跳转到这个gadget开始执行)
pop {r3, pc}进行的操作:
它会从栈顶取出一个值放入 r3寄存器,然后再取栈顶的下一个值放入 pc寄存器
在栈顶放上 system_addr和第二个gadget的地址
执行这个gadget后,system_addr就被放入 r3寄存器,第二个gadget的地址放入 pc寄存器(程序就会跳转到第二个gadget开始执行)
执行第二个gadget(mov r0, sp ; blx r3)时,会从当前的栈顶(也就是 cmd的位置)取出一个值放入 r0寄存器
然后跳转到 r3寄存器中的地址(也就是 system函数)去执行

sudo apt-get install binwalk
sudo apt-get install binwalk
sudo apt-g0et install binwalk
binwalk -Me US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin
sudo apt-g0et install binwalk
binwalk -Me US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin
cd _US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/
cd _US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/
cd squashfs-root/
mksquashfs /rootfs /rootfs.img
mksquashfs /rootfs /rootfs.img
mount -t squashfs -o loop /path/to/rootfs.img /mnt/rootfs
mount -t squashfs -o loop /path/to/rootfs.img /mnt/rootfs
readelf -h bin/busybox
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0xbf80
Start of program headers: 52 (bytes into file)
Start of section headers: 380400 (bytes into file)
Flags: 0x5000002, Version5 EABI, <unknown>
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 24
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0xbf80
Start of program headers: 52 (bytes into file)
Start of section headers: 380400 (bytes into file)
Flags: 0x5000002, Version5 EABI, <unknown>
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 24
cd _US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/
cd _US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/
cd squashfs-root/
sudo apt install qemu-user-static
sudo apt install qemu-user-static
cp $(which qemu-arm-static) ./
cp $(which qemu-arm-static) ./
sudo chroot ./ ./qemu-arm-static ./bin/httpd
sudo chroot ./ ./qemu-arm-static ./bin/httpd
mkdir -p ./proc/sys/kernel
mkdir -p ./proc/sys/kernel
/home/iot/Desktop/iot-cve/_US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/squashfs-root/bin
/home/iot/Desktop/iot-cve/_US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/squashfs-root/bin
if ( a2 && (v6 = strlen(a2), v6 > 5) )
if ( a2 && (v6 = strlen(a2), v6 > 5) )
if ( sub_5125C(a2, *a3) )
{
v21 = "similar to username";
}
if ( sub_5125C(a2, *a3) )
{
v21 = "similar to username";
}
if ( *v7 && sub_5125C(a2, (int)v7) )
{
v21 = "similar to gecos";
}
if ( *v7 && sub_5125C(a2, (int)v7) )
{
v21 = "similar to gecos";
}
v8 = (void *)sub_53750();
v9 = sub_5125C(a2, (int)v8);
free(v8);
if ( v9 )
{
v21 = "similar to hostname";
}
v8 = (void *)sub_53750();
v9 = sub_5125C(a2, (int)v8);
free(v8);
if ( v9 )
{
v21 = "similar to hostname";
}
for ( i = 0; i < v6; ++i )
{
v11 = (unsigned __int8)a2[i];
if ( (unsigned __int8)(v11 - 97) > 0x19u )
{
if ( (unsigned __int8)(v11 - 65) > 0x19u )
{
if ( (unsigned __int8)(v11 - 48) > 9u )
v9 |= 8u;
else
v9 |= 4u;
}
else
{
v9 |= 2u;
}
}
else
{
v9 |= 1u;
}
}
for ( i = 0; i < v6; ++i )
{
v11 = (unsigned __int8)a2[i];
if ( (unsigned __int8)(v11 - 97) > 0x19u )
{
if ( (unsigned __int8)(v11 - 65) > 0x19u )
{
if ( (unsigned __int8)(v11 - 48) > 9u )
v9 |= 8u;
else
v9 |= 4u;
}
else
{
v9 |= 2u;
}
}
else
{
v9 |= 1u;
}
}
do
{
v14 = strchr(v12, v11);
v15 = v14;
if ( !v14 )
break;
v12 = v14 + 1;
++v13;
}
while ( v15[1] );
if ( v6 <= 2 * v13 )
{
v21 = "too many similar characters";
goto LABEL_38;
}
do
{
v14 = strchr(v12, v11);
v15 = v14;
if ( !v14 )
break;
v12 = v14 + 1;
++v13;
}
while ( v15[1] );
if ( v6 <= 2 * v13 )
{
v21 = "too many similar characters";
goto LABEL_38;
}
v16 = 4;
v17 = 14;
v18 = 1;
do
{
v19 = (v18 & v9) == 0;
v18 *= 2;
if ( !v19 )
v17 -= 2;
--v16;
}
while ( v16 );
if ( v6 < v17 )
{
v21 = "too weak";
}
v16 = 4;
v17 = 14;
v18 = 1;
do
{
v19 = (v18 & v9) == 0;
v18 *= 2;
if ( !v19 )
v17 -= 2;
--v16;
}
while ( v16 );
if ( v6 < v17 )
{
v21 = "too weak";
}
if ( !*a1 || !sub_5125C(a2, (int)a1) )
return 0;
v21 = "similar to old password";
if ( !*a1 || !sub_5125C(a2, (int)a1) )
return 0;
v21 = "similar to old password";
int __fastcall sub_F804(int a1, int a2, int a3)
{
// 声明一些局部变量
int v4; // r6
int v5; // r0
struct passwd *v6; // r4
char *v7; // r0
char *v8; // r5
char *v10; // r6
int v11; // r7
size_t v12; // r0
char *pw_shell; // r0
int v14[7]; // [sp+4h] [bp-1Ch] BYREF
// 一系列初始化操作
v14[1] = a3;
dword_6CCF8 = 3;
v14[0] = 0;
// 获取系统信息
openlog((const char *)dword_6DECC, 0, 32);
// 解析环境变量
dword_6DEF4 = (int)"t+";
sub_4D454(a2, "t:", v14);
v4 = optind;
if ( *(_DWORD *)(a2 + 4 * optind) )
{
// 关闭文件描述符 0,1
close(0);
close(1);
// 从文件中读取数据
v5 = sub_D728(*(_DWORD *)(a2 + 4 * v4), 2);
// 创建复制文件描述符
dup(v5);
// 关闭文件描述符2
close(2);
// 创建一个新的文件描述符,复制已存在的文件描述符
dup(0);
}
if ( !isatty(0) || !isatty(1) || !isatty(2) )
{
// 如果任一文件描述符(0、1、2)不是终端设备,则显示错误信息
dword_6CCF8 = 2;
sub_D03C((int)"not a tty");
}
sub_50BA4();
// 获取root用户的信息
v6 = getpwuid(0);
if ( !v6 )
// 如果没有找到root用户,显示错误信息
sub_D03C((int)"no password entry for root");
while ( 1 )
{
// 询问输入root用户的密码,接收输入的密码
v7 = (char *)sub_4BFDC(
0,
v14[0],
"Give root password for system maintenance\n(or type Control-D for normal startup):");
v8 = v7;
if ( !v7 || !*v7 )
break;
// 验证输入的密码是否正确
v10 = (char *)sub_52C4C(v7, v6->pw_passwd);
v11 = strcmp(v10, v6->pw_passwd);
// 释放动态分配的内存
free(v10);
if ( !v11 )
{
v12 = strlen(v8);
// 清空输入的密码字符串
memset(v8, 0, v12);
// 如果密码正确,记录系统维护模式
sub_4E414("System Maintenance Mode");
// 获取环境变量 SUSHELL 或者 sushell 的值,如果这两个都没有定义,就使用 root 的默认 shell
pw_shell = getenv("SUSHELL");
if ( !pw_shell )
{
pw_shell = getenv("sushell");
if ( !pw_shell )
pw_shell = v6->pw_shell;
}
// 启动一个新进程运行 shell
sub_53658(pw_shell, 1, 0, 0);
}
else
{
// 如果密码错误,等待3秒后再次提示输入密码
sub_4C188(3);
// 记录密码错误信息
sub_4E414("Login incorrect");
}
}
// 正常启动系统
sub_4E414("Normal startup");
// 返回0表示函数执行完毕
return 0;
}
int __fastcall sub_F804(int a1, int a2, int a3)
{
// 声明一些局部变量
int v4; // r6
int v5; // r0
struct passwd *v6; // r4
char *v7; // r0
char *v8; // r5
char *v10; // r6
int v11; // r7
size_t v12; // r0
char *pw_shell; // r0
int v14[7]; // [sp+4h] [bp-1Ch] BYREF
// 一系列初始化操作
v14[1] = a3;
dword_6CCF8 = 3;
v14[0] = 0;
// 获取系统信息
openlog((const char *)dword_6DECC, 0, 32);
// 解析环境变量
dword_6DEF4 = (int)"t+";
sub_4D454(a2, "t:", v14);
v4 = optind;
if ( *(_DWORD *)(a2 + 4 * optind) )
{
// 关闭文件描述符 0,1
close(0);
close(1);
// 从文件中读取数据
v5 = sub_D728(*(_DWORD *)(a2 + 4 * v4), 2);
// 创建复制文件描述符
dup(v5);
// 关闭文件描述符2
close(2);
// 创建一个新的文件描述符,复制已存在的文件描述符
dup(0);
}
if ( !isatty(0) || !isatty(1) || !isatty(2) )
{
// 如果任一文件描述符(0、1、2)不是终端设备,则显示错误信息
dword_6CCF8 = 2;
sub_D03C((int)"not a tty");
}
sub_50BA4();
// 获取root用户的信息
v6 = getpwuid(0);
if ( !v6 )
// 如果没有找到root用户,显示错误信息
sub_D03C((int)"no password entry for root");
while ( 1 )
{
// 询问输入root用户的密码,接收输入的密码
v7 = (char *)sub_4BFDC(
0,
v14[0],
"Give root password for system maintenance\n(or type Control-D for normal startup):");
v8 = v7;
if ( !v7 || !*v7 )
break;
// 验证输入的密码是否正确
v10 = (char *)sub_52C4C(v7, v6->pw_passwd);
v11 = strcmp(v10, v6->pw_passwd);
// 释放动态分配的内存
free(v10);
if ( !v11 )
{
[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!