首页
社区
课程
招聘
[分享]KCTF 2026 - Rosetta Calibration
发表于: 2026-8-13 20:49 12

[分享]KCTF 2026 - Rosetta Calibration

2026-8-13 20:49
12

题目概述

kctf_rosetta.exe 是一个 64 位 Windows 控制台程序(PE32+ / x86-64),纯静态链接,无加壳,.text 段仅约 0x556 字节。

  • 已知合法键对:
    • Name : CAAA1C964B15D9B3
    • Serial : $/.;-):,(-#!%*-&?+@
  • 目标:求出 Name = KCTF 的 Serial。
  • 程序提示语:
    • Sixteen symbols. Sixteen meanings. One surviving record is the dictionary.(16 个符号 ↔ 16 个含义,唯一的幸存记录就是“字典”)

解题环境

ubuntu + objdump + wine

一、基本信息收集

file kctf_rosetta.exe
# PE32+ executable for MS Windows 6.00 (console), x86-64

sha256sum kctf_rosetta.exe
# caaa1c964b15d9b3250be0dc64412e5f64c29e297a971c226e8dd705d6b87e67  (与 PUBLIC_INFO 一致)

strings -a kctf_rosetta.exe
# Serial : / Name   : / [-] Calibration rejected. / [-] Invalid name format.
# [-] The inscription cannot be translated.
# [+] The symbols have found their meanings. / [+] Calibration accepted.
# 导出: ExitProcess / GetStdHandle / ReadFile / WriteFile (KERNEL32.dll)

程序逻辑非常直白:读入 Name → 读入 Serial → 一系列校验 → 输出成功/失败信息。

二、逆向算法

用 objdump -d kctf_rosetta.exe 得到完整反汇编,逐段还原。

1. 输入与格式校验

Name 长度(0x14000123E):

lea -0x21(%rbx),%eax      ; eax = len - 0x21
cmp $0xffffffe0,%eax
jae 0x140001260           ; 合法则继续
; 否则打印 "Invalid name format."

利用无符号比较技巧:len ∈ [1, 32] 才合法。

Name 字符范围(0x140001270 循环):

add $0xdf,%cl
cmp $0x5e,%cl
jae error

等价于要求每个字符 c ∈ [0x21, 0x7E](可打印 ASCII ! ~ ~)。

Serial 长度(0x140001288):

cmp $0x13,%r12d
jne 0x140001354          ; 必须恰好 19 字符

Serial 分隔符(0x140001292 起):

mov $0x4210,%ebp         ; 二进制 0100 0010 0001 0000 -> bit4/bit9/bit14
bt %r14d,%ebp
jb 0x1400012b0           ; 这三位必须是 '-'

即 Serial 格式为 XXXX-XXXX-XXXX-XXXX,第 4、9、14 位为 -,其余 16 位是“符号”。

2. 字典解码(Serial → 16 个 nibble)

函数 0x140001540:

add $0xdf,%cl            ; 相当于 c - 0x21 (对 '!'..'@')
cmp $0x1f,%cl
ja return -1
mov 0x140002000(%rax*4),%eax   ; 查字典表

0x140002000 处的 32 个 DWORD 就是“字典”(每个索引对应一个字符 !~`@`):

idx: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
val: 04  -  07 0c 01 09  -  03 0b 0e 06 0d  -  08 0a  -
idx: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
val:  -   -   -   -   -   -   -   -   -   -  0f 05  -   -   -  03

字符到 nibble 的唯一映射(注意函数 0x140001520 对 '(' 额外 ^1,即 '(' 表值 3 → 实际 2,弥补表中缺失的 nibble 2):

| nibble | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|----|
| 字符 | ? | % | ( | @ | ! | ; | + | # | . | & | / | ) | $ | , | * | : |

校验中还有:

  • 解码值必须 ≤ 0xF(合法 nibble);
  • 用位掩码 edi 检查 16 个 nibble 互不重复,最终 edi == 0xFFFF;
  • 数量必须恰好 16 个。

即 Serial 的 16 个符号解码成 0~15 的一个排列。

3. Name → 期望 nibble(核心函数 0x1400013E0)

参数:rcx = name, edx = len, r8 = 输出 16 字节。

uint8_t T[16] = {0,1,2,...,15};
uint32_t sum = 0, r9 = 0;

for (int i = 0; i < len; i++) {
    uint8_t c = name[i];
    uint32_t nib = (c >> 4) ^ c;        // 高半字节 ^ 低半字节
    sum += nib;
    uint32_t j = (nib ^ r9) & 0xF;      // r9 = 3*i
    uint32_t k = i & 0xF;
    swap(T[k], T[j]);                    // 交换
    r9 += 3;
}

int p = sum & 0xF;
for (int k = 0; k < 16; k++)
    out[k] = T[(p + k) & 0xF];

说明:

  • r9 每轮累加 3,等价于 3*i,注意它并不是死代码,它参与交换下标 j 的异或运算。
  • 输出位置 8 在汇编里用 T[(p & 0xF) ^ 8],因 x ^ 8 == (x+8) & 0xF,等价于 T[(p+8)&0xF],只是编译器的优化写法。
  • 输出第 15 位用 T[(p-1) & 0xF],同样等价于 (p+15)&0xF。

4. 最终比对

movdqa 0x40(%rsp),%xmm0        ; Serial 解码出的 16 个 nibble
pcmpeqb 0x30(%rsp),%xmm0       ; Name 计算出的 16 个期望 nibble
pmovmskb %xmm0,%eax
xor $0xffff,%eax
jne rejected

Serial 解码得到的排列必须与 Name 推导出的排列逐字节相等。

三、编写 Keygen 并验证

按上述算法用 Python 实现,先用已知合法键对自检:

TABLE = [0x04,-1,0x07,0x0c,0x01,0x09,-1,0x03,
         0x0b,0x0e,0x06,0x0d,-1,0x08,0x0a,-1,
         -1,-1,-1,-1,-1,-1,-1,-1,
         -1,0x0f,0x05,-1,-1,-1,0x00,0x03]
INV = {4:'!', 1:'%', 7:'#',12:'$',9:'&',3:'@',11:')',14:'*',
       6:'+',13:',',8:'.',10:'/',15:':',5:';',0:'?',2:'('}

def name_nibbles(name):
    T = list(range(16)); s = 0; r9 = 0
    for i, ch in enumerate(name):
        c = ord(ch)
        nib = (c >> 4) ^ c
        s += nib
        j = (nib ^ r9) & 0xF
        T[i & 0xF], T[j] = T[j], T[i & 0xF]
        r9 += 3
    p = s & 0xF
    return [T[(p+k) & 0xF] for k in range(16)]

def make_serial(name):
    n = name_nibbles(name)
    s = ''.join(INV[v] for v in n)
    return s[0:4]+'-'+s[4:8]+'-'+s[8:12]+'-'+s[12:16]

自检结果:对合法 Name CAAA1C964B15D9B3 生成的 Serial 恰为 $/.;-):,(-#!%*-&?+@,与题面完全一致,算法正确。

四、求解目标

Name = KCTF
期望 nibbles = [9,10,3,12, 13,14,0,15, 4,7,11,1, 5,6,2,8]
Serial = &/@$-,*?:-!#)%-;+(.

五、真机验证(Wine)

安装 wine 后直接运行验证:

$ printf 'KCTF\n&/@$-,*?:-!#)%%-;+(.\n' | wine kctf_rosetta.exe
...
[+] The symbols have found their meanings.
[+] Calibration accepted. Key correct!

用错误的 Serial 测试则被拒绝:

$ printf 'KCTF\nAAAAAAAAAAAAAAAAAAA\n' | wine kctf_rosetta.exe
[-] The inscription cannot be translated.

最终答案

Name   : KCTF
Serial : &/@$-,*?:-!#)%-;+(.

冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回