首页
社区
课程
招聘
[原创] KCTF2025 - 第十题 涅槃,亦是新生 一血题解(Trace/爆破)
发表于: 2025-9-3 07:23 3152

[原创] KCTF2025 - 第十题 涅槃,亦是新生 一血题解(Trace/爆破)

2025-9-3 07:23
3152

题目的 flag 校验字节互不相关,在校验函数调用处下断,以 KCTF{uuid} 作为输入,uuid 取全 0全 f共 16 种各 Tarce 一次,记录正确的字符组成 flag 即可。
图片描述

题目给了一个 exe 一个 dll,dll 并没有数字签名看起来是自己编译的,不排除有魔改的可能,我们先看 exe

exe 程序本身也不小,看起来是静态打了不少库/C++运行时,然后有很多 OoooOooooOOoO 的字样应该是源码位置被抹去了,笔者逆向做得少,并不知道这是什么自动去除异常信息的工具:
图片描述
但这无伤大雅,简单看下两个 TLSCallback 里看起来没有反调试,那就先直接跑起来看看,如果别的地方藏了反调试再说
在输入flag的地方断下来,随便输点什么之后一路跟到一个简易代码块分发的函数,我们在的地方是 case 0,下面还有 case 1-9;
图片描述

这里花了点时间确认上面的其他函数看起来就是加载并输出

的函数,其中 KCTF 2025 基本是直接输出的,但是 Flag: 则是一个一个 char 地输出,看来字符串可能并没有特别复杂的加密,只是逐个输出(后面看来打印正确与错误的字符串也是一样)。

而我们一开始跟入的地方大概读了个 string 进来,(char*)(*((void*)v160+1)) + 16 的地方就是我们的输入

这里想到第四题埋头逆了好久还原逻辑的伤心事,决定先大致看一遍逻辑再决定怎么逆

这里还在想如果是大 vm 就直接不吃了,感觉 CTF 还是多来些技巧/新知识/非预期会比较快乐,做堆工作量的题那不是就和上班一样了吗(?)

首先是紧随读取后面有个类似长度检测的地方:
图片描述
如果预期长度确实是 42 的话,那么输入格式有可能是 flag{uuid} 或者 KCTF{uuid} ;
然后下面 case 4 看起来是正确或者错误的分支,需要一个初始为零的计数器等于 42:
图片描述
case 4 剩余部分输出的方式和上面输出 KCTF 2025 Flag: 的方式一样(逐个 char 输出),是 error! 和 correct!

那么基本可以确定我们在看的就是程序的主体了,接着往下看可以看到一些看起来不那么复杂的校验操作,但是在着手分析前,我们在 case 9 里面找到了更有趣的地方:
图片描述
这里似乎是 16 个 char 一组,逐字节检测,更耐人寻味的是,由于输入长度应该是42,这里在最后一轮会在检测完第 42 个字符之后直接跳出。
逐字节检测 + 随意终止让笔者觉得本题的输入处理或许看起来 16 字节一组,但实际上这 16 字节互不相关,也就是可以试试逐字节爆破:
图片描述
用 KCTF{uuid}作为输入尝试,发现许多格式确定的位返回都是 1,而其他 uuid 的部分则大部分是 0。

这时候就可以写脚本调试试一下了,一个更快速的测试思路是直接用全 0 ~ 全 f 的 16 种 uuid 全试一次,记录返回为 1 的地方,然后打印结果:

发现运气爆棚,和猜测的一样,1s 就拿到了 flag :
图片描述

好辣,就是这样,大家拜拜!

KCTF 2025
Flag:
KCTF 2025
Flag:
import ctypes, frida, time
 
EXE_PATH = r"[X]:\[REDACTED]\hardrock.exe"
MODULE   = "hardrock.exe"
TEST_RVA = 0x57C9F
WANT     = 42
HEXSET   = "0123456789abcdef"
 
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
LPVOID = ctypes.c_void_p; LPWSTR = ctypes.c_wchar_p
DWORD = ctypes.c_uint32; HANDLE = ctypes.c_void_p; BOOL = ctypes.c_int
class STARTUPINFO(ctypes.Structure):
    _fields_=[("cb",DWORD),("lpReserved",LPWSTR),("lpDesktop",LPWSTR),("lpTitle",LPWSTR),
              ("dwX",DWORD),("dwY",DWORD),("dwXSize",DWORD),("dwYSize",DWORD),
              ("dwXCountChars",DWORD),("dwYCountChars",DWORD),("dwFillAttribute",DWORD),
              ("dwFlags",DWORD),("wShowWindow",ctypes.c_ushort),("cbReserved2",ctypes.c_ushort),
              ("lpReserved2",LPVOID),("hStdInput",HANDLE),("hStdOutput",HANDLE),("hStdError",HANDLE)]
class PROCESS_INFORMATION(ctypes.Structure):
    _fields_=[("hProcess",HANDLE),("hThread",HANDLE),("dwProcessId",DWORD),("dwThreadId",DWORD)]
class SECURITY_ATTRIBUTES(ctypes.Structure):
    _fields_=[("nLength",DWORD),("lpSecurityDescriptor",LPVOID),("bInheritHandle",BOOL)]
CreatePipe=k32.CreatePipe; SetHandleInformation=k32.SetHandleInformation; CreateProcessW=k32.CreateProcessW
WriteFile=k32.WriteFile; CloseHandle=k32.CloseHandle; ResumeThread=k32.ResumeThread
WaitForSingleObject=k32.WaitForSingleObject; TerminateProcess=k32.TerminateProcess
STARTF_USESTDHANDLES=0x100; CREATE_SUSPENDED=4; CREATE_NO_WINDOW=0x8000000; HANDLE_FLAG_INHERIT=1
CreatePipe.argtypes=[ctypes.POINTER(HANDLE),ctypes.POINTER(HANDLE),ctypes.POINTER(SECURITY_ATTRIBUTES),DWORD]
SetHandleInformation.argtypes=[HANDLE,DWORD,DWORD]
CreateProcessW.argtypes=[LPWSTR,LPWSTR,ctypes.POINTER(SECURITY_ATTRIBUTES),ctypes.POINTER(SECURITY_ATTRIBUTES),BOOL,DWORD,LPVOID,LPWSTR,ctypes.POINTER(STARTUPINFO),ctypes.POINTER(PROCESS_INFORMATION)]
WriteFile.argtypes=[HANDLE,LPVOID,DWORD,ctypes.POINTER(DWORD),LPVOID]
CloseHandle.argtypes=[HANDLE]; ResumeThread.argtypes=[HANDLE]; WaitForSingleObject.argtypes=[HANDLE,DWORD]; TerminateProcess.argtypes=[HANDLE,DWORD]
 
JS=f"""
'use strict';
const a=Process.getModuleByName("{MODULE}").base.add(ptr({hex(TEST_RVA)}));
var n=0;
Interceptor.attach(a,{{onEnter(){{
  if(n<{WANT}){{const al=this.context.rax.and(0xFF).toInt32();send({{type:'al',i:n,al}});n++;}}
}}}});
"""
 
def spawn(buf):
    sa=SECURITY_ATTRIBUTES(); sa.nLength=ctypes.sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle=True
    r,h=HANDLE(),HANDLE()
    if not CreatePipe(ctypes.byref(r),ctypes.byref(h),ctypes.byref(sa),0): raise OSError
    if not SetHandleInformation(h,HANDLE_FLAG_INHERIT,0): raise OSError
    si=STARTUPINFO(); si.cb=ctypes.sizeof(STARTUPINFO); si.dwFlags=STARTF_USESTDHANDLES; si.hStdInput=r
    pi=PROCESS_INFORMATION()
    if not CreateProcessW(EXE_PATH,None,None,None,True,CREATE_SUSPENDED|CREATE_NO_WINDOW,None,None,ctypes.byref(si),ctypes.byref(pi)): raise OSError
    if buf:
        n=DWORD(0); raw=(ctypes.c_char*len(buf)).from_buffer_copy(buf)
        if not WriteFile(h,raw,len(buf),ctypes.byref(n),None): raise OSError
    CloseHandle(h); return pi,r
 
def run_once(data):
    if not data.endswith(b"\n"): data+=b"\n"
    pi,r=spawn(data); pid=pi.dwProcessId
    s=frida.attach(pid); vals=[None]*WANT; got=0
    def on(m,d):
        nonlocal got
        if m["type"]=="send" and m["payload"].get("type")=="al":
            i=m["payload"]["i"];
            if 0<=i<WANT and vals[i] is None: vals[i]=m["payload"]["al"]; got+=1
    scr=s.create_script(JS); scr.on("message",on); scr.load(); ResumeThread(pi.hThread)
    t=time.time()
    while True:
        if got>=WANT: TerminateProcess(pi.hProcess,0); break
        if WaitForSingleObject(pi.hProcess,0)==0: break
        if time.time()-t>20: TerminateProcess(pi.hProcess,0xDEAD); break
        time.sleep(0.01)
    try: s.detach()
    except: pass
    CloseHandle(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(r)
    return vals
 
def build_payload(ch):
    a=['\0']*WANT; a[0:5]=list("KCTF{"); a[41]='}'
    hy={13,18,23,28}
    for i in range(5,41): a[i]='-' if i in hy else ch
    return "".join(a).encode("ascii")
 
def main():
    unk=[i for i in range(5,41) if i not in {13,18,23,28}]
    fixed={0:'K',1:'C',2:'T',3:'F',4:'{',13:'-',18:'-',23:'-',28:'-',41:'}'}
    dec={i:None for i in unk}
    for ch in HEXSET:
        v=run_once(build_payload(ch))
        hit=[i for i in unk if v[i] is not None and (v[i]&1)]
        print(f"{ch}: {hit}")
        for i in hit:
            if dec[i] is None: dec[i]=ch
    out=['?']*WANT
    for i,c in fixed.items(): out[i]=c
    for i,c in dec.items(): out[i]=c if c else '?'
    print("".join(out))
 
if __name__=="__main__":
    main()
import ctypes, frida, time
 
EXE_PATH = r"[X]:\[REDACTED]\hardrock.exe"
MODULE   = "hardrock.exe"
TEST_RVA = 0x57C9F
WANT     = 42
HEXSET   = "0123456789abcdef"
 
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
LPVOID = ctypes.c_void_p; LPWSTR = ctypes.c_wchar_p
DWORD = ctypes.c_uint32; HANDLE = ctypes.c_void_p; BOOL = ctypes.c_int
class STARTUPINFO(ctypes.Structure):
    _fields_=[("cb",DWORD),("lpReserved",LPWSTR),("lpDesktop",LPWSTR),("lpTitle",LPWSTR),
              ("dwX",DWORD),("dwY",DWORD),("dwXSize",DWORD),("dwYSize",DWORD),
              ("dwXCountChars",DWORD),("dwYCountChars",DWORD),("dwFillAttribute",DWORD),
              ("dwFlags",DWORD),("wShowWindow",ctypes.c_ushort),("cbReserved2",ctypes.c_ushort),
              ("lpReserved2",LPVOID),("hStdInput",HANDLE),("hStdOutput",HANDLE),("hStdError",HANDLE)]
class PROCESS_INFORMATION(ctypes.Structure):
    _fields_=[("hProcess",HANDLE),("hThread",HANDLE),("dwProcessId",DWORD),("dwThreadId",DWORD)]
class SECURITY_ATTRIBUTES(ctypes.Structure):
    _fields_=[("nLength",DWORD),("lpSecurityDescriptor",LPVOID),("bInheritHandle",BOOL)]
CreatePipe=k32.CreatePipe; SetHandleInformation=k32.SetHandleInformation; CreateProcessW=k32.CreateProcessW
WriteFile=k32.WriteFile; CloseHandle=k32.CloseHandle; ResumeThread=k32.ResumeThread
WaitForSingleObject=k32.WaitForSingleObject; TerminateProcess=k32.TerminateProcess
STARTF_USESTDHANDLES=0x100; CREATE_SUSPENDED=4; CREATE_NO_WINDOW=0x8000000; HANDLE_FLAG_INHERIT=1
CreatePipe.argtypes=[ctypes.POINTER(HANDLE),ctypes.POINTER(HANDLE),ctypes.POINTER(SECURITY_ATTRIBUTES),DWORD]
SetHandleInformation.argtypes=[HANDLE,DWORD,DWORD]
CreateProcessW.argtypes=[LPWSTR,LPWSTR,ctypes.POINTER(SECURITY_ATTRIBUTES),ctypes.POINTER(SECURITY_ATTRIBUTES),BOOL,DWORD,LPVOID,LPWSTR,ctypes.POINTER(STARTUPINFO),ctypes.POINTER(PROCESS_INFORMATION)]
WriteFile.argtypes=[HANDLE,LPVOID,DWORD,ctypes.POINTER(DWORD),LPVOID]
CloseHandle.argtypes=[HANDLE]; ResumeThread.argtypes=[HANDLE]; WaitForSingleObject.argtypes=[HANDLE,DWORD]; TerminateProcess.argtypes=[HANDLE,DWORD]
 
JS=f"""
'use strict';
const a=Process.getModuleByName("{MODULE}").base.add(ptr({hex(TEST_RVA)}));
var n=0;
Interceptor.attach(a,{{onEnter(){{
  if(n<{WANT}){{const al=this.context.rax.and(0xFF).toInt32();send({{type:'al',i:n,al}});n++;}}
}}}});
"""
 
def spawn(buf):
    sa=SECURITY_ATTRIBUTES(); sa.nLength=ctypes.sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle=True

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 4
支持
分享
最新回复 (1)
雪    币: 508
活跃值: (2716)
能力值: ( LV13,RANK:429 )
在线值:
发帖
回帖
粉丝
2
666
2025-9-4 13:21
0
游客
登录 | 注册 方可回帖
返回