# NEUQCSA-2023-11 月赛题解
# 解题情况总览
Misc : Signin USB Rainbow
Pwn : 白给の puts 白给の fmt 白给の heap 王神の stack_overflow
Reverse : 白给の pyc
# Pwn
# 白给の puts
纯白给
不会做的建议紫菜(bushi)
出题人:Jmp.Cliff
难度:简单
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,我是菜徐坤,
做这直接打 got 表的题都还用了 16min,狠狠地拷打。
人家出题人都好心让你输入 buf 地址了你还赖着不输入 puts 的 got 表地址干啥,还迟疑个 der.
直接 <(D_D) つ (>_<) 手动拷打
1 |
|
# 王神の stack_overflow
王神の栈是全栈
出题人:brain_z
难度:简单
Unlock-Hint-for-0-points
csu 开 read 栈迁移泄露 libc 再开 read 打 orw
好好好,hint 直接帮我说完思路了,直接放 exp。csu 真是让人又爱又恨啊。
1 | #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 |
# 白给の 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 | #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 |
# 白给の 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 实现,这样打通的几率更大。
1 | #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 |
# 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 |
|
下面就是手搓的反编译的 py 代码了
注意这里算是里面的运算优先级,多加几个括号没有坏处。因为我就在这里被坑了
1 | def listcomp(input_str): |
前一半 flag 还很好计算,但是后边一半应该是没有办法直接还原的。
所以我们直接爆破,爆破的为时间复杂度 O(len * charnum),完全可以接受。(到了大学还分析时间复杂度感觉有点别扭)
1 |
|
最后输出了 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 | import os |
# rainbow
碰上彩虹,____ 出题人:鞠佳凝
打开 zip 是两张图片。ribbon 有 16 个色块,可以想到是 hex,通过 flag.png 验证开头是 50 4B,于是就可以知道是个压缩包
请人工智能写代码(真是的,这点代码都写不对,还要我修改一堆),自己再修改一下输出格式就可以提取出色块了:
1 | import numpy as np |
所有输出的 flag 的色块为:
1 | (255,0,255) |
我们要匹配的色块按照下标排序为:
1 | "(0,0,0)", |
回到老本行 C++:
1 |
|
于是得到 hex
1 | 504b03040a0000000000ae957557add2d7802a0000002a00000008000000666c61672e747874666c61677b61616531356665652d303137652d343632372d393837322d6530313466346664346339327d504b01023f000a0000000000ae957557add2d7802a0000002a000000080024000000000000002000000000000000666c61672e7478740a002000000000000100180051c061d6671cda0100000000000000000000000000000000504b050600000000010001005a0000005000000000000000000000000000 |
Winhex 直接保存为.zip 打开就是 flag