ByteCTF Guess Cookie 出题思路详解

本文最后更新于 2024.10.28,总计 28099 字 ,阅读本文大概需要 6 ~ 22 分钟
本文已超过 23天 没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

0x01 写在前面

前段时间在字节 CTF 的大师赛上出了一个杂项,算是前段时间研究某个中间件的衍生产物,觉得很有兴趣,最近有时间便拿出来分享一下出题思路

0x02 小玩笑

当今时代,消息中间件已成为实现分布式系统和微服务架构的关键组件之一,市面上的常见的消息中间件主要有ActiveMQ、Kafka、RabbitMQ、RocketMQ,其中 RabbitMQ 主要用于金融业、Kafka 主要用于大数据行业,并且随着时间的推移,RabbitMQ 成为了最受欢迎和使用最多的消息队列之一。但是针对于 RabbitMQ 的利用和安全问题,在业内却少有研究。本题主要以 RabbitMQ 的erlang Node节点通信安全为核心,考察参赛选手对整个通信过程的安全理解。

当打开题目所给的 pcap 数据包后,可以看到172.20.10.24 和 172.20.10.4 之间通信的主要的协议有http、tcp、amqp、 ErlDP 等,如下图所示:

1.png

题目里设置了假的 flag,如果直接搜索 flag,会搜到 fakerflag{ByteDance@2024} 和 this is a faker flag that is ByteCTF{1e1111dce-cdf7-423f-8a8b-a62dec323d17}(开个小玩笑)

2.png

3.png

0x03 通信过程分析

进入正题。
根据题目描述:

请你尝试分析这段数据,解出 172.20.10.24 和 172.20.10.4 之间通信的 cookie

根据已有协议,http 协议的 cookie 为:

4.png

将其 转换为 32 位小写的 md5 后,提交发现 flag 错误,因此看看其他协议:AMQP、EPMD、ErlDP

实际上这三个协议主要是为 RabbitMQ 提供服务的,AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个开放标准的应用层网络协议,用于在分布式系统中进行异步消息传递。它是RabbitMQ等消息中间件广泛支持的主要协议之一。AMQP协议支持多种认证机制,其中PLAIN-SASL(简单认证和安全层)是常用的一种。在PLAIN-SASL认证中,客户端通过提供用户名(username)和密码(password)进行身份验证。

除此之外,AMQP还支持其他SASL机制,如 EXTERNAL、ANONYMOUS 等,具体使用哪种机制取决于服务器的配置和安全需求。认证成功后,客户端需要经过一系列步骤才能开始消息传输,包括建立连接、创建通道、声明交换机和队列等。AMQP协议本身不使用cookie进行会话管理,而是依赖于长连接和通道的概念来维护通信状态。因此这个协议也可以排除筛选。那么就剩下两个协议了:EPMD 和 ErlDP。

这两个协议也是本题所考察的内容。即 Erlang 节点协议认证过程。

EPMD 全称为 Erlang Port Mapper Daemon,在 RabbitMQ 中主要充当"名称服务器"的作用。它的主要功能包括:

  1. 将符号节点名称映射到具体的 IP 地址和端口号。
  2. 维护一个活跃 Erlang 节点的注册表。
  3. 协助 Erlang 节点之间建立初始连接。

EPMD 通常在端口 4369 上运行,为 Erlang 集群中的节点提供服务。

Erlang Distribution Protocol(有时简称为 Erlang Distribution 或 Erldp)是 Erlang 编程语言中用于实现分布式系统通信的核心协议。它的主要特点包括:

  1. 用于 Erlang 节点之间的通信。
  2. 支持远程过程调用(RPC)和消息传递。
  3. 提供内置的容错和错误处理机制。

对于节点间的认证,Erlang 使用了一种称为 "Magic Cookie" 的机制:

  1. 每个 Erlang 节点都有一个 cookie,这是一个字符串值。
  2. 当两个节点尝试建立连接时,它们会交换并比较各自的 cookie。
  3. 只有当两个节点的 cookie 相同时,连接才会建立。
  4. cookie 通常存储在一个名为 .erlang.cookie 的文件中。

当然,虽然这种机制被称为 "cookie",但它与 Web 浏览器中使用的 HTTP cookie 完全不同。Erlang 的 cookie 是一种简单的共享密钥机制,用于节点认证。本题最终的 flag 也就是这个 cookie 32 位 MD5 的小写值。
筛选与 Erlang 通信相关的协议(epmd || erldp)可以发现:

5.png

想要分析这段通信的含义就要首先了解 RabbitMQ 中 Erlang节点通信验证的逻辑,过程如下:

Client Node Server Node

|                                                |
|        1. SEND (name, flags, creation)         |
|----------------------------------------------->|
|                                                |
|        2. CHALLENGE (challenge, flags)         |
|<-----------------------------------------------|
|                                                |
|        3. CHALLENGE_REPLY (digest)             |
|----------------------------------------------->|
|                                                |
|        4. CHALLENGE_ACK (digest)               |
|<-----------------------------------------------|
|                                                |

现在详细分析每个步骤:
第一步:SEND 客户端节点发送一个SEND消息。包含以下信息:

  • 节点名称:标识客户端节点
  • 标志(flags):包含版本信息和其他元数据
  • 创建信息(creation):用于区分同名但不同时间创建的节点

{SEND,
NodeName, % 例如 'rabbit@node01'
Flags, % 例如 [DFLAG_EXTENDED_REFERENCES, DFLAG_DIST_MONITOR, ...]
Creation % 例如 1
}

对应pcap 包中的 471、517、851以及 970 行

6.png

第二步,CHALLENGE 服务端节点收到SEND后,回复一个CHALLENGE消息。包含:

  • 挑战值(challenge):一个随机生成的大整数
  • 标志(flags):服务端支持的功能标志

{CHALLENGE,
Challenge, % 例如 1234567890
Flags % 例如 [DFLAG_EXTENDED_REFERENCES, DFLAG_DIST_MONITOR, ...]
}

对应pcap 包中的 475、521、855 以及 974 行

7.png

第三步,CHALLENGE_REPLY 客户端接收到CHALLENGE后,计算并发送响应:

  • 摘要(digest):使用共享的"magic cookie"和挑战值计算的MD5哈希

{CHALLENGE_REPLY,

  Digest          % 服务端计算的MD5

}

对应pcap 包中的 976 行

8.png

第四步,也即最后一步,CHALLENGE_ACK 服务端验证客户端的响应,如果正确,发送ACK:

  • 摘要(digest):服务端使用相同方法计算的摘要,用于双向认证

{CHALLENGE_ACK,

  Digest         % 服务端计算的MD5

}

对应 pcap 包中的 977 行:

9.png

这就是整个验证过程,结合协议过程、pcap 包或许还不能够理解整个过程的话,还可以看看erlang节点的 cookie 认证过程代码,在官方仓库中 erlang/otp/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java 文件里有相关定义,其主要代码如下:

    protected void recvChallengeAck(final int our_challenge)
            throws IOException, OtpAuthException {

        final byte[] her_digest = new byte[16];
        try {
            final byte[] buf = read2BytePackage();
            @SuppressWarnings("resource")
            final OtpInputStream ibuf = new OtpInputStream(buf, 0);
            final int tag = ibuf.read1();
            if (tag != ChallengeAck) {
                throw new IOException("Handshake protocol error");
            }
            ibuf.readN(her_digest);
            final byte[] our_digest = genDigest(our_challenge,
                    localNode.cookie());
            if (!digests_equals(her_digest, our_digest)) {
                throw new OtpAuthException("Peer authentication error.");
            }
        } catch (final OtpErlangDecodeException e) {
            throw new IOException("Handshake failed - not enough data");
        } catch (final Exception e) {
            throw new OtpAuthException("Peer authentication error.");
        }

        if (traceLevel >= handshakeThreshold) {
            System.out.println("<- " + "HANDSHAKE recvChallengeAck" + " from="
                    + peer.node + " digest=" + hex(her_digest) + " local="
                    + localNode);
        }
    }
...

    protected void sendChallengeReply(final int challenge, final byte[] digest)
            throws IOException {

        @SuppressWarnings("resource")
        final OtpOutputStream obuf = new OtpOutputStream();
        obuf.write2BE(21);
        obuf.write1(ChallengeReply);
        obuf.write4BE(challenge);
        obuf.write(digest);
        obuf.writeToAndFlush(socket.getOutputStream());

        if (traceLevel >= handshakeThreshold) {
            System.out.println("-> " + "HANDSHAKE sendChallengeReply"
                    + " challenge=" + challenge + " digest=" + hex(digest)
                    + " local=" + localNode);
        }
    }

...
    protected byte[] genDigest(final int challenge, final String cookie) {
        int i;
        long ch2;

        if (challenge < 0) {
            ch2 = 1L << 31;
            ch2 |= challenge & 0x7FFFFFFF;
        } else {
            ch2 = challenge;
        }
        final OtpMD5 context = new OtpMD5();
        context.update(cookie);
        context.update("" + ch2);

        final int[] tmp = context.final_bytes();
        final byte[] res = new byte[tmp.length];
        for (i = 0; i < tmp.length; ++i) {
            res[i] = (byte) (tmp[i] & 0xFF);
        }
        return res;
    }

大致流程如下:
生成摘要: 使用 genDigest() 方法生成摘要。这个方法使用三个参数:challenge、Cookie 和一个固定字符串。

protected byte[] genDigest(final int challenge, final String cookie) {
    // ... 
    context.update(cookie);
    context.update("" + ch2);
    // ...
}

交换challenge: 双方交换挑战值,但不直接交换 Cookie。

验证digest: 每一方使用接收到的challenge和自己的 Cookie 生成digest,然后与对方发送的digest进行比较。

final byte[] our_digest = genDigest(our_challenge, localNode.cookie());
if (!digests_equals(her_digest, our_digest)) {
    throw new OtpAuthException("Peer authentication error.");
}

判定 Cookie 正确: 如果双方生成的digest相同,就认为 Cookie 是正确的。因为只有双方使用相同的 Cookie 才能生成相同的摘要。

设置 cookieOk 标志: 当认证成功时,设置 cookieOk = true。

java
Copy
cookieOk = true;
sendCookie = false;

后续通信: 在后续的通信中,如果 cookieOk 为 true,就不再重复完整的认证过程。

0x04 一个例子来说明

举例来说,具体如下。
远程的 IP为:101.x.x.145
其流程主要如下:

步骤消息类型通信方向内容说明
1SEND_NAME本地 → 远程rabbit@nodes本地节点向远程节点发送自己的节点标识名称
2SEND_STATUS远程 → 本地ok远程节点确认找到该节点后返回确认状态
3SEND_CHALLENGE远程 → 本地0xd47f02d3远程节点向本地节点发送挑战值
4SEND_CHALLENGE_REPLY本地 → 远程挑战值: 0x89164a06
摘要值: 1fd30d5a0286c8bf72535e80db246256
本地节点回复自己的挑战值和摘要值
5SEND_CHALLENGE_ACK远程 → 本地摘要值: aa8cbfa4f6401bac2aed672a11c8a7a8远程节点比对收到的摘要值,如果匹配则返回自己的摘要值

远程发送的:challenge:0xd47f02d3
远程发送的:digest:aa8cbfa4f6401bac2aed672a11c8a7a8
本地发送的:challenge::0x89164a06
本地发送的:digest:1fd30d5a0286c8bf72535e80db246256
远程 Cookie:VLWCCLWXYOZNQPJKAKIO
本地 Cookie:VLWCCLWXYOZNQPJKAKIO
0x89164a06 和 VLWCCLWXYOZNQPJKAKIO 生成 aa8cbfa4f6401bac2aed672a11c8a7a8
0xd47f02d3 和 VLWCCLWXYOZNQPJKAKIO 生成 1fd30d5a0286c8bf72535e80db246256

如下图所示:

10.png

如果获取到 SEND_CHALLENGE_ACK阶段返回的 digest,那么就可以轻松的猜测出 Cookie 的值(非用户自定义,默认生成的情况下)
上例中的验证脚本如下:

package Rabbitmq;

public class CookieHashDemo {
    public static byte[] genDigest(final int challenge, final String cookie) {
        int i;
        long ch2;

        if (challenge < 0) {
            ch2 = 1L << 31;
            ch2 |= challenge & 0x7FFFFFFF;
        } else {
            ch2 = challenge;
        }
        final OtpMD5 context = new OtpMD5();
        context.update(cookie);
        context.update("" + ch2);

        final int[] tmp = context.final_bytes();
        final byte[] res = new byte[tmp.length];
        for (i = 0; i < tmp.length; ++i) {
            res[i] = (byte) (tmp[i] & 0xFF);
        }
        return res;
    }
    private static boolean digests_equals(final byte[] a, final byte[] b) {
        int i;
        for (i = 0; i < 16; ++i) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }
    static String hex0(final byte x) {
        final char tab[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f' };
        int uint;
        if (x < 0) {
            uint = x & 0x7F;
            uint |= 1 << 7;
        } else {
            uint = x;
        }
        return "" + tab[uint >>> 4] + tab[uint & 0xF];
    }
    static String hex(final byte[] b) {
        final StringBuffer sb = new StringBuffer();
        try {
            int i;
            for (i = 0; i < b.length; ++i) {
                sb.append(hex0(b[i]));
            }
        } catch (final Exception e) {
            // Debug function, ignore errors.
        }
        return sb.toString();

    }
    public static void main(String[] args) {
        String local_cookie = "VLWCCLWXYOZNQPJKAKIO";
        int local_challenge = 0x89164a06;
        String remote_digest = "aa8cbfa4f6401bac2aed672a11c8a7a8";
        byte[] our_digest = genDigest(local_challenge,local_cookie);
        System.out.println(hex(our_digest));
        if (!digests_equals(remote_digest.getBytes(), hex(our_digest).getBytes())) {
            System.out.println("Peer authentication error.");
        }else {
            System.out.println("Success!");
        }
    }
}

11.png

0x05 题解

回到本题,我们已经通过 pcap 包获取到了Challenge值(0x60ea7bde)以及 SEND_CHALLENGE_ACK阶段返回的 digest值(f0e2967976d3ad1d0e8d2e85e7146f1a),因此只需要本地 fuzz 即可推断出 cookie 值。

但是这里有个问题,fuzz 前我们必须知道 生成 cookie 长什么样,cookie 的生成逻辑是什么,不然没法写 fuzz 脚本。
实际上,如果你在自己的机器上可以很轻松的搭建一个 RabbitMQ 服务。

RabbitMQ 的 cookie 默认保存在 /var/lib/rabbitmq/.erlang.cookie 文件里,由 20 个大写的英文字母组成,类似如下:

12.png

而 cookie 的生成算法如下(官方仓库:/erlang/otp/blob/master/lib/kernel/src/auth.erl):

-module(cookie_generator).
-export([create_cookie/1]).

%% next_random/1 function
next_random(X) ->
    (X*17059465+1) band 16#fffffffff.
...
%% random_cookie/3 function
random_cookie(0, _, Result) ->
    lists:reverse(Result);
random_cookie(Count, X0, Result) ->
    X = next_random(X0),
    Letter = X*($Z-$A+1) div 16#1000000000 + $A,
    random_cookie(Count-1, X, [Letter|Result]).
...
%% create_cookie/1 function
create_cookie(Name) ->
    io:format("Seed_1: ~p~n", [abs(erlang:monotonic_time() bxor erlang:unique_integer())]),
    Seed = abs(erlang:monotonic_time() bxor erlang:unique_integer()),
    io:format("Seed_2: ~p~n", [abs(erlang:monotonic_time() bxor erlang:unique_integer())]),
    Cookie = random_cookie(20, Seed, []),
    io:format("Cookie: ~p~n", [Cookie]),
    Cookie.

可以看到,在这个cookie 生成的逻辑中,其关键点主要在于 seed 的值。

Seed = abs(erlang:monotonic_time() bxor erlang:unique_integer()),

  • erlang:monotonic_time():表示从 Erlang 虚拟机启动到调用的时间(以纳秒为单位)
  • erlang:unique_integer():返回一个整数

这两个数实际上很难预测出来

但是可以发现,这两个值进行异或计算后,生成的值是一个大整数

13.png

因此可以反复的模拟生成cookie的过程(要不断的重启 erlang 虚拟机才可以),观察 seed 的特征:

14.png

经过观测发现,其 seed 大部分处于 350,000,000 ~ 550,000,000 之间,且 seed 的大小和所处的机器相关,当处理器较多的时候,其生成的 seed 相对于少的时候而言是偏大的,以下是单核生成的 30 万个 seed 所处的区间统计:

15.png

当然,由于是本地破解,实际上运算速度很快,知道了 seed 的大致值,我们可以设置从 300000000 开始到 900000000 结束(当然,你也可以从 1 开始,就是花的时间多就是)
最终脚本如下:
Payload.java

package Rabbitmq;

public class Payload {
    public static byte[] genDigest(final int challenge, final String cookie) {
        int i;
        long ch2;

        if (challenge < 0) {
            ch2 = 1L << 31;
            ch2 |= challenge & 0x7FFFFFFF;
        } else {
            ch2 = challenge;
        }
        final OtpMD5 context = new OtpMD5();
        context.update(cookie);
        context.update("" + ch2);

        final int[] tmp = context.final_bytes();
        final byte[] res = new byte[tmp.length];
        for (i = 0; i < tmp.length; ++i) {
            res[i] = (byte) (tmp[i] & 0xFF);
        }
        return res;
    }
    private static boolean digests_equals(final byte[] a, final byte[] b) {
        int i;
        for (i = 0; i < 16; ++i) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }
    static String hex0(final byte x) {
        final char tab[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f' };
        int uint;
        if (x < 0) {
            uint = x & 0x7F;
            uint |= 1 << 7;
        } else {
            uint = x;
        }
        return "" + tab[uint >>> 4] + tab[uint & 0xF];
    }
    static String hex(final byte[] b) {
        final StringBuffer sb = new StringBuffer();
        try {
            int i;
            for (i = 0; i < b.length; ++i) {
                sb.append(hex0(b[i]));
            }
        } catch (final Exception e) {
        }
        return sb.toString();

    }
    public static long nextRandom(long x) {
        return (x * 17059465 + 1) & 0xfffffffffL;
    }

    public static String deriveCookie(long seed, int size) {
        long x = seed;
        char[] cookie = new char[size];
        for (int i = size - 1; i >= 0; i--) {
            x = nextRandom(x);
            cookie[i] = (char) ('A' + (26 * x) / 0x1000000000L);
        }
        return new String(cookie);
    }
    private static double nanoToMinutes(long nanos) {
        return nanos / (60.0 * 1_000_000_000);
    }
    public static void main(String[] args) {
        long seed_start = 300000000;
        long seed_end = 900000000;
        int size = 20;
        int local_challenge = 0x60ea7bde;
        String remote_digest = "f0e2967976d3ad1d0e8d2e85e7146f1a";
        long startTimeNano = System.nanoTime();
        for(long i = seed_start; i<=seed_end; i++){
            String local_cookie =  deriveCookie(i, size);
            byte[] our_digest = genDigest(local_challenge,local_cookie);
            if (!digests_equals(remote_digest.getBytes(), hex(our_digest).getBytes())) {
                System.out.println("[*] Seed: " + i + ", Try Cookie " + local_cookie + " : Peer authentication error.");
            }else {
                System.out.println("[*] Seed: " + i + ", Success! Your Cookie is " + local_cookie);
                long endTimeNano = System.nanoTime();
                long durationNano = endTimeNano - startTimeNano;
                double durationMinutes = nanoToMinutes(durationNano);
                System.out.printf("Fuzz 时间: %.4f 分钟%n", durationMinutes);
                break;
            }
        }
    }
}

OtpMD5.java

package Rabbitmq;
class OtpMD5 {

    static final long S11 = 7;
    static final long S12 = 12;
    static final long S13 = 17;
    static final long S14 = 22;
    static final long S21 = 5;
    static final long S22 = 9;
    static final long S23 = 14;
    static final long S24 = 20;
    static final long S31 = 4;
    static final long S32 = 11;
    static final long S33 = 16;
    static final long S34 = 23;
    static final long S41 = 6;
    static final long S42 = 10;
    static final long S43 = 15;
    static final long S44 = 21;

    private final long state[] = { 0x67452301L, 0xefcdab89L, 0x98badcfeL,
            0x10325476L };
    private final long count[] = { 0L, 0L };
    private final int buffer[];

    public OtpMD5() {
        buffer = new int[64];
        int i;
        for (i = 0; i < 64; ++i) {
            buffer[i] = 0;
        }
    }

    private int[] to_bytes(final String s) {
        final char tmp[] = s.toCharArray();
        final int ret[] = new int[tmp.length];
        int i;

        for (i = 0; i < tmp.length; ++i) {
            ret[i] = tmp[i] & 0xFF;
        }
        return ret;
    }

    private int[] clean_bytes(final int bytes[]) {
        final int ret[] = new int[bytes.length];
        int i;

        for (i = 0; i < bytes.length; ++i) {
            ret[i] = bytes[i] & 0xFF;
        }
        return ret;
    }

    private long shl(final long what, final int steps) {
        return what << steps & 0xFFFFFFFFL;
    }

    private long shr(final long what, final int steps) {
        return what >>> steps;
    }

    private long plus(final long a, final long b) {
        return a + b & 0xFFFFFFFFL;
    }

    private long not(final long x) {
        return ~x & 0xFFFFFFFFL;
    }

    private void to_buffer(final int to_start, final int[] from,
                           final int from_start, final int num) {
        int ix = num;
        int to_ix = to_start;
        int from_ix = from_start;
        while (ix-- > 0) {
            buffer[to_ix++] = from[from_ix++];
        }
    }

    private void do_update(final int bytes[]) {
        int index = (int) (count[0] >>> 3 & 0x3F);
        final long inlen = bytes.length;
        final long addcount = shl(inlen, 3);
        final long partlen = 64 - index;
        int i;

        count[0] = plus(count[0], addcount);

        if (count[0] < addcount) {
            ++count[1];
        }

        count[1] = plus(count[1], shr(inlen, 29));
        if (inlen >= partlen) {
            to_buffer(index, bytes, 0, (int) partlen);
            transform(buffer, 0);

            for (i = (int) partlen; i + 63 < inlen; i += 64) {
                transform(bytes, i);
            }

            index = 0;
        } else {
            i = 0;
        }
        to_buffer(index, bytes, i, (int) inlen - i);
    }

    @SuppressWarnings("unused")
    private void dumpstate() {
        System.out.println("state = {" + state[0] + ", " + state[1] + ", "
                + state[2] + ", " + state[3] + "}");
        System.out.println("count = {" + count[0] + ", " + count[1] + "}");
        System.out.print("buffer = {");
        int i;
        for (i = 0; i < 64; ++i) {
            if (i > 0) {
                System.out.print(", ");
            }
            System.out.print(buffer[i]);
        }
        System.out.println("}");
    }

    private long F(final long x, final long y, final long z) {
        return x & y | not(x) & z;
    }

    private long G(final long x, final long y, final long z) {
        return x & z | y & not(z);
    }

    private long H(final long x, final long y, final long z) {
        return x ^ y ^ z;
    }

    private long I(final long x, final long y, final long z) {
        return y ^ (x | not(z));
    }

    private long ROTATE_LEFT(final long x, final long n) {
        return shl(x, (int) n) | shr(x, (int) (32 - n));
    }

    private long FF(final long a, final long b, final long c, final long d,
                    final long x, final long s, final long ac) {
        long tmp = plus(a, plus(plus(F(b, c, d), x), ac));
        tmp = ROTATE_LEFT(tmp, s);
        return plus(tmp, b);
    }

    private long GG(final long a, final long b, final long c, final long d,
                    final long x, final long s, final long ac) {
        long tmp = plus(a, plus(plus(G(b, c, d), x), ac));
        tmp = ROTATE_LEFT(tmp, s);
        return plus(tmp, b);
    }

    private long HH(final long a, final long b, final long c, final long d,
                    final long x, final long s, final long ac) {
        long tmp = plus(a, plus(plus(H(b, c, d), x), ac));
        tmp = ROTATE_LEFT(tmp, s);
        return plus(tmp, b);
    }

    private long II(final long a, final long b, final long c, final long d,
                    final long x, final long s, final long ac) {
        long tmp = plus(a, plus(plus(I(b, c, d), x), ac));
        tmp = ROTATE_LEFT(tmp, s);
        return plus(tmp, b);
    }

    private void decode(final long output[], final int input[],
                        final int in_from, final int len) {
        int i, j;

        for (i = 0, j = 0; j < len; i++, j += 4) {
            output[i] = input[j + in_from] | shl(input[j + in_from + 1], 8)
                    | shl(input[j + in_from + 2], 16)
                    | shl(input[j + in_from + 3], 24);
        }
    }

    private void transform(final int block[], final int from) {
        long a = state[0];
        long b = state[1];
        long c = state[2];
        long d = state[3];
        final long x[] = new long[16];

        decode(x, block, from, 64);

        a = FF(a, b, c, d, x[0], S11, 0xd76aa478L); 
        d = FF(d, a, b, c, x[1], S12, 0xe8c7b756L); 
        c = FF(c, d, a, b, x[2], S13, 0x242070dbL); 
        b = FF(b, c, d, a, x[3], S14, 0xc1bdceeeL); 
        a = FF(a, b, c, d, x[4], S11, 0xf57c0fafL); 
        d = FF(d, a, b, c, x[5], S12, 0x4787c62aL); 
        c = FF(c, d, a, b, x[6], S13, 0xa8304613L); 
        b = FF(b, c, d, a, x[7], S14, 0xfd469501L); 
        a = FF(a, b, c, d, x[8], S11, 0x698098d8L); 
        d = FF(d, a, b, c, x[9], S12, 0x8b44f7afL); 
        c = FF(c, d, a, b, x[10], S13, 0xffff5bb1L); 
        b = FF(b, c, d, a, x[11], S14, 0x895cd7beL);  
        a = FF(a, b, c, d, x[12], S11, 0x6b901122L); 
        d = FF(d, a, b, c, x[13], S12, 0xfd987193L); 
        c = FF(c, d, a, b, x[14], S13, 0xa679438eL);  
        b = FF(b, c, d, a, x[15], S14, 0x49b40821L); 
        a = GG(a, b, c, d, x[1], S21, 0xf61e2562L); 
        d = GG(d, a, b, c, x[6], S22, 0xc040b340L); 
        c = GG(c, d, a, b, x[11], S23, 0x265e5a51L);  
        b = GG(b, c, d, a, x[0], S24, 0xe9b6c7aaL); 
        a = GG(a, b, c, d, x[5], S21, 0xd62f105dL); 
        d = GG(d, a, b, c, x[10], S22, 0x2441453L); 
        c = GG(c, d, a, b, x[15], S23, 0xd8a1e681L);
        b = GG(b, c, d, a, x[4], S24, 0xe7d3fbc8L);
        a = GG(a, b, c, d, x[9], S21, 0x21e1cde6L);  
        d = GG(d, a, b, c, x[14], S22, 0xc33707d6L); 
        c = GG(c, d, a, b, x[3], S23, 0xf4d50d87L); 
        b = GG(b, c, d, a, x[8], S24, 0x455a14edL); 
        a = GG(a, b, c, d, x[13], S21, 0xa9e3e905L); 
        d = GG(d, a, b, c, x[2], S22, 0xfcefa3f8L); 
        c = GG(c, d, a, b, x[7], S23, 0x676f02d9L);
        b = GG(b, c, d, a, x[12], S24, 0x8d2a4c8aL); 
        a = HH(a, b, c, d, x[5], S31, 0xfffa3942L); 
        d = HH(d, a, b, c, x[8], S32, 0x8771f681L); 
        c = HH(c, d, a, b, x[11], S33, 0x6d9d6122L); 
        b = HH(b, c, d, a, x[14], S34, 0xfde5380cL); 
        a = HH(a, b, c, d, x[1], S31, 0xa4beea44L); 
        d = HH(d, a, b, c, x[4], S32, 0x4bdecfa9L); 
        c = HH(c, d, a, b, x[7], S33, 0xf6bb4b60L);  
        b = HH(b, c, d, a, x[10], S34, 0xbebfbc70L); 
        a = HH(a, b, c, d, x[13], S31, 0x289b7ec6L); 
        d = HH(d, a, b, c, x[0], S32, 0xeaa127faL);  
        c = HH(c, d, a, b, x[3], S33, 0xd4ef3085L);  
        b = HH(b, c, d, a, x[6], S34, 0x4881d05L); 
        a = HH(a, b, c, d, x[9], S31, 0xd9d4d039L);  
        d = HH(d, a, b, c, x[12], S32, 0xe6db99e5L);  
        c = HH(c, d, a, b, x[15], S33, 0x1fa27cf8L); 
        b = HH(b, c, d, a, x[2], S34, 0xc4ac5665L);  
        a = II(a, b, c, d, x[0], S41, 0xf4292244L); 
        d = II(d, a, b, c, x[7], S42, 0x432aff97L);  
        c = II(c, d, a, b, x[14], S43, 0xab9423a7L); 
        b = II(b, c, d, a, x[5], S44, 0xfc93a039L);  
        a = II(a, b, c, d, x[12], S41, 0x655b59c3L);  
        d = II(d, a, b, c, x[3], S42, 0x8f0ccc92L);  
        c = II(c, d, a, b, x[10], S43, 0xffeff47dL);  
        b = II(b, c, d, a, x[1], S44, 0x85845dd1L);  
        a = II(a, b, c, d, x[8], S41, 0x6fa87e4fL); 
        d = II(d, a, b, c, x[15], S42, 0xfe2ce6e0L); 
        c = II(c, d, a, b, x[6], S43, 0xa3014314L); 
        b = II(b, c, d, a, x[13], S44, 0x4e0811a1L);
        a = II(a, b, c, d, x[4], S41, 0xf7537e82L); 
        d = II(d, a, b, c, x[11], S42, 0xbd3af235L); 
        c = II(c, d, a, b, x[2], S43, 0x2ad7d2bbL); 
        b = II(b, c, d, a, x[9], S44, 0xeb86d391L); 

        state[0] = plus(state[0], a);
        state[1] = plus(state[1], b);
        state[2] = plus(state[2], c);
        state[3] = plus(state[3], d);
    }

    public void update(final int bytes[]) {
        do_update(clean_bytes(bytes));
    }

    public void update(final String s) {
        do_update(to_bytes(s));
    }

    private int[] encode(final long[] input, final int len) {
        final int output[] = new int[len];
        int i, j;
        for (i = 0, j = 0; j < len; i++, j += 4) {
            output[j] = (int) (input[i] & 0xff);
            output[j + 1] = (int) (input[i] >>> 8 & 0xff);
            output[j + 2] = (int) (input[i] >>> 16 & 0xff);
            output[j + 3] = (int) (input[i] >>> 24 & 0xff);
        }
        return output;
    }

    public int[] final_bytes() {
        final int bits[] = encode(count, 8);
        int index, padlen;
        int padding[], i;
        int[] digest;

        index = (int) (count[0] >>> 3 & 0x3f);
        padlen = index < 56 ? 56 - index : 120 - index;
        padding = new int[padlen];
        padding[0] = 0x80;
        for (i = 1; i < padlen; ++i) {
            padding[i] = 0;
        }

        do_update(padding);

        do_update(bits);

        digest = encode(state, 16);

        return digest;
    }
}

因而得到最终的 cookie 值和 seed 值如下:

[*] Seed: 426271377, Success! Your Cookie is OXZHUNYQHJBWDLCGNUKZ

单线程 Fuzz 的时间为:18.1437 分钟

16.png

得到 cookie 值后,将其转换为 32 位小写的 md5 值:

17.png

因而最终的 flag 为:ByteCTF{e1347d3b4a848e4fc850b069dbfab71d}

0x06 写在最后

其实这题本来是属于 web 类的,但是由于种种原因,删减了题目,阉割成了杂项题,以后有机会在聊聊更多内容

「感谢老板送来的软糖/蛋糕/布丁/牛奶/冰阔乐!」

panda

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码打赏

版权属于:

Panda | 热爱安全的理想少年

本文链接:

https://cnpanda.net/ctf/1301.html(转载时请注明本文出处及文章链接)

 spring 审计常见 tricks 没有了 

添加新评论

暂无评论