梦开始的比赛。去年纯小白直接参赛,结果自然是被血虐。之后开始慢慢学,今年总算是做出些题。不过难一些的 PWN 题还是做不出……( ),就多练。
Misc
火锅链观光打卡
签到题。
浏览器安装一个 MetaMask 钱包用于区块链操作。连接钱包后答题,收集任意7个不同食材图片后,点击兑换 NFT ,得到含 flag 的图片:
得到 flag :
1 | flag{y0u_ar3_hotpot_K1ng} |
Power_Trajectory_Diagram
这是一道基于功耗分析的侧信道攻击题,搜索相关关键词,在看雪上找到一篇文章。根据文章内容可知,输入密码逐位比对,输入正确时和错误时功耗曲线有明显不同。
将得到的 npz 加载后打印数据,发现一共有13*40组数据,40对应着40个字符,猜测13为密码位数。打印所有功耗曲线,可以发现:
每40组曲线中,会有一组曲线的最大波动处横坐标明显右移,例如上图第37组曲线最大波动处相比于第36组以及其他1-40组的最大波动处都有一定程度右移。推测是密码错误时会出现最大波动,而第37组最大波动右移代表着当前输入的密码字符是正确的,错误发生在下一位。
使用这种方法可以找到每40组曲线中最特殊的一组,并映射为相应的字符。(除了第481组到第520组,因此认为密码只有12位)
特殊曲线到字符的映射脚本如下:
1 | data = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', |
得到结果_ciscn_2024_
,因此 flag 为:
1 | flag{_ciscn_2024_} |
Crypto
古典密码
题目给了一个字符串AnU7NnR4NassOGp3BDJgAGonMaJayTwrBqZ3ODMoMWxgMnFdNqtdMTM9
,没有说明经过何种处理。
放到 CyberChef 选择 Encrption / Encoding 逐个尝试,用 Atbash Cipher 解密后 Base64 解码,得到:
1 | fa{2b838a-97ad-e9f743lgbb07-ce47-6e02804c} |
根据题目的提示想到栅栏密码,将字符串对半分,然后Z形拼接就能得到 flag:
1 | flag{b2bb0873-8cae-4977-a6de-0e298f0744c3} |
Reverse
gdb_debug
IDA反编译,注意到程序中设置随机数种子的代码:
1 | v3 = time(0LL); |
实际上随机数种子恒为0x60000000,因此该程序中的随机数都可以确定,可以使用 ctypes 来调用 libc 库设置相应的随机数种子,获取每一次调用 rand()
返回的随机数。剩下的就是根据反编译的程序使用 z3 进行约束求解,exp 如下:
1 | from pwn import * |
其中第三次获取38个随机数时,我使用 ctypes 得到的随机数与实际的随机数不符,因此直接在 gdb 中打印 v31 这个数组在与随机数异或前后的值,得到第三轮的38个随机数。不清楚是什么导致了这种差异,但或许这就是题目提示“动静结合”的原因?
最后得到flag:
1 | flag{78bace5989660ee38f1fd980a4b4fbcd} |
Pwn
gostack
一道简单的栈溢出+ROP题目,一开始被 golang 唬住了,逆向了一会儿没找到缓冲区的大小,然后直接在 gdb 中看就清楚多了。
首先checksec:
1 | Arch: amd64-64-little |
有栈溢出 + 没有canary + 没有PIE + gadgets = 简单 ROP
找到要用的gadgets,构造 ROP chain ;在 gdb 中计算出缓冲区开头与返回地址的距离为0x1d0字节,加上填充就得到 payload。exp 如下:
1 | from pwn import * |
有 syscall
但是没有 syscall ; ret
,因此我们的 ROP chain 最多只能有一次 raw syscall ,因此 read 选择使用函数地址而不是 raw syscall。get shell 之后得到 flag :
1 | flag{08c559f9-81f7-4c74-a983-9eb59502de34} |
orange_cat_diary
首先用 IDA 反编译程序,在程序中发现以下漏洞:
- heap overflow(8字节的溢出)
- UAF(只能使用一次,因为只能 delete 一次)
- write after free
- read after free
再根据题目名称的提示可以知道,可以使用 House of Orange 进行攻击(利用 heap overflow 和 read after free),泄露出 libc 地址和堆地址。由于 libc 的版本为2.23,因此最简便的方法就是劫持 __malloc_hook
。使用 pwndbg 的 find_fake_fast
命令找到用于覆盖 __malloc_hook
内容的 fast bin 地址,然后利用 write after free 劫持 fast bin ,使其返回该 chunk ,然后将__realloc_hook
写为one_gadget
,将__malloc_hook
写为realloc
,这样做更容易满足one_gadget
条件。
利用代码如下:
1 | from pwn import * |
get shell 并得到 flag :
1 | flag{2a6de11d-8a93-484d-9444-7d1046c55134} |
EzHeap
我刚开始放 payload 的堆选了0x80大小,根本放不下 ROP chain ,直接导致比赛结束时没来得及将这题做完,赛后十来分钟改了个大小就打通了。
又一道堆题,但是使用 seccomp 限制了系统调用:
1 | $ seccomp-tools dump ./EzHeap |
因此很容易想到先用 mprotect
更改页面权限,然后 orw 直接读 flag。但是我最后没有使用 mprotect
,直接在栈上构造 ROP chain 来进行 orw 。
分析程序,发现漏洞:
- 极大 heap overflow
- 输入无 0 截断可导致相邻内存泄漏
而且最多允许我们 malloc
80个堆块,因此应该有不少利用方法。我主要利用 tcache poisoning 。攻击思路如下:
首先利用堆溢出和相邻内存泄露,通过程序内已经有的 unsorted bins 等堆块,泄露 libc 和 heap 地址
计算 libc 中
__environ
的地址,利用 tcache poisoning 获得该地址处的堆块进行读,泄露 stack 地址libc 版本为2.35,因此要手动 safe link
在某个堆块中写入
flag\x00
用于 orw ,搜集 gadgets 构造 ROP chain 。值得注意的是不能直接调用库函数 orw ,因为库函数的open
往往使用openat
系统调用,会被禁止。因此我直接选择全部使用syscall ; ret
gadget ,这也是导致我 payload 巨大的原因。在
malloc_heap
操作对应函数的ret
处下断点,计算此时 stack 地址与泄露 stack 地址的偏移,然后再利用 tcache poisoning 获得目标地址附近(target_stack-0x8
,因为要16字节对齐且不能破坏canary)的堆块进行写。payload 为 8 字节的 rbp 填充加上 ROP chain 。malloc_heap
返回时会被劫持到该 ROP chain 。
exp 如下:
1 | from pwn import * |
最后得到 flag :
1 | flag{c9112d19-27e3-41ec-9957-fefb3f109229} |
条评论