攻防世界 x Nepnep x CATCTF 2022 Nepnep战队官方WP
CatCTF2022 官方WP
目录
感谢
感谢Nepnep战队的各位成员、攻防世界的运维小哥哥小姐姐们、公开招募的出题人和志愿者们以及各位CTF选手对此次公益比赛的支持,让🐱🐱们可以饱餐一顿。最终本次活动共成功募集到1吨猫粮,以每只小猫每顿60g猫粮来计,超1.6w只流浪小猫将能饱餐一顿。
拯救喵喵大作战计划大获成功👏👏
https://mp.weixin.qq.com/s/EKiSEZk7Kj7aTZYk4hWhew
招新
Nepnep大力招新,感兴趣的师傅们可以投递简历到1764763743@qq.com,期待各位师傅的加入QWQ!
PWN
bitcoin🪙
这个题我没有出好,当时阳了=-= 脑子嗡嗡的 希望各位pwn不要介意
EXP
1 | from pwn import * |
非预期
我沙盒检测机制设置的不是很好,mprotect被利用了,利用csu+shellcode直接梭哈
非预期发现者(第一个和我反馈的也许有别的师傅也发现了):gxh191师傅
EXP
1 | #encoding=utf-8 |
第二种,直接跳转到0x404EA4把flag读出来 这是最常见的方法
源码
链接:https://pan.baidu.com/s/1iTLK3IkVw_a9OqK5kkXO5w?pwd=yrmv
提取码:yrmv
--来自百度网盘超级会员V4的分享
HRPVM🖥️
想出一些有意义的题目,题目漏洞虽然简单,但是比较适合新人去调试布局内存,出题不是为了折磨选手,是为了大家都能学到东西
漏洞
create_file函数可以无限创建文件没有检查file_count的大小
1 | __int64 __fastcall create_file(__int64 a1) |
查看bss段发现控制用户权限的结构体在文件HF的下面
1 | .bss:0000000000204120 ?? HF db ? ; |
利用思路非常简单,创建32个文件刚好覆盖到users,然后通过逆向分析可得,users结构体的一个数据存放的是UID和GID,调试发现,第32个文件的mode刚好可以覆盖到UID和GID,并且由于mode是可以通过伪汇编修改的,open使用方法在程序的README里面照着写就行了,把mode改成0
1 | mov rdi,35;mov rsi,0;call open,2; |
之后就变成了UID&GID==0的用户了,现在可以进入DEBUG控制台了。
分析debug函数,里面主要的功能就是file input和mmap,mmap可以自定义权限为3的指定地址段。
file input可以把程序外指定的文件拉取到虚拟机内,拉取后默认的权限模式是1000也就是不可读状态。此时,一般的选手可能想着flag拉进来了然后exit出debug再去user态直接cat 就可以获取flag了
但是注意,exit后程序会把users结构体初始化
1 | deleEnter(buf); |
此时UID和GID变成1000,然后dest这次存放的就是开机的时候要求输入的持有者也清0了,通过调试不难发现这个持有者变量和刚才第32个文件的存放文件名的地址重合了,因为exit这里被清0了,那么此时的flag可是在0x23个文件的下面,如果去cat 遇见第0x23个文件strcmp文件名的时候会因为地址无效程序崩溃,但是此时能获取到已知可读写的地址方法就一个,就是mmap!
1 | 0x55a2e6708500 <HF+992>: 0x00000000000003e8 0x000055a2e7e66c00 |
当我们mmap自定义地址后(我选择的是0x123000),还需要把地址填写到这,就像我上面分析的一样通过调试不难发现这个持有者变量和刚才第32个文件的存放文件名的地址重合了,唯一可以输入这个变量的地方就是在启动机器的时候,在machine_start函数可以找到reboot功能
1 | if ( !strncmp(off_2040B8, (const char *)buf, v7) ) |
此时填充holder为0x123000即可,等到虚拟机重启进来后,再用伪汇编调整flag文件的open模式即可用cat抓取
EXP
1 | from pwn import * |
源码
1 |
|
injection2.0💉
题目提供的init文件分析
1 | #!/bin/sh |
echo 0 | tee /proc/sys/kernel/yama/ptrace_scope
./target >pso.file 2>&1 &
setsid /bin/cttyhack setuidgid 0 /bin/sh
这3句是关键,现在是root,然后开启了ptrace,并且把target程序挂到了后台
target
读取flag到栈上后就死循环输出helloworld
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
利用思路
root下可使用ptrace接口,直接利用ptrace搜索进程内存,ps -ef获取到进程的PID 再去/proc/pid/maps获取到栈地址
利用ptrace获取栈内数据比对flag格式字符串,我本地测试用的flag格式就是flag{}
还有一个小细节,我的qemu里面放了so文件可以跑动态链接程序。
exp
1 |
|
上传脚本
1 | from pwn import * |
kernel-test 本次资源合集,包括源码,EXP,docker🧬
1 | 链接:https://pan.baidu.com/s/1h7xSB1qJp5s3MsoOCWUDig?pwd=2v64 |
Ret2usr
这种技术已经很老了而且很好防范开启smep直接寄了,但是真的很好用,方便,不用找gadget不用控制bp,sp寄存器
网上已经有了很多很多的官方阐述,我这里只做通俗理解。
简易讲解
对于操作系统,我们都知道存在用户态和内核态,驱动挂载是在内核态里面跑汇编的,普通二进制程序是在用户态里跑的。
如果qemu没有开启kvm64,kvm64 +smep都可以在用户态代码空间去执行任意代码。通俗讲smep就是相当于平常题目的NX。
思路很简单,用户打开驱动,用交互模块去利用漏洞。比如溢出,用read泄露canary,write构造rop。
控制rip从内核态跳转到用户态,执行我们exp中写好了的提权函数。
其实不用把内核驱动看的多神秘,说白了本质上还是二进制文件,只是代码执行区段有区分,有不同的保护机制,堆栈上本质的特性不会变的。不过随着GCC和内核版本提高,很多gadget已经消失了,常见的rop手法和栈迁移手法基本上算是寄了
这里简单讲下自己出的这个例题
源码
1 |
|
重点
这里是构造qmemcpy进行溢出,这个内存复制操作只有汇编能完成这样的溢出,普通的strcpy,memcpy会被GCC编译的时候在函数里直接强塞检测函数,杜绝了溢出操作,所以在现实中,真有这么操蛋的溢出基本上是不可能的,只是为了学习研究。
1 | __asm__( |
攻击思路
先看read函数,泄露canary
这里copy_to_user可以把内核空间的数据给到用户,大小限制固定0x40,刚好buf数组大小也是0x40,所以在EXP里面到时候read回来的时候下标为0的地方就是canary了
1 | static ssize_t HRP_module_read (struct file * file, char __user * user , size_t size, loff_t *p) |
write函数可以把用户的数据给到内核中,这里是保存在BSS段上
1 | static ssize_t HRP_module_write (struct file * file, const char __user * user, size_t size, loff_t * p) |
结合ioctl中的内存复制操作,可以造成很大的溢出。
这样思路就很简单的,填充canary,填充用户态利用函数地址就行了。
EXP
我开启了动态地址随机化,所以在init提前保存了习惯地址,给选手用来计算偏移,find_symbols函数就是计算offset和寻找真实地址的
save_status函数是用来保存用户态的栈情况的,从内核到用户态的过程中,栈情况是内核的,如果最后返回到用户态执行完了代码但是咩有还原到用户态的栈,你执行了也没用的还是sh是不会返回给你的。
get函数用来修改uid的,执行commit_creds(prepare_kernel_cred(0))修改UID到root,下面的汇编就是用来切换到用户态并且执行shell 的
1 | void get(){ |
1 | // gcc exploit.c -static -masm=intel -g -o exploit |
1 | gcc exploit.c -o exploit -no-pie -static |
上传脚本
1 | #!/usr/bin/env python |
tips
调试的时候把init文件改成root用户,在启动脚本那把kalsr改成nokalsr,方便调试,具体操作可以加载符号表那些看我的mygdbinit
驱动加载地址用lsmod查看,找gadget自己用ropper找吧,ropgadget算是寄了。还有题目的vmlinux提取用下面这个
因此还有一个工具可以使用:vmlinux-to-elf
使用这个工具之前系统中必须装有高于3.5版本的python
1 | COPYsudo apt install python3-pip |
使用方式:
1 | COPY# vmlinux-to-elf <input_kernel.bin> <output_kernel.elf> |
之后解压出来的 vmlinux 就是带符号的,可以正常被 gdb 读取和下断点。
调试用gef,不卡顿,不会有符号表问题,用就完事了
1 | pip3 install capstone unicorn keystone-engine ropper |
调试细节那些,打断点很讲究,尽量不要打在push rbp上;然后直接多看看内核驱动报错,可以看见出问题的地址,变相送个调试器(不是)
zip💼
分析发现此题实现的zlib压缩,unzip需要购买root,root的CDK根据题目提示
n=221 e=7,可以联想到RSA,分析CDK检测算法发现3个字符串结果的比对,尝试RSA解密
1 | import gmpy2 |
得到CDK就是HRP
zip可以压缩任何文件并指定压缩后文件名,直接把flag或者/bin/sh压缩为pwn,此时不会影响程序运行,因为程序此时没有结束是被挂载在proc目录下的。
unzip只能输入一个字符,但是这里不难发现unzip和zip都是被main所调用的并且filename变量大小都是一样的,利用子函数同栈内存大小相等,在unzip的时候发送Ctrl+D(手动输入),程序就会结束输入,这样unzip的默认filename就是上次在zip输入的压缩后的文件名,也就是pwn。
等下次再去nc题目的时候就会执行报错得到flag(同文件名覆盖不影响权限)
1 | q@ubuntu:~$ nc 121.37.24.208 49472 |
非预期
榜一大哥Esifiel 的题解
源码
1 |
|
welcome_cat_ctf🎉
gdb修改glod变量
set *0x55555575e388=0x8888888
然后移动到@下面按下j,向服务器发送flag获取请求即可得到flag
chao 🌿
感谢winmt师傅投递的题目
前言
首先,祝各位师傅们元旦快乐,2023新的一年挖洞如水喝,拿shell拿到手软!
其实,这个题我自己也没感觉很难,更没想到在这次CATCTF跨年赛上能坚挺到最后,成为Pwn方向唯一一个零解的题,应该是师傅们都忙着跨年陪对象了呜呜呜。如果有师傅在这题上花了太多时间,影响了新年的好心情,winmt在此跪下QAQ
本题在漏洞挖掘方面,考察了去除符号表的C++程序逆向恢复 以及 C++中常见的由虚函数引起的类型混淆漏洞。本题代码量并不大,虽然去除了符号表,但是逆向思路整体还是比较清晰的。弄清逻辑后,类型混淆漏洞应该也不难发现。主要考察点在漏洞利用方面,很容易发现有栈溢出,但是并不好利用,此处需要了解C++异常处理的栈回退机制。关于C++异常机制的Pwn题其实很早就出现过,也并不新奇,但是本题在catch到异常后,直接退出了程序,也不好绕过canary保护,无法栈溢出。不过可调试发现,能够通过栈溢出劫持到异常处理中调用的函数指针,继而通过栈迁移完成利用。
下面给各位师傅们呈上本题的WriteUp,对本题有任何其他想法的师傅,也都欢迎找我一起交流~
逆向分析
本题是一道32位的C++ Pwn题,并且保护全开、去除了符号表。因此,需要先找到类的虚表,方便之后对类的逆向恢复:
通过start找到main函数之后,发现直接反编译会有问题(疑似IDA的bug),将报错的0x1FF5处(cout的一部分)nop掉即可成功反编译main函数:
可以看到本题开启了沙盒,不过由于其他考点较多,就没有在沙盒考察上增加难度了,只禁用了execve:
创建功能:最多创建10次,每次可以创建0和1两种对象存放数据,数据的大小不能超过0x100。 (1)创建类型为0的对象,首先申请一个堆块,将Class_one的虚表地址放在堆块头,输入的内容长度存在偏移0x4的位置,输入的内容通过strdup申请的指针存放在0x8的位置。需要注意的是,strdup调用了strlen获取长度,因此输入的内容相当于会被\x00截断:
创建类型为1的对象,同样会申请一个堆块,将Class_two的虚表地址放在堆块头,输入的内容长度存在偏移0x4的位置,不过还会又创建一个对象,在该对象中存放输入内容的长度以及通过strdup为输入的内容申请的堆块地址,并将该对象存放在为Class_two创建的堆块偏移0x8的位置。
更新功能:不论是Class_one还是Class_two的对象,都会调用Class_one虚表中的sub_2290函数,由此可以判断:Class_two是Class_one的派生类。在sub_2290函数中,会释放掉偏移为0x8处的堆块,更新长度,并通过strdup为新输入的内容产生新的堆块存放在偏移0x8处。此处存在一个类型混淆漏洞:sub_2290函数明显只是针对Class_one的操作,而Class_two更新的时候也会调用这个函数,故产生了类型混淆,可利用此处漏洞控制size和buf地址。
输出功能:均会调用Class_one类虚表中的sub_22EA函数,其中获取size的时候,会分布调用Class_one和Class_two各自虚表中的相应函数:
其中,Class_one会用strlen获取偏移为0x8处的buf的长度,而Class_two会获取偏移为8处的对象中开头的size,这更呼应了上一点的类型混淆漏洞的可利用性。
这里之后调用alloca动态分配栈,分配的大小是size+10,然后首先strcpy到栈上”Content: “字符串9个字节,再将需要输出的内容从对象中的buf区域(这里与上面获取size类似,Class_one直接获取偏移0x8处的buf,Class_two会获取偏移为8处的对象中偏移为4的buf,可通过类型混淆漏洞利用)strcat到栈上之后的区域(存在缓冲区溢出)。
不过,若是strcat之后buf的长度大于length-1(这里可利用计算机补码造成整型溢出),则会用C++异常处理中的throw抛出错误,否则将buf输出出来:
C++异常处理的catch部分在0x204C处,会输出Error!并通过exit(-1)结束程序:
漏洞利用
- 首先,根据逆向分析的结果,存在类型混淆漏洞,可申请Class_two的对象,并通过update功能,任意修改size和buf,并通过display功能,可进行任意地址读,且利用display中的strcat可造成缓冲区溢出。
- 泄露libc地址:虽然我们可利用类型混淆漏洞进行任意地址读,但是此题保护全开,我们最开始是拿不到任何地址的。由于每次申请的堆块大小不得超过0x100,故需要先将tcache填满,再free到unsorted bin的堆块中就会有libc残留地址。紧接着,布局堆风水(这里需要注意一些细节,此处不赘述了),使得update申请到的堆块的0x4偏移位置是libc的残留地址,其中仍然存放着libc地址(main_arena+0x40),即可得到libc_base。
- 泄露heap地址:有了libc地址之后,虽然main_arena中有heap地址,不过在display中,当strcat没遇到\x00,就会一直拼接,main_arena附近很长一段都没有\x00,这就会直接乱码栈溢出,导致崩溃。因此,这里利用environ附近有heap段末地址的特性,泄露heap地址(需要注意避开末尾的\x00)。
- 泄露PIE地址:在为对象分配的堆中,开头会存放程序中对应的虚表地址,可利用此处泄露PIE地址。当然,在libc或ld中找程序相关地址泄露也是可以的。
- 在上面泄露地址的时候(由于是32位的程序,也可通过爆破一位程序地址来泄露,概率1/16),虽然已经尽可能保证拼接的长度小,不会直接乱码栈溢出。不过,仍然可能会超出分配的数组本身的大小,通过C++异常处理throw抛出error。这里可以利用计算机补码,因为比较的时候是与length-1比较的,故可以使length为0,这样减1后根据补码的相关知识,就变成了0xffffffff,即最大的数,就可以绕过异常处理了。
- 然而,本题是开了canary保护的,虽然我们可以从比如TLS中泄露出canary值,不过canary末两位一定是00,这样如果直接将payload发过去,在strcat的时候会被截断。因此,得另辟蹊径绕过canary保护。注意到这里有一个C++的异常处理,通过栈溢出将返回地址改到try与catch中间的位置(即可被捕获到异常的位置,如下图0x2044处),这样就能跳到catch继续执行了。不过,这里与常规的利用catch栈溢出的思路不同:因为这里catch之后直接exit了,也有canary保护。
当执行到catch中的cxa_begin_catch时,会跳转到ebx+0x1c中的地址:
而ebx又是esi赋值过来的,esi是ebp-8中的值,是可控的:
此时,若通过缓冲区溢出在ebp写入payload地址-4,ebx+0x1c写入存放着leave; ret的地址,即可栈迁移,绕过canary保护。需要注意的是,当缓冲区溢出执行的时候,会将栈上存放buf和length的指针覆盖掉,这里需要伪造一下,使得其能走到异常处理的throw处。
这里需要用ORW的payload绕过沙盒,也就需要先将payload写入堆中,再栈迁移跳转过去。然而,此payload中必然会出现\x00,被截断,无法完整地发送过去。因此,这里我们可以先读入gets的payload,跳转执行。再通过gets读入ORW的payload到某可写地址处,再栈迁移过去即可。
这里还需要考虑到stdin输入缓冲区的性质:当输入缓冲区中开头有\n,则gets/fgets这类走输入缓冲区的函数不会再读入数据(可自行查看相关源码)。因此,我们在之前所有cin读入的操作处,都用空格作为截断符,即可避免这个问题,使gets的payload正常读入数据(会先将输入缓冲区中残留的一个空格符写入到目标地址)。
exp
1 | from pwn import * |
源码
1 | // g++ -o pwn source.cpp -m32 |
Cat of magic🧙♀️
made by xiaoji233
根据题意,是一个 20*20 的迷宫,然后有10层。根据所给文件,我们可以得到要救出100次猫,在第一层下楼之后就会得到 flag。猫应该是在第 10 层的 18,18 的位置。
通过 dfs 算法把迷宫样貌跑出来,然后手走,走到第10层之后发现 18,18 的位置中被墙壁封锁了。但是给了个锤子,应该是要用锤子砸墙壁然后救到猫,但是每次都要走到第一层,时间上来不太够。于是考虑其它方法,观察到全局变量中有 RescueCat 变量,并且最后判断的时候只需要判断救出的猫是否 >=100 即可,并且移动选择器的算法中,在处理的时候会强制把当前位置变成’.’,因此拿了锤子直接在第一层开始砸墙,向上进行变量覆盖,覆盖任意一个字节或两个字节到 RescueCat 变量中再从第一层走出即可拿到 flag。
exp
dfs的python脚本:
1 | from pwn import * |
在这个脚本中,会记录地图一层的样貌,可以手走。得到手走的payload之后可以在line变量末尾添加,再以此为基础再进行dfs,多次来回之后可以得到地图全貌和路线。
地图
1 | #####################<#...............a##.#.######A##########...#......#.......######B####.#.#####C##aaaa.#c#..#.#...#.########.#.##.#.#.#.##.A.#.A.#..#.#.#.#.##.#.A.#.##.#.#.#.#.##.#######..#.#.#.#.##.........##.#.#.#.##.##########.#.#.#.##.#...#..#..A#.#.#.##.#.#.#.####.#b#.#.##.#.#.#..#...###.#.##.#.#.##.#.#.#.#.#.##...#.#..#.#.#...#.######.#.##.#.#.#.#.##..........#...#.#>##########################################<#............#...##.#.###.######.#.#.##...#.#.#.#..#.#.#.######.#.#.C.b#.#.#.##.........#..#.#.#.#########.#######.#.##...#.a#.........#.##.#.#.##.#########.##.#.#.##.#.........##.#.#..#.#.##########.#.##.#.#....B....##.#....#.#.#.#.###.##.#.####.#.#.#a#c#.##.#.AAb#.#.#.#a#.#.##.######.#.#.###.#.##........###.A...#.##.########>#######.##........#B........##########################################<.................########.##########.##..........#...#b#.########.#..#.#.#.#.##....a#.#..#.#.#.#.##.#####.#....#.#.#.##.#a..C.######.#.#.##.#####......#.#.#.##.C.#...######.#.#.##.#.#.#......#.A.#.##.#.#.#.####.#####.##.#.#.#....#.#a....##.#.#.#.####.########.#.#.#....#B.....c##.#.#.####.#.########.#.#.#....#A.....c##.#.#.######.########.#...#.....A.....>##########################################<#a...............##.################.##..................##.###################.A...............b##.###################.C...............a##.###################.#.A.#.a.#.BA#.CB>##.Bc#ba.#.B.#Ac.#..##.###################.B..............aa##.###################.C.............bbb##.###################.AA............bbb##.###################.BBB.............c##########################################<#................##.#.##############.##.#..............#.##.##############.#.##.#..............#.##.#.##############.##.#..............#.##.##############.#.##.#ac#...#...#...#.##.##B..#...#...#.#.##.################.##..................##b###################.#...............>##.#C#################.#................##.################.##A.................##########################################<#.a..............##.#.##############.##...#...........b#.######A############.##................#.#################.#.##...Ca#c.......#.#.##.#.#a########A#.#.##.#.#.#........#.#.##.#.#.#.########.#.##.#.#.#.#.C....#.#.##.#.#.#.#>#.##.#.#.##.#.#.#A######.#.#.##.#.#..........#.#.##.#.############.#.##.#................##.###################.B...............c##########################################<................a##.################.##..................###################A##..................##.###################..................##.################.##.#aB.#c####>#...#.##.#.#.#..##.C#.#.#.##.#.#.#.####.#.#.#.##.#.#.#..##..#.#.#.##.#.#.##.##.##.#.#.##.#.#.#..##..#.#.#.##.#.#.#.####.#.#.#.##.#.#.#..##.b#.#.#.##.#.#.##.##.##.#.#.##...#....##....#.A.##########################################<#...............c##.#.#################..................#################C####c#..............#>##.#.############.#.##.#.#.........a#.#.##.#.#.##########.#.##.#.#..........#.#.##.#.##########.#.#.##.#.#..........#.#B##.#.#.##########.#.##.#.#.............C##.#.#.############.##.#.#.#...#...#b##.##.#.#.#.#.#.#.#.##.##.#.#.#.#.#.#.#.##.##.A.#...#...#.A.##a##########################################<#.......a........##.#C##############C##cc................####A#################>#.C.............b##.#.#################.#.#..............##.#.#.############.##.#.#.#..a#........##.#.#.#.#.#.####.#.##.#.#.#.#.#......#.##.#.#.#.#.#.####.#.##.#.#.#.#.#......#.##.#.#.#.#.######.#.##.#.#.#.#.#..A.#.#.##.#...#.#.#.c#...#.##.#####.#.########.##.C.....#.B........##########################################<.................####.##############C##...#....#...#...#.##.#...##...#...#b#.##.#.##############.##.#..............#.##.##############.#.##.#..............#.##.#.##############.##.#..............#.##.##############.#.##.#..............#.##.#.##############.##.#..............#.##.##############B#T##.#a.............####A###################...............c#-##################### |
WEB
ez_js🐦
Cat cat😼
Edited by lx56
信息收集与尝试
题目首页
进入题目首页可得以下界面
尝试点击绿色文字可以跳转到如下页面,可以猜测可能存在任意文件读取
尝试读取系统文件
检测是否能任意文件读取,读取/etc/passwd成功
读取源码
先读取cmdline获取源码文件名
通过../app.py读取源码
上图读出来的源码很乱,但由前面b开头可知这是python中的bytes类型
可以直接使用bytes的decode()方法获取格式化的源码,如下
1 | a = b'abc\nabc' |
获取源码如下
app.py
1 | import os |
代码审计
从源码可知Python3程序,使用了Flask框架
审计app.py
flag部分
首先关注含有flag的部分,以下代码可知程序一启动就读取并删除flag文件
1 | if os.path.isfile("/flag"): |
关注到admin路由可以获取flag,但是需要完成session伪造
需要伪造内容为{"admin" : 1}
的session,则需要获取secret key
1 |
|
secret key部分如下,是生成一个uuid然后去除-
再拼接*abcdefgh
组成的
1 | app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" |
文件读取部分
可以看到任意文件读取功能是info路由提供的,
注意到可控参数有三个,分别是file,start和end
还注意到其中有个cat函数
1 |
|
分析源码可知cat函数由cat.py提供
1 | from cat import cat |
审计cat.py
使用同样的方法读取cat.py的源码
1 | import os, sys, getopt |
文件读取功能
cat.py功能比较简单,整段源码最重要的部分如下
下面代码的作用是读取文件并以bytes返回,观察可知可以设定读取位置(start、end)
1 | def cat(filename, start=0, end=0)->bytes: |
使用方法
使用方法如下
例如新建一个a.txt,内容如下
1 | abcdefg |
使用app.py读取a.txt,从第1个位置开始到第3个位置
1 | python3 cat.py -s 1 -e 3 -f a.txt |
解题
这题的关键点就是伪造session,从而访问admin路由获取flag
但伪造session需要获取secret key
获取secret key
这里可以利用python存储对象的位置在堆上这个特性,
app是实例化的Flask对象,而secret key在app.config['SECRET_KEY']
,
所以可以通过读取/proc/self/mem来读取secret key
读取堆栈分布
由于/proc/self/mem内容较多而且存在不可读写部分,直接读取会导致程序崩溃,
所以先读取/proc/self/maps获取堆栈分布
1 | map_list = requests.get(url + f"info?file={bypass}/proc/self/maps") |
读取对应位置内存数据
然后读取/proc/self/mem,读取对应位置的内存数据,
再使用正则表达式查找内容
1 | res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}") |
伪造session
session伪造可以利用如下项目
1 | https://github.com/noraj/flask-session-cookie-manager |
一键获取flag
1 | # coding=utf-8 |
写在最后
提供docker-compose等文件:https://blog.lxscloud.top/static/post/CTF_Python_Flask/catcat/cat_cat.zip
若师傅想要直接复现可以到我的CTF平台:https://ctfm.lxscloud.top/category/test/challenge/13
同时提供exp文件下载(Python3.6以上):https://blog.lxscloud.top/static/post/CTF_Python_Flask/catcat/getFlag.py
ez_bypass🍥
简单的 bypass filter
ez_curl🔗
1 | POST / HTTP/1.1 |
两个trick
https://github.com/ljharb/qs/blob/7e937fafdf67330d54547bbd34909f1f0c11ed72/lib/parse.js
express的parameterLimit默认为1000,传1000个参数PHP中的admin=false会被忽略。
https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
根据rfc,header字段可以通过在每一行前面至少加一个SP或HT来扩展到多行。以此绕过对headers的过滤。
wife💃
简单的原型链污染,然后蹭了一下原神的热度。题目逻辑很简单,要邀请码才能注册为admin,普通用户只能拿到wife,没有flag。
当时题目是黑盒的条件,师傅们可以通过 fuzz 得到有用的信息,因为在后面题目还是 0 解的情况下我们放出了 hint:后端某处采用了 Object.assign()
这里我们放出源码:看一下注册的逻辑
1 | app.post('/register', (req, res) => { |
稍微搜一下 Object.assign 可以发现这个方法是可以触发原型链污染的,然后污染 __proto__.isAdmin
为 true 就可以了。
贴一个payload
1 | {"__proto__":{"isAdmin":true} |
不过admin虽然可以拿到flag,但是没有wife。:)
web_challenge💪
先注册并登录,是一道 XSS 打 Leak
漏洞点
exp.html 如下
1 | <script> |
webhook:https://webhook.site/
正确回显是 2,错误回显是 3,发送请求 ,这是错误的,回显为 3
1 | http://IP:port/exp.html?text=catctf{t1 |
正确的回显为 2
1 | http://IP:port/exp.html?text=catctf{tes |
本题的脚本若是用 python 写是无法实现的,需要用 js 或 ts 来编写,有兴趣可以联系 LemonPerfect 师傅(QQ:1476136743)
CRYPTO
DDH_Game
图片来自 A Graduate Course in Applied Cryptography(Version 0.5)
这道题显然是让我们求解椭圆曲线上的DDH问题(ECDDHP)。
解法一
由于题目中的BLS曲线是配对友好曲线,所以可以计算双线性对。
双线性对满足 $e(aG, bG) = e(G, abG)$
这就给了我们一个解DDHP的后门。因此如果随便选一个椭圆曲线点群,ECDDH假设通常是不成立的,并且攻击方法就很简单:看等式$e(aG, bG) = e(G, cG)$ 是否成立。
1 | # sagemath 9.5 |
解法二
其实是非预期解,不过测题的时候队里有其他师傅想到了,感觉这个思路也是直击DDH问题中的’Decisional’ 。
题目中点G的阶为
做法类似Pohlig-Hellman算法中使用的原理,不过我们不用算出a, b和c,而是在模3, 模11, 模10177等的意义下计算出a,b和c。再对应考察同余式是否成立: $ab \equiv c \pmod{3}$ $ab \equiv c \pmod{11}, … $ 。如果成立那么大概率有$ab = c$。打印出来看看flag对不对就行了。
1 | # sagemath 9.5 |
动机
DDH假设是一个很重要的话题,Game则是密码理论中的security game。双线性对也是很重要的密码学工具。希望能够抛砖引玉,让大家对密码理论有更多的关注。
cat_theory
题解
根据交换图,两个明文先做加法再加密,其结果与先加密再做密文乘法相同。因此CatCrypto是一个同态加密,具有加法同态性。
其实这道题是Paillier-DJN算法,Paillier的一个变种。见Paillier半同态加密:原理、高效实现方法和应用
1 | from Crypto.Util.number import long_to_bytes |
动机
因为是Cat CTF,并且说出题可以不限于传统的CTF思路,所以想涉及一些猫论(category theory, 范畴论)。找了一些资料来看:Categorical & Diagrammatic methods In Cryptography and Communication 确实有一些人提出范畴论与密码学结合,想法很有意思(比如p35~36的内容)。但这些想法似乎还没有太多应用。并且我自己对范畴论了解也很有限,也没试过自己编造一个密码方案。一时间想不到怎么用这个出一道题。
正好想到Craig Gentry(第一个全同态方案的提出者)给出的用交换图概括同态加密,交换图可以算是范畴论的东西,并且在密码学中也还是经常能看到的。于是干脆来个Paillier-DJN同态加密方案(Paillier的改进方案),给一张交换图来提示同态性。
cat’s gift🎁
题解
跨年气氛题,想起Amann的Analysis上有一只猫猫,翻了一会儿找到一个与pi相关的公式。注意到flag示例中 CatCTF{apple} CatCTF{banana}是小写开头英文单词,都是食物,以及题目中提到这是一份礼物,因此提交的flag不是pi而是pie。
有四种解法
法一:直接猜结果是pie
法二:手算,写出arctanx的幂级数展开,然后把x=1带进去
法三:编程算近似值,然后猜
1
2
3
4
5
6
7
8def solve_gift(n=100):
ans = 0
for i in range(n):
ans += (-1)**i * 1/(2*i + 1)
return ans * 4
solve_gift(n=10000000)法四:问猫猫(根据题目描述的提示)
找到Amann的 Analysis I p389
后记
其实pi=pie也是一种数学文化,3.14那天有一些人会吃派庆祝,因此就把这个题放在了跟数学最相近的crypto里面。出题时感觉应该很自然能想到pie吧,没想到很多朋友因为没有get到所以没做出来…
忘记了这是一种小众文化,出题人随缘在此向各位深表歉意。
盖茨比&can you tell AES from blackbox?&sampl出题人前言
笔者本学期上了一门非常重要的课,密码分析学,学习了很多在ctf之外传统密码算法分析领域的知识;加之笔者本人是做侧信道分析的,所以本次出题,特意反常规的ctf crypto=数论+格子,三道题一道是披着工作模式皮的古典频率分析,一道是传统对称算法分析的经典场景,最后一道则是侧信道。
盖茨比
这题还是叫PCBC叭,题目名称出错了(笑)。
这个题其实大部分都是非预期,但大方向思路不错,毕竟mtp也是大方向上属于利用了自然语言的特征,跟古典的频率分析在一个角度上。
本题使用的工作模式是PCBC,因为现在已经没有哪里在用了所以也没在pycrypto里找到,只能自己写,所以,大家自己逆这个过程就会发现一个解密之后的异或结构,进而利用mtp还原;另一种思路是直接爆破iv,但有一说一,复杂度应该很高,希望大家没有爆太久【笑】,对此方法有个小后门,就是padding之后末尾的填充值都固定,那么确定其中一部分,我们就可以确定末尾的大部分,进而降低爆破的复杂度。
摘抄两个解法的脚本如下:
mtp
1 | # Python3 |
爆破
看大部分人用的爆破脚本都是这个,但是只爆破了最后一位,蛮好奇前面这个魔数怎么得到的,或许是因为前边爆破太久了所以示意一下?hhhhhh,谁知道这个魔数iv怎么爆出来的欢迎cue我。
1 | c = b64decode(c) |
can you tell AES from blackbox?
本题原始出题背景是AES的中间相遇攻击和除了传统四种攻击模型之外的区分攻击模型,原初的情况如下:
$C_{11}^{(2)}$只与$t_{11}$及一些由密钥和明文中的常数字节决定的固定值有关,于是我们可以构造由a11到$ C_{11}^{(2)} $的映射表,可知该表只由c1和c5两个未知量确定,所以满足条件的映射表共有2^16种,而随机映射每次明文的某个字节都有2^8种映射可能,所以提供了4次机会,随机置换总共的可能性则为2^32种,于是可知,随机置换落入AES的可能性为1/256,可能性较小,所以可以作为一个区分指标。
但是作为传统密码分析,题目的信息量摆在这里,解不是唯一的,只要能解决,那就是找到了合理的特征。随机置换模型有一个问题,就是有可能导致某个字节变化后,其对应字节的值反而没变,而根据上式可知,这个映射应该是一个单射,所以实际我们使用的其实是另一个相关的密码算法,规避掉这个基本的随机特征(根据生日攻击,这个特征在给定模型是a11遍历256时,对于反向判断是更致命的)。
因为这个题目比较简单,所以最开始出题在最头部加入了一轮轮密钥加,并设计了相关的算法来恢复,但因为麻烦,而且打通率低于预期(其实后来找到了原因,但题目已经提交上去了,只能下次再说),所以最后题目降低难度,恢复原状,将首部的轮密钥加去掉,即只是将遍历下降到4次机会,这就导致了从a11到t11的过程是敌手可控的,那么如果我能控制t,就会发现当t只有最后一个字节变化时,只会在最后一次列混合造成扩散,前面的12字节都是相同的,那么这也是个可行的区分特征,但泛化性不如原思路,在头部加入轮密钥的时候就会失效。
此外还有师傅在上面的思路之外利用差分将第二轮的密钥影响消除掉,这个也是常见的trick,可以把上面的2^16的表进一步下降到2^8的水平。但这位师傅是在区分攻击之外更进一步,做起了密钥恢复,这二者虽然密不可分,但对于区分攻击而言增加了工作量。
我的脚本如下:
1 | from pwn import * |
因为出题的时候就评估到了,本题所提供的信息量就那些,收上来的wp这几种做法虽然有变换字节的位置和变换字节的环节不同,甚至最后确定的特征有区别,但都是下放到了合适的数据复杂度,所以只要是构造到具体字节从而降低复杂度进行攻击的方案都是预期的。如果是复现,还请各位师傅大胆尝试不同的位置,希望能帮到各位在传统对称密码算法分析的情景下找到新的兴趣点。
sample
论文是今年的Single-Trace Side-Channel Attacks on ω-Small Polynomial Sampling: With Applications to NTRU, NTRU Prime, and CRYSTALS-DILITHIUM,主要是分析在算法高斯采样预处理的过程中对三元多项式的直接攻击,除此之外在2018年的另一篇论文中也探讨了在揭秘环节直接攻击三元多项式的一些方案,但因为时间较为久远,对应的代码仓库没有了,猜测是那种实现最终被NTRU官方ban了,所以最后改换了这篇。
使用的仿真工具是GitHub - sca-research/GILES,相比于ELMO似乎更好上手些(他本身原来就叫ELMO2),但是没有合适的出题样例了,所以借这次写个完整的工具使用文档。
GILES处理的是可以直接移植到arm cortex M0机器上的指令,但同时因为他有仿真所用的部分函数,也不能直接交叉编译,所以我们还需要一个GitHub - sca-research/thumb-sim: Thumb Timing Simulator来生成编译文件。仿真器的函数头使用的是elmo-funcs.h download,主要包括触发器相关的功能和读写字节相关的功能,需要注意的是字节读写似乎需要对齐到某个值,不然无法读取。
在利用GILES生成数据的时候,有电源模式和汉明重两种预设模式,但实际体验下来电源模式的数据生成感觉比较草率,不如ELMO的数据看起来真实,所以我出题一般是使用hw模式。btw他也提供模式的自定义,想着如果有机会能不能搜罗一下其他的模式像是汉明距啥的,然后做下开源开发者这样。
产生的数据是trs格式,用python的trsfile库可以读取,主要包括数据的采集轨迹和对应的注释数据,整体结构还很清晰。
NTRU实现有很多种版本,各有细微差异还都是官方的。第三轮提交文档中主要是分为steam Prime还是LPRrime,但是官方的代码又分hps和hrss两套算法实现版本,对应的参数不太一致;除此之外之前的NTRU open project实现还有具体差异,但代码删档了,无从考证。这次出题涉及到sample主要是在hps中,hrss系列使用的采样不完全一致,先找了相对好实现的。
NTRU三元多项式采样的整体思想是,先采样大量的随机数,然后在随机数后附加2bit的三元信息(0,1,2,其中2代表-1),然后对数组执行一遍类似快排的算法(但也不全是,感觉有点小差异)。为保证交换不会产生明显的timing问题,并更加贴合硬件执行,交换的算法比较有意思:用了一个比较复杂的比较系统,然后利用异或的逻辑,如果不需要交换那么掩码为0,整体都异或0,于是不交换;而如果需要交换,则将掩码设为全1,这样就可以留下a^b的结果,每个值都异或他,自然就得到了交换的结果。这个写法规避了分支判断会带来的时序差异,但是由于这个掩码全0还是全1有明显的汉明重差异,所以可以猜测会有明显的实际电源信号差异,原作者的实验结果也如是。那么我们的目的就是发现并还原这一组交换信号,自然就知道排序后的私钥表达式如何了。
但实际遇到一些难采样的问题是,由于标准实现中这个置换是通过宏定义实现的,而这段代码又是一个纯数据流代码,没什么控制性的代码,所以其分界线很模糊,时间片也不稳定(因为程序调用中间时间差很多,最短最长能差出两倍)或许通过电源模型好找相关性,但是在汉明重模型下太容易出错了,而这个顺序序列对误报又很严苛,所以这么出题也太折磨选手了,所以最后将宏定义写成函数,利用函数调用是压栈跳转的控制流来做分界,方便些。
理清这个思路,并熟悉trsfile和plt之后,其实实际做题就很容易了:先扒参数(使用的是比较小的原始参数N=251,q=256,p=3,d=72),然后通过官方源码计算敏感交换部分执行了多少次,然后在trs中大胆猜测(指已知敏感信息是32和0,那么在示例中可以看看有什么情况,甚至说根据题目trs直接猜间隔,毕竟总有能肉眼分布的连续32的数据,来判断最小轮间隔)小心求证(既然知道这个间隔是函数调用控制流引起的,那么其他地方的函数调用会不会导致误报,是不是还有可以再区分的特征?),然后扒出来序列,塞进源码中魔改一下就直接正向还原了,也没有多少逆向要读懂的部分。
题目将上架攻防世界,不再赘述,可见附件。
wp代码:
1 | import numpy as np |
C 源码
1 | #include <stdio.h> |
然后按示例md5。
Reverse
CatFly🛫🐱
计划中是个难度中等偏下的送分RE题,但是解太少了
找到输出文字的地方
数据xor于sub_62B5
dword_E1E8 除了函数本身会修改,还有一处地方会修改
根据printf输出的字符数修改,测试可得 42 + log(count)
1 | #include<stdio.h> |
PS: 出题时循环次数为705980581,但是线性同余随机数算法出现了循环导致在100427942就出现了flag,若只考虑数组的最低字节,能在100001958得到flag
附:nyancat开源 https://github.com/klange/nyancat
ReadingSection
其实最早的预期解是硬读LLVM IR .txt,因为故意算法设计的很简单,也不是很长,熟悉LLVM IR的选手大概一个多小时可以做出来。后面发现此题可以有更简便的方法。
使用llvm-as可以反序列化IR到.bc文件
1 | PS D:\2022CTF\Myself\ReadingSection> llvm-as .\1.txt -o 1.bc |
会有一个报错,这个数组本来是存的flag,被我改掉了导致长度定义与声明不匹配。随便将其数组内容改为33长度即可。然后llvm-as可以成功编译为bc文件。而后用 clang -c将bc编译为.o对象
1 | PS D:\2022CTF\Myself\ReadingSection> llvm-as .\1.txt -o 1.bc |
编译成.o对象后,就可以用IDA打开了,打开后会发现check函数是被加了非标准平坦化的。
然后这就又要用到去混淆神器D810了。把D810插件打开,选用默认规则,再F5,混淆基本被去干净了
可以看到就是一个异或+TEA
1 |
|
StupidOrangeCat2
链接:https://pan.baidu.com/s/1nvAzABW9Fv34xe-WQwmoQw?pwd=21g7
提取码:21g7
--来自百度网盘超级会员V4的分享
The cat did it
设计思路和解法
这里先给师傅们磕头了
可能很多师傅没有看懂这个题目的意思,直接猜出了概率为0%(其实这是修改后的,修改前可能更看不懂 https://www.desmos.com/calculator/wzuskcjd34
本题是我在学习概率论时出的,不过这里作为签到题目减去了复杂的计算只需要稍微的分析一下就可以了。
这里“人”在正态分步的函数图像上行走,(这里是采用了中心极限定理,人数应该够了😶)而函数给到的定义域在`-200之间,猫猫的定义域包含正态函数的定义域的。
那么接下来就很简单了,有概率密度求在x在猫猫横坐标之外的概率。由于只给了+-200之间,之间的多余没有给,对立求一个大约的概率为
$$
P = \lfloor1 - (\Phi(\frac {200-b}a) - \Phi(\frac {-200-b}a)) \rfloor= \lfloor 1-(1-2\Phi(\frac {-200-b}a)) \rfloor=\lfloor2\Phi(\frac {-200-b}a)\rfloor
$$
可以得到概率为0%
猫猫镇楼
可能让师傅们做的云里雾里的,再次磕头了,整成烂活了。
其实这里面有BGM的
spaces%252FfgjZFp4NZA2UpGnWN2k8%252Fuploads%252FwjX2bQFO6aTRM30TAoPc%252Fmusic.mp4
BugCat
一、前言
这里给出的是一个预设解,这个解法可能不会是第一次分析该题就可以联想到的,不过却是解题最快的思路,由于详细的思路涉及到出题相关的细节,而且包含许多前置知识点,会在后期更新到如下专栏中,并给出从选手角度来说的预期攻击方案以及从出题人角度对题目的一些分析
二、解题方案
打开程序,被要求输入特定字符串,我们简单分析可得到如下信息
- 输入内容为**flag{xxxxxxxxxxxxxxxxxxxxxxxx}**,长度为30
- 代码内夹杂着大量花指令
- 代码注册了VEH异常回调,并在其中布置了4个硬件断点且会在特定情况下修改寄存器的值
- 主体代码没有花指令,非常清晰,是对flag括号内的内容进行hashcode,然后与内置值比较
我们首先分析主体代码,会发现如果按这个逻辑计算hashcode,必然存在多解,根据题目描述可知,算法存在BUG,那么注册的异常处理部分应该就是负责修复BUG的存在
这里我们附加程序,处理掉ZwSetInformationThread函数,然后修改ZwCreateThreadEx函数的Flags参数为2(跳过TLS回调),之后在主体代码int2d指令后的代码处下断点,观察调试寄存器,得到四个硬件断点的位置,之后分别在相应硬件断点所对应代码前后下断点,调试执行,根据前后寄存器的变化以及被下断点的代码的原始作用可以得出以下信息
- flag内的内容被四个一组进行hashcode的计算
- 乘子的值为257
- 每次hash异或的值会根据启用断点位置及数量的不同而变化,考虑到是CRC的校验值
- 正确的flag计算结果应该从内置的数组下标(从0开始)2处读取
对于寻找CRC校验,我们在可以GetModuleHandleA下断点,可以找到一处被加了大量花指令的代码,简单分析可以知道这里是一处crc校验用代码,这里直接重新载入程序,重设EIP到CRC校验处,在ntdll.LdrxCallInitRoutine的返回处下断点,即可得到正确的CRC校验值
至此,我们得到了解题所需要的所有数据,写个脚本爆破出flag即可
三、解题脚本
最终解题脚本如下,大约在2分钟内即可爆破出正确的flag
flag{bu9CAT+C@P0o-1S_s0-CUTe~}
1 | hashTable = [0x66BF1BAC, 0x473AC6FC, 0x4433C0D0, 0x289B6CEF, 0x71A8B6EC, 0x53775C73] |
MISC
catnim
nim博弈而已,有兴趣可以了解具体的博弈思路以及选择的思路,只需要确保我拿走的时候所有石头的异或和为0即可。
exp
1 | from pwn import * |
miao~
下载附件得到一个含有音频文件的图片
分离出来一个wav,通过改变频谱可以看到里面有一个CatCTF的字样,那么我们可以猜测这是一个密码
可以尝试关于wav的隐写,是deepsound,使用工具加载文件,提示需要输入密码,那么密码就是CatCTF
得到flag.txt文件,里面有喵呜的字样,猜测是兽语,搜索兽语翻译器兽音译者在线编码解码 - 兽音翻译咆哮体加密解密 (iiilab.com)
得到最后flag
CatCTF{d0_y0u_Hate_c4t_ba3k1ng ? M1ao~}
Peekaboo
躲猫猫,预期解是一个osint题目
通过附件发现水印,是一个qq空间,找到空间后发现有很多二维码(或者说汉信码)
扫一下发现唯一有用的线索:sina.兔*****
52258
搜索微博用户兔52258可以找到有关flag的信息
找到用户的王者账号,王者营地搜索对应账号得到本赛季只使用了一个英雄:百里玄策
CatCTF{bailixuance}
MeowMeow
010打开直接看答案
CatCTF{CAT_GOES_MEOW}
CatFlag
只有CatCTF才能出Cat都会拿的flag
cat flag
,就能通过猫猫拿到flag了
Windows下PowerShell也能通过猫猫拿flag
Windows下cmd可以打字出flag type flag
什么?你要通过记事本vim010Editor拿flag? 糊你猫脸.png
有部分选手先strings flag
,请不要这样做
PS1:出题原理
ANSI escape code
https://en.wikipedia.org/wiki/ANSI_escape_code
控制光标的移动,输出随机字符,输出的量足够多就会有动态效果
1 | FILE = open("flag",'wb') |
PS2:彩蛋
CatPaw
这是作为Misc的压轴题出的,难度较大(Lunatic)
分析文件,观察到是小米手机的备份文件,实际也是ANDROID BACKUP文件,去掉小米的header
https://github.com/nelenkov/android-backup-extractor 解压
分析apk ,可以看到输入的密码md5 hash是8b9b0ad9c324204fac87ae0fc2c630bd
分析lib函数,发现没有实际的调用,有函数 打开并读取文件 “/dev/input/event1” 到自己的目录下
分析 ef/1666666666 发现结构是24byte 对齐
提出所有数据,结合event struct结构体,大致猜测
53,54对应X,Y,58且值为1000为按下,提出所有的按键
这里有选手翻到了kernel底层源码解析event结构体
https://www.kernel.org/doc/Documentation/input/input.txt
输入密码时都会弹出安全键盘,因为是小米的备份文件,寻找小米安全键盘布局,能找到一个
1 | import struct |
发现会输入符号,大小写,安全键盘的符号布局怎么搞?
大小写可以观察CatCTF的输入,输入字母时Shift不会灭,有2位选手最后卡在了这里,
CatCTF{the_C4t_!5_REc0rdiN9_$cr34n_by_inPUt_evEN7}
没有分析清楚大小写规律,遗憾
1. 拥有小米手机,可以查看对应的符号布局
恰好有小米手机确实可以偷鸡
2.寻找小米安全键盘的符号布局
没有小米手机好像很难的样子,出题人没找到
3.合理猜测
符号p 出现次数很多,猜测 _
CatCTF{the_C4t_ 5_Rec0rdiN9_ cr33n_by_inPut_evEn7}
发现只差2个,爆破md5就能出了 或者根据语义推测出 ! $
CatCTF{the_C4t_!5_Rec0rdiN9_$cr33n_by_inPut_evEn7}
4.重放?
未曾设想的道路,写入到 /dev/input/event1 应该可行?
要求小米且同型号同屏幕大小手机,很难的辣,不过确实有选手尝试了,并且打出了CatC
然后提示invalid argument
猜测是一次数据太多,建议尝试下分段写入
(然后这名选手事后通过读取数据adb shell input重放了,也是厉害)
也有选手尝试在模拟器上重放,出现了错误,具体原因未验证
CatJump
前言
出题5天,被秒半小时,当时做环境的时候数据忘了清干净,导致被非预期了,原本挺有趣的题目没多少人玩到。 感兴趣的师傅后续也可以试试原有的解题思路,希望可以给各位有些帮助。
出题灵感来源
- 一直在玩黑苹果,虽然11代intel已经是黑果的绝唱了,但中间的一些思路倒是很有意思的。
- 安装黑果的重点就在于硬件能否被苹果系统识别,因此就有了本题,大致就是通过检测系统的硬件配置,实现一个简单的macOS检测机制。
- 选手需要通过oc或者其他方式修改nvram中的内容,让程序识别到该系统的某些配置。
正文
当时预设了2种解题方式
解题思路1
- 拿vmdk文件,提取其中的可执行文件进行分析,最终锁定openssl解密的部分,利用题目描述中的「cat_jump」进行解密获得flag。
解题思路2
拿到vmdk,进行仿真挂载,但发现该磁盘无法在ubuntu等预设中进行挂载,因为这里用的是Alpine Linux,并且是Alpine是UEFI,Vmware中一般都是bios引导启动,如果是vmware workstation的话需要在创建完毕后,高级中设置为UEFI启动,即可进入游戏。
进入游戏后,这里有个小脑洞,一般的游戏都会有「退出」模块,一般为「q」,但这里用的是「]」,退出后即可发现读取文件失败了。
读取的是nvram中的一个特定变量,如无法检测到这个变量的话,即使进了系统登录界面也无济于事。
通过opencore做一个引导驱动,在nvram中写入报错中的特定变量名和变量值,将该引导挂载到虚拟机中,我这直接用的U盘创了个ESP分区,把EFI丢进去了。
如果U盘是USB3.1的记得改下兼容性
启动到固件后,选择opencore启动,这样EFI引导就是走的opencore,模拟出的nvram也就能获取到。
最终进入启动界面即可获取flag。
解题思路3(非预期)
- strings cat_jump_clean.vmdk | grep CatCTF{
- 010编辑搜索
- 取证大师提取原始数据
- 记事本打开
- …..
总之就是磁盘数据没清干净,被一把梭了。
CatCat
rabbit解密,密钥在图片中使用strings搜索password即可找到
将解密结果采用base91解密,采用在线网站(https://ctf.bugku.com/tool/brainfuck)转换Ook,最后解密即可
CatCTF{Th1s_V3ry_cute_catcat!!!}
CatchCat
题目附件给出的是GPS的NMEA格式轨迹数据
最简单的方法就是NMEA转KML可视化还原GPS轨迹
https://www.h-schmidt.net/NMEA/
NMEA转KML之后再可视化就可以了
https://mygeodata.cloud/converter/
为了方便大家识别,这里大大缩短了flag的内容。
CatCTF{GPS_M1ao}
另外一种方法就是写脚本提取数据还原GPS轨迹,大家根据自己的喜好来解决就好啦。
1 | import matplotlib.pyplot as plt |
CatCTF{GPS_M1ao}
Nepnep 祝你新年快乐啦!
视频评论区置顶,视频最后一帧也有
H4ppy_n3w_y34r