看雪学院 08月25日
KCTF 2025 比赛:智破幻阵题解与设计思路
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

2025 KCTF 比赛中的“智破幻阵”题目以其独特的 MOV 混淆和 IOCP 模拟虚拟机指令执行机制,对选手提出了较高的逆向挑战。该题采用两字节加法表优化 MOV 混淆,并利用“调用码”和异常分发机制实现复杂混淆。解题思路主要在于通过动态调试分析异常处理和数据流,识别出虚拟机的操作,并逆向推导出加密算法。最终,通过模拟虚拟机执行并逆向变换,可以找到正确的序列号。

💡 **题目核心技术:** “智破幻阵”题目的主要难点在于其高度混淆的代码,采用了优化的 MOV 混淆技术,使用两字节加法表配合进位实现加法,并引入“调用码”机制,通过异常分发来执行特定的 MOV 指令,模拟虚拟机指令执行,极大地增加了逆向分析的难度。

🔍 **解题思路与方法:** 解决此题的关键在于深入理解其混淆机制。选手需要通过动态调试(如使用 x64dbg)来跟踪程序执行流程,重点关注异常处理( VEH - Vectored Exception Handling)和 IOCP(I/O Completion Ports)的使用。通过分析异常发生时的地址和数据,推断出虚拟机的操作码、参数传递方式以及返回值的处理,逐步还原出其加密算法。

⚙️ **加密算法解析:** 题目中的加密过程是将用户名进行 SHA256 哈希,然后将哈希结果的每两位作为变换选择子,应用于一系列的变换(共128种)。解题者需要逆向分析这些变换的具体实现(如 `dt0` 到 `dt3` 函数及其逆向 `et0` 到 `et3`),并将这些变换序列逆向应用到最终的加密结果上,最终得到全零的输入,从而推导出正确的密码(序列号)。

2025 KCTF 2025-08-25 17:59 上海

本题只有3支战队成功提交flag

2025 KCTF 于8月15日中午12点正式开赛!比赛设置了多维度的评分体系,包括难度值、火力值和精致度积分。今天中午12点,第五题《智破幻阵》已截止答题。

*注意:签到题《废柴少年觉醒》持续开放,整个比赛期间均可提交答案获得积分

本题只有3支战队成功提交flag,看来难度不小呀~他们分别是:tacesrever】、【1023】、【雨落星沉

KCTF评委团队点评:

本题通过 mov 完备性与异常分发机制 实现复杂混淆,结合 IOCP 模拟虚拟机指令执行,考察选手对内存数据流、异常处理及自定义指令逆向的理解。算法设计简洁却颇具趣味,解题思路在分析数据变换关系后逐步清晰,兼具学习与挑战价值。

一起来看看本题的设计思路和解题思路吧~

一、

设计思路

出题者:n00bzx

来自【newbiezx 战队】

公开用户名:59A35804E4F4C235

公开序列号:7FF3675E036335E65E5D29D121FC149197556E6EA39F2E1A1A05437A9609EBC2

 

答案用户名:KCTF

答案序列号:AC1999741568E0A6B7CA93FD53FD900AE8EB599B8AE49E0797E91B106BE5DA53

 

题目名:FZREU考试系统登录器

错误提示:

正确提示:(在我的机器上运行1~2s即可输出结果)

混淆设计思路

由于网上常见的mov混淆会使用4字节加法表,导致程序文件空间过大。所以我做了优化,使用2字节加法表,配合进位实现mov加法,类似的方法用于其他表,大大减少了表数据的体积,进而减少了程序大小。但是代码较为复杂,内联同样会文件大小巨大,所以与传统mov混淆的内联运算符不同,所以我使用了调用码方式执行。就是输入一个fsgs段的地址,跳转到对应运算符的mov指令实现.如:

事先安装好veh,由于该地址的访问必定会产生异常,所以可以根据异常发生地址读取指令操作数头部的调用码(图中为0x12),进入分发器,分发到指定运算符的mov指令实现去执行。

分发器分为主分发器,api分发器和调用分发器,主分发器中实现了5个调用,这几个调用的调用码(图中为0x3)紧跟着magic(0x66)。如图:

00:一般调用分发器 

01:winapi分发器 

02:绝对跳转 

03:虚拟栈push 

04:虚拟栈pop

一般调用分发器:准备一个表,分发器查表跳转。

winapi分发器:iat地址提取,写入表,查表跳转。

虚拟栈push:将需要push的内容写入虚拟栈操作寄存器,然后移动栈指针写入栈顶。

虚拟栈pop:pop对应内容到虚拟栈操作寄存器。

调用号码表(0开始enum):

虚拟栈主要用于mov代码中寄存器的保存和一些绝对跳转。

跳转分为两种,一种为调用分发器中的跳转调用,使用跳转表中的跳转偏移id进行跳转,另一种在虚拟栈上,根据压栈的绝对地址跳转。条件跳转为第一种,mov函数调用的调用和返回为第二种。

不是所有的栈操作都在虚拟栈上,未被混淆函数和api的返回地址在原栈上,因为要响应未混淆的ret指令。

设置的虚拟寄存器如下:

reg0-reg16 通用寄存器

reg17,reg18 参数寄存器

reg19,reg20 返回值寄存器

21,22,23,24 OF,SF,ZF,CF (标志寄存器)

reg25-reg60 通用寄存器

reg61 虚拟栈顶寄存器

reg62 虚拟栈操作寄存器

reg63 循环计数器

为了省时间(技术差),标志寄存器没有全部实现,仅仅实现了常见的那4个。通用寄存器用于mov调用,参数寄存器用于传递参数到mov调用,返回地址寄存器用于暂存mov调用的返回值。

reg25-reg60用处繁多,用于选择器和一些寄存器的备份(比如当虚拟寄存器被用来存储程序基址的时候,需要先备份被使用的虚拟寄存器)。

使用简单的几条中间指令,再由中间指令转化为mov指令,中间指令如下:

中间指令的使用详见mov_il.code,mov指令的转换详见Stockholm.h/.cpp,mov代码详见程序文件。

混淆是以函数为单位的,所以有部分函数没混淆(由于我水平太低,多线程支持没搞,带锁的函数没混淆,以后会慢慢搞),还有部分(其实是一大堆)指令没支持的,以及一些可优化的,残余的设定部分,以后再慢慢搞。

题目设计思路(源码见原帖附件):

使(lan)用了winiocp模型,取其vm特性,以控制码为opcode

输入用户名和序列号,序列号长度为64.转成数值后,分成8dword

使用PostQueuedCompletionStatus发送完成数据包连接opcode,使用GetQueuedCompletionStatus阻塞接收opcode,根据每个opcode,对包中id对应的dword进行一次opcode对应的加密操作。

opcode 2333为按钮单击事件,opcode 6666为结束,0,1,2,3都为计算指令,每个指令均满足交换律并且可逆。

将用户名进行sha256 hash,将结果每2 bit转化为128位opcode,应用于8个dword。

最后,如果八个dword的结果均为0则正确,其他为错误,8个dword均可乱序执行。

输入64个大写hex字符,验证输入合法并转换分割后,将id乱序,起到迷惑性。混淆了线程函数(单线程,无锁)和sha256的几个函数.

题目概览:

ida打开,定位到start

打开入口点,动态获取api,设置veh,分配内存。

winapi实现的窗口创建

iocp初始化

线程函数反编译失败

函数stub,目的是从未被混淆的函数调用一个被混淆的函数。

所谓混淆大概看过去是这样的。

解题参考过程:

首先安装了veh,然后发现程序一直发生异常,看到iocp后,应该要去找线程函数,找到线程函数后,按c识别代码,会发现全是mov指令。

这种模式出现得很多,往6088和6090写入的东西,然后触发异常。

第一次触发异常之前会传入一个地址,这个地址为第二次异常发生地址的后一条,跟进第一次异常,动态调试,会发现第二次异常后会跳回到该地址。

同时会发现回到该地址之后,总会从6098取东西,动态调试可发现为返回结果。  

多次黑盒即可猜测该异常为运算符调用,6088,6090为参数,6098为返回,根据返回结果,判断该调用为用于替换运算符的mov代码,并得出对应运算符。

往下翻,发现一类形式不同的异常,动调跟进发现为api函数。

发现初始化了iocp,从窗体函数还能发现发送了0x91d完成请求,又调用了获取请求的函数,推断题目使用了iocp做了一些操作。

然后在GetQueuedCompletionStatus上下断点,返回后在返回位置下断点,拦截接收到的完成请求包。

找到输入内容,发现宽字节转单字节,再由16进制字符串被转成字节数据。

在其上面下写入断点,每次改变的时候都会进入调用。

离开调用后,观察每次调用的结果。

在发送完成请求的函数(PostQueuedCompletionStatus)上下断点。

会发现只有4个handler.(4个opcode,为完成请求发送参数)  

向上翻代码,并浏览内存即可获得栈中的opcode数据。根据每一个mov调用的含义和opcode推出每一个handler的含义。

写出每一个handler,模拟虚拟机代码(具体见keygen源码),进一步逆向后,发现6666 opcode的handler,会判断加密结果并输出正误判断信息多次。

输入给出的序列号和结果,发送6666号opcode后,在接收处下断。

通过动调获得接收到的结果:

接收随意输入的序列号产生的结果。得出结论,正确的加密结果为全0(每个dword均为0).写出模拟虚拟机算法,从0倒推即可得出正确序列号,具体脚本见keygen。

附件里有原版的抽风报告,其实这就是一个简单的指令替换,指令结构和去年一样都没混淆,也没有表达式混淆,也没有反调试(有异常了,就没加)。本来就是尽量为了趣味(感谢公司老大教我的,ctf题不是越难越好,而是要有趣味性),展示下mov混淆的思路,所以算法很简单。出题时初步估计5解左右,明年会加上一些奇奇怪怪的混淆。

混淆源码请到原帖下载:https://bbs.kanxue.com/thread-287463.htm

二、

赛题解析

该解析由论坛会员ID:HHHso提供

该题定义四种变换,通过对用户名进行sha256,对256位hash值每两位构成变换选择子,构成一组128个变换选择子构成的变换序列,对32位0开始逆变换得到用户密码,用户密码正变换得到32位0则正确。题目利用mov的完备性和异常分发机制对原控制流进行混淆,但这里我们不考虑混淆代码控制流和相关数据流,直接分析内存数据变换情况着手。

Step 1

一眼32字节长password,测试是否几种常见的hash,如下图,不是直接hash结果,也可以考虑多迭代几次试试。

un='59A35804E4F4C235'

pw='7FF3675E036335E65E5D29D121FC149197556E6EA39F2E1A1A05437A9609EBC2'

import hashlib

for in dir(hashlib):

    if n.startswith(('md5','sha')):

        try:

            hf = getattr(hashlib,n)

            hv = hf(un.encode()).hexdigest().upper()

            print(f"{n.ljust(8)} {len(hv)//2} {hv}")

        except Exception as er:

            pass

Step 2

但在登录成功后,我们检索内存,可以看到有疑似有sha256(user_name)的存在,如下图:

Step 3

xdbg64中,在GetWindowTextW下断点,输入登录断下,在dump中追踪获取username或password的初步存放内存及其附近,如下图:

Step 4

在内存变化分析中,我们得到几个关键内存位置,8字(32字节)每字计算结果中间存放位置,字序位置、变换选择子位置、变换次序位置;

这些位置的值,也可以在输入username和password点击登录后进行观察:

Step 5

在计算结果存放位置设置硬件写断点,通过计算结果的变换,得到变换关系,如下是结果值变换情况样例:

x0=t0(xi) #00

E6356303 19CA9CFC F8339539

5E67F37F A1980C80 01433019

D1295D5E 2ED6A2A1 425DAD45

9114FC21 6EEB03DE BCDDD607

6E6E5597 9191AA68 D1232354

1A2E9FA3 E5D1605C B9CBA2C0

7A43051A 85BCFAE5 CB0B79F5

C2EB0996 3D14F669 D27A29EC

x1=t0(x0) #01

F8339539 07CC6AC6 8C0F98D5

01433019 FEBCCFE6 CDFD799F

425DAD45 BDA252BA 757B44A5

BCDDD607 432229F8 F0864453

D1232354 2EDCDCAB 565DB9B9

B9CBA2C0 46345D3F 7E8C68BA

CB0B79F5 34F4860A 1469E90C

D27A29EC 2D85D613 265B0BAC

x2=t2(x1) #02

8C0F98D5 AC607CC6 3C6D8CCB

CDFD799F FE6FEBCC 6E621BC1

757B44A5 2BABDA25 BBA62A28

F0864453 9F843222 0F89C22F

565DB9B9 CAB2EDCD 5ABF1DC0

7E8C68BA D3F46345 43F99348

1469E90C 60A34F48 F0AEBF45

265B0BAC 6132D85D F13F2850

x3=t1(x2) #03

3C6D8CCB 86C03675 06CEB0D8 F9314F27

6E621BC1 D4CFA17F F42FFA99 0BD00566

BBA62A28 010B9096 7212C021 8DED3FDE

0F89C22F B5247891 8F1236A4 70EDC95B

5ABF1DC0 E012A77E 54EFDC02 AB1023FD

43F99348 F95429F6 853EDF2A 7AC120D5

F0AEBF45 4A0305FB 60BF6940 9F4096BF

F13F2850 4B9292EE 525DC972 ADA2368D

x4=t1(x3) #04

F9314F27 439CF599 9EB32873 614CD78C

0BD00566 B17DBFD8 B7FB162F 4804E9D0

8DED3FDE 37408560 10AC06E8 EF53F917

70EDC95B CA4073E5 0E7CB948 F18346B7

AB1023FD 11BD9943 B3286237 4CD79DC8

7AC120D5 C06C9A6B 934D780D 6CB287F2

9F4096BF 25ED2C01 A58024BD 5A7FDB42

ADA2368D 170F8C33 F18662E1 0E799D1E

x5=t1(x4) #05

614CD78C DBE16D32 2DA65B7C D259A483

4804E9D0 F2A9536E 2A6DDE55 D59221AA

EF53F917 55FE43A9 C8752ABF 378AD540

F18346B7 4B2EFC09 DF812965 207ED69A

4CD79DC8 F67A2776 44EEDECF BB112130

6CB287F2 D61F3D4C E7A99AC3 1856653C

5A7FDB42 E0D261FC 4C3F9C1A B3C063E5

0E799D1E B4D427A0 84F4169A 7B0BE965

x6=t0(x5) #06

D259A483 2DA65B7C F85B4CB6

D59221AA 2A6DDE55 AA54DBBC

378AD540 C8752ABF 7F90EA55

207ED69A DF812965 CBBF0252

BB112130 44EEDECF 9E89DDBD

1856653C E7A99AC3 87CF5335

B3C063E5 4C3F9C1A 34987F38

7B0BE965 84F4169A 3509E82D

x7=t3(x6) #07

F85B4CB6  1:C2DA65B7  2:16D32DBE 

AA54DBBC  1:52A6DDE5 

7F90EA55  1:FC8752AB  2:E43A955F  3:21D4AAFF 

CBBF0252  1:5DF81296  2:EFC094B2  3:7E04A597  4:F0252CBB 

9E89DDBD  1:F44EEDEC  2:A2776F67  3:13BB7B3D  4:9DDBD9E8  5:EEDECF44 

87CF5335  1:3E7A99AC  2:F3D4CD61  3:9EA66B0F  4:F533587C  5:A99AC3E7  6:4CD61F3D 

34987F38  1:A4C3F9C1  2:261FCE0D  3:30FE7069  4:87F38349  5:3F9C1A4C  6:FCE0D261  7:E706930F 

3509E82D  1:A84F4169  2:427A0B4D  3:13D05A6A  4:9E82D350  5:F4169A84  6:A0B4D427  7:05A6A13D  8:2D3509E8 

x8=t3(x7) #08

16D32DBE  1:B6996DF0  2:B4CB6F85 

52A6DDE5  1:9536EF2A 

21D4AAFF  1:0EA557F9  2:752ABFC8  3:A955FE43 

F0252CBB  1:812965DF  2:094B2EFC  3:4A5977E0  4:52CBBF02 

EEDECF44  1:76F67A27  2:B7B3D13B  3:BD9E89DD  4:ECF44EED  5:67A2776F 

4CD61F3D  1:66B0F9EA  2:3587CF53  3:AC3E7A99  4:61F3D4CD  5:0F9EA66B  6:7CF53358 

E706930F  1:3834987F  2:C1A4C3F9  3:0D261FCE  4:6930FE70  5:4987F383  6:4C3F9C1A  7:61FCE0D2 

2D3509E8  1:69A84F41  2:4D427A0B  3:6A13D05A  4:509E82D3  5:84F4169A  6:27A0B4D4  7:3D05A6A1  8:E82D3509 

x9=t3(x8) #09

B4CB6F85  1:A65B7C2D  2:32DBE16D 

9536EF2A  1:A9B77954 

A955FE43  1:4AAFF21D  2:557F90EA  3:ABFC8752 

52CBBF02  1:965DF812  2:B2EFC094  3:977E04A5  4:BBF0252C 

67A2776F  1:3D13BB7B  2:E89DDBD9  3:44EEDECF  4:2776F67A  5:3BB7B3D1 

7CF53358  1:E7A99AC3  2:3D4CD61F  3:EA66B0F9  4:533587CF  5:99AC3E7A  6:CD61F3D4 

61FCE0D2  1:0FE70693  2:7F383498  3:F9C1A4C3  4:CE0D261F  5:706930FE  6:834987F3  7:1A4C3F9C 

E82D3509  1:4169A84F  2:0B4D427A  3:5A6A13D0  4:D3509E82  5:9A84F416  6:D427A0B4  7:A13D05A6  8:09E82D35 

x10=t2(x9) #0A

32DBE16D 6996DF0B F99B2F06

A9B77954 A54DBBCA 35404BC7

ABFC8752 955FE43A 05521437

BBF0252C 65DF8129 F5D27124

3BB7B3D1 89DDBD9E 19D04D93

CD61F3D4 A66B0F9E 3666FF93

1A4C3F9C E0D261FC 70DF91F1

09E82D35 A84F4169 3842B164

Step 6

通过分析,我们得到四种变换,及其逆变换,python复现如下,其中rorx_w和one_count用于发现计算结果之间的计算关系,如果a和b通过one_count得到其中为1的位数相同,则考虑用rorx_w发现是否循环移动及移动位数;如果和位32,则优先考虑取反等。

import struct

from ctypes import *

 

def rorx_w(w):

    for in range(0x20):

        print(f'{i:2} {ror32(w,i):08X}')

 

def one_count(s):

    if isinstance(s,int):

        return bin(s).count('1')

    return bin(int(bytes.fromhex(s)[::-1].hex(),0x10)).count('1')

 

def notw(w):

    return c_ulong(~c_ulong(w).value).value

 

def rorw(w,s):

    s=s%32

    return ((w>>s)|(w<<(32-s)))&0xFFFFFFFF

 

def rorwc(w,s,c):

    for in range(c):

        w=rorw(w,s)

    return w

 

def pws(ws,file=sys.stdout):

    wstr = ",".join([f"{w:08X}" for in ws])

    print(f"{wstr}",file=file)

 

def dt0(xws,file=sys.stdout):

    yws = [ror32(notw(w),7for in xws]

    pws(yws,file)

    return yws

 

def et0(xws,file=sys.stdout):

    yws = [notw(ror32(w,32-7)) for in xws]

    pws(yws,file)

    return yws

 

def dt1(xws,file=sys.stdout):

    yws = [notw(ror32(w^0xbaadbabe,19)) for in xws]

    pws(yws,file)

    return yws

 

def et1(xws,file=sys.stdout):

    yws = [c_ulong(0xbaadbabe^ror32(notw(w),32-19)).value for in xws]

    pws(yws,file)

    return yws

 

def dt2(xws,file=sys.stdout):

    yws = [c_ulong(0x900df00d^ror32(w,5)).value for in xws]

    pws(yws,file)

    return yws

 

def et2(xws,file=sys.stdout):

    yws = [ror32(0x900df00d^w,32-5for in xws]

    pws(yws,file)

    return yws

 

def dt3(xws,file=sys.stdout):

    sc=[2,1,3,4,5,6,7,8]

    yws = [rorwc(w,29,c) for w,c in zip(xws,sc)]

    pws(yws,file)

    return yws

 

def et3(xws,file=sys.stdout):

    sc=[2,1,3,4,5,6,7,8]

    yws = [rorwc(w,32-29,c) for w,c in zip(xws,sc)]

    pws(yws,file)

    return yws

 

dts=[dt0,dt1,dt2,dt3]

ets=[et0,et1,et2,et3]

 

def loadfbs(file):

    with open(file,'rb') as fin:

        return fin.read()

 

def load_trs(file):

    fbs = loadfbs(file)

    trs = list(struct.unpack('128L',fbs))

    return trs

Step 7

实测中,我们发现128次(0x80)的变换序列就在内存中,如下图,

可通过savedata "D:\ctf05\trs.bin",0x68CFBA0,0x200 命令保存

通过加载变换序列,我们可以观察password验证计算过程,如下:

trs1=load_trs(r'D:\ctf05\trs.bin')

 

rp='7FF3675E036335E65E5D29D121FC149197556E6EA39F2E1A1A05437A9609EBC2'

rws=list(struct.unpack('8L',bytes.fromhex(rp)))

xi = [rws[1],rws[0]]+rws[2:]

 

= xi

for i,t in enumerate(trs1):

    print(f"{i:02X}: ",end='')

    = dts[t](x)

 

     

00: F8339539,01433019,425DAD45,BCDDD607,D1232354,B9CBA2C0,CB0B79F5,D27A29EC

018C0F98D5,CDFD799F,757B44A5,F0864453,565DB9B9,7E8C68BA,1469E90C,265B0BAC

023C6D8CCB,6E621BC1,BBA62A28,0F89C22F,5ABF1DC0,43F99348,F0AEBF45,F13F2850

03: F9314F27,0BD00566,8DED3FDE,70EDC95B,AB1023FD,7AC120D5,9F4096BF,ADA2368D

04614CD78C,4804E9D0,EF53F917,F18346B7,4CD79DC8,6CB287F2,5A7FDB42,0E799D1E

05: D259A483,D59221AA,378AD540,207ED69A,BB112130,1856653C,B3C063E5,7B0BE965

...

7B8AA48A82,55245414,51549150,0A2A922A,41455245,A828AA48,15051549,22A0A2A9

7C: A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2

7D: BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE

7E: FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF

7F00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

逆变换过来就可以得到password:

yi = [0]*8

= yi

for i,t in enumerate(trs1[::-1]):

    print(f"{i:02X}: ",end='')

    = ets[t](y)

 

yws = [y[1],y[0]]+y[2:]

yp=struct.pack('8L',*yws).hex().upper()

print(f"password: {yp}")

 

00: FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF,FFFFFFFF

01: BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE,BAADBABE

02: A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2,A922A0A2

038AA48A82,55245414,51549150,0A2A922A,41455245,A828AA48,15051549,22A0A2A9

04: ADBABEBA,6DD5F5D5,55B757D7,EAB6EAFA,5D56DD5F,EBAADBAB,7D755B75,AFAEAB6E

0522A0A2A9,15051549,24541455,A48A828A,54915051,2A922A0A,45524541,28AA48A8

...

7B: F9314F27,0BD00566,8DED3FDE,70EDC95B,AB1023FD,7AC120D5,9F4096BF,ADA2368D

7C3C6D8CCB,6E621BC1,BBA62A28,0F89C22F,5ABF1DC0,43F99348,F0AEBF45,F13F2850

7D8C0F98D5,CDFD799F,757B44A5,F0864453,565DB9B9,7E8C68BA,1469E90C,265B0BAC

7E: F8339539,01433019,425DAD45,BCDDD607,D1232354,B9CBA2C0,CB0B79F5,D27A29EC

7F: E6356303,5E67F37F,D1295D5E,9114FC21,6E6E5597,1A2E9FA3,7A43051A,C2EB0996

password: 7FF3675E036335E65E5D29D121FC149197556E6EA39F2E1A1A05437A9609EBC2

Step 8

同理,我们输入KCTF,登录,然后通过savedata命令,dump处变换序列,加载,进行逆变换,即可得到【KCTF】的password【AC1999741568E0A6B7CA93FD53FD900AE8EB599B8AE49E0797E91B106BE5DA53】

trs2=load_trs(r'D:\ctf05\trs_kctf.bin')

yi = [0]*8

= yi

for i,t in enumerate(trs2[::-1]):

    print(f"{i:02X}: ",end='')

    = ets[t](y)

 

yws = [y[1],y[0]]+y[2:]

yp=struct.pack('8L',*yws).hex().upper()

print(f"password: {yp}")

Step 9

实际分析128次(0x80)变换序列,我们可以发现,其实际就是sha256(user_name)的256位值中,每两位两两组成的变换选择子(从四种变换中选一种),于是我们可以写出相应的keygen:

import hashlib

 

def key_from(trs):

    yi = [0]*8

    = yi

    for i,t in enumerate(trs[::-1]):

        = ets[t](y)

    #

    yws = [y[1],y[0]]+y[2:]

    yp=struct.pack('8L',*yws).hex().upper()

    return yp

 

def keygen(uname='KCTF'):

    if isinstance(uname,str):

        uname=uname.encode()

    elif isinstance(uname,bytes):

        pass

    else:

        assert 0,f'uname should be bytes for str'

    #

    int256 = int(hashlib.sha256(uname).digest()[::-1].hex(),0x10)

    tbit=2

    tmsk=(1<<tbit)-1

    tcnt=256//2

    trs = [0]*tcnt

    for in range(tcnt):

        trs[i]=int256&tmsk

        int256>>=tbit

    #

    return key_from(trs)

Step 10 

以下参考AI问答

1

扫码参赛

第六题《秘辛揭露》火热挑战中

关于KCTF

看雪CTF(简称KCTF)是圈内知名度最高的技术竞技,从原CrackMe攻防大赛中发展而来,采取线上PK的方式,规则设置严格周全,题目涵盖Windows、Android、iOS、Pwn、智能设备、Web等众多领域。

扫码进入2025 KCTF

主办方

支持单位

看雪CTF比赛分为两个阶段,所有论坛会员均可参与,第一阶段是防守篇,防守方根据比赛要求制作题目,根据题目被破解的时间排名,被破解时间长者胜出。第二阶段为攻击篇,攻击第一阶段的题目,根据攻击成功的时间与题目排名,破解时间短且破解题目数多者胜。既给了防守方足够的施展空间,也避免过度浪费攻击方的时间。从攻防两个角度看,都是个难得的竞技和学习机会。

看雪CTF比赛历史悠久、影响广泛。自2007年以来,看雪已经举办十多个比赛,与包括金山、360、腾讯、阿里等在内的各大公司共同合作举办赛事。比赛吸引了国内一大批安全人士的广泛关注,历年来CTF中人才辈出,汇聚了来自国内众多安全人才,高手对决,精彩异常,成为安全圈的一次比赛盛宴,突出了看雪论坛复合型人才多的优势,成为企业挑选人才的重要途径,在社会安全事业发展中产生了巨大的影响力。

- End -

球分享

球点赞

球在看

混淆源码,请点击“阅读原文”,查看原帖附件

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

KCTF 2025 智破幻阵 逆向工程 MOV 混淆 VEH IOCP 虚拟机 加密算法 CTF Reverse Engineering Obfuscation Virtual Machine Encryption
相关文章