XCTF攻防世界Misc难度四所有整合wp
本文最后更新于75 天前,其中的信息可能已经过时,如有错误请发送邮件到1416359402@qq.com

前言:

把攻防世界难度四全部整合了,有点难度,还好,脑洞题,有些挺简单的

作者-叁玖

1.Cat_Jump

010搜索CatCTF{

就行

CatCTF{EFI_1sv3ry_funn9}

2.miao~

图片进行foremost 可以得到音频

Audacity 查看频谱图

可以发现CatCTF 这个应该是一个密钥

用DeepSound隐写

兽音解密

CatCTF{d0_y0u_Hate_c4t_ba3k1ng_?_M1ao~}

3.cantnim

经典的 Nim 游戏

有多堆石子,玩家轮流从任意一堆中取出任意数量的石子(至少一个)
无法取石子的玩家输
先手玩家有 50 秒时间限制,必须通过多轮游戏获胜才能获得 flag
关键:当且仅当所有堆石子数的 **异或和(Nim-sum)不为 0** 时,先手有必胜策略
必胜策略:每次移动后使所有堆石子数的异或和变为 0
操作方法:计算当前异或和 `nim_sum`,找到一堆石子 `piles[i]` 使得 `piles[i] ^ nim_sum < piles[i]`,从该堆取走 `piles[i] - (piles[i] ^ nim_sum)` 个石子

解题步骤

连接服务器:选择正式游戏(输入 `N`)
数据解析:服务器返回格式为 `Now pile:` 后跟一长串数字,表示各堆石子数
自动交互:编写 Python 脚本实现:
- 解析石子堆数据(注意:服务器使用 **0-based 索引**)
- 计算异或和并执行必胜策略
- 依次发送 `堆索引` 和 `取石数量`
重复游戏:持续进行直到获胜

py3脚本呈现

from pwn import *
import re

def solve():
    # 连接服务器
    host = '61.147.171.35'
    port = 57796
    conn = remote(host, port)

    # 选择正式游戏(不要试玩)
    conn.recvuntil(b'(Y/N)?')
    conn.sendline(b'N')

    game_round = 1

    try:
        while True:
            # 接收数据直到"where:"
            data = conn.recvuntil(b'where:').decode()

            # 打印调试信息(前几轮)
            if game_round <= 5:
                print(f"n=== Round {game_round} ===")
                print(f"Received data:n{data[-500:]}")

            # 检查是否获胜或失败
            if 'You win' in data or 'You lose' in data:
                print(f"nGame Over: {'Win' if 'You win' in data else 'Lose'}")
                print("Full data:", data)
                break

            # 查找石子堆信息
            # 可能有多行,找到最后出现的"Now pile:"
            now_pile_index = data.rfind('Now pile:')
            if now_pile_index == -1:
                print("Error: 'Now pile:' not found")
                print("Data received:", data)
                break

            # 提取石子堆行
            pile_line = data[now_pile_index:]
            # 可能包含换行,取第一行
            if 'n' in pile_line:
                pile_line = pile_line.split('n')[0]

            # 提取数字
            numbers = re.findall(r'bd+b', pile_line)
            # 跳过"Now pile:"这个词本身
            if numbers and numbers[0].isdigit():
                piles = list(map(int, numbers))
            else:
                print("Error: No numbers found in pile line")
                print("Pile line:", pile_line)
                break

            # 打印当前石子堆信息
            print(f"Round {game_round}: Found {len(piles)} piles")
            if len(piles) <= 10:
                print(f"Piles: {piles}")
            else:
                print(f"First 5 piles: {piles[:5]}")

            # 计算Nim游戏的异或和
            xor_sum = 0
            for p in piles:
                xor_sum ^= p

            print(f"Nim xor sum: {xor_sum}")

            # 寻找必胜策略
            move_found = False
            pile_index = -1
            take_amount = 0

            if xor_sum != 0:
                # 有必胜策略,找到一堆使异或和变为0
                for i, pile in enumerate(piles):
                    if pile ^ xor_sum < pile:
                        pile_index = i  # 0-based索引
                        take_amount = pile - (pile ^ xor_sum)
                        move_found = True
                        break

            if not move_found:
                # 没有必胜策略(必败局面),从第一个非空堆取1个
                for i, pile in enumerate(piles):
                    if pile > 0:
                        pile_index = i
                        take_amount = 1
                        print(f"Warning: No winning move found, using fallback")
                        break

            if pile_index == -1:
                print("Error: No valid move found")
                break

            print(f"Move: Take {take_amount} from pile {pile_index}")

            # 发送堆索引(0-based)
            conn.sendline(str(pile_index).encode())

            # 接收"count:"提示
            count_prompt = conn.recvuntil(b'count:')

            # 发送取石数量
            conn.sendline(str(take_amount).encode())

            game_round += 1

            # 检查是否超过一定轮数(防止无限循环)
            if game_round > 1000:
                print("Warning: Exceeded 1000 rounds, stopping")
                break

    except EOFError:
        print("nConnection closed by server")
        # 尝试获取最后的数据
        try:
            final_data = conn.recvall(timeout=2).decode()
            print("Final data received:")
            print(final_data)

            # 检查是否有flag
            if 'flag{' in final_data.lower() or 'ctf{' in final_data.lower():
                print("n" + "="*50)
                print("FLAG FOUND!")
                print("="*50)
                # 尝试提取flag
                flag_match = re.search(r'(flag{[^}]+}|ctf{[^}]+})', final_data, re.IGNORECASE)
                if flag_match:
                    print(f"Flag: {flag_match.group(1)}")
                else:
                    print("Could not extract flag, here's the full data:")
                    print(final_data)
        except:
            pass
    except Exception as e:
        print(f"nError occurred: {e}")
        import traceback
        traceback.print_exc()
    finally:
        conn.close()

if __name__ == '__main__':
    solve()
cyberpeace{aca1afe82bab143749aac938be674d59}

4.Time_losing

主要看时间

将所有文件按数字排序(从0到46,因为文件名是0.txt到46.txt)
对于每个文件,计算修改时间与2033-05-18 11:33:20的时间差(秒)
将时间差(整数秒)转换为字符(ASCII)
合并所有字符得到flag,flag格式是XMan{}
  1. 获取文件夹中所有txt文件,按文件名数字排序
  2. 将参考时间设为2033-05-18 11:33:20
  3. 遍历每个文件,获取修改时间,计算与参考时间的时间差(秒)
  4. 将时间差转换为整数,然后转换为对应的ASCII字符
  5. 合并这些字符,最后用XMan{}包裹

py3脚本呈现

import os
import datetime

def extract_flag():
    txt_files = [f for f in os.listdir('.') if f.endswith('.txt')]

    def file_num(f):
        return int(f.split('.')[0])

    txt_files.sort(key=file_num)

    reference = datetime.datetime(2033, 5, 18, 11, 33, 20)

    flag_chars = []

    for filename in txt_files:
        mtime = os.path.getmtime(filename)
        file_dt = datetime.datetime.fromtimestamp(mtime)
        time_diff = (file_dt - reference).total_seconds()
        ascii_val = int(time_diff)
        flag_chars.append(chr(ascii_val))

    flag = ''.join(flag_chars)
    return flag

if __name__ == "__main__":
    result = extract_flag()
    print(f"XMan{{{result}}}")
XMan{seems_to_be_related_to_the_special_guests}

5.Keyword

需要用这个工具

livz/cloacked-pixel: LSB steganography and detection

图片给了密码

lsb隐写

python lsb.py extract 1.png 1.txt lovekfc
PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}

是这个密码

一个变种

py3脚本呈现

import string

def decode_nihilist_cipher():
    txt = 'PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}'
    table = 'LOVEKFCABDGHIJMNPQRSTUWXY'
    flag = ''

    for i in txt:
        if i in string.ascii_lowercase:
            # 在table的小写版本中查找索引
            index = table.lower().index(i)
            # 使用相同索引的ASCII小写字母替换
            flag += string.ascii_lowercase[index]
            continue
        if i in string.ascii_uppercase:
            # 在table的大写版本中查找索引
            index = table.upper().index(i)
            # 使用相同索引的ASCII大写字母替换
            flag += string.ascii_uppercase[index]
            continue
        # 非字母字符直接保留
        flag += i

    return flag

result = decode_nihilist_cipher()
print(f"解码结果: {result}")
QCTF{cCgeLdnrIBCX9G1g13KFfeLNsnMRdOwf}

6.picture2

010查看

看文件头发现是jpg图片

直接binwalk

发现是base64

kp应该是一个zip

直接解码为hex

全部小写

4b5003041400010000003930974c6ce31f7c5a0000004e00000004000000636f6465e3de81f00fae476784c1b681bf3afc01c7a52d0632a0ab47db36d5b3f8dd2aa67ac2fea101a8e74963cf8cc872dab122c4c7576e7ed664c4eef15cf6097921489fedfd7fac72291ea97b9ea28f6fed8cea744bbb0854f266ffc3504b01023f001400010000003930974c6ce31f7c5a0000004e000000040024000000000000002000000000000000636f64650a0020000000000001001800005bc38385dad3013c8009ad87dad3013c8009ad87dad301504b05060000000001000100560000007c000000dc005b507974686f6e20322e375d0d0a3e3e3e20a87da87da87d0d0a0d0a54726163656261636b20286d6f737420726563656e742063616c6c206c617374293a0d0a202046696c6520223c70797368656c6c23303e222c206c696e6520312c20696e203c6d6f64756c653e0d0a20202020a87da87da87d0d0a5a65726f4469766973696f6e4572726f723a20a87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87da87d203c2d2070617373776f7264203b290d0a3e3e3e2000

010导入十六进制 改一下文件头

需要密码密码是这个报错提示

[Python 2.7]
>>> ▆▆▆

Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    ▆▆▆
ZeroDivisionError: ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ <- password ;)
>>> 

根据这个 Python 2.7 的交互式会话错误信息,这是一个 ZeroDivisionError(除零错误)。在 Python 中,当试图除以零时会引发此错误。

密码是:

integer division or modulo by zero

UUencode解码

CISCN{2388AF2893EB85EB1B439ABFF617319F}

7.test.pyc

一个pyc文件使用 uncompyle6进行反编译

# 直接输出反编译后的代码到控制台
uncompyle6 1.pyc

# 将反编译结果保存到 .py 文件
uncompyle6 1.pyc > 2.py

2.py

# uncompyle6 version 3.9.2
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]
# Embedded file name: test.py
# Compiled at: 2016-10-19 09:08:27

-- Stacks of completed symbols:
START ::= |- stmts . 
and ::= expr . JUMP_IF_FALSE_OR_POP expr COME_FROM
and ::= expr . jmp_false expr e_come_from_opt
and ::= expr . jmp_false expr come_from_opt
assert ::= assert_expr . jmp_true LOAD_ASSERT RAISE_VARARGS_1
assert2 ::= assert_expr . jmp_true LOAD_ASSERT expr CALL_FUNCTION_1 RAISE_VARARGS_1
assert_expr ::= expr . 
assert_expr_and ::= assert_expr . jmp_false expr
assert_expr_or ::= assert_expr . jmp_true expr
assign ::= expr . DUP_TOP designList
assign ::= expr . store
assign2 ::= expr . expr ROT_TWO store store
assign3 ::= expr . expr expr ROT_THREE ROT_TWO store store store
aug_assign1 ::= expr . expr inplace_op ROT_FOUR STORE_SLICE+3
aug_assign1 ::= expr . expr inplace_op ROT_THREE STORE_SLICE+1
aug_assign1 ::= expr . expr inplace_op ROT_THREE STORE_SLICE+2
aug_assign1 ::= expr . expr inplace_op ROT_THREE STORE_SUBSCR
aug_assign1 ::= expr . expr inplace_op ROT_TWO STORE_SLICE+0
aug_assign1 ::= expr . expr inplace_op store
aug_assign2 ::= expr . DUP_TOP LOAD_ATTR expr inplace_op ROT_TWO STORE_ATTR
bin_op ::= expr . expr binary_operator
buildclass ::= LOAD_CONST . expr mkfunc CALL_FUNCTION_0 BUILD_CLASS
call ::= expr . CALL_FUNCTION_0
call_stmt ::= expr . POP_TOP
classdefdeco1 ::= expr . classdefdeco1 CALL_FUNCTION_1
classdefdeco1 ::= expr . classdefdeco2 CALL_FUNCTION_1
compare_chained ::= expr . compared_chained_middle ROT_TWO POP_TOP e__come_froms
compare_chained ::= expr . compared_chained_middle ROT_TWO POP_TOP _come_froms
compare_single ::= expr . expr COMPARE_OP
delete ::= expr . DELETE_ATTR
delete_subscript ::= expr . expr DELETE_SUBSCR
expr ::= LOAD_CONST . 
expr_jitop ::= expr . JUMP_IF_TRUE_OR_POP
expr_jt ::= expr . jmp_true
if_exp ::= expr . jmp_false expr JUMP_ABSOLUTE expr
if_exp ::= expr . jmp_false expr JUMP_FORWARD expr COME_FROM
if_exp_lambda ::= expr . jmp_false expr return_if_lambda return_stmt_lambda LAMBDA_MARKER
if_exp_not ::= expr . jmp_true expr _jump expr COME_FROM
if_exp_not_lambda ::= expr . jmp_true expr return_if_lambda return_stmt_lambda LAMBDA_MARKER
if_exp_true ::= expr . JUMP_FORWARD expr COME_FROM
import ::= LOAD_CONST . LOAD_CONST alias
import_from ::= LOAD_CONST . LOAD_CONST IMPORT_NAME importlist POP_TOP
import_from_star ::= LOAD_CONST . LOAD_CONST IMPORT_NAME IMPORT_STAR
importmultiple ::= LOAD_CONST . LOAD_CONST alias imports_cont
mkfuncdeco ::= expr . mkfuncdeco CALL_FUNCTION_1
mkfuncdeco ::= expr . mkfuncdeco0 CALL_FUNCTION_1
print_items_nl_stmt ::= expr . PRINT_ITEM e_print_items_opt PRINT_NEWLINE_CONT
print_items_nl_stmt ::= expr . PRINT_ITEM print_items_opt PRINT_NEWLINE_CONT
print_items_stmt ::= expr . PRINT_ITEM e_print_items_opt
print_items_stmt ::= expr . PRINT_ITEM print_items_opt
print_nl_to ::= expr . PRINT_NEWLINE_TO
print_to ::= expr . print_to_items POP_TOP
print_to_nl ::= expr . print_to_items PRINT_NEWLINE_TO
raise_stmt1 ::= expr . RAISE_VARARGS_1
raise_stmt2 ::= expr . expr RAISE_VARARGS_2
raise_stmt3 ::= expr . expr expr RAISE_VARARGS_3
ret_and ::= expr . JUMP_IF_FALSE_OR_POP return_expr_or_cond COME_FROM
ret_or ::= expr . JUMP_IF_TRUE_OR_POP return_expr_or_cond COME_FROM
return ::= return_expr . RETURN_VALUE
return_expr ::= expr . 
return_expr_lambda ::= return_expr . RETURN_VALUE_LAMBDA
return_expr_lambda ::= return_expr . RETURN_VALUE_LAMBDA LAMBDA_MARKER
slice0 ::= expr . DUP_TOP SLICE+0
slice0 ::= expr . SLICE+0
slice1 ::= expr . expr DUP_TOPX_2 SLICE+1
slice1 ::= expr . expr SLICE+1
slice2 ::= expr . expr DUP_TOPX_2 SLICE+2
slice2 ::= expr . expr SLICE+2
slice3 ::= expr . expr expr DUP_TOPX_3 SLICE+3
slice3 ::= expr . expr expr SLICE+3
subscript ::= expr . expr BINARY_SUBSCR
subscript2 ::= expr . expr DUP_TOPX_2 BINARY_SUBSCR
testfalse ::= expr . jmp_false
testtrue ::= expr . jmp_true
unary_convert ::= expr . UNARY_CONVERT
unary_not ::= expr . UNARY_NOT
unary_op ::= expr . unary_operator
with ::= expr . SETUP_WITH POP_TOP e_suite_stmts_opt POP_BLOCK LOAD_CONST COME_FROM_WITH WITH_CLEANUP END_FINALLY
with ::= expr . SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK LOAD_CONST COME_FROM_WITH WITH_CLEANUP END_FINALLY
with_as ::= expr . SETUP_WITH store e_suite_stmts_opt POP_BLOCK LOAD_CONST COME_FROM_WITH WITH_CLEANUP END_FINALLY
with_as ::= expr . SETUP_WITH store suite_stmts_opt POP_BLOCK LOAD_CONST COME_FROM_WITH WITH_CLEANUP END_FINALLY
yield ::= expr . YIELD_VALUE
Instruction context:

 L.   1         0  LOAD_CONST               '=cWbihGfyMzNllzZ'
->                 3  NOP              
                   4  NOP              
                   5  NOP              
                   6  LOAD_CONST               '0cjZzMW'
                   9  LOAD_CONST               'N5cTM4Y'
                  12  LOAD_CONST               'jYygTOy'
                  15  LOAD_CONST               'cmNycWNyYmM1Ujf'
                  18  BINARY_ADD       
                  19  STORE_NAME            0  'str'

从反编译的字节码中提取所有LOAD_CONST加载的字符串常量,这些常量会通过BINARY_ADD进行拼接,最终存储到str变量中。我们只需要提取这些字符串并按顺序拼接

# 提取字节码中的LOAD_CONST字符串常量
const_strs = [
    '=cWbihGfyMzNllzZ',
    '0cjZzMW',
    'N5cTM4Y',
    'jYygTOy',
    'cmNycWNyYmM1Ujf'
]

# 拼接所有常量(字节码中BINARY_ADD会按栈顺序拼接)
flag_content = ''.join(const_strs)

flag = f'flag{{{flag_content}}}'
print(flag)
=cWbihGfyMzNllzZ0cjZzMWN5cTM4YjYygTOycmNycWNyYmM1Ujf
#反转
fjU1MmYyNWcyNmcyOTgyYjY4MTc5NWMzZjc0ZzllNzMyfGhibWc=
#base64解码
~552f25g26g2982b681795c3f74g9e732|hbmg
#反转
gmbh|237e9g47f3c597186b2892g62g52f255~

我们发现gmbh|237e9g47f3c597186b2892g62g52f255

gmbh就是flag 相差1 直接凯撒

flag{237d9f47e3b597186a2892f62f52e255}

发现不对

发现是ASCII减一 ,如4 变成3 b变成a这个样

py3脚本呈现

s = "gmbh|237e9g47f3c597186b2892g62g52f255"
new_s = ''.join([chr(ord(c) - 1) for c in s])
flag = f"{{{new_s}}}"
print(flag)
flag{126d8f36e2b486075a1781f51f41e144}

8.Avatar

outguess 隐写先下载

outguess -r 035bfaa85410429495786d8ea6ecd296.jpg -t flag.txt

flag

We should blow up the bridge at midnight

9.qr-easy

一个二维码

需要修复

需要单独提取出来ps但是不会ps 所以看wp了

文章地址:【愚公系列】2022年02月 攻防世界-进阶题-MISC-83(qr-easy)_攻防世界qr-easy-CSDN博客

此二维码的大小为 29×29,版本V的大小为N × N,N = 17 + 4V,所以这是版本 3。

二维码格式信息

该区域表示二维码的格式信息。实际上,格式信息是 15 位长,区域有最后 8 位。

搜索所有格式信息字符串的列表,我们可以发现类型信息位是001001110111110.所以这个二维码有ECC级别H和掩码模式1。

所有格式信息字符串的列表:

1 号掩码有公式(row) mod 2 == 0。注意行号是从0开始的,所以我们要切换坐标为0,2,4,…,28的行的位。

所以4行2列一组,原始的 D1-D26 是:

D1  = 0b11101100
D14 = 0b10000010
D2  = 0b11111000
D15 = 0b10010101
D3  = 0b00110110
D16 = 0b00111101
D4  = 0b01110110
D17 = 0b01100010
D5  = 0b00100010
D18 = 0b11101001
D6  = 0b11110001
D19 = 0b10100001
D7  = 0b00110111
D20 = 0b11100101
D8  = 0b01010010
D21 = 0b11010101
D9  = 0b00010111
D22 = 0b00101101
D10 = 0b11011110
D23 = 0b10010111
D11 = 0b01000100
D24 = 0b10001011
D12 = 0b01010100
D25 = 0b01111000
D13 = 0b11001101
D26 = 0b11000110

(行) mod 2 == 0 掩码之后

D1  = 0b00100000
D14 = 0b01001110
D2  = 0b00110100
D15 = 0b01011001
D3  = 0b11111010
D16 = 0b00001110
D4  = 0b01000101
D17 = 0b01010001
D5  = 0b00010001
D18 = 0b11011010
D6  = 0b00111101
D19 = 0b10010010
D7  = 0b00000100
D20 = 0b11010101
D8  = 0b10011110
D21 = 0b00011001
D9  = 0b11010100
D22 = 0b00010001
D10 = 0b00010100
D23 = 0b00001110
D11 = 0b11011101
D24 = 0b00010010
D12 = 0b11010010
D25 = 0b00011111
D13 = 0b01010100
D26 = 0b01000000

数据解码

解码有模式指示符:

0001:数字模式(每 3 位 10 位)
0010:字母数字模式(每 2 个字符 11 位)
0100:字节模式(每个字符 8 位)
1000:汉字模式(每个字符 13 位)
0111: ECI 模式

字符计数指示符跟在模式指示符之后。

版本 1-9
数字模式:10 位
字母数字模式:9位
字节模式:8位
汉字模式:8位
版本 10–26
数字模式:12位
字母数字模式:11 位
字节模式:16位
汉字模式:10位
版本 27–40
数字模式:14位
字母数字模式:13 位
字节模式:16位
汉字模式:12位

Ruby脚本

data = '00100000' 
       '00110100' 
       '11111010' 
       '01000101' 
       '00010001' 
       '00111101' 
       '00000100' 
       '10011110' 
       '11010100' 
       '00010100' 
       '11011101' 
       '11010010' 
       '01010100' 
       '01001110' 
       '01011001' 
       '00001110' 
       '01010001' 
       '11011010' 
       '10010010' 
       '11010101' 
       '00011001' 
       '00010001' 
       '00001110' 
       '00010010' 
       '00011111' 
       '01000000'
alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'.chars

def read(str, size)
  str.slice!(0, size)
end

def kanji(num)
  if num >= 0x1740
    (0xC140 + num / 0xC0 * 0x100 + num % 0xC0)
      .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
  else
    (0x8140 + num / 0xC0 * 0x100 + num % 0xC0)
      .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
  end
end

loop do
  case mode = read(data, 4)
  when '0010' # Alphanumeric
    count = read(data, 9).to_i(2)
    (count / 2).times do
      chunk = read(data, 11).to_i(2)
      print alphanumeric[chunk / 45] + alphanumeric[chunk % 45]
    end
    print alphanumeric[read(data, 11).to_i(2)] if count.odd?
  when '0100' # Byte
    count = read(data, 8).to_i(2)
    count.times do
      print read(data, 8).to_i(2).chr
    end
  when '1000' # Kanji
    count = read(data, 8).to_i(2)
    count.times do
      print kanji(read(data, 13).to_i(2))
    end
  when '0000' # Terminate
    break
  else
    fail "Unhandled mode #{mode}"
  end
end
SECCON{PSwIQ9d9GjKTdD8H}

10.message

考点基于文本隐写

7fffffffffffffffffffffffffffffffbffff0e10419dff07fdc3ffdaeab6deffdbfff6ffed7f7aef3febfffb7ff1bfbc675931e33c79fadfdebbae7aeddedb7dafef7dc37df7ef6dbed777beedbedb77b6de24718f260e0e71879fffffffffffffffffffffffffffffffffffffffffff07f87fc7f9ffffffffdbfbbfdbfeffffffffebfdffdfff7ffffff871c33e6fe7bffffffd5aefeed62dcffffffeadf9fb8bb0efffffff56df5db6dbf7ffffffaa0c21e19e3bffffe07ffffffffff9fffffffffffffffffffffffffffffffffffffff

将十六进制转换成二进制 长度为1679 二进制按照73*23排列

py3

with open('msg.txt', 'r') as f:
    hex_data = f.read().strip()

binary_str = bin(int(hex_data, 16))[2:].zfill(1679)

result_lines = []
for i in range(23):
    line = binary_str[i*73:(i+1)*73]
    result_lines.append(line)

with open('1.txt', 'w') as f:
    for line in result_lines:
        f.write(line + 'n')

缩小可以看到flag

RCTF{ArEciBo_mEsSaGe}

11.mysql

直接010看ibdata1

搜索flag

71e55075163d5c6410c0d9eae499c977

12.give_you_flag

看图片最后有一个二维码 用stegsolve工具进行动态图片分离

或者gifsplitter2.0分离

发现不完整

少了三个定位符加上就行

flag{e7d478cf6b915f50ab1277f78502a2c5}

13.stegano

提取 PDF 元数据,查看所有字段:

 Tm9wZSAsIG5vdCBoZXJlIDspCg==
 解码:Nope , not here ;)

直接提取pdf所有内容

py3脚本呈现

import pdfplumber

# -------------------------- 配置项 --------------------------
PDF_PATH = "d802bcf9530b45e0b37170c67b8efcea.pdf"  # PDF文件路径(与脚本同目录)
OUTPUT_TXT = "1.txt"  # 输出文本文件名
# -------------------------------------------------------------

def pdf_extract_text(pdf_path, output_txt):
    # 初始化文本内容
    all_text = ""

    try:
        # 打开PDF文件
        with pdfplumber.open(pdf_path) as pdf:
            # 遍历所有页面(pdf.pages是所有页面的列表)
            for page_num, page in enumerate(pdf.pages, start=1):
                print(f"正在提取第 {page_num} 页文字...")

                # 提取当前页文本(extract_text()返回字符串,自动处理换行)
                page_text = page.extract_text()

                # 若当前页有文本,添加到总文本中(加页分隔符便于阅读)
                if page_text:
                    all_text += f"===== 第 {page_num} 页 =====n"
                    all_text += page_text + "nn"

        # 将所有文本写入1.txt(UTF-8编码,避免中文乱码)
        with open(output_txt, "w", encoding="utf-8") as f:
            f.write(all_text)

        print(f"✅ 提取成功!所有文字已写入 {output_txt}")
        print(f"📄 共提取 {len(pdf.pages)} 页内容")

    except FileNotFoundError:
        print(f"❌ 错误:未找到PDF文件!请检查路径是否正确:{pdf_path}")
    except Exception as e:
        print(f"❌ 提取失败:{str(e)}")

# 执行提取
if __name__ == "__main__":
    pdf_extract_text(PDF_PATH, OUTPUT_TXT)
BBAAAABBBAAAAAAAAAAAAAABBAAABBBBAAAAABAAAABBBBBAAAAAABBBBABAAAABBBBBAAAABBBABBBAAABAABAABBAABBAAAABBBBAABBAAABABAABBAABAABBABBBBABAB

摩斯密码

把A换成 . ,B换成-

--....---..............--...----.....-....-----......----.-....-----....---.---...-..-..--..--....----..--...-.-..--..-..--.----.-.-

有问题这个题目

提取应该是

XXXX这个但是下面的BBA是不对的

查看wp

发现确实不一样所以这个题有问题的

-.-. — -. --. .-. .- - …- .-… .- - … — -. … --…-- …-. .-… .- --. —… .---- -. …- .---- … .---- -… .-… …-- – …-- … … …- --. …–
flag{1nv151bl3m3554g3}

14.坚持60s

jar进行解压发现没有什么东西

jd-gui进行java反编译

发现flag

flag{RGFqaURhbGlfSmlud2FuQ2hpamk=}

里面进行base64解码

flag{DajiDali_JinwanChiji}

15.gif

打开发现有很多图片白的黑的可以联想到0和1二进制

方法一:手动收入:01100110011011000110000101100111011110110100011001110101010011100101111101100111011010010100011001111101

flag{FuN_giF}

方法二:

脚本编写:

代码实现:

from PIL import Image

result = ""
# 遍历104张图片
for i in range(104):
    try:
        # 使用原始字符串处理路径,避免转义问题
        img = Image.open(r"C:Users14163Desktop张文攻防世界Misc难度四gifgif/{}.jpg".format(i))
        im = img.convert("RGB")
        # 获取(1,1)位置的RGB值
        r, g, b = im.getpixel((1, 1))
        # 根据红色通道值判断二进制位
        result += "1" if r != 255 else "0"
    except Exception as e:
        print(f"处理图片{i}.jpg时出错: {e}")
        continue

# 每8位一组转换为ASCII字符
for i in range(0, len(result), 8):
    byte = result[i:i+8]
    if len(byte) == 8:  # 确保有8位
        print(chr(int(byte, 2)), end="")
flag{FuN_giF}

16.掀桌子

c8e9aca0c6f2e5f3e8c4efe7a1a0d4e8e5a0e6ece1e7a0e9f3baa0e8eafae3f9e4eafae2eae4e3eaebfa
ebe3f5e7e9f3e4e3e8eaf9eaf3e2e4e6f2

看起来像十六进制
俩个一组,转化为十进制,减去128,再转字符串得到flag,有一点投机取巧。
代码:

str="c8e9aca0c6f2e5f3e8c4efe7a1a0d4e8e5a0e6ece1e7a0e9f3baa0e8eafae3f9e4eafae2eae4e3eaebfaebe3f5e7e9f3e4e3e8eaf9eaf3e2e4e6f2"
strlen=len(str)
print(strlen)
for i in range(0,118,2):
    byte=str[i:i+2]
    s=int(byte,16)-128
    print(chr(s),end="")
flag{hjzcydjzbjdcjkzkcugisdchjyjsbdfr}

17.双色块

可以看到

绿色和粉色的格子

绿色是0, 粉色是1

先将多个图像叠加在一起看看

from PIL import Image
import numpy as np
import os
from natsort import natsorted

dir_path = "gifframe"
file_names = natsorted(os.listdir(dir_path))
new_img_array = np.zeros((240, 240, 4), np.uint8)
for file_name in file_names:
    image = Image.open(os.path.join(dir_path, file_name))
    image_array = np.array(image)
    new_img_array += image_array
new_img = Image.fromarray(new_img_array)
new_img.show()
new_img.save("combined_image.png")

不像二维码,给他绿色0, 粉色是1 转成二进制

py3脚本呈现

from PIL import Image
import numpy as np
import os
from natsort import natsorted

dir_path = r"F:笔记练习靶场笔记攻防世界Misc难度四双色块gifframe"
file_names = natsorted(os.listdir(dir_path))[:576]
binary_576 = ""
row = 0
column = 0

for file_name in file_names:
    img = Image.open(os.path.join(dir_path, file_name))
    img_arr = np.array(img)[..., 0:3]
    binary_576 += "0" if img_arr[row*10, column*10][1] == 255 else "1"
    column += 1
    if column % 24 == 0:
        row += 1
        column = 0

print("576位二进制(24列×24行):")
for i in range(0, 576, 24):
    print(binary_576[i:i+24])

with open("576位二进制.txt", "w", encoding="utf-8") as f:
    for i in range(0, 576, 24):
        f.write(binary_576[i:i+24] + "n")

二进制转ASCII

o8DlxK+H8wsiXe/ERFpAMaBPiIcj1sHyGOMmQDkK+uXsVZgre5DSXw==hhhhhhhhhhhhhhhh

原GIF可以binwalk 如何foremost提取

发现密钥

ctfer2333

DES解密就行

flag{2ce3b416457d4380dc9a6149858f71db}

18.ewm

85张需要拼接二维码 纯难为人

按修改时间排序:这是关键,因为原脚本就是按修改时间排序的

14行6列网格:这是最重要的线索,原脚本使用14×6=84格,85张图片可能多一张

多种尝试方法

  • 方法1:严格按14×6网格,按修改时间排序(最可能正确)
  • 方法2:按相同尺寸分组排列
  • 方法3:暴力尝试不同的网格组合

py3脚本呈现

import os
import cv2
import numpy as np
from PIL import Image
import hashlib
import shutil
from datetime import datetime

def create_output_dir():
    output_dir = "output"
    if os.path.exists(output_dir):
        for filename in os.listdir(output_dir):
            file_path = os.path.join(output_dir, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception:
                pass
    else:
        os.makedirs(output_dir)
    return output_dir

def sort_by_modification_time(img_dir):
    files = []
    for filename in os.listdir(img_dir):
        if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
            filepath = os.path.join(img_dir, filename)
            mtime = os.path.getmtime(filepath)
            files.append((mtime, filename, filepath))

    files.sort(key=lambda x: x[0])
    return [filename for _, filename, _ in files]

def analyze_image_sizes(img_dir, image_names):
    sizes = {}
    size_groups = {}

    for filename in image_names[:20]:
        filepath = os.path.join(img_dir, filename)
        img = cv2.imread(filepath)
        if img is not None:
            h, w = img.shape[:2]
            size_key = f"{w}x{h}"
            sizes[filename] = (w, h)

            if size_key not in size_groups:
                size_groups[size_key] = []
            size_groups[size_key].append(filename)

    print("尺寸分组统计:")
    for size, files in size_groups.items():
        print(f"  尺寸 {size}: {len(files)} 张")

    return sizes, size_groups

def compose_by_time_sorted(img_dir, output_dir, rows=14, cols=6, cell_size=256):
    image_names = sort_by_modification_time(img_dir)

    print(f"按修改时间排序,共 {len(image_names)} 张图片")
    print(f"前10张图片: {image_names[:10]}")

    if len(image_names) != rows * cols:
        print(f"警告: 图片数量({len(image_names)})不等于网格数量({rows*cols})")

    total_width = cols * cell_size
    total_height = rows * cell_size

    result = Image.new('RGB', (total_width, total_height))

    idx = 0
    for y in range(rows):
        for x in range(cols):
            if idx >= len(image_names):
                break

            img_path = os.path.join(img_dir, image_names[idx])
            try:
                img = Image.open(img_path)
                img = img.resize((cell_size, cell_size), Image.Resampling.LANCZOS)
                result.paste(img, (x * cell_size, y * cell_size))
                idx += 1
            except Exception as e:
                print(f"处理图片 {image_names[idx]} 失败: {e}")
                idx += 1
                continue

    output_path = os.path.join(output_dir, f"time_sorted_{rows}x{cols}.png")
    result.save(output_path)
    print(f"保存到: {output_path}")
    return output_path

def compose_by_size_groups(img_dir, output_dir, size_groups):
    most_common_size = max(size_groups.items(), key=lambda x: len(x[1]))[0]
    print(f"n使用最常见尺寸: {most_common_size}")

    files_in_size = size_groups[most_common_size]

    cell_w, cell_h = map(int, most_common_size.split('x'))

    rows = 10
    cols = 9

    if len(files_in_size) < rows * cols:
        rows = int(np.sqrt(len(files_in_size)))
        cols = int(len(files_in_size) / rows)

    total_width = cols * cell_w
    total_height = rows * cell_h

    result = Image.new('RGB', (total_width, total_height))

    idx = 0
    for y in range(rows):
        for x in range(cols):
            if idx >= len(files_in_size):
                break

            img_path = os.path.join(img_dir, files_in_size[idx])
            try:
                img = Image.open(img_path)
                result.paste(img, (x * cell_w, y * cell_h))
                idx += 1
            except Exception as e:
                print(f"处理图片 {files_in_size[idx]} 失败: {e}")
                idx += 1
                continue

    output_path = os.path.join(output_dir, f"size_group_{most_common_size}.png")
    result.save(output_path)
    print(f"保存到: {output_path}")
    return output_path

def compose_qr_pattern(img_dir, output_dir, image_names):
    qr_version = 7
    qr_size = (qr_version * 4) + 17
    cell_size = 30

    total_size = qr_size * cell_size
    result = Image.new('RGB', (total_size, total_size))

    idx = 0
    for y in range(qr_size):
        for x in range(qr_size):
            if idx >= len(image_names):
                break

            img_path = os.path.join(img_dir, image_names[idx])
            try:
                img = Image.open(img_path)
                img = img.resize((cell_size, cell_size), Image.Resampling.LANCZOS)
                result.paste(img, (x * cell_size, y * cell_size))
                idx += 1
            except Exception:
                idx += 1
                continue

    output_path = os.path.join(output_dir, "qr_pattern_attempt.png")
    result.save(output_path)
    print(f"保存到: {output_path}")
    return output_path

def try_decode_qr(image_path):
    try:
        import pyzbar.pyzbar as pyzbar
        from PIL import Image

        img = Image.open(image_path)
        results = []

        methods = [
            ("原始", img.convert('L')),
            ("二值化", img.convert('L').point(lambda x: 0 if x < 128 else 255, '1')),
            ("高对比度", img.convert('L').point(lambda x: 0 if x < 100 else 255, '1')),
            ("低对比度", img.convert('L').point(lambda x: 0 if x < 150 else 255, '1')),
        ]

        for method_name, processed_img in methods:
            try:
                decoded = pyzbar.decode(processed_img)
                for d in decoded:
                    try:
                        data = d.data.decode('utf-8')
                        results.append((method_name, data))
                        print(f"  {method_name}: 解码成功 -> {data[:50]}...")
                        if 'flag' in data.lower() or '{' in data:
                            return data
                    except:
                        pass
            except:
                pass

        return None
    except ImportError:
        print("提示: 安装pyzbar库: pip install pyzbar")
        return None

def brute_force_grids(img_dir, output_dir, image_names):
    total_images = len(image_names)
    print(f"n尝试不同的网格组合 (共 {total_images} 张图片)")

    possible_grids = []

    for rows in range(5, 20):
        for cols in range(5, 20):
            if rows * cols >= total_images - 5 and rows * cols <= total_images + 5:
                possible_grids.append((rows, cols))

    possible_grids.sort(key=lambda x: abs(x[0]*x[1] - total_images))

    results = []

    for i, (rows, cols) in enumerate(possible_grids[:10]):
        print(f"n尝试 {i+1}: {rows}行 x {cols}列 = {rows*cols}格")

        cell_size = 256

        total_width = cols * cell_size
        total_height = rows * cell_size

        result = Image.new('RGB', (total_width, total_height))

        idx = 0
        for y in range(rows):
            for x in range(cols):
                if idx >= len(image_names):
                    break

                img_path = os.path.join(img_dir, image_names[idx])
                try:
                    img = Image.open(img_path)
                    img = img.resize((cell_size, cell_size), Image.Resampling.LANCZOS)
                    result.paste(img, (x * cell_size, y * cell_size))
                    idx += 1
                except Exception:
                    idx += 1
                    continue

        output_path = os.path.join(output_dir, f"grid_{rows}x{cols}.png")
        result.save(output_path)
        results.append(output_path)

    return results

def main():
    print("二维码拼图解题脚本")
    print("=" * 60)

    output_dir = create_output_dir()
    print(f"输出目录: {output_dir}")

    img_dir = "all"
    if not os.path.exists(img_dir):
        print(f"错误: 目录 '{img_dir}' 不存在")
        print("请将图片放在 'all' 目录下")
        return

    image_names = sort_by_modification_time(img_dir)
    print(f"找到 {len(image_names)} 张图片")

    if len(image_names) != 85:
        print(f"警告: 图片数量 {len(image_names)} 不等于预期的 85")

    sizes, size_groups = analyze_image_sizes(img_dir, image_names)

    print("n" + "=" * 60)
    print("方法1: 按修改时间排序 (14x6网格)")
    result1 = compose_by_time_sorted(img_dir, output_dir, rows=14, cols=6)

    print("n" + "=" * 60)
    print("方法2: 按相同尺寸分组")
    result2 = compose_by_size_groups(img_dir, output_dir, size_groups)

    print("n" + "=" * 60)
    print("方法3: 暴力尝试不同网格")
    results = brute_force_grids(img_dir, output_dir, image_names)

    print("n" + "=" * 60)
    print("尝试解码所有生成的图片...")

    all_results = [result1, result2] + results

    found_flag = False
    for result_file in all_results:
        print(f"n解码: {os.path.basename(result_file)}")
        flag = try_decode_qr(result_file)
        if flag:
            print(f"n🎉🎉🎉 找到flag: {flag} 🎉🎉🎉")
            print(f"图片文件: {result_file}")
            found_flag = True
            break

    if not found_flag:
        print("n自动解码未成功,请尝试:")
        print("1. 手动扫描 output 目录中的图片")
        print("2. 调整图片对比度后再扫描")
        print("3. 尝试不同的二维码扫描APP")

        print("n推荐尝试的图片:")
        print(f"  - {result1} (最有可能,14x6网格)")
        print(f"  - {result2} (相同尺寸分组)")

        for result_file in results[:3]:
            print(f"  - {result_file}")

if __name__ == "__main__":
    try:
        from PIL import Image
    except ImportError:
        print("请安装PIL库: pip install Pillow")
        exit(1)

    try:
        import numpy as np
    except ImportError:
        print("请安装numpy库: pip install numpy")
        exit(1)

    main()
flag{g00d_g00d_study_1jf8988}

19.Disk

使用AccessData FTK Imager 挂载镜像

看flag0.txt的文件列表有二进制直接flag0.txt-flag3.txt全部提取出来

0110011001101100011000010110011101
1110110011010001000100010100110101
1111001100010110111001011111010001
0000110001011100110110101101111101

二进制转ASCII

flag{4DS_1n_D1sk}

20.crc

检测一下是否是伪加密

发现是真加密

爆破也不行

题目名字叫CRC 所以是CRC爆破

工具下载:theonlypwner/crc32: CRC32 tools: reverse, undo/rewind, and calculate hashes

缩包里有 1.txt、2.txt、3.txt 这些小文件(大小都是 6 字节),适合 CRC 爆破,因为文件小,爆破速度快

2.txt BCEE7ED5
1.txt CC86365B
3.txt CCCA7E74
D:ToolMisc压缩包隐写crc32-0.1>python crc32.py reverse 0xBCEE7ED5

2.txt 91ctf 这个密码符合CTF命名规则 这个所以这个就是第二个密码

91ctf_

1.txt

forum_

3.txt

com_66

按照顺序拼接密码

forum_91ctf_com_66

二进制转ASCII

base64转图片

flag{owid0-o91hf-9iahg}

总结:

制作不易,累了

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇