Theme Preview

HG8120C持久root shell及完美解决IPv6丢包

由 李晓岚 在 2016年08月11日发表

在博文PWN华为HG8120C光猫(三)中,虽然取得了root shell,但不能持久,设备重启后需要重新获取。PWN华为HG8120C光猫之后虽然确定了IPv6-in-IPv4丢包的是ONT引起的,但没有给出解决方案,只是规避问题。强迫症发作的你是否也对这两问题耿耿于怀呢?这次就来将两遗留问题一并完美解决,才能做到真正释怀。

查找IPv6-in-IPv4丢包的罪魁祸首

通过PWN华为HG8120C光猫之后的分析,怀疑某个内核驱动对IPv6-in-IPv4数据包进行了错误转发。但是lsmod列出的模块众多,也可能问题模块是作为built-in编译在内核里面,所以当时就匆匆使用桥接方案规避了问题模块。接下来的时间里,心里就一直挂念着如何找到完美解决方案,导致食无味,寝不安。

这天,下定决心要把整个lsmod列出的所以模块都检查一遍,一边运行ping6 ipv6.google.com,一边一个一个模块进行移除,观察ping6反馈的结果。这个过程进行得十分缓慢,原因是许多模块的卸载过程会导致随机重启,模块数量有上百个,而且引用关系复杂。

WAP(Dopra Linux) # lsmod|wc -l
119

每当随机重启后,只能将有依赖关系的模块重新卸载,才能继续,很可能又会很快导致随机重启,近乎死循环。但是好歹还是慢慢向前推进着。当进行到l3sfwd_ipv6这个模块时,感觉很像罪魁祸首,因为名字中带ipv6,可以肯定和IPv6有关,fwd也像是单词forward(转发)缩写,l3看起来像是指layer 3,那个错误转发也确实是发生在三层上,一切看起来都能说得通。然而移除l3sfwd_ipv6之后问题依旧。没办法,只能继续检查剩下的模块,在移除了l3sfwd模块后,丢包现象消失了。费了九牛二虎之力终于找到罪魁祸首了,这个罪恶模块应该是和IPv4转发相关的,毕竟IPv6-in-IPv4还是IPv4协议。

为了进一步确定不是多个模块组合造成的丢包,重启设备后,只移除l3sfwd和两个依赖模块l3sfwd_ipv6rawip_adpt,丢包问题消失,并且也不影响网络和电话功能,一切都很正常,哈哈哈哈!虽然找到了问题的根源,但如何保证设备掉电重启后,自动移除这三个引起问题的模块呢?新的问题又来了,不能高兴得太早。

持久root shell

之前获取的root shell,都只是在内存tmpfs中操作,设备重启后将消失。设备在/mnt/jffs2上挂载了闪存文件系统ubifs(没错,是ubifs,不是jffs2),可读写,用于保存设备的配置文件等信息。虽然可以将root shell所需的文件保存在这个路径下,即使设备掉电文件也不会消失,但还必须找到方法,实现自启动,root shell才能持久。

如何实现自启动

实现自启动最容易想到的办法就是修改/etc/rc.d下的启动脚本,添加启动命令到其中。虽然最容易想到,但并不代表最容易实现。首先/etc/rc.d位于根文件系统中,而根文件系统使用的是squashfs只读文件系统。其次修改根文件系统风险比较高,不能fail safe,一旦根文件系统损坏,设备可能就变砖,对于在线正在使用中的网络设备,高风险的变砖情况是不可接受的,一旦变砖网络和电话就都瘫痪了。其实,HG8120C这个设备实现了双系统,但目前不知道如何操作才能保证在修改一个系统的根文件系统导致启动失败的情况下,如何自动或手动切换到另一个正常系统。综上所述,我需要找到一个低风险,fail safe的添加自启动的方法。

唯一符合这样条件的自启动方案,就是将启动命令文件存储在/mnt/jffs2路径下,诱导某个系统程序在启动过程中来执行之,这样可以避免损坏rootfs

寻找可能被诱导的程序

跟随系统的启动脚本流程,仔细检查每个可能的切入点。最先发现可能的切入点在/etc/rc.d/rc.start/1.sdk_init.sh的第76行,如果文件/mnt/jffs2/Equip.sh存在,就运行/bin/Equip.sh,运行成功之后就退出。

76  [ -f /mnt/jffs2/Equip.sh ] && /bin/Equip.sh && exit

继续查看/bin/Equip.sh,发现文件最后一行会执行/mnt/jffs2路径下的某个程序:

/mnt/jffs2/equipment/bin/aging &

乍一看,这个看似可以被利用来执行我们指定的程序/mnt/jffs2/equipment/bin/aging,但是对比1.sdk_init.sh后续的启动命令和/bin/Equip.sh发现有太多差异,恐怕启动后设备功能不正常。所以只能当作最后的救命稻草,只有在找不到其他更好的切入点时才会使用。继续检查1.sdk_init.sh,426行代码如下:

426 #start for hw_ldsp_cfg进行单板差异化配置,必须放在前面启动
427 iLoop=0
428 echo -n "Start ldsp_user..."
429 if [ -e /bin/hw_ldsp_cfg ]
430 then
431   hw_ldsp_cfg &
432   while [ $iLoop -lt 50 ] && [ ! -e /var/hw_ldsp_tmp.txt ]
433   do
434     #echo $iLoop
435     iLoop=$(( $iLoop + 1 ))
436     sleep 0.1
437   done
438
439   if [ -e /var/hw_ldsp_tmp.txt ]
440   then
441       rm -rf /var/hw_ldsp_tmp.txt
442   fi
443 fi

这几行代码的意思是执行/bin/hw_ldsp_cfg并等待某个标志文件的创建,等待超时5秒。分析/bin/hw_ldsp_cfg发现,如果/mnt/jffs2/Equip.sh/mnt/jffs2/flashtest两个文件同时都存在的情况下,会执行这个/mnt/jffs2/equipment/bin/prbstest程序,这三个文件都位于/mnt/jffs2路径下,都能被我们控制,同时也没有改变1.sdk_init.sh中后续的正常启动流程,所以这是一个很好的候选者。

利用/bin/hw_ldsp_cfg来执行/mnt/jffs2/equipment/bin/prbstest需要满足的两个条件,其中一个是/mnt/jffs2/Equip.sh,而这个文件存在表明设备处于“装备模式”,grep后发现系统中很多程序在“装备模式”下,行为都会发生改变,这会增加太多的不确定性,所以当我们利用/mnt/jffs2/Equip.sh启动我们的程序后,需要将此文件删除,尽可能保持系统原来的行为。同时也注意到/mnt/jffs2/Equip.sh也是上面我们最后救命稻草的启动条件,也就是说如果文件存在,启动流程压根就不会走到1.sdk_init.sh的426行,该如何解决这个冲突呢?hw_ldsp_cfgelf文件,使用open系统调用检查文件的存在性,而1.sdk_ini.sh76行使用shell脚本的[ -f /mnt/jffs2/Equip.sh]来测试存在并且是普通文件。“普通文件”这个条件比单纯的系统调用open更加严格,对设备文件等特殊文件来说[ -f ... ]会失败,但open调用不会,所以如果在路径/mnt/jffs2/Equip.sh创建一个设备文件或管道等非普通文件,就可以达到我们的目的。

上述利用看起来能够完美工作,即不改变额外的系统原有行为,也达到了启动我们程序的目的。确实很完美,但只能完美工作一次,第二次设备重启后就不会工作了,因为为了尽可能保持系统原有行为,/mnt/jffs2/Equip.sh已经被我们删除了,避免启用设备的“装备模式”行为。

持久 VS 保持系统原有行为

自启动要持久,特殊文件/mnt/jffs2/Equip.sh就必须保留在ubifs文件系统上,保证设备重启后继续存在,但要保持系统原有行为就又必须使得/mnt/jffs2/Equip.sh不存在,设备不处于“装备模式”,两者完全对立。想到的办法就是当我们的程序运行起来后,插入一个内核模块,劫持对ubifs文件系统的访问,将/mnt/jffs2/Equip.sh隐藏起来,但实际文件还存在,重启后到插入我们的劫持模块前文件是可见的,这样便能使得“持久”和“保持系统原有行为”很好的共存了。详细可参考劫持模块源代码hijack.c

完整流程

  1. 创建符号链接/mnt/jffs2/Equip.sh,将其指向设备文件/dev/mtd2ro,这样保证[ -f /mnt/jffs2/Equip.sh]失败。
  2. 创建空文件/mnt/jffs2/flashtest
  3. 创建自启动文件/mnt/jffss/equipment/bin/prbstest,在其中实现:

    1. 检查failsafe文件是否存在,如果存在立即退出,保证如果因为插入hijack劫持模块或卸载模块导致系统重启后不再继续我们的自启动,打破重启循环。
    2. 创建failsafe文件。
    3. 插入劫持模块hijack.ko,用于中断符号链接/mnt/jffs2/Equip.sh的follow link,保证open等系统调用失败,保持原有系统行为。
    4. 移除l3sfwd_ipv6rawip_adptl3sfwd三个模块,解决IPv6-in-IPv4丢包问题。
    5. 启动dropbear,实现持久root shell
    6. 启动正常,移除failsafe文件,保证下次重启后自启动继续得到执行。

总结

至此,HG8120C ONT上IPv6-in-IPv4丢包问题算是正式完美解决了,前后历时一个多月(原谅我比较懒,等文章写出来都已经是三四个月之后的事情了)。由于是在线设备,所以一切操作都选择了风险较小的做法,而不是高风险直接修改rootfs的方法,保证fail safe,保证不损坏设备,网络和电话功能正常,额外还收获持久的设备root访问权限。

关于自启动逻辑中的fail safe,实际重启测试中,未发现插入或移除内核模块导致重启的情况,但是还是以防万一,毕竟小心行得万年船,避免任何不必要的风险。

标签:ONTPWNIPv6-in-IPv4

comments powered by Disqus