Dragon Knight CTF官方wp
wwwww
题目复现网站
https://www.qsnctf.com/#/main/driving-range
Crypto
签到
1 | import gmpy2 |
Matrix_revenge
出这个revenge版主要是希望师傅们学到一般线性群的阶
4阶模p矩阵的阶为
然后就是和RSA一样的思路
求一个d,满足
exp.py
1 | from Crypto.Util.number import * |
EzDES
1 | from Crypto.Cipher import DES |
根据题目描述,从密钥入手,这道题主要想让师傅们了解到DES的弱密钥。
网上随便找个弱密钥就可以解了
exp.py
1 | from Crypto.Cipher import DES |
MidRSA
part1
由题意知
我们取两个不同的i,j
于是有
取两组这样的值,然后求公因数即可得到n
得到n之后右移300位即可得到前半部分的flag
part2
利用共模攻击的思路,设存在x,y,使得下式成立
我们便有
这里gcd(e1,e2) != 1
我们需要再进行一次共模攻击
假设存在s,t,使得下式成立
即
则有
exp.py
1 | from Crypto.Util.number import * |
Myencrypt
求解P
1 | def getMyPrime(): |
由题目可知,根据p,q的特殊性,我们可以把n写成关于r的式子,即
我们直接对n开13次方,得到的值记为temp,这个temp与实际上的r相差不大(这一点可以自己生成数据进行验证)。
在得到tmp之后,我们采取爆破的方式求解r,有了r就很容易求出p,q,然后再进行RSA求解得到LCG的模,也就是P
求解flag
上一步得到P之后,题目接下来的加密就是LCG的过程,只不过给出的状态值不全,而是模了2^{16}之后的值。此时我们需要进行推导
由
拆成高低位来写,则有
其中H,L代表当前状态的高低位,化简得
于是有
即
将常数记为B
写到这里是为了后面更好理解B_i如何计算
回到题目,简单来写就是
也就是
构造格
约后我们可以得到H_1,即可求得
再求解seed
exp.py
1 | from Crypto.Util.number import * |
希望师傅们能有所收获。另外夹带一点私货,师傅们可以关注下我的博客
https://dexterjie.github.io
PWN
ez_quiz
题目开了PIE和Canary保护:
先本地运行附件,观察逻辑,首先需要在2s内输入token:
分析代码,题目先将输入字符串在encode函数先异或0xff再进行base32加密处理,并将处理后的字符串和“XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC”比较:
此处可以打断点动态调试看内存的token,也可以直接用Cyberchef解密得到token:DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}
使用eval()计算式子:
根据token提示,存在格式化字符串漏洞:
pwndbg调试发现Canary 在 Stack 上的地址为第0xd位,将Stack第0xa位地址-0x2042得到基址
程序存在pop rdi
通过格式化字符串泄露Canary和PIE后可写rop链执行 system(“/bin/sh”);
1 | from pwn import * |
stack
其实就是一个栈迁移,但是栈迁移一次无法达到我们控制栈上栈顶数据的作用,需要栈迁移两次,再搞个ROP就行。或者ogg直接getshell
1 | def exploit(): |
**Canary **
fork函数存在,于是可以爆破cnanry,爆破完后写一个orw就行
1 | from pwn import * |
seccomp
flag文件是没有read权限的,srop要先调用chmod改flag文件权限,再orw输出flag文件内容
srop的frame直接写到bss段
payload2的作用是栈迁移到bss段启动srop
1 | # -*- coding=utf-8 -*- |
Resverse
elec_go
一个普通的electron程序
主程序都被封装到了resources/app.asar里
进入resources目录 解包
1 | PS D:\Electron\elec_go\elec_go-win32-x64\resources> asar extract .\app.asar ./tmp |
src里是主要程序 node_modules只是一些包
index.js 中是主程序,preload.js是浏览器预先加载的一些脚本,renderer.js是用于建立主程序与浏览器通信的设置
1 | Mode LastWriteTime Length Name |
第一种做法
如果纯逆的话,步骤如下
1.查看preload.js
2.用node运行下面的js脚本
1 | const escodegen=require("escodegen") |
输出结果如下
1 | () => { |
然后分析dll(根目录下的mydll.dll)
对应的Output函数
1 | // main.Output |
实际上是个固定输出的sha1,sha1的输入为”Dragon Knight”
输出即为0f0d105e8ec0eb28e43dfff700c32fe145949c5c(实际是个字节数组),同时通过slice取的其前16字节污染Object原型的toString元素
通过toString("never gonna give you up")
将被污染的0f0d105e8ec0eb28e43dfff700c32fe1
发送给index.js
再看index.js
base64语句实际上就是这个
1 | (event, arg) => { |
密文就是wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=
而key就是0f0d105e8ec0eb28e43dfff700c32fe1
加密方式AES-ECB-NoPadding
另一种做法
将app.asar的导出文件全部放在resources目录下的app目录(自己新建)
更改index.js
1 | ipcMain.on('k3y',(event,argk)=>{ |
也能达成直接爆flag的效果
flower_tea
考点:花指令去除,tea算法
观察main函数:
主函数的大概是这样。
如果要调试,要先把第一个函数nop掉(实际上并不用)
这里先看encode函数,点开后是爆红的,所以先解花指令
这个是一个简单的jmp花指令,把后面的jmp nop掉,然后可以看到第一部分。
这时最上面还是有标红
在汇编界面看看哪里还有花
这里有一个奇怪的call:
逻辑是:call完之后把ret的值+0xC然后返回
把这一部分按u解除,在加0xC后的位置再反编译
所以ret之后就会到pop的位置
把中间这一段全部nop,然后把整个函数u,然后c,再浏览一下函数,中间有一个怪jmp,删掉
然后再p,得到解完花的函数
1 | __int64 __fastcall encode(__int64 a1, __int64 a2) |
这个的特征很明显是xxtea,并且没有魔改,网上直接搜脚本
exp:(需要用clang)
(网上的脚本https://www.cnblogs.com/zpchcbd/p/15974293.html)
1 | #include <stdio.h> |
得到假flag:
1 | DRKCTF{Sorry.There_is_no_more_flower_tea.Please_try_again!!} |
很明显,这个不是真flag,这说明:
动态调试的时候和正常的时候运行的逻辑不一样
第一时间会想到这个可能是smc或者hook
所以先查看encode的交叉引用。
于是找到这个函数:
可以看到上层函数
修改了encode中的前几个字节用ret的方法返回到sub_140012A0中
这里的第一个是反调试,在x64下,调试标志位在PEB表偏移0x2的位置,通过获取gs寄存器找到peb表的位置:
readsqword(0x62)得到调试标志位并判断当前进程是否在调试
block是单纯地得到对应的两个函数地址
get_virtual_protect中,通过异或把virtualprotect函数名隐藏并通过搜索它在kernel32.dll中位置返回函数地址
通过上面的分析,可以得出我们需要查看sub_140012A0的内容,这里才是真正的加密函数
打开,还是花QAQ
汇编中,可以看到函数后段全是一个指令+一个jmp
由于汇编不是很好看,改成流程图看奇怪的地方。
可以猜测:如果一个地方有一块代码,并且有连续jmp,这里可能是人工加的花
那么就先看一下那个很远的环和上面一排没有入口的块
1
这里能看到push和pop,所以从push进入花,从pop离开花,可以看出可以这样还原
2
这里有一个call,尝试跟着call走,它下一步修改了返回地址,减去5A
即:140001460
转换一下这里的指令
所以把call nop了就好
3
nop之后
然后全部u,c,p还原函数
这里又可以看到上面的一个奇怪return,看汇编可以发现还是一个花
直接把call到retn去掉(除了shl)
这是一个变体tea,尝试化简这个函数
写脚本直接解
exp:
1 | #include<stdio.h> |
Debug
先是一个upx壳,用x64dbg自带的scylla工具脱一下
然后得到dump_SYC.exe拖进ida动态分析
在main函数开始之前藏了两个IsDebuggerPresent进行反调试
之后在函数的开始和结尾部分也利用了GetTickCount函数判断程序是否执行的很慢,如果程序执行的很慢则就是在调试,退出程序
题目通过读取当前目录下的myflag.txt中的flag来检验flag,过掉反调试动调到这里可以发现被解密后的 文件名称即myflag.txt
之后进入main2函数,函数利用了SMC,解密加密代码,再对读取的flag进行加密
这里也放有检测时间间隔的反调试函数。
一种方法是过掉所有反调试,动调到SMC后面即可看到正确的程序逻辑
但是也可以看明白汇编逻辑后,手动修改回来这段SMC代码贴回去
最后一个函数就是比较函数了
加密过程很简单,取出密文写出解密脚本即可
1 | enc = [0xCF, 0xD9, 0xC0, 0xC8, 0xDF, 0xCD, 0x0C, 0xD2, 0x43, 0x98, 0x10, 0xC0, 0x83, 0x43, 0x9A, 0x10, 0xCD, 0x42, 0x8C, 0x4A, 0x10, 0xC8, 0x82, 0x83, 0x4A, 0x9F, 0x8C, 0xDF, 0x98, 0x42, 0x8C, 0xDF, 0x84, 0x82, 0x83, 0x46, 0x52, 0x52, 0x52, 0x0E] |
flag: DRKCTF{Y0u_Kn0w_F1a9_Con9raTu1aTion5!!!}
一起做杯下午茶吧
题目考点
基础花指令,基础反调试,tea ,xtea,vm
题解
打开题目
运行exe
第一关
爆红 发现花指令(3处花)很简单的花 直接去
tab进去 发现这道题有两部分
先解第一部分,就是一个最基础的tea加密,但是长得有点丑了,改一下名称,
最终的比较数据
注意key的反调试相关操作
正常没有调试的时候,异或的是key0和key3
所以写脚本
1 | #include <stdio.h> |
第一部分密钥,这个会运用到第二部分的密钥
put_some_sugar!!
第二关
通过check找到最终的密文
进入加密的主要部分
进去看可以看出来是vm题型,有相关的函数和opcode
可以看出来规律 0xF开头的是定义的不同函数,根据vm做题经验,我们直接自动化把这个opcode一行一行列出来,然后就是翻译他每个函数的意义了
1 | unsigned char vm_code[] = { |
F0 中 不难看出有两种操作,一个是E0 一个是E1
先去分析这个vm的相关储存结构是怎么构成的
根据不同函数的共同参数a1,并且都在最后加了一定的数值,结合opcode,判断出a1就是栈指针eip
结合第一条机器码语句
0xf0,0xe0,0x05,0x4D,
这里选择了 f0 e0 然后一个小数5 一个大数0X4D,大胆猜测这个就是MOV指令,把0x4D的值存到了5寄存器中
谨慎验证 动调
v1[0]现在指向了F0 那么顺延下去,v1[1]就是E0 进行了类型的选择,v[2]v[3]进行了数据的储存,验证成功,去看别的函数
这个v2赋值的数据,我们步步跟进,可以分析出是存储寄存器的地址
这里存了0
我们直观去看opcode 不太容易分析出 我们的加密的数据到底在哪,这就涉及另一个函数了
在这个函数中涉及了新的数据(我重命名为data_address)我们跟进去看
竟然存着我们输入的数据sugar和flag[0](flag0是我们在外层函数中,发现了类似tea加密的数据循环存储形式,推断出他是进去加密的flag[0],flag[1]结构)
第三个unknown跟进,发现是114514,感觉是delta,
这个三个部分分别占据了data_address的1 2 3位,找一句执行0XF9的语句进行大胆分析
0xf9,0xe0,0x00,0x00,
选择e0方式,两个0,第一个0代表了寄存器型号的选择,第二个0代表了data_address中 三个地址的选择,这里明显就是选择了flag,然后将值存入了0寄存器中,
后面的eip[6]是干啥的,明显是以flag第一位为索引,去看具体取数组中的第几位,跟进上一个语句
0xf0,0xe0,0x06,0x01, mov了一个1进6寄存器,这里就是说取了flag+1指针所指向的值 即flag[1]
一些加减乘除位移异或的函数分析就不做具体分析了,jmp和cmp的汇编有经验的师傅们应该也很容易识别出
最后的函数命名如下
我们就可以依据这个写出汇编,思路其实就是一个XTEA的加密(看出XTEA因为其中有一句对于key取位的操作)
delta和密文密钥我们都有了,注意这里的循环次数进行了魔改是4D,原opcode的注释呈上
1 | unsigned char vm_code[] = { |
写出XTEA解密脚本
1 | #include <stdio.h> |
得到最终的flag
DRKCTF{Y0ur_t3a_te4sT_NoT_b4d!!}
Web
穿梭隐藏的密钥
源码发现路由
访问路由/c3s4f.php
参数爆破,得到参数shell
需要本地才能实现文件读取
开始ssrf伪造
但是过滤了gopher,127,@,0等,以下是正则
1 | '/ftp|ftps|gopher|telnet|dict|file|ldap|@|127|0|[|localhost|https/i' |
这里可以用302跳转或者域名解析IP绕过
1 | sudo.cc指向IP地址127.0.0.1。A记录就是域名指向ip地址,然后可以通过A记录转向访问 |
故构造如下:
1 | ?shell=http://sudo.cc/ |
发现回到了首页,跨越成功
但是要求是秘密只给127.0.0.1
猜测为secret.php(或者扫目录)
1 | ?shell=http://sudo.cc/secret.php |
拿到key ,这里的key值是DrKn的参数和cha11eng3.php路由
challenge1:
需要绕过file_get_contents()函数
用data://伪协议绕过
所以第一部分payload:
1 | /cha11eng3.php?DrKn=data://text/plain,MSIBLG |
challenge2:
关键代码如下
1 | hash("md4", $damei) == $damei |
传入的值被md4加密后跟原来的相等
利用php的松散性绕过,也就是0e
1 | 初始值: 0e001233333333333334557778889 |
php非法传参
在给参数传值时,如果参数名中存在非法字符,比如空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。
并且,在PHP8之前,如果参数中出现中括号[,那么中括号会被转换成下划线_,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
第二部分payload:
1 | /cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889 |
challenge3:
md5针对强类型逻辑比较绕过,同弱类型逻辑比较中利用php特性MD5处理数组默认返回Null进行绕过手法
(Null类型强(弱)等于Null类型)
第三部分payload:
1 | get传参: |
EzLogin
ctrl+U查看前端源码,发现有个/register.html,先进入/register.html
随便注册一个帐号进去,登录发现我不是admin,查看cookie:
TOKEN=65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7766513d3d
扔到cyberchef直接出明文:
修改is_admin为1,base64和hex加密后得到的
65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7866513d3d
打入token,刷新页面。
很明显的sql注入,注入点是用户名,不断修改账户名来尝试,在TOKEN里有一个小token,这里是md5加密后的username,所以写个脚本,这是加密函数:
1 | def encode(payload): |
根据注入正确和失败的两个回显,尝试布尔盲注。
exp:
1 | import base64 |
Ezsignin
瞎bb:超级简单的签到,看到 wp 人快碎了,一半非预期(
先扫目录或者试试敏感文件泄露,找到 index.php.bak,源码就不放这了
看到这里
需要本地,但是上面是存在一个变量覆盖函数的,所以可以直接覆盖这个变量
传入?_SERVER[REMOTE_ADDR]=127.0.0.1即可进入文件上传的界面,这边因为我的 upload.php没有做相同的处理,导致可以直接利用表单给 upload.php上传文件,没办法,人太懒了(((
后面就是上传一个 webshell,会发现不解析 php 文件,是因为我在 upload 下放了个.htaccess,我认为这很安全,这样你们上传的 php 就不会被解析了[doge]
由于我没有对文件上传做任何限制,所以你可以上传一个.htaccess文件来打开 php 的解析引擎,这样你的 webshell 就可以正常解析了。
非预期做法:
真是漏网之鱼啊,这里的 username可控,一点过滤没写,导致可以目录穿越写到 html 下面,就不受upload下的.htaccess的限制了,果然懒狗开发确实该死(bushi
MISC
签到
将浅色背景切换为深色二维码就显现了,扫码就行了
神秘的文字
直接搜这段,是可以找到相关文章,但是复现发现是不行的
深入搜索可以找到Martin Kleppe项目,aem1k.com/transliterate.js,发现其实就是jsfuck的变种,理解了原理,找到缺少内容alert(‘’);拼成完整的密文就可以弹窗得到密钥了
DNA-5
1 | ata = |
AgedSLATE
相信那你在查看文档时也找到了名字好像很可疑,这就是第一段flag,对应的hint是:注意隐私保护,很多出题人出题时是没在意名字问题的等于自己把自己开了,本意是借此提醒
接着分析doc会发现大量的01,很明显可以猜测为文档隐藏了内容全选后换个字体颜色就行了,,这段01猜测为01 to img,写个脚本就可以了(帮你们试过了大部分的工具出来的都是重叠,还是老老实实写脚本吧。如果你觉得文本量大不好处理,那为什么不试试word的替换功能呢)
转成的图片再识图,可以找到海嗣文字,需要反着读镜像后对照即可。“?”为“_“最开始图片上有明确展示
拼接两段得到flag
Steal_Data
这个题其实就是一个自制的免杀马(但是其实什么也过不了)
然后回到题目本身
打开流量包,很多杂七杂八的包,看一下协议分级
可以看到有HTTP,那就看http
已经看到shell了,那就直接追踪http流
参数有一个show_source,猜测返回的代码是shell的源码,复制出来去前端看
很简单的逻辑,然后再解开请求包
1 | echo openssl_decrypt('q0c8bvkt+9/LeMRp7RaaDA==' , "AES-128-ECB", 'd0c3a4017c22f6c3'); |
再一个个地解开,看它的结果
执行的命令
1 | whoami |
可以看到核心是后面的py
然后搜一下51建模B题
可以发现是一个求最短路径的题目,于是猜测给出的数组是路径图
用networkx库求最短路径
1 | import networkx as nx |
flag{wish_DrAGonKNI9HtCXF-BET2eR}
func_Pixels
这个题目其实有一点点脑洞,给各位师傅道个歉,
描述中的(0,0)提出来转为ascii就是DRK,
但是后面那个hint给的其实很明显,应该都能想到就是green通道的位置是计数器的二次方,那么blue通道就是计数器的三次方,red通道就是计数器的一次方,看一下加密代码更清楚
1 | def func_encode(img ,flag): |
根据加密函数写一下就完事
1 | def func_decode(img): |
有一点脑洞,给各位👴跪了
Osint
给大家道个歉吧,这题本来是不好做的,某些原因导致可以直接识图找到了
Ai 识别发现并不在国内,在美国
搜索美国标志性摩天轮一个个排查就可以找到了,因为角度比较刁钻看不到海岸线的特征会 有很多的误导项,找到最著名的几个可以佛罗里达州,直接搜佛罗里达州摩天轮就行了
朋友卡的非常好,但凡漏出红色缆车都会被秒