0%

路由器安全初探

这几天看了看路由器安全的一些入门内容,部分复现了一个漏洞。这里(凌乱地)记录一下遇到的坑,谈一谈感受。

搭建环境

这里参考一位大佬的博客文章 路由器漏洞挖掘环境搭建

binwalk

需要下载安装的一些 package 在 ubuntu apt universe 中,其中一些需要手动添加,不然找不到:

1
sudo add-apt-repository universe

如何搜索一个 ubuntu package:https://askubuntu.com/questions/378558/unable-to-locate-package-while-trying-to-install-packages-with-apt

官网中关于 ubuntu repository 的一些介绍:https://help.ubuntu.com/community/Repositories/Ubuntu(未看)

buildroot

博客园介绍:https://www.cnblogs.com/arnoldlu/p/9553995.html(未看)

我的 linux 内核版本是 5.8.0 的,buildroot-2020.11.4 版本支持该内核版本,而 2019 年和 2021 年 5 月则不支持。

不能以 root 权限编译。如果以 root 权限编译,失败后要将权限更改过来(不然一些文件没有权限访问)。

编译需要较长时间

qemu 网络配置

一开始参考的 路由器漏洞挖掘环境搭建 。要安装一个 ifupdown,不然不能 sudo service networking restart

使用 tap 配置 qemu 网络:Setting up Qemu with a tap interface(实际操作时参考的这个)

注意每次都要用 ifconfig 设置一下qemu 虚拟机的 ipv4 地址和子网掩码,不然不能 ping。

关键概念:VLAN、网桥(br)、tap/tun。

关于 qemu 网络配置的一些知识:https://blog.csdn.net/rfidunion/article/details/55096935

Linux 中的网络知识:https://blog.gmem.cc/network-faq#bridging(很详细,未看)

gdb 和 gdbserver

buildroot 的 gcc 需要选择 Toolchain -> Enable WCHAR supporthttps://stackoverflow.com/questions/63477057/buildroot-cross-compile-error-when-gdbserver-included-conflicting-declaration,然后需要重新编译。

最后选择用 buildroot 编译 gdbserver。。。:https://stackoverflow.com/questions/50253929/buildroot-cross-compiling-gdb-server-for-mips32。然而找不到静态编译的 gdbserver。。。

最后的最后根据 gdb官方文档交叉编译教程编译:https://sourceware.org/gdb/wiki/BuildingCrossGDBandGDBserver。然而调试时 segmentation fault(可能是因为编译选项有问题,也有可能因为 gdbserver 版本太高了)。

最后的最后的最后选择下载别人编译的 7.8 版本的 gdbserver:https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver。终于成功了。

复现学习

MIPS 汇编

路由器的架构一般是 mips 和 arm,这次我复现的路由器采用的是 mips 架构,所以学习了一下 mipsel 32 位汇编。

  1. add 和 addu

    addu 会忽略 overflow,而 add 会触发 trap。

  2. jump and link:改变 pc 寄存器的值,将下一个指令的地址存入 $31

  3. 函数调用约定(根据某大学课设要求):MIPS Calling Convention (感觉很有用,但还没看)

    下面的推断是调试一个 32 位 mipsel 程序(stack_bof_01)得到的:

    1. mips 没有 push 和 pop 指令,因为它是 load-store 架构(和 arm 一样)

    2. mips 没有 ebp 寄存器,那它怎么寻址栈上的变量呢:可以用 fp 或 sp。fp 在 sp 减少(创建栈帧后)被赋上 sp 的值(这点和 x86 不同);在准备退出函数时 sp 被赋上 fp 的值。

      由于 sp 可能变化,此时就可以用 fp 寻找栈上的变量了:Frame Pointer Explanation

      pwndbg 同时显示了 fp 和 s8,s8 是正确的,fp 是错的。

    3. mips 没有 call、ret,那它怎么进入和退出函数呢:被调用者会用 sw 在栈上存放 ra 和 fp 的值。

    4. gp 寄存器的作用:用于以偏移访问全局的变量(x86_64 下要么直接用地址访问,要么用 rip 以偏移访问,mips 不行。这样可以有利于 PIC啥的)

      ida 和 pwndbg 反汇编时对 gp 的处理不同:ida 中用 gp 以偏移访问全局的变量时,会省略 gp 和偏移,直接显示变量;而 pwndbg 中则会显示出 gp 和偏移

      gp 保存在栈上参数的高地址处(arg0arg3分别在 spsp+0xc,gp 则在 sp+0x10)(观察 stack_bof_01 得到的结果),8 字节对齐。说明 gp 可能也算 saved regitser

    5. t9 寄存器是干嘛的:配合 gp 实现对全局变量寻址,但又有区别

      每次函数调用前,先要将函数的地址存入 t9 寄存器中,然后 jalr t9 跳转到目标函数。目标函数开头会保存 gp 到栈上,然后把 t9 赋给 gp。这样就实现了以函数为最小单位的全局变量寻址(x86_64 使用的 “rip+偏移” 可以说是以指令为最小单位)

  4. mips 栈 rop

    mips 的栈上 rop 比 x86 要复杂一些,原因如下:

    1. 叶子函数与非叶子函数的区别:

      叶子函数不会调用其它的函数。根据这个特点,mips 调用叶子函数和非叶子采用了不同的做法。调用非叶子函数时,会将返回地址存放在栈上。所以 rop 只能在非叶子函数上进行。

    2. t9 造成的问题:每个函数的开头都会把 t9 赋给 gp,如果直接覆盖返回地址为函数地址,jr $ra 时,访问全局变量时出现会出现段错误。解决办法有两个:

      1. 跳过函数开头对 gp 的赋值操作。然而如果是不同模块间的跳转(比如可执行文件和 libc ),这样做应该会失败(没有尝试过)。
      2. ROP 给 t9 赋正确的值:libc 中有可用的 gadget:0x00006b20: lw $t9, ($sp); jalr $t9
    3. 跳转指令后的指令:通常在函数调用处有这样的 gadget:

      1
      2
      3
      4
      0x00046974:
      addiu $a0, $a0, -0x3954
      jalr $t9
      move $a1, $zero

      由于 pipeline 和 delay slot 等机制的影响,jalr 后的一条指令会与 jalr “同时” (原谅我用这种不准确的描述)执行。

  5. 其它

    1. mips Delay slot,以及扩展阅读 Hazard (computer architecture)(未读)