吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 520|回复: 6
收起左侧

[CTF] 【2026春节】某CTF新手的屑write up(只有初级题)

[复制链接]
craftAlex 发表于 2026-3-4 23:17

【说明】 本人一直是ctf菜鸡,我的能力并不强,今年来做题练练手,这篇文章作为我本人的纪念和心得,求大佬们轻喷。由于时间问题没有在中级和高级题深入研究,这几天也在学习,感谢各位大佬能够分享思路!

解题领红包之二 {Windows 初级题}

这题是我目前见到过的打卡题目里面比较复杂的了,打开程序观察:
(这题走了弯路,我其实应该直接IDA拆的)

========================================
CrackMe Challenge v2.5 - 2026
========================================
Keywords: 52pojie, 2026, Happy new year
Hint: Fake flag; length is key
----------------------------------------

目前得到的信息: 几个关键词、假Flag、长度
根据简单题目的尿性,首先会有长度判断,然后才是验证逻辑,所以我先想到了动调直接绕过长度判定。在此之前试了一下52pojie2026Happynewyear发现有里程碑

[!] You're getting closer...

后面回看发现是假flag
首先查壳:*Unknown exe , EP : C7 05 .. , 09 sections [ Linker 2.36 ] [ CRC Set ] - GCC exeHeader Big sec. 01 [ .text ] ,基本判定为无壳GCC,同时exeinfo提示这是32位程序

直接动态调试搜字符串[!],搜出几个字符串(筛选flag、fl@g之类的发现无结果,跳过)

地址=010CD2C0 "52pojie2026Happy"
地址=010CD2D7 "\n[!] You're getting closer..."
地址=010CD30B "\n[!] Hint: The length is your first real challenge."
地址=010CD3A9 "\n[!] Checksum failed! Something is wrong..."
地址=010CD3C9 "[!] Expected: 44709, Got: "
地址=010CD3FC "\n[!] Nice try, but not quite right..."
地址=010CD415 "\n[X] Access Denied!"
地址=010CD435 "[X] Wrong password. Keep trying!"
地址=010CD450 "\n========================================"
地址=010CD470 "        *** SUCCESS! ***                "
地址=010CD489 "========================================"
地址=010CD4A2 "[+] Congratulations! You cracked it!"
地址=010CD4BB "[+] Correct flag: "

目前得到的信息: 可疑字符串52pojie2026Happy、存在长度判定、存在flag完整性检验
右键转到字符串,发现了长度判定的代码

010CD301 | E8 B23BF5FF              | call <JMP.&strlen>                      |
010CD306 | 83F8 1F                  | cmp eax,1F                              |
010CD309 | 74 44                    | je 0x010CD34F                           |

不难看出此处为长度判定,判断输入字符串长度是否为0x1F,如果是的话跳转,那么我们现在就知道了flag长度为31。
继续往下翻,翻到了判定和checksum验证:

010CD364 | E8 6743F3FF              | call 0x10016D0       |
010CD369 | 84C0                     | test al,al           |
010CD36B | 0F84 A4000000            | je 0x10CD415         | ->密码错误消息
…
010CD39C | 817D A0 A5AE0000         | cmp dword ptr ss:[ebp-60],AEA5 |
010CD3A3 | 0F84 A7000000            | je 0x10CD450                   | ->继续流程

目前得到的信息: flag长度31、某个checksum0xAEA5
查看了call的方法,发现全是一大堆运算,逐个分析太浪费时间,所以我们掏出IDA直接开拆。Shift+F12打开字符串面板,找到密码错误提示那里双击转到对应的部分,按X打开引用,找到对应引用部分按F5拆出伪代码:

But then, I had a great idea! I used F5! Using F5 gave me a whole new perspective and i was able to find out how to see more text!
——Kenadian(这段话是玩梗,与题目无关)

……
SetConsoleOutputCP(0xFDE9u);
sub_4C7E50((int)&dword_4D27C0, "========================================");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "   CrackMe Challenge v2.5 - 2026        ");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "========================================");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "Keywords: 52pojie, 2026, Happy new year");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "Hint: Fake flag; length is key");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "----------------------------------------");
((void (*)(void))sub_4015B0)();
v22 = &v24;
v23 = 0;
v24 = 0;
v16 = 1;
v13 = (int *)&v22;
sub_4C7E50((int)&dword_4D27C0, "\n[?] Enter the password: ");
v13 = (int *)&v22;
v2 = sub_401560();
v13 = (int *)&v22;
sub_4C5840(&dword_4D25E0, &v22, v2);
Str = v22;
v3 = sub_401740(v22);
v4 = 53;
v5 = v3;
v6 = 0;
if ( !v5 )
{
while ( Str[v6] == v4 )
{
if ( ++v6 == 16 )
{
v16 = 1;
sub_4C7E50((int)&dword_4D27C0, "\n[!] You're getting closer...");
v16 = 1;
goto LABEL_9;
}
v4 = byte_4D3032[v6];
}
if ( strlen(Str) != 31 )
{
v16 = 1;
sub_4C7E50((int)&dword_4D27C0, "\n[!] Hint: The length is your first real challenge.");
goto LABEL_9;
}
v16 = 1;
if ( (unsigned __int8)sub_4016D0(Str, 31) )
{
Str = 0;
v8 = *v22;
if ( !*v22 )
goto LABEL_16;
v9 = 0;
do
{
Str += ++v9 * v8;
v8 = v22[v9];
}
while ( v8 );
if ( Str != (char *)44709 )
{
LABEL_16:
v16 = 1;
sub_4C7E50((int)&dword_4D27C0, "\n[!] Checksum failed! Something is wrong...");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "[!] Expected: 44709, Got: ");
sub_47DE70(Str);
sub_4015B0(v10);
LABEL_17:
v16 = 1;
goto LABEL_10;
}
v16 = 1;
sub_4C7E50((int)&dword_4D27C0, "\n========================================");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "        *** SUCCESS! ***                ");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "========================================");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "[+] Congratulations! You cracked it!");
((void (*)(void))sub_4015B0)();
v11 = sub_4C7E50((int)&dword_4D27C0, "[+] Correct flag: ");
sub_4C4330(v11, v22, v23);
}
else
{
v16 = 1;
sub_4C7E50((int)&dword_4D27C0, "\n[X] Access Denied!");
((void (*)(void))sub_4015B0)();
sub_4C7E50((int)&dword_4D27C0, "[X] Wrong password. Keep trying!");
}
((void (*)(void))sub_4015B0)();
goto LABEL_17;
}
sub_4C7E50((int)&dword_4D27C0, "\n[!] Nice try, but not quite right...");
LABEL_9:
sub_4015B0(Time);
LABEL_10:
sub_4017C0();
sub_4B1AE0(v13);
sub_40C600(v15);
return 0;
}

不难发现这里的判断逻辑,如果你完全不懂的话也可以发给ai分析,现在的ai真的是越来越强了。
我们观察代码可以得到以下信息:

验证点 条件 提示信息
1 假Flag "You're getting closer..."
2 字符串总长度是否等于 31 "The length is your first real challenge"
3 sub_4016D0(Str, 31) 返回值是否为真 "Access Denied"
4 Checksum计算结果是否等于 44709 "Checksum failed"

Checksum算法:

v9 = 0;
do {
    Str += ++v9 * v8;  // 累加:位置×字符值
    v8 = v22[v9];
} while (v8);
                       // 最终 Str 应该等于 44709

即:

$$\sum_{i=1}^{n} (i \times ASCII(s[i])) = 44709$$

接下来我们双击sub_4016D0,转到判断函数内,再次F5,拆出伪代码:

bool __cdecl sub_4016D0(int a1, int a2)
{
  unsigned __int8 *Block; // ebp
  int v3; // eax
  int v4; // ebx
  bool v5; // dl

  Block = (unsigned __int8 *)sub_4CB710(0x64u); // 分配100字节内存
  sub_401620(Block);                            // 用正确密码填充Block
  if ( a2 <= 0 )
  {
    v4 = 0;
  }
  else
  {
    v3 = 0;
    v4 = 0;
    do
    {
      v5 = *(char *)(a1 + v3) == Block[v3];     // 逐字符比较
      ++v3;
      v4 += v5;                                 // 统计匹配字符数
    }
    while ( a2 != v3 );
  }
  j_j_free(Block);
  return a2 == v4;                              // 所有字符都匹配才返回true
}

那么接下来就很简单了,直接拆出sub_401620就是密码的生成逻辑:

_BYTE *__cdecl sub_401620(int a1)
{
  _BYTE *result; // eax

  *(_DWORD *)a1 = 758280311;
  *(_DWORD *)(a1 + 4) = 1663511336;
  *(_DWORD *)(a1 + 8) = 1880974179;
  *(_DWORD *)(a1 + 12) = 494170226;
  *(_DWORD *)(a1 + 16) = 842146570;
  *(_DWORD *)(a1 + 20) = 657202491;
  *(_DWORD *)(a1 + 24) = 658185525;
  *(_BYTE *)(a1 + 30) = 99;
  *(_WORD *)(a1 + 28) = 12323;
  result = (_BYTE *)a1;
  do
    *result++ ^= 0x42u;
  while ( result != (_BYTE *)(a1 + 31) );
  *(_BYTE *)(a1 + 31) = 0;
  return result;
}

这个函数生成的逻辑就是:写入DWORD → 异或0x42 → 得到密码
我们直接让ai帮我们写一个答案生成的脚本,然后运行一下就行了。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 原始DWORD/WORD/BYTE值
data = [
    758280311,    # DWORD 0-3
    1663511336,   # DWORD 4-7
    1880974179,   # DWORD 8-11
    494170226,    # DWORD 12-15
    842146570,    # DWORD 16-19
    657202491,    # DWORD 20-23
    658185525,    # DWORD 24-27
    12323,        # WORD  28-29
    99,           # BYTE  30
]

# 转换为字节流 (小端序)
bytes_list = []
for i, val in enumerate(data):
    if i < 7:  # DWORD (4字节)
        bytes_list.extend(val.to_bytes(4, 'little'))
    elif i == 7:  # WORD (2字节)
        bytes_list.extend(val.to_bytes(2, 'little'))
    else:  # BYTE (1字节)
        bytes_list.append(val)

print(f"原始字节长度: {len(bytes_list)}")

# XOR 0x42 解密
flag = ''.join(chr(b ^ 0x42) for b in bytes_list)

print(f"🚩 Flag: {flag}")
print(f"📏 长度: {len(flag)}")

# 验证Checksum
checksum = sum((i + 1) * ord(flag[i]) for i in range(len(flag)))
print(f"🔢 Checksum: {checksum} (期望: 44709)")

运行之后得到:

原始字节长度: 31
🚩 Flag: 52pojie!!!_2026_Happy_new_year!
📏 长度: 31
🔢 Checksum: 44709 (期望: 44709)

完美符合我们得出的状态和预想的结果,flag52pojie!!!_2026_Happy_new_year!

解题领红包之三 {Android 初级题}

这一题就是简单的华容道拼图,没想那么多,几步就做出来了。(图片字体看着奇怪是因为改了系统字体)

5b89f7f80c517e50436aa8562f120aa8.jpg

flag:flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}

解题领红包之四 {Windows 初级题}

这一题是简单的pyinstaller题目,看到图标一眼pyinstaller打包的,直接开拆。
刷到过的一篇很不错的教程:PyInstaller逆向——解包问题与工具使用
拆包工具:extremecoders-re/pyinstxtractor: PyInstaller Extractor
拆包后发现一个很显眼的入口点crackme_easy.pyc,uncompyle6开拆(python3.14,也可以用PyLingual在线拆):

# filename = 'crackme_easy.py'
# Bytecode_version = '3.14rc3 (3627)'

import hashlib
import base64
import sys
def xor_decrypt(data, key):
    """XOR解密"""
    result = bytearray()
    for i, byte in enumerate(data):
        result.append(byte ^ key ^ i & 255)
    return result.decode('utf-8', errors='ignore')
def get_encrypted_flag():
    """获取加密的flag"""
    enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'
    return base64.b64decode(enc_data)
def generate_flag():
    """动态生成flag"""
    encrypted = get_encrypted_flag()
    key = 78
    result = bytearray()
    for i, byte in enumerate(encrypted):
        result.append(byte ^ key)
    return result.decode('utf-8')
def calculate_checksum(s):
    """计算校验和"""
    total = 0
    for i, c in enumerate(s):
        total += ord(c) * (i + 1)
    return total
def hash_string(s):
    """计算字符串哈希"""
    return hashlib.sha256(s.encode()).hexdigest()
def verify_flag(user_input):
    """验证flag"""
    correct_flag = generate_flag()
    if len(user_input)!= len(correct_flag):
        return False
    else:
        for i in range(len(correct_flag)):
            if user_input[i]!= correct_flag[i]:
                return False
        return True
def fake_check_1(user_input):
    """假检查1"""
    fake_hash = 'a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890'
    return hash_string(user_input) == fake_hash
def fake_check_2(user_input):
    """假检查2"""
    fake_hash = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
    return hash_string(user_input) == fake_hash
def main():
    """主函数"""
    print('==================================================')
    print('   CrackMe Challenge - Python Edition')
    print('==================================================')
    print('Keywords: 52pojie, 2026, Happy New Year')
    print('Hint: Decompile me if you can!')
    print('--------------------------------------------------')
    user_input = input('\n[?] Enter the password: ').strip()
    if fake_check_1(user_input):
        print('\n[!] Nice try, but not quite right...')
        input('\nPress Enter to exit...')
        return None
    else:
        if fake_check_2(user_input):
            print('\n[!] You\'re getting closer...')
            input('\nPress Enter to exit...')
        else:
            if verify_flag(user_input):
                checksum = calculate_checksum(user_input)
                expected_checksum = calculate_checksum(generate_flag())
                if checksum == expected_checksum:
                    print('\n==================================================')
                    print('        *** SUCCESS! ***')
                    print('==================================================')
                    print('[+] Congratulations! You cracked it!')
                    print(f'[+] Correct flag: {user_input}')
                else:
                    print('\n[!] Checksum failed!')
            else:
                print('\n[X] Access Denied!')
                print('[X] Wrong password. Keep trying!')
            input('\nPress Enter to exit...')
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('\n\n[!] Interrupted by user')
        sys.exit(0)

相比第二题,注释能还原出来就很方便了,直接编写flag生成代码:

import base64
enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'

# base64 解码
encrypted = base64.b64decode(enc_data)
print(f"Base64: {encrypted.hex()}")

# XOR78
key = 78
result = bytearray()
for byte in encrypted:
    result.append(byte ^ key)

flag = result.decode('utf-8', errors='replace')
print(f"Flag: {flag}")

运行后得到Flag:

Base64: 7b7c3e7e246f7d6d7c7e7c7864067a3e3e3763007d3963177d7a3c0e0e0e
Flag: 52p0j!3#2026*H4ppy-N3w-Y34r@@@

直接得到了我们需要的结果:flag:52p0j!3#2026*H4ppy-N3w-Y34r@@@

解题领红包之六 {番外篇 初级题}

首先我们观察压缩包结构

$ ls
CatchTheCat.exe*  credits.txt       msvcp120.dll*
OpenAL32.dll*     license.txt  lua51.dll*   msvcr120.dll*
SDL2.dll*         love.dll*    mpg123.dll*

第一眼没法看出这是什么框架的程序,发现有creditslicence,直接打开,查看发现程序是love2d写的,在网上搜索love2d discompiler,搜到一个love2d-unpacker.py的文件:

import argparse
import os, os.path
import zipfile
import io

def readui32(file):
        bytes = file.read(4)
        number = bytes[0]
        number += bytes[1] << 8
        number += bytes[2] << 16
        number += bytes[3] << 24
        return number

def skip2zip(file):
        SIGNATURE = b'PK\x05\x06'

        # retrieve the file size
        file.seek(0, 2)
        filesize = file.tell()

        # scan the last 65k (2^16) for the zip signature
        signature_position = filesize
        while signature_position > filesize - (2 << 16):
                file.seek(signature_position, 0)
                data = file.read(len(SIGNATURE))
                if data == SIGNATURE:
                        break
                signature_position -= 1
        else:
                raise ValueError("Corrupted zip archive.")

        # skip 8 bytes
        file.seek(8, 1)

        # read size and offset of central directory
        size = readui32(file)
        offset = readui32(file)

        # Calculate beginning of the zip file:
        # There is a "central directory" with the size 'size' located at 'offset' (relative to the zip
        # file). The signature is appended directly after the central directory. We have already found
        # the signature start and know the size of the central directory, so we can calculate the
        # beginning of the central directory via 'signature_position - size'. The result is the "real"
        # offset inside the packed executable. The supposed offset inside the zip file is stored at
        # 'offset', so we can calculate the beginning of the zip-file.
        start = (signature_position - size) - offset

        # seek to the beginning position
        file.seek(start, 0)

def unpack(executablename, unzipdestination=None, lovefilename=True):
        with open(executablename, 'rb') as executable:
                skip2zip(executable)

                data = executable.read()

        if lovefilename:
                with open(lovefilename, 'wb') as lovefile:
                        lovefile.write(data)

        if unzipdestination:
                if not os.path.isdir(unzipdestination):
                        os.makedirs(unzipdestination)

                zipdata = io.BytesIO(data)
                with zipfile.ZipFile(zipdata, 'r') as zip:
                        zip.extractall(unzipdestination)

if __name__=="__main__":
        parser = argparse.ArgumentParser(description="Unpack a love game executable which has been "   \
                "fused by appending the .love file to a love binary. Choose one of two modes: either "     \
                "just separate the .love file from the binary, using the '--love' (or '-l') option, or "   \
                "extract the contained files into a directory specified with '--extract' (or '-x'). You "  \
                "can also specify both options.")
        parser.add_argument('-x', '--extract', metavar="UNZIPDESTINATION", type=str, help="Unzip "     \
                "files to this folder (it will be created if it doesn't exist, otherwise the contents "    \
                "will be overwritten!)")
        parser.add_argument('-l', '--love', metavar="LOVEFILE", type=str, help="Split off the .love "  \
                "file and save it to this file (it will be overwritten if it already exists!)")
        parser.add_argument('executable', metavar="EXECUTABLE", type=str, help="The love game "        \
                "executable to unpack.")

        args = parser.parse_args()

        unpack(args.executable, unzipdestination=args.extract, lovefilename=args.love)

程序里面有说明,直接python3 ../temp/love2d-unpacker.py CatchTheCat.exe -x ./CatchTheCat_unpacked拆包,拆完之后:

$ ls -R
.:
assets/  conf.lua  main.lua

./assets:
cat/  flag.dat

./assets/cat:
bottom_left/  left/  top_left/

直接打开flag.dat发现不可识读,打开main.lua搜索flag.dat看看能不能搜到解密逻辑:

local function getWinMessage()
    local content = nil
    if love.filesystem.getInfo("assets/flag.dat") then
        content = love.filesystem.read("assets/flag.dat")
    end
    if not content or currentDifficulty ~= "hard" then
        return "You WIN!"
    end
    local key = "52pojie"
    local keyLen = #key
    local result = {}
    local bit = require("bit")
    for i = 1, #content do
        local b = string.byte(content, i)
        local k = string.byte(key, ((i - 1) % keyLen) + 1)
        table.insert(result, string.char(bit.bxor(b, k)))
    end
    return table.concat(result)
end

直接运行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def getWinMessage():
    try:
        with open("CatchTheCat_unpacked/assets/flag.dat", "rb") as f:
            encrypted_data = f.read()
    except FileNotFoundError:
        return
    key = "52pojie".encode('utf-8')
    key_len = len(key_bytes)
        result = []
    for i, byte in enumerate(encrypted_data):
        key_byte = key[i % key_len]
        result.append(byte ^ key_byte)
    flag = bytes(result).decode('utf-8', errors='replace')
    print("Flag:", flag)

if __name__ == "__main__":
    getWinMessage()

轻松拿下flag: flag{52pojie_2026_Happy_New_Year!_>w<}!

免费评分

参与人数 3威望 +2 吾爱币 +102 热心值 +2 收起 理由
Tendro + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

DingDangMao001 发表于 2026-3-5 11:14
不错不错这个思路,但是我那个python初级没有拿uncompyle6反编译出来,说什么魔幻字不对
wangwen94 发表于 2026-3-5 16:24
Hmily 发表于 2026-3-6 21:55
加油,可以借鉴下大佬们的中级解题思路来扩充自己的知识。
zhaijing521 发表于 2026-3-7 11:07
感谢楼主分享,收获很大!
jkchen520 发表于 2026-3-7 19:38
好人一生平安谢谢大哥
liyuan1 发表于 2026-3-8 18:09
看来我还是很菜,看着看着就感觉看不懂了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-3-10 04:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表