router_vuln_basic_knowledge
路由器漏洞基础知识
准备与工具
路由器漏洞分类
路由器具有无线功能,开启Wi-Fi功能后,设备可以通过密码认证的方式连接到路由器上网,加密认证方式通常有三种:
- WPA
- WPA2
- WEP(强度弱逐渐被淘汰)
后来路由器引入了一种叫做WPS
的技术,即一键加密键,由Wi-Fi联盟退出的全新Wi-Fi安全防护设定(Wi-Fi Protected Setup
)标准,为了解决长久以来无线网络加密认证设定的步骤过于繁杂艰难的问题。通常用户只需要按下WPS按钮,或者输入PIN码,经过简单操作就可以完成无限加密设置,在客户端和路由器见建立一个安全的连接。这个功能可以在Web管理界面设置开关。
问题在于,该路由器的PIN码中最后一位是校验位,可以不必破解计算得出。假设该PIN码为8位,前7位在实施PIN的身份识别时,无线路由器只要找出这个PIN码的前半部分(前4位)和后半部分(后3位)是否正确即可。在第一次PIN认证连接失败后,路由器会向客户端发回一个EAP-NACK
信息,通过该信息,客户端可以知道这两部分PIN码是否正确,这就意味着,只需要从中找出一个4位的PIN码和一个3位的PIN码即可。破解的难度从10^7变成了3^10+4^10=11000
。因此使用WPS功能也增加了被攻击的风险;
Web漏洞
管理界面中含有的漏洞,SQL注入、命令执行、CSRF等等;
路由器有两个比较重要的相关密码:
- Wi-Fi密码
- 后台管理密码
这些密码并不经常变动,所以攻击者铜鼓哦认证绕过漏洞、弱密码或者默认路由器管理密码登陆,从而进行恶意操作;
后门漏洞
软件开发程序员为了方便调试和检测留下的超级管理权限,但被利用则可以RCE;
溢出漏洞
路由器是嵌入式设备,运行的程序会因为存在缓冲区漏洞而被利用,攻击者通过分析路由器系统及其允许的服务程序,进行大量的分析和模糊测试,发现缓冲区溢出漏洞,从而实现远程控制;
基础知识和工具
路由器的Linux有两个特点:
- 指令架构:MIPS、ARM
- 路由器的Shell是基于BusyBox的
MIPS Linux
MIPS属于RISC精简指令集体系;
路由器根文件系统与Linux基本一致,通常有Linux根目录的目录文件,其中:
bin
、sbin
、usr/bin
和usr/sbin
都是用于存放应用程序的目录;lib
与usr/lib
用于存放程序运行时需要的动态库文件;etc
则是存放路由器的配置文件,主要村反程序自启动配置文件、脚本文件和各种服务程序的配置文件等;
BusyBox
路由器系统中存储空间有限,所以使用的shell通常由BusyBox程序提供,shell的命令都指向BusyBox的符号链接,所以命令种类的多少主要看BusyBox剪裁的程度;
一些工具
工具这里就不多介绍了,编辑工具vim、nano,编译器GCC等;
MIPS汇编基础
处理器指令体系:MIPS I、MIPS II、MIPS III、MIPS IV、MIPS V;
嵌入式指令体系:MIPS16、MIPS32、MIPS64;
MIPS32
一种基于固定长度的定期编码指令集,采用导入/存储,即load/store
的数据模型,是路由器中经常使用的MIPS架构;
特点有:
-
固定
4 bytes
指令长度,即32 bit
; -
内存数据访问
(load/store)
必须严格对齐; -
跳转指令只有
26 bit
目标地址,加上2 bit
对齐位,寻址28 bit
空间(256MB); -
条件分支指令只有
16 bit
目标地址,加上2 bit
对齐位,寻址18 bit
空间(256KB); -
$ra
存放返回地址,嵌套函数则用其他机制; -
流水线效应,执行到分支语句时,刚把跳转的地址填充好,还未执行本条指令时,分支语句后面的指令已经执行了。几条指令同时执行,只是处于阶段不同,例如:
mov $a0, $s2
jalr strchr
move $a0, $s0
在执行第二行进行跳转时,第三行的move
指令已经执行完了,因此上面的指令序列中,strchr
函数的参数来自于第三行的$s0
而非第一 行的$s2
;
寄存器
RISC的显著特点就是大量使用寄存器
,这主要是因为寄存器的存取可以在一个时钟周期内完成,同事简化了寻址方式,所以除了load/store
,都使用寄存器或者立即数作为操作数,以便通过保留寄存器数据来提高性能。
寄存器分为两类:
- 通用寄存器GPR
- 特殊寄存器
通用寄存器GPR
32个通用寄存器,可以用编号$0-$31
来表示,也可以额直接用寄存器名字表示:$sp, $ti, $ra
等;
编号 | 寄存器 | 描述 |
---|---|---|
0 | zero |
值始终为0的寄存器 |
1 | $at |
保留寄存器 |
2~3 | $v0 ~$v1 |
values,保存表达式或函数返回结果 |
4~7 | $a0 ~$a3 |
arguments,函数的前4个参数 |
8~15 | $t0 ~$t7 |
temporaries,供汇编程序使用的临时寄存器 |
16~23 | $s0 ~$s7 |
saved values,子函数使用时需要先保存源寄存器的值 |
24~25 | $t8 ~$t9 |
temporaries,供汇编程序使用的临时寄存器,对$t0~$t7的补充 |
26~27 | $k0 ~$k1 |
保留,中断处理函数调用 |
28 | $gp |
global pointer,全局指针 |
29 | $sp |
stack pointer,堆栈指针,指向堆栈的栈顶 |
30 | $fp |
frame pointer,保存栈指针 |
31 | $ra |
return address,返回地址 |
zero
:没有像x86那样的标志寄存器,条件判断通过比较两个寄存器是否相等来完成。通常用slt
、beq
、bne
等指令和zero
寄存器的0
值产生所有的比较条件
$at
:汇编保留,用做汇编器的暂时变量;
$v0~$v1
:存放子程序的返回值或非浮点数结果,不够存放时则存放在内存中;
$a0~$a3
:前4个参数传递,不够存放时则用堆栈传参;
$t0~$t7
:子函数可以不用保存随意使用的寄存器,临时变量存放的寄存器;
$s0~$s7
:保证函数返回后,寄存器内容恢复为原先的值;
$t8~$t9
:对$t0~$t7
的补充;
$k0~$k1
:终端函数调用时保存系统的参数;
$gp
:简化对静态数据的访问,保留了全局指针gp
,编译时,数据需要在以gp
为base pointer
的64KB范围内;
$sp
:MIPS没有单独对栈的操作指令(PUSH/POP),对栈的操作是统一的内存访问方式。在函数调用时,调用函数把要用的寄存器压入栈中,被调用函数把$ra
和保留寄存器压入栈中,调整$rp
,并在返回时从堆栈中恢复寄存器;
$fp
:不同编译器操作不同,GNU MIPS C
使用了$fp
,而SGI C
没有使用,反而当做$s8
保留寄存器使用;
$ra
:存放返回地址。MIPS中的jal,jump and link
指令,在跳转到某地址时,把下一条指令的地址放入$ra
中。例如,调用函数时,把参数放入寄存器,保存寄存器$a0~$a3, $s0~$s7, $gp, $sp, $fp, $ra
,然后执行jal addr
跳转到addr
地址,调用结束后,把结果放入寄存器,执行jr $ra
返回到保存的地址;
特殊寄存器
3个特殊寄存器,分别是:
- PC:程序计数器
- HI:乘除结果高位寄存器,乘法时存结果的高位,除法时存余数
- LO:乘除结果低位寄存器,乘法时存结果的低位,除法时存商
寻址方式
四种寻址方式分别为:
- 寄存器寻址
- 立即数寻址
- 寄存器相对寻址:主要被
load/store
指令使用,对一个16bit的立即数进行符号扩展,然后与指定通用寄存器的值相加,得到有效地址; - PC相对寻址:主要被转移指令使用,转移指令提供一个16bit的立即数,其左移2bit并进行符号扩展,与PC的值相加,得到有效地址;
指令格式
在MIPS架构中,指令的最高6 bit
为Opcode码,剩下的26 bit
可以分为三种类型:
-
R型:连续3个
5 bit
的二进制码表示3个寄存器的地址,1个5 bit
二进制码表示以为的位数,最后6 bit
是Function code
,它与Opcode决定R型指令的具体操作方式;[ Opcode(6) | Rs(5) | Rt(5) | Rd(5) | Shamt(5) | Funct(6) ] <- 32 bit
[ 操作码 | 第一个源操作数寄存器 | 第一个源操作数寄存器 | 存放操作结果的目的操作数 | 位移量 | 函数 ]
-
I型:连续2个
5 bit
的二进制码表示2个寄存器的地址,1个16 bit
的二进制码表示1个立即数二进制码;[ Opcode(6) | Rs(5) | Rt(5) | Immediate(16) ] <- 32 bit
-
J型:
26 bit
二进制码表示跳转目标点指令地址(实际指令地址为32 bit
,其中最低2 bit
为’00’,最高4 bit
由PC当前地址决定;[ Opcode(6) | Address(26) ] <- 32 bit
常用指令
一些语法表述方式:
$
标记一个寄存器,$Rd
为目的寄存器,$Rs
为源寄存器,$Rt
为中间缓存寄存器;imm
表示立即数;MEM[]
表示RAM中的一段内存;offset
表示偏移量;
一些指令:
-
LOAD/STORE
:存取类型的指令有14条,分别是:lb lbu lh lhu ll lw lwl lwr <- 加载指令,从存储器中读数据
sb sc sh sw swl swr <- 存储指令,将数据保存在存储器中-
LA(Load Address),将一个地址或标签存入一个寄存器;
[la $Rd, Label]
la
$t0
, val_1 复制val_1(一个Label)表示的地址到$t0寄存器 -
LI(Load Immediate),将一个立即数存入一个通用寄存器;
[li $Rd, imm]
li
$t1
, 40 寄存器$t1赋值为40,相当于addi $t1, $zero, 40;
-
LW(Load Word),从指定地址加载一个word类型的值到一个寄存器中;
[lw $Rt, offset($Rs)]
lw
$s0
,0($sp)
$s0 = MEM[$sp+0];
,取堆栈地址偏移0内存word大小的值到$s0寄存器中 -
SW(Store Word),将源寄存器中的值存入指定的地址;
sw $Rt, offset($Rs)
sw
$a0
,0($sp)
MEM[$sp+0] = $a0;
,将$a0
寄存器中的一个word大小的值存入堆栈,且$sp
自动抬栈 -
MOVE,用于寄存器之间值的传递;
move $Rt, $Rs
move
$t5
,$t1
$t5 = $t1;
,$t5
寄存器赋值为$t1
寄存器的值
-
-
运算指令类型有21条,并且满足要求:
- 所有操作数都是寄存器,不能直接使用RAM地址或间接寻址;
- 操作数大小都是word(
4 bytes
=32 bit
)
加:add addi addiu addu
减:sub subu
比较:clo clz st slti sltiu sltu
乘:mul mult multu
乘累加:madd maddu
乘累减:msub msubu
除:div divu
-
SYSCALL
产生一个软终端,从而实现系统调用:
- 系统调用好存放在
$v0
中; - 参数存放在
$a0~$a3
中; - 返回值存放在
$v0
中,发生错误会返回错误号到$a3
; - 参数过多的时候会用另一套机制处理;
- 系统调用好存放在
-
分支跳转指令,通过比较寄存器的值决定是否跳转
b beq blt ble bgt bge bne
-
跳转指令
j jr jal
以上只是粗略记录,深入了解还需要去细读相关专业书籍。