吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 486|回复: 1
上一主题 下一主题
收起左侧

[CTF] 【2026春节解题红包】WriteUP(2、3、4、5、6、7、9)

  [复制链接]
跳转到指定楼层
楼主
nanaqilin 发表于 2026-3-4 08:25 回帖奖励
本帖最后由 nanaqilin 于 2026-3-4 08:29 编辑

春节解题红包2026题解

前言

  • 首先感谢吾爱提供天大的机缘,感谢出题老师我们醍醐灌顶
  • 祝各位坛友们,岁岁平,岁岁安,岁岁平安
  • 本次参加的目的主要是学习大佬们的出题的思路,进而提高自己的知识水平
  • 本着能逆则逆,不能逆也要硬逆的原则,有些题解可能多余,但这即是我的道心

工具与运行环境

  • IDA:二进制程序静态分析
  • 逍遥模拟器:主要主于安卓运行与动态调试
  • JEB: APK程序静态分析
  • JADX:APK程序静态分析
  • Python3.14: 第四题会用到,其余题解只要是pytho3就可以运行
  • DeepSeek: 有些事情让Ai帮忙辅助解决和实现会更快一些

第二题(Windows简单)

  • 动态调试即可以出结果
// 计算flag的核心函数
_BYTE *__cdecl calc_flag(int a1)
{
  _BYTE *result; // eax

  *(_DWORD *)a1 = 0x2D327077;
  *(_DWORD *)(a1 + 4) = 0x63272B28;
  *(_DWORD *)(a1 + 8) = 0x701D6363;
  *(_DWORD *)(a1 + 12) = 0x1D747072;
  *(_DWORD *)(a1 + 16) = 0x3232230A;
  *(_DWORD *)(a1 + 20) = 0x272C1D3B;
  *(_DWORD *)(a1 + 24) = 0x273B1D35;
  *(_BYTE *)(a1 + 30) = 0x63;
  *(_WORD *)(a1 + 28) = 0x3023;
  result = (_BYTE *)a1;
  do
    *result++ ^= 0x42u;
  while ( result != (_BYTE *)(a1 + 31) );
  *(_BYTE *)(a1 + 31) = 0;
  return result; // 在此处下断,就可以看到flag
}
  • 解题如下
enc_bytes = [
    0x77,0x70,0x32,0x2D,
    0x28,0x2B,0x27,0x63,
    0x63,0x63,0x1D,0x70,
    0x72,0x70,0x74,0x1D,
    0x0A,0x23,0x32,0x32,
    0x3B,0x1D,0x2C,0x27,
    0x35,0x1D,0x3B,0x27,
    0x23,0x30,
    0x63,
]
list_str = "".join([chr(enc_bytes[i] ^ 0x42) for i in range(len(enc_bytes))])
print(list_str)

第三题(Android简单)

  • 当成游戏玩,完成拼图后,点宝箱即可得到flag

  • 但是这样做胜之不武,代码混的稀烂,不忍直视,那也一定要逆,“逆则成仙,顺则成仁”

package r1;

public abstract class DataMatrix {
    public static final int[][] matrix;
    // 此处是关键字串
    static {
        DataMatrix.matrix = new int[][]{new int[]{80, 109, 0x77, 0x7B, 77}, new int[]{97, 0x74, 34, 45, 105}, new int[]{102, 49, 0x7C, 45, 5, 94}, new int[]{4, 49, 36, 42, 105}, new int[]{101, 0x71, 100, 45, 88, 102, 73}, new int[]{0x70, 50, 101, 104, 7, 0x77, 34, 0x70, 75}};
    }
}
  • 在此处进行调用和解密
int[][] arr2_v = DataMatrix.matrix;
C c1 = new C(25, new byte[]{54, 1, 22, 28});
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append("");
int v19 = 0;
for(int v20 = 0; v20 < 6; ++v20) {
   int[] arr_v = arr2_v[v20];
   ++v19;
   if(v19 > 1) {
      stringBuilder1.append("");
   }
   // 此处会调用C.q0方法
   U.c.g(stringBuilder1, arr_v, c1);
}
stringBuilder1.append("");
  • C.q0方法很长,但我们关心case 25即可
int v = 0;
k.e(((int[])object0), "part");
StringBuilder stringBuilder0 = new StringBuilder();
// 块是最核心的解密算法
// 将加密字串分别与前面的【54,1,22,28】进行异或,然后求出结果
while(v < ((int[])object0).length) {
    stringBuilder0.append(((char)(((int[])object0)[v] ^ ((byte[])this.k)[v % ((byte[])this.k).length] & 0xFF)));
    ++v;
}

String s = stringBuilder0.toString();
k.d(s, "toString(...)");
return s;
  • 最简单的方法可以用JEB直接全盘分析得到Flag,但仍然胜之不武
if((v17 ^ 305419896L) == 0xE30FE54D0L) {
   int[][] arr2_v = DataMatrix.matrix;
   C c1 = new C(25, new byte[]{54, 1, 22, 28});
   StringBuilder stringBuilder1 = new StringBuilder();
   stringBuilder1.append("");
   int v19 = 0;
   for(int v20 = 0; v20 < 6; ++v20) {
       int[] arr_v = arr2_v[v20];
       ++v19;
       if(v19 > 1) {
           stringBuilder1.append("");
       }

       U.c.g(stringBuilder1, arr_v, c1);
   }

   stringBuilder1.append("");
   k.d("flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}", "joinTo(StringBuilder(), …ed, transform).toString()");
   s1 = "flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}";
}
  • 以下是题解
matrix = [
    [80, 109, 0x77, 0x7B, 77],
    [97, 0x74, 34, 45, 105],
    [102, 49, 0x7C, 45, 5, 94],
    [4, 49, 36, 42, 105],
    [101, 0x71, 100, 45, 88, 102, 73],
    [0x70, 50, 101, 104, 7, 0x77, 34, 0x70, 75],
]
key = [54, 1, 22, 28]
list_str = ""
for i in range(len(matrix)):
    for j in range(len(matrix[i])):
        list_str += chr((matrix[i][j] ^ key[j % len(key)]) & 0xFF)

print(list_str)

第四题(Windows简单)

  • python题,需要进行解包与反编译

  • 下载解包的工具

    git clone https://github.com/extremecoders-re/pyinstxtractor
  • 将pyinstxtractor.py 文件放在与app.exe同目录下,然后执行

python pyinstxtractor.py app.exe
  • 会出现如下提示
[+] Processing app.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.14
[+] Length of package: 8875932 bytes
[+] Found 64 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: crackme_easy.pyc
[+] Found 130 files in PYZ archive
[+] Successfully extracted pyinstaller archive: app.exe

You can now use a python decompiler on the pyc files within the extracted directory
  • 在解压出来的目录里找到crackme_easy.pyc文件,偿试下面的反编译,没有一个支持python3.14版

  • [x] uncompyle6

  • [x] decompyle3

  • [x] pycdc

  • 网上找了一段可以转字节码的代码,写到py2dis.py文件中

import dis, marshal

with open("crackme_easy.pyc", "rb") as f:
    f.read(16) # 跳过 pyc 文件头
    code_obj = marshal.load(f)

dis.dis(code_obj)
  • 将字节码存到文档当中
python py2dis.py > dis.txt 
  • 基本上代码已经可以看懂个七七八八,直接丢给AI去翻译吧,不费那脑子,AI给译出的结果如下
import hashlib
import base64
import sys

def xor_decrypt(data, key):
    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():
    enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'
    return base64.b64decode(enc_data)

def generate_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):
    correct_flag = generate_flag()
    if len(user_input) != len(correct_flag):
        return False
    for i in range(len(correct_flag)):
        if user_input[i] != correct_flag[i]:
            return False
    return True

def fake_check_1(user_input):
    fake_hash = 'a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890'
    return hash_string(user_input) == fake_hash

def fake_check_2(user_input):
    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

    if fake_check_2(user_input):
        print("\n[!] You're getting closer...")
        input('\nPress Enter to exit...')
        return

    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('[+] 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)
  • 到这已经基本上没啥难度了,以下是题解
import base64

buf = "e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O"
data = base64.decodebytes(buf.encode())

list_str = "".join(chr(data[i] ^ 78) for i in range(len(data)))
print(list_str)

第五题(Window中级)

  • python题,还是需要解包与反编译

  • 用第四题的方法解包失败,看来又上难度了,又进入了空白的领域,继续现学现卖吧

  • 查壳工具查一下

Packer: Nuitka [OneFile]
  • 论坛上发现了一个大佬的贴子,帮了我很大的忙,整个解题过程也是完全参照这个来进行的
https://www.52pojie.cn/thread-2063208-1-1.html
https://j1nxem-o.github.io/2026/02/01/python%E9%80%86%E5%90%91%E4%B9%8BNuitka/
  • 下载工具,解包
git clone https://github.com/extremecoders-re/nuitka-extractor
# 将nuitka-extractor.exe放到与app.exe相同目录下
./nuitka-extractor.exe app.exe
  • 在解包的文件找到crackme_hard.dll文件

  • 放入IDA分析那部分,跟大佬的也是相同的,此处就略掉了

  • 然用restuner提取资源文件RC数据,里面只有一个文件,提取出来重命名为main.bin

  • 下载Nuitka开源代码,以便修复解析环境

    
    git clone https://github.com/Nuitka/Nuitka.git
    # 需要用参考的代码位置
    # nuitka/build/static_src/HelperConstantsBlob.c

* 字节码的提取参照大佬的代码,在此基础上参考Nuitka源码进行解析的修复,缺啥补啥就行
```python
import io
import struct

def read_u8(b): return struct.unpack("<B", b.read(1))[0]
def read_u32(b): return struct.unpack("<I", b.read(4))[0]

def read_varint(b):
    shift = 0
    r = 0
    while True:
        c = read_u8(b)
        r |= (c & 0x7F) << shift
        if c < 0x80:
            return r
        shift += 7

def read_cstring(b):
    s = b""
    while True:
        c = b.read(1)
        if not c or c == b"\x00":
            break
        s += c
    return s

def decode(b):
    t = chr(read_u8(b))

    # --- ignore filler ---
    if t == ".":
        return None

    # --- strings ---
    if t in ("a", "u"):
        s = read_cstring(b)
        return s.decode("utf-8", "ignore")

    if t == "w":
        return b.read(1).decode("utf-8", "ignore")

    if t == "s":
        return read_cstring(b).decode("utf-8", "ignore")

    if t == "v":
        n = read_varint(b)
        return b.read(n).decode("utf-8", "ignore")

    # --- bytes ---
    if t == "b":
        n = read_varint(b)
        return b.read(n)

    if t == "c":
        return read_cstring(b)

    if t == "d":
        return b.read(1)

    # --- numbers ---
    if t in ("l", "q"):
        v = read_varint(b)
        return v if t == "l" else -v

    if t in ("t", "F", "n"):
        return {
            "t": True,
            "F": False,
            "n": None
        }[t]

    if t in ("g", "G"):
        len = read_varint(b)
        data = b.read(len)
        return int.from_bytes(data, byteorder='little', signed=True)

    # --- tuple ---
    if t == "T":
        n = read_varint(b)
        return tuple(decode(b) for _ in range(n))

    # --- list ---
    if t == "L":
        n = read_varint(b)
        return [decode(b) for _ in range(n)]

    # --- dict ---
    if t == "D":
        n = read_varint(b)
        d = {}
        for _ in range(n):
            k = decode(b)
            v = decode(b)
            d[k] = v
        return d

    # --- set / frozenset ---
    if t in ("P", "S"):
        n = read_varint(b)
        items = [decode(b) for _ in range(n)]
        return set(items) if t == "P" else frozenset(items)

    # Slice object
    if t == ":":
        start = read_varint(b) 
        stop = read_varint(b) 
        step = read_varint(b) 
        return slice(start,stop,step)

    raise ValueError(f"Unhandled type {t!r}")

def main():
    with open("main.bin", "rb") as f:
        data = f.read()

    b = io.BytesIO(data)

    magic = read_u32(b)
    size = read_u32(b)

    print(f"magic={hex(magic)} size={size}")

    while b.tell() < size:
        name = read_cstring(b).decode()
        blob_size = read_u32(b)
        count = struct.unpack("<H", b.read(2))[0]

        print(f"[+] blob {name} count={count}")

        if name == "__main__":
            for i in range(count):
                obj = decode(b)
                print(f"{i}: {obj}")
            break
        else:
            b.seek(b.tell() + blob_size - 2)

if __name__ == "__main__":
    main()
  • 解析出来的效果如下
magic=0xdcaa9d4e size=5635498
[+] blob .bytecode count=323
[+] blob  count=107
[+] blob __main__ count=83
0: [b'dc!a;`b', b'\x11', b'cacg', b'/', b'\x19e!!(', b'\x0e', b'\x1fb&', b'\x0e', b'\x08be#', b'ppp']
1: _parts
2: 81
3: _key
4: 30
5: _total_len
6: 解密单个字符
7: current
8: _decrypt_char
9: 获取指定位置的字符
10: self
11: _get_char_at_position
12: 验证用户输入
13: total
14: 计算校验和
15: aflag
16: checksum
17: 获取目标校验和
18: hashlib
19: sha256
20: encode
21: hexdigest
22: slice(110, 108, 8)
23: None
24: 16
25: 哈希函数
26: 305419896
27: -32511
  • 感觉这玩艺儿怪怪的,看不到decrypt_char那个函数,好在算法不难,仍给AI就能给算出解果了

  • 以下是题解

parts = [b'dc!a;`b', b'\x11', b'cacg', b'/', b'\x19e!!(', b'\x0e', b'\x1fb&', b'\x0e', b'\x08be#', b'ppp']
key = 81
full_bytes = b''.join(parts)

list_str = "".join([chr(full_bytes[i]^key) for i in range(len(full_bytes))])

print(list_str)

第六题(翻外篇)

  • 论坛里最火的圈小猫,过了就能出flag,但是胜之不武,我要逆,顺为凡,逆则仙

  • 查壳工具先查一下,发现开发语言是lua,属于zip加包

Language: Lua
Archive: Zip (2.0) [1 file]
  • 直接7zip解压缩
[drwxrwxrwx]  .
├── [drwxrwxrwx]  assets
│   ├── [drwxrwxrwx]  cat
│   │   ├── [drwxrwxrwx]  bottom_left
│   │   │   ├── [-rwxrwxrwx]  1.png
│   │   │   ├── [-rwxrwxrwx]  2.png
│   │   │   ├── [-rwxrwxrwx]  3.png
│   │   │   ├── [-rwxrwxrwx]  4.png
│   │   │   └── [-rwxrwxrwx]  5.png
│   │   ├── [drwxrwxrwx]  left
│   │   │   ├── [-rwxrwxrwx]  1.png
│   │   │   ├── [-rwxrwxrwx]  2.png
│   │   │   ├── [-rwxrwxrwx]  3.png
│   │   │   ├── [-rwxrwxrwx]  4.png
│   │   │   └── [-rwxrwxrwx]  5.png
│   │   └── [drwxrwxrwx]  top_left
│   │       ├── [-rwxrwxrwx]  1.png
│   │       ├── [-rwxrwxrwx]  2.png
│   │       ├── [-rwxrwxrwx]  3.png
│   │       ├── [-rwxrwxrwx]  4.png
│   │       └── [-rwxrwxrwx]  5.png
│   └── [-rwxrwxrwx]  flag.dat
├── [-rwxrwxrwx]  conf.lua
└── [-rwxrwxrwx]  main.lua
  • 其中在main.lua可以找到我们需要核心代码
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
  • 以下是题解
with open("flag.dat", "rb") as f:
    encdata = f.read()

key = b"52pojie"
flag = "".join(
    [chr(encdata[i] ^ key[i % len(key)]) for i in range(len(encdata))]
)
print(flag)

第七题(Windows中级)

  • 先看一下壳,是UPX,支持自动脱壳,直接用现成的工具进行脱壳即可
upx -d CM1.exe
  • 直接IDA进行分析主程序逻辑
INT_PTR __fastcall DialogFunc(HWND hwnd, int a2, __int16 a3)
{
  INT_PTR result; // rax
  FILE *fp_in; // rsi
  FILE *fp_out; // rdi
  int isFalse; // r12d
  HMODULE ModuleHandleA; // rax
  HICON IconA; // rsi
  HDC DC; // rsi
  int DeviceCaps; // ebp
  HFONT FontW; // rsi
  HWND DlgItem; // rax
  HWND v14; // rax
  HWND v15; // rax
  HWND v16; // rax
  CHAR input_flag[80]; // [rsp+70h] [rbp-328h] BYREF
  WCHAR Text[80]; // [rsp+C0h] [rbp-2D8h] BYREF
  WCHAR String[284]; // [rsp+160h] [rbp-238h] BYREF

  if ( a2 == 272 )
  {
    ModuleHandleA = GetModuleHandleA(nullptr);
    IconA = LoadIconA(ModuleHandleA, (LPCSTR)0x14);
    SendMessageA(hwnd, 0x80u, 1u, (LPARAM)IconA);
    SendMessageA(hwnd, 0x80u, 0, (LPARAM)IconA);
    DC = GetDC(hwnd);
    DeviceCaps = GetDeviceCaps(DC, 88);
    ReleaseDC(hwnd, DC);
    FontW = CreateFontW((int)((double)DeviceCaps / 96.0 * 20.0), 0, 0, 0, 400, 0, 0, 0, 0, 7u, 0, 5u, 0x31u, "C");
    DlgItem = GetDlgItem(hwnd, 2);
    SendMessageW(DlgItem, 0x30u, (WPARAM)FontW, 1);
    v14 = GetDlgItem(hwnd, 3);
    SendMessageW(v14, 0x30u, (WPARAM)FontW, 1);
    v15 = GetDlgItem(hwnd, 4);
    SendMessageW(v15, 0x30u, (WPARAM)FontW, 1);
    v16 = GetDlgItem(hwnd, 12);
    sub_7FF6DAA37F40(v16, (DeviceCaps > 191) + 30);
    SetDlgItemTextA(hwnd, 2, "flag.png.encrypted");
    SetDlgItemTextA(hwnd, 3, "flag.png");
    SetDlgItemTextA(hwnd, 4, "flag{HEX_ME}");
  }
  else if ( a2 == 273 )
  {
    if ( a3 == 10 )
    {
      MessageBoxW(
        hwnd,
        L"吾爱破解 2026 春节解题领红包之 HEX_ME by 爱飞的猫\n"
         "\n"
         "本故事纯属虚构。文中及程序中出现的公司、人物、事件和情节均为艺术创作,与任何真实的公司、人物、事件或情节无关。如有雷同,纯属巧合。",
        L"关于",
        0x40u);
    }
    else if ( a3 == 11 )
    {
      memset(input_flag, 0, sizeof(input_flag));
      memset(String, 0, 0x200u);
      GetDlgItemTextW(hwnd, 2, String, 255);
      fp_in = _wfopen(String, "r");
      if ( fp_in )
      {
        GetDlgItemTextW(hwnd, 3, String, 255);
        fp_out = _wfopen(String, L"wb");
        if ( fp_out )
        {
          GetDlgItemTextA(hwnd, 4, input_flag, 0x4F);
          // 此处是关键函数,只有返回0时才能显示成功
          isFalse = check_flag(input_flag, fp_in, fp_out);
          fclose(fp_in);
          fclose(fp_out);
          memset(Text, 0, sizeof(Text));
          sub_7FF6DAA38D70((__int64)Text);
          if ( isFalse )
            MessageBoxW(hwnd, Text, L"错误", 0x10u);
          else
            MessageBoxW(hwnd, Text, L"成功", 0x40u);
        }
        else
        {
          fclose(fp_in);
          MessageBoxW(hwnd, &word_7FF6DAA3A0B0, L"错误", 0x10u);
        }
      }
      else
      {
        MessageBoxW(hwnd, &word_7FF6DAA3A080, L"错误", 0x10u);
      }
    }
  }
  else
  {
    result = 0;
    if ( a2 != 16 )
      return result;
    EndDialog(hwnd, 0);
  }
  return 1;
}
  • 主函数的逻辑很清晰,继续跟进关键函数进行分析
// 返回0时,提示正确的flag
__int64 __fastcall check_flag(char *input_flag, FILE *in_file, FILE *out_file)
{
  size_t flag_len; // rax
  __int64 check_num; // rdi
  char v8; // dl
  __int64 result; // rax
  __int64 v10; // rdi
  unsigned __int64 file_len; // r13
  DWORD *v12; // rax
  DWORD *file_stream; // r12
  __int64 v14; // rdx
  unsigned int v15; // r8d
  unsigned __int64 v16; // rax
  DWORD file_head[4]; // [rsp+20h] [rbp-858h] BYREF
  _DWORD buf[530]; // [rsp+30h] [rbp-848h] BYREF

  init(buf);// 初始化crc64的参数
  sub_7FF6DAA38500(buf, "52pojie_2026_", 14);// 先计算一次crc64
  flag_len = strlen(input_flag);                // 最大长度是80
  sub_7FF6DAA38500(buf, input_flag, flag_len); // 再用输入的flag计算一次
  check_num = crc64(buf); // 获取最终的crc64
  fread(file_head, 16u, 1u, in_file);
  v8 = sub_7FF6DAA38310(buf, check_num, file_head);// v8=1时才是正确的key
                                                // 函数的功能是将文件头写到buf中
  result = 1;
  if ( v8 )
  {
    fseek(in_file, 0, 2);
    v10 = ftell(in_file);
    result = 2;
    file_len = v10 - 16;
    if ( (v10 & 7) == 0 )
    {
      fseek(in_file, 16, 0);
      v12 = (DWORD *)malloc(v10 - 16);
      file_stream = v12;
      if ( v12 )
      {
        fread(v12, 1u, v10 - 16, in_file);
        crc32_update(buf, file_stream, v10 - 16);// 将解密后的文件进行crc32校验
                                                 // 最后的crc32值写入了buf[72]中
        if ( (unsigned __int8)check_buf(buf, v14, v15) )// 需要返回值为1
                                                // buf[73]=0x8D8445A2
                                                // 条件是~buf[72]==buf[73]
                                                // buf[72]需要= 0x727bba5d 才是正确的逻辑
        {
          v16 = *((unsigned __int8 *)file_stream + v10 - 17);
          if ( file_len < v16 )
          {
            free(file_stream);
            return 5;
          }
          else
          {
            fwrite(file_stream, 1u, file_len - v16, out_file);
            free(file_stream);
            return 0;                           // 此处是正确的出口
          }
        }
        else
        {
          free(file_stream);
          return 4;
        }
      }
      else
      {
        return 3;
      }
    }
  }
  return result;
}
  • init函数是初始化初值

    // 与CRC64的计算比较接近
    void __fastcall sub_7FF6DAA38500(_DWORD *buf, _DWORD *input, __int64 len)
    {
    __int64 v3; // rax
    _DWORD *v4; // r11
    char *v6; // r8
    _DWORD *v7; // r8
    int v8; // r10d
    int v9; // ecx
    
    v3 = *(_QWORD *)buf;
    v4 = buf - 12672;
    v6 = (char *)input + len - 46851;
    if ( (char *)input - 46851 < v6 )
    {
    v7 = v6 + 46851;                            // v7 = input+len
    v8 = buf[514] + 1 - (_DWORD)input;
    do
    {
      v3 = *(_QWORD *)&v4[2 * (unsigned __int8)(*(_BYTE *)input ^ v3) + 12674] ^ ((unsigned __int64)v3 >> 8);
      v9 = v8 + (_DWORD)input;
      input = (_DWORD *)((char *)input + 1);
      buf[514] = v9;
    }
    while ( input != v7 );
    }
    *(_QWORD *)buf = v3;
    }
    // 与前面的函数功能差不太多
    __int64 __fastcall crc64(_DWORD *buf)
    {
    __int64 v1; // rax
    unsigned __int64 v2; // rbx
    int v3; // r8d
    int v4; // edx
    
    v1 = 0;
    v2 = *(_QWORD *)buf;
    v3 = buf[514] + 1;                            // buf[514]记录的是下一个地址
    do
    {
    v2 = *(_QWORD *)&buf[2 * (unsigned __int8)(*((_BYTE *)buf + v1 + 2056) ^ v2) + 2] ^ (v2 >> 8);
    v4 = v3 + v1++;
    buf[514] = v4;
    }
    while ( v1 != 4 );
    memset(buf, 0, 0x810u);
    return ~v2;
    }
  • 下面的函数是解密文件前,初始化状态信息和加密key

    // 复制文件头中的关键信息
    void *__fastcall sub_7FF7C15D8360(_DWORD *buf, __int64 num, _DWORD *head_2)
    {
    __int64 v3; // rax
    
    v3 = 0;
    *(_QWORD *)buf = num;
    do
    {
    *((_BYTE *)buf + v3 + 16) = *((_BYTE *)head_2 + v3);
    ++v3;
    }
    while ( v3 != 16 );
    // 此处将S-BOX表,复制到buf的第32字节偏移处
    return memcpy(buf + 8, byte_7FF6DAA3A270, 256u);
    }
    // 初始化buf表中的数据
    __int64 __fastcall sub_7FF6DAA38310(_DWORD *buf, __int64 check_num, DWORD *file_head)
    {
    __int64 result; // rax
    DWORD v6; // eax
    
    result = 0;
    if ( file_head )
    {
    if ( *file_head == '62MC' )
    {
      sub_7FF6DAA38360(buf, check_num, file_head + 2);
      v6 = file_head[1];
      buf[72] = -1;
      buf[73] = v6;
      return 1;
    }
    }
    return result;
    }
  • 下面的函数是解密文件,并计算crc32的值

    // 非线性数据混合与解密
    char __fastcall mix_dword(char *ctx, DWORD *filebuf)
    {
    int v2; // r8d
    __int64 v3; // r11
    __int64 v5; // rax
    char *pTable; // rcx
    unsigned __int64 v7; // rax
    char *pFbuf; // rdx
    char v9; // al
    char result; // al
    
    v2 = 8;                                       // 总计转8次
    v3 = *(_QWORD *)filebuf;                      // 保存上一次的内容
    v5 = *(_QWORD *)ctx;
    pTable = ctx - 21569;
    v7 = __ROL8__(v5, 3);                         // 操作的是buf的前8字节
    do
    {
    v7 = (v7 << 8) | (unsigned __int8)ctx[(HIBYTE(v7) | 2233088) - 2233056];// 地址偏移
                                                // 2233088-2233056=32
                                                // 与s_box混合,实现非线性随机
    --v2;
    }
    while ( v2 );
    *(_QWORD *)ctx = v7;                          // 写回第0个字节
                                                // buf=v7
    pFbuf = (char *)(filebuf + 9422);             // 地址偏移
                                                // 9422*4 = 37688
    do
    {
    v9 = *(pFbuf - 37688);                      // v9=filebuf + index
    ++pTable;
    ++pFbuf;                                    // index++
    result = pTable[21568] ^ pTable[21584] ^ v9;// 地址偏移
                                                // 21584-21569+1 = 16
                                                // buf[index-1]^buf[index-1+16]^filebuf
    *(pFbuf - 37689) = result;                  // filebuf[index-1] = result
    }
    while ( pTable != ctx - 21561 );              // 8字节
    *((_QWORD *)ctx + 2) = v3;                    // buf第16字节需要在此处更新
                                                // 下一轮中将会用到这个数值
    return result;
    }
    // 更新加crc32数值
    __int64 __fastcall crc32_update(_DWORD *buf, DWORD *filebuf, __int64 len)
    {
    char *v3; // rbx
    char *buf_len; // rdi
    _DWORD *key; // r12
    __int64 result; // rax
    
    v3 = (char *)filebuf - 24627;
    buf_len = (char *)filebuf + (len & 0xFFFFFFFFFFFFFFF8uLL) - 24627;// len是文件长度,定值
    if ( (char *)filebuf - 24627 < buf_len )
    {
    key = buf + 72;
    do
    {
      v3 += 8;
      mix_qword(buf, (DWORD *)(v3 + 24619));    // 每8个字节做一次mix
      result = crc32(key, (unsigned __int64)(v3 + 24619), 8);// 8字节filebuf校验
    }
    while ( v3 < buf_len );
    }
    return result;
    }
  • 主逻辑函数我注释的比较清晰,总结一下就是,先经过一个类似CRC64的操作将输入的flag转成一个(check_num)8字节数值

  • 再用这个check_num值参与解密图片文件,计算解密后数据的crc32,如果输入的flag正确,则校验成功

  • 分析到这的时候,我遇到一个问是,别说现在不知道check_num, 就算知道了check_num,也无法逆推flag

  • 难道需要暴力破解?这可是8字节,跑到正月十五也不可能找到flag,但是有人分分钟就找到了答案,这是啥脑子呢

  • 后面的CRC32也不可逆,两头都被毒死了,真的让人无路可走啊

  • 于是乎换了个思路,我何不把图片也给解密了,万一里面有料呢,万一呢

  • 仔细分析了一下上面的那个mix_qword函数,发现这个函数是可逆的,我们只要知道原图片中的前8字节就可以逆推出,check_num

  • 然后就可以用这个check_num把图片也搞出来吧

  • 百度了一下png图片的标准头,前8个字节就是一个固定的值

PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
  • 顺着这个思路,我们可以写一个反推check_num的函数
def reverse_key(last_key):
    invS_Box = [0] * len(S_BOX)
    for i in range(len(S_BOX)):
        invS_Box[S_BOX[i]] = i

    key = last_key
    for _ in range(8):
        low = key & 0xFF
        hi = invS_Box[low]
        key = (key >> 8) | ((hi << 56) & 0xFFFFFFFFFFFFFFFF)

    return ror64(key, 3)

def inverse():
    pre_mix = PNG_HEADER[0:8]
    cur_mix = FILE_DATA[8:16]
    nex_mix = FILE_DATA[16:24]
    cur_key = [pre_mix[i] ^ cur_mix[i] ^ nex_mix[i] for i in range(8)]
    pre_key = bytes_to_uint64_le(cur_key)
    return reverse_key(pre_key)
  • 然后通过这个check_num理论上就应该把图片恢复
  • 然后以文本的方式打开图片,flag果然在图片里,啊哈
  • 以下是题解
import re

FILE_DATA = [
    0x43, 0x4D, 0x32, 0x36, 0xA2, 0x45, 0x84, 0x8D, 0xF5, 0x69, 0x73, 0x60,
    0x01, 0xCB, 0x35, 0xBC, 0xFB, 0xDD, 0x1B, 0x92, 0x2B, 0xEF, 0xE3, 0x23,
    0x10, 0xEB, 0x81, 0x4C, 0x45, 0x04, 0x48, 0x95, 0xDF, 0x42, 0xBF, 0x52,
    0xF7, 0xD0, 0xB7, 0x79, 0x2D, 0x6E, 0x59, 0xF3, 0x67, 0xA8, 0x5A, 0x19,
    0x35, 0xBC, 0x9E, 0x85, 0xD4, 0xC4, 0x7E, 0x99, 0x03, 0x7F, 0x43, 0xAD,
    0x4A, 0xAF, 0x2F, 0xC5, 0xE4, 0x6C, 0x31, 0x59, 0x32, 0x52, 0x8E, 0x07,
    0xCF, 0xC6, 0xDB, 0x6F, 0x90, 0xDB, 0xA6, 0x10, 0xBC, 0x6F, 0x2B, 0x5F,
    0x84, 0x3E, 0xF4, 0x3B, 0xF8, 0x49, 0x20, 0x74, 0xDA, 0x1F, 0x2B, 0xEA,
    0x0E, 0xC8, 0xB3, 0x6B, 0x06, 0x56, 0x9B, 0x6B, 0xF4, 0x8A, 0x02, 0xB6,
    0x8B, 0xEC, 0x26, 0x95, 0x66, 0x24, 0xE1, 0x81, 0x94, 0xD7, 0x5D, 0x56,
    0x23, 0xF8, 0x01, 0xC4, 0x59, 0xDF, 0x53, 0xB2, 0xE5, 0x08, 0xAD, 0x21,
    0xD8, 0x82, 0x9E, 0xFC, 0x27, 0xCD, 0x91, 0xDB, 0x42, 0x32, 0x13, 0x09,
    0xBE, 0xD3, 0xB7, 0xFD, 0x02, 0xB8, 0x98, 0x07, 0x96, 0xDE, 0x86, 0x0E,
    0x5B, 0x00, 0xB7, 0xE6, 0xD5, 0xB3, 0xE5, 0x47, 0x13, 0x91, 0x83, 0xC7,
    0xA2, 0x4C, 0xDA, 0xCE, 0x8C, 0x92, 0x58, 0x73, 0x6F, 0x21, 0x7D, 0x92,
    0x1E, 0x1D, 0x3A, 0x75, 0x70, 0x3C, 0x82, 0xFE, 0xB1, 0x79, 0xF7, 0x18,
    0x4C, 0x33, 0x1A, 0x50, 0x93, 0x22, 0xFF, 0x74, 0xC7, 0x67, 0xA4, 0x84,
    0x35, 0xB9, 0x93, 0x94, 0xD1, 0x36, 0xFA, 0xB5, 0x57, 0xE5, 0x0F, 0x4B,
    0x70, 0xF2, 0x20, 0x61, 0x0A, 0x41, 0xB9, 0xBC, 0xC3, 0x70, 0xF7, 0x10,
    0x47, 0xA3, 0x70, 0x5E, 0x70, 0x57, 0xB3, 0x4E, 0x48, 0x26, 0xA2, 0xCE,
    0xC0, 0x13, 0xB2, 0x22, 0x58, 0x95, 0x32, 0x8A, 0xD2, 0xA6, 0xB8, 0xEF,
    0x7D, 0x2D, 0xE5, 0x5A, 0xE4, 0xF0, 0x0C, 0x4D, 0xF0, 0x57, 0xD6, 0x93,
    0x37, 0xB3, 0x16, 0xDB, 0x71, 0x57, 0x29, 0xFD, 0xEB, 0xF7, 0x7E, 0x05,
    0x3A, 0xD7, 0x6B, 0x13, 0xCB, 0xC1, 0x14, 0xFC, 0xB1, 0x53, 0x64, 0x25,
    0x0A, 0xBF, 0xA5, 0x32, 0xCD, 0xBB, 0x53, 0x92, 0x38, 0x10, 0xD2, 0x8B,
    0xE4, 0x54, 0xFA, 0x56, 0x37, 0xF1, 0xDB, 0x1A, 0xF2, 0x31, 0x40, 0x74,
    0x91, 0xBF, 0x8F, 0xA1, 0xC4, 0x93, 0xBE, 0x7D, 0x00, 0x49, 0x30, 0xF2,
    0x91, 0x62, 0xEE, 0x78, 0xD3, 0xAB, 0x95, 0x56, 0xA6, 0xAD, 0x65, 0x32,
    0x17, 0x4A, 0x66, 0x5E, 0xBF, 0xCD, 0x9F, 0x3D, 0xFE, 0xE1, 0xFB, 0xBE,
    0xEE, 0x3D, 0xFC, 0xEE, 0x0D, 0xA3, 0x2E, 0xB9, 0x63, 0xD1, 0x24, 0x9B,
    0x97, 0x7B, 0x64, 0x49, 0x46, 0x10, 0x55, 0xC6
]

# AES S-Box
S_BOX = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,
    0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
    0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,
    0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
    0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
    0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,
    0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,
    0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
    0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,
    0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,
    0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
    0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
    0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,
    0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
    0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
    0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,
    0xB0, 0x54, 0xBB, 0x16
]

# 标准PNG文件头
PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]

def bytes_to_uint64_le(array):
    return int.from_bytes(array, byteorder="little")

def uint64_to_bytes_le(value):
    value &= 0xFFFFFFFFFFFFFFFF
    array = value.to_bytes(8, byteorder="little", signed=False)
    return array

def rol64(value, shift):
    return ((value << shift) & 0xFFFFFFFFFFFFFFFF) | (value >> (64 - shift))

def ror64(value, shift):
    return (value >> shift) | ((value << (64 - shift)) & 0xFFFFFFFFFFFFFFFF)

# 反推key
def reverse_key(last_key):
    invS_Box = [0] * len(S_BOX)
    for i in range(len(S_BOX)):
        invS_Box[S_BOX[i]] = i

    key = last_key
    for _ in range(8):
        low = key & 0xFF
        hi = invS_Box[low]
        key = (key >> 8) | ((hi << 56) & 0xFFFFFFFFFFFFFFFF)

    return ror64(key, 3)

# 数据非线性混合
def mix_qword(buf, mixdata, offset):

    v5 = bytes_to_uint64_le(buf[0:8])
    v7 = rol64(v5, 3)
    for _ in range(8):
        hi = (v7 >> 56) & 0xFF
        low = S_BOX[hi]
        v7 = ((v7 << 8) & 0xFFFFFFFFFFFFFFFF) | low
    buf[0:8] = uint64_to_bytes_le(v7)
    tmp = mixdata[offset : offset + 8]

    for i in range(8):
        mixdata[offset + i] = buf[i] ^ buf[i + 16] ^ mixdata[offset + i]

    buf[16:24] = tmp

# 逆向求解
def inverse():
    pre_mix = PNG_HEADER[0:8]
    cur_mix = FILE_DATA[8:16]
    nex_mix = FILE_DATA[16:24]
    cur_key = [pre_mix[i] ^ cur_mix[i] ^ nex_mix[i] for i in range(8)]
    pre_key = bytes_to_uint64_le(cur_key)
    return reverse_key(pre_key)

# 解密整个文件,并提取flag
def decrypt_file(key):
    buf = [0] * 32
    buf[0:8] = list(uint64_to_bytes_le(key))
    buf[16:24] = FILE_DATA[8:16]
    filelen = len(FILE_DATA) - 16
    filelen &= 0xFFF8
    filedata = FILE_DATA[16 : 16 + filelen]
    for i in range(0, filelen, 8):
        mix_qword(buf, filedata, i)

    with open("flag_decrypted.png", "wb") as f:
        f.write(bytes(filedata))

    str = bytes(filedata).decode("utf-8", errors="ignore")

    result = re.findall(r"flag{\S+?}", str)
    print(result)

def main():
    check_num = inverse()
    print("checknum=" + hex(check_num))
    decrypt_file(check_num)

if __name__ == "__main__":
    main()
  • 最后展示一下王铁锤的logo,奇怪好像在哪里见过呢

logo

第九题(Web中级)

  • “如呼吸一般轻松,是产品的设计目标。”,这才是本题的精华
// 如此之校验,让我不能呼吸,心疼我电脑
async function checkCode(code, expectedHash) {
    const enc = new TextEncoder()
    let current = enc.encode(code)

    for (let i = 0; i < 0x2026; i++) {
        current = await crypto.subtle.digest('SHA-256', current)
    }

    const hashArray = Array.from(new Uint8Array(current))
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')

    return hashHex === expectedHash
}
  • 此函数用来重新产生flag

    checkboxText.addEventListener('click', async () => {
        const uidInput = document.getElementById('uid')

        if (!uidInput.value) {
            uidInput.focus()
            return
        }

        const uid = parseInt(uidInput.value) || 0
            const voice = document.getElementById('voice').value

            try {
                const challenge = wasm_bindgen.gen(uid, voice) // 此函数是产生语音的地方
                currentHash = challenge.h

                audio.src = URL.createObjectURL(new Blob([challenge.a], { type: 'audio/wav' }))

                challengeView.style.display = 'block'

                checkboxText.classList.remove('btn-important')
                document.getElementById('verifyBtn').classList.add('btn-important')

                while (n = w.nextNode()) n.data.includes`)` && (n.remove(), i = 0x2026)

                audio.play().catch(e => console.warn("Auto-play blocked:", e))

                document.getElementById('verifyInput').focus()

                checkboxText.innerText = "重新生成语音验证码"

            } catch (e) {
                console.error(e)
            }
        })
  • 继续跟进,会跟到wasm中的gen函数,函数非常的长,这里就不贴了
  • 核心的流程大概是把uid与一些特定的随机数,然后进行HMAC计算hash,最后再进行base64产生字符
  • base64码表在内存中的地址是固定的1295903,可以在内存中直接查看a-zA-Z0-9?!
  • 最后算出来的这个base64值就是语音播报的内容,后面应该是合成语言的过程,没有分析
  • 所以,只需要将这个base64的值给dump出来就可以了

i32.load8_u offset=1295903 // 这个值是base64码表的位置
local.set $var8
local.get $var3
i32.load offset=416
local.get $var0
i32.eq
if
  local.get $var3
  i32.const 416
  i32.add
  call $func39
end
local.get $var3 //var3中保存了内存的基址,抓包发现是定值1047920
i32.load offset=420 //这个是地址偏移量
local.get $var6 // var6是索引后的偏移,地址的计算法方是[[var3+420] + var6]
i32.add
local.get $var8 //此处是查完了base64表的值
i32.store // 此处保存回内存
local.get $var3
local.get $var0
i32.const 1
i32.add
local.tee $var0
i32.store offset=424
local.get $var2
i32.const 6
      local.get $var0
      i32.const 1
      i32.add
      local.tee $var0
      i32.store offset=424
      br $label15
    end
    i32.const 4
    i32.const 200
    call $func65
    unreachable
  end
  i32.const 1
  i32.const 37
  call $func65
  unreachable
end $label1
local.get $var3 // 在此处下断比较合适,这地方刚好计算完50个字符
i32.load offset=420
local.set $var6
local.get $var0
if (result i32)
  • 计算内存偏移的核心部分,用AI给翻译了一下,这块注意一下,不要给AI太多的数据,要不AI会直接崩溃
// 这是一个代码块,用于错误处理或提前退出
{
    // 分配内存并检查分配结果
    ptr = allocate(37, 1);
    if (ptr != NULL) {
        // 第一部分:数据准备和初始化

        // 对var0进行字节拆分和异或操作
        // 从var3+83到var3+80读取4个字节,分别与var0的4个字节异或
        byte0 = var0 ^ memory[var3+80];
        byte1 = (var0 >> 8) ^ memory[var3+81];
        byte2 = (var0 >> 16) ^ memory[var3+82];
        byte3 = (var0 >> 24) ^ memory[var3+83];

        // 将结果存入分配的内存
        ptr[0] = byte0;
        ptr[1] = byte1;
        ptr[2] = byte2;
        ptr[3] = byte3;

        // 复制var3+80处的数据到ptr+4
        memcpy(ptr+4, var3+80, 8);  // 复制8字节
        memcpy(ptr+12, var3+88, 8); // 复制8字节
        ptr[20] = memory[var3+96];  // 复制1字节

        // 将异或结果存回var3+100到var3+103
        memory[var3+100] = byte3;
        memory[var3+101] = byte2;
        memory[var3+102] = byte1;
        memory[var3+103] = byte0;

        // 分配栈空间
        stack = malloc(352);

        // 初始化栈空间为0
        memset(stack, 0, 352);

        // 从内存地址1295967复制14字节到栈空间
        memcpy(stack, 1295967, 14);

        // 将栈空间的数据复制到var3+416处
        memcpy(var3+416, stack, 64);

        // 释放栈空间
        free(stack);

        // 对var3+416开始的64字节进行异或操作(常数54)
        for(i=0; i<64; i+=4) {
            memory[var3+416+i] ^= 54;
            memory[var3+416+i+1] ^= 54;
            memory[var3+416+i+2] ^= 54;
            memory[var3+416+i+3] ^= 54;
        }

        // 初始化哈希状态和调用哈希函数
        memcpy(var3+480, 1295984, 32);  // 复制32字节
        var3+512 = 1;  // 设置计数器
        hash_function(var3+480, var3+416, 1);  // 调用哈希函数

        // 再次对var3+416开始的64字节进行异或操作(常数106)
        for(i=0; i<64; i+=4) {
            memory[var3+416+i] ^= 106;
            memory[var3+416+i+1] ^= 106;
            memory[var3+416+i+2] ^= 106;
            memory[var3+416+i+3] ^= 106;
        }

        // 初始化另一个哈希状态并调用哈希函数
        memcpy(var3+520, 1295984, 32);  // 复制32字节
        var3+552 = 1;  // 设置计数器
        hash_function(var3+520, var3+416, 1);  // 调用哈希函数

        // 复制哈希结果
        memcpy(var3+560, var3+480, 80);  // 复制80字节
        memcpy(var3+600, var3+520, 80);  // 复制80字节

        // 从var3+560复制152字节到var3+256
        memcpy(var3+256, var3+560, 152);

        // 初始化var3+336处的65字节为0
        memset(var3+336, 0, 65);

        // 从var3+256复制152字节到var3+104
        memcpy(var3+104, var3+256, 152);

        // 处理数据块
        offset = memory[var3+248];  // 获取当前偏移
        if(offset >= 43) {
            // 如果偏移>=43,处理剩余数据
            remaining = 64 - offset;
            if(remaining != 0) {
                memcpy(var3+184+offset, ptr, remaining);
            }
            var3+136 += 1;  // 增加计数器
            hash_function(var3+104, var3+184, 1);  // 调用哈希函数

            if(offset != 43) {
                memcpy(var3+184, ptr+remaining, offset-43);
            }
            new_offset = 0;
        } else {
            // 如果偏移<43,直接复制数据
            memcpy(var3+184+offset, ptr, 21);
            new_offset = offset + 21;
        }

        // 更新偏移
        memory[var3+248] = new_offset;

        // 从var3+104复制152字节到var3+256
        memcpy(var3+256, var3+104, 152);

        // 在缓冲区的适当位置添加0x80
        buffer_offset = memory[var3+400];
        buffer[var3+336+buffer_offset] = 0x80;

        // 计算消息长度并进行字节序转换
        length = buffer_offset * 8;  // 转换为比特数
        // 进行复杂的字节序转换操作
        length_bytes = convert_to_big_endian_64(length);

        // 处理填充
        if(buffer_offset != 63) {
            // 如果缓冲区不满,填充0
            fill_count = 63 - buffer_offset;
            if(fill_count != 0) {
                memset(var3+336+buffer_offset+1, 0, fill_count);
            }

            if(buffer_offset <= 56) {
                // 如果长度可以放在当前块
                hash_function(var3+256, var3+336, 1);
                // 准备最终块
                memset(var3+560, 0, 80);
                var3+616 = length_bytes;  // 存储长度
                hash_function(var3+256, var3+560, 1);
            } else {
                // 如果长度需要下一个块
                var3+392 = length_bytes;  // 存储长度
                hash_function(var3+256, var3+336, 1);
            }
        } else {
            // 缓冲区已满的情况
            var3+392 = length_bytes;  // 存储长度
            hash_function(var3+256, var3+336, 1);
        }

        // 重置缓冲区偏移
        memory[var3+400] = 32;

        // 对哈希状态进行字节序转换
        for(i=0; i<8; i++) {
            word = *(int*)(var3+256+i*4);
            // 转换为大端序
            big_endian_word = ((word << 24) & 0xFF000000) |
                              ((word << 8) & 0x00FF0000) |
                              ((word >> 8) & 0x0000FF00) |
                              ((word >> 24) & 0x000000FF);
            *(int*)(var3+336+i*4) = big_endian_word;
        }

        // 处理最终的哈希块
        length = *(long long*)(var3+328) * 8;  // 转换为比特数
        length_bytes = convert_to_big_endian_64(length | 0x100);

        // 准备填充
        memset(var3+368, 0, 17);
        memory[var3+368] = 0x80;
        var3+392 = length_bytes;

        // 调用最终的哈希函数
        hash_function(var3+296, var3+336, 1);

        // 获取哈希结果并转换为大端序
        hash0 = *(int*)(var3+296);
        hash1 = *(int*)(var3+300);
        hash2 = *(int*)(var3+304);
        hash3 = *(int*)(var3+308);

        // 将结果存回分配的内存
        *(int*)(ptr+21) = convert_to_big_endian_32(hash0);
        *(int*)(ptr+25) = convert_to_big_endian_32(hash1);
        *(int*)(ptr+29) = convert_to_big_endian_32(hash2);
        *(int*)(ptr+33) = convert_to_big_endian_32(hash3);

        // 第二部分:类似Base64编码

        // 分配输出缓冲区
        output = allocate(200, 4);
        if(output != NULL) {
            var3+416 = 50;  // 设置块大小
            var3+420 = output;  // 设置输出指针
            var3+424 = 0;  // 设置输出索引

            input_index = 0;
            bit_buffer = 0;
            bit_count = 0;
            input_ptr = ptr;

            // 处理输入数据
            for(byte_index=0; byte_index<37; byte_index++) {
                // 读取一个字节
                byte = input_ptr[byte_index];

                // 添加到位缓冲区
                bit_buffer = (bit_buffer << 8) | byte;
                bit_count += 8;

                // 处理完整的6位组
                while(bit_count >= 6) {
                    // 提取6位
                    index = (bit_buffer >> (bit_count - 6)) & 0x3F;

                    // 检查是否需要调用函数
                    if(var3+416 == input_index) {
                        call_function(var3+416);
                    }

                    // 从表中查找并存储
                    table_value = lookup_table[1295903 + index];
                    output[input_index] = table_value;
                    input_index++;
                    var3+424 = input_index;

                    bit_count -= 6;
                }
            }

            // 处理剩余的位
            if(bit_count > 0) {
                // 提取剩余的位
                index = (bit_buffer << (6 - bit_count)) & 0x3F;

                // 检查是否需要调用函数
                if(var3+416 == input_index) {
                    call_function(var3+416);
                }

                // 从表中查找并存储
                table_value = lookup_table[1295903 + index];
                output[input_index] = table_value;
                input_index++;
                var3+424 = input_index;
            }

            // 块结束
        } else {
            // 内存分配失败
            error(4, 200);
        }
    } else {
        // 内存分配失败
        error(1, 37);
    }
}
  • 直接查内存就可以定位到flag了,顺便写了个dump的函数
__exports.Dump = function(){
    ptr = 1048340;
    len = 4;
    buffer = getArrayU8FromWasm0(ptr,len);
    ptr = 0;
    for (let i = 0; i < buffer.length; i++) {
        console.log("buffer["+i+"]="+buffer[i]);
        ptr = (ptr << 8) | buffer[buffer.length - i - 1]; 
    }
    console.log("Address:"+ptr);
    strbuf = getStringFromWasm0(ptr,200);
    const codestr = strbuf.replace(/[^a-zA-Z0-9?!]/g, '');
    console.log("CodeLen:"+codestr.length);
    console.log("CheckCode:"+codestr);
}
  • 断点断下来后,就可以直接调试窗口中运行此函数,即可得到50字节的flag
  • 总之做这道题有点费劲,不太懂JS,完全现学现卖
  • 也不知道用啥工具来进行分析,此题全作仍赖AI的翻译,解的题的方法也比较笨,欢迎指点

总结

  • 今年中级题比较多,而且题出的也比较有意思,作者们都是有故事的人啊
  • 今年开始学习了python,但是不是很熟,题解也尽量都用python开写的,写的不好,多多指点
  • 个人来讲中级题免强能做,但是做出来还是比较费劲,由其代码中加入一些混淆以后,对我的干扰还是很大的,不知道有没有大佬讲讲如何在IDA中去混淆,让代码可读性更好一些
  • 解第七题的时候,一开始一直想着是不是需要暴力破解,写题解的时候,我才发现,作者有提醒,暴力不可取,哎,作者用心良苦啊,耐何我才看到,呵呵
  • 还有那个Android代码的混淆,对于我来说直的是无从下手,目前也搜不到好的方法可以反混淆,不知道有没有大佬能提点一二
  • 今年中级题遇到的最大的挑战就是,Android中的反调试,一点都不会破,IDA一附加上去程序就崩,我也泪崩
  • 总之,通过跟着论坛的贴子一点一点的积累,从啥也不懂的小白,到今年,自我感觉成绩还不错,明年若有机会,我会继续加油努力
  • 最后,希望吾爱论坛越做越好,吾爱百年,百年吾爱!

免费评分

参与人数 5吾爱币 +4 热心值 +4 收起 理由
蚯蚓翔龙 + 1 + 1 热心回复!
gunxsword + 1 + 1 热心回复!
Breezy_Saint + 1 + 1 我很赞同!
代陌 + 1 谢谢@Thanks!
yxzzz666 + 1 我很赞同!

查看全部评分

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

沙发
z297171662 发表于 2026-3-4 09:26
吾爱百年,百年吾爱!点赞
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-3-4 11:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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