首页
论坛
课程
招聘
[原创]首届“安洵杯”国际赛站-WMCTF2022 官方WP之MISC
2022-8-30 11:02 6850

[原创]首届“安洵杯”国际赛站-WMCTF2022 官方WP之MISC

2022-8-30 11:02
6850

首届“安洵杯”国际赛站-WMCTF2022 官方WP

MISC

1!5!

1.打开流量,可以发现由两部分组成,由quic和tcp组成,先看看底部tcp

 

image-20220724031354864

 

image-20220724031401128

 

2.可以看见有websockets流量,并且可以发现每段都发送了加密数据

 

image-20220724031457118

 

3.将其全部提取作为备用,手工或者脚本都可以,可以看得出来是一些加密内容,但是不知道加密方式,先留着,看看内存

 

4.分析内存,发现是lime镜像,意味着是linux系统镜像

1
strings memory.mem |grep 'Linux version'

image-20220724033419259

 

获得关键信息,Linux version 4.19.0-21-amd64,且发现是debian系统,经过内核搜索

 

Deep Security 12.0 Supported Linux Kernels (trendmicro.com),可以得知是debian10的系统

 

image-20220724033628595

 

5.下载其iso并安装后,制作其对应的符号表镜像 以方便后续取证

1
2
3
4
5
6
7
8
git clone https://github.com/volatilityfoundation/volatility.git
cd volatility
pip2 install pycrypto
pip2 install distorm3
cd tools/linux
make
cd ../../
zip volatility/plugins/overlays/linux/Debian10.zip tools/linux/module.dwarf /boot/System.map-4.19.0-21-amd64

最后使用python2 vol.py --info | grep debian即可发现符号表制作成功

 

image-20220724034056166

 

6.进行内存取证,查看一下历史命令

1
python2 vol.py -f ../memory.mem --profile=Linuxdebian10x64 linux_bash

image-20220724034341040

 

image-20220724034229166

 

image-20220724034310750

 

7.根据历史记录,可以发现服务器运行另一个http3的一个服务端,这与流量的quic吻合,并且记录了SSLKEYLOGFILE的路径,可以看见是在桌面上的,然后在最后发现了eval.js,这比较奇怪

 

8.看一看进程

 

image-20220724034528887

 

说明运行了apache2的服务器,

 

image-20220724034544376

 

再次遇见了eval.js说明比较重要,尝试使用linux_find_file指令进行查找

 

9.出于vol的弊端,并不能通过linux_find_file来找到目标文件的缓存地址,

 

image-20220724035041227

 

由于内存镜像本质的原理,而且我们已经掌握了关键信息,我们通过strings来快速过滤我们的关键内容

 

10.通过strings memory.mem|grep eval,来快速定位一下我们的相关信息

 

可以看到大量的eval.js相关内容,可以注意到一个比较奇特的一串eval开头的js代码

 

image-20220724035311550

 

结合前后数据的位置,可以确认该段js就是eval.js的内容,将其赋值出来进行反混淆。

 

11.通过对其反混淆,可以获得如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function randomString(e) {
    e = e || 32;
    var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
        a = t.length,
        n = "";
    for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
    return n
}
 
function encrypto(a, b, c) {
    if (typeof a !== 'string' || typeof b !== 'number' || typeof c !== 'number') {
        return
    }
    let resultList = [];
    c = c <= 25 ? c : c % 25;
    for (let i = 0; i < a.length; i++) {
        let charCode = a.charCodeAt(i);
        charCode = (charCode * 1) ^ b;
        charCode = charCode.toString(c);
        resultList.push(charCode)
    }
    let splitStr = String.fromCharCode(c + 97);
    let resultStr = resultList.join(splitStr);
    return resultStr
}
var b1 = new Encode() var ws = new WebSocket("ws://localhost:2303/flag");
ws.onopen = function(a) {
    console.log("Connection open ...");
    ws.send("flag")
};
ws.onmessage = function(a) {
    var b = randomString(5) n = a.data res = n.padEnd(9, b) s1 = encrypto(res, 15, 25) f1 = b1.encode(s1) ws.send(f1) console.log('Connection Send:' + f1)
};
ws.onclose = function(a) {
    console.log("Connection closed.")
};
 
function Encode() {
    _keyStr = "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC";
    this.encode = function(a) {
        var b = "";
        var c, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        a = _utf8_encode(a);
        while (i < a.length) {
            c = a.charCodeAt(i++);
            chr2 = a.charCodeAt(i++);
            chr3 = a.charCodeAt(i++);
            enc1 = c >> 2;
            enc2 = ((c & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64
            } else if (isNaN(chr3)) {
                enc4 = 64
            }
            b = b + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4)
        }
        return b
    }
    _utf8_encode = function(a) {
        a = a.replace(/\r\n/g, "\n");
        var b = "";
        for (var n = 0; n < a.length; n++) {
            var c = a.charCodeAt(n);
            if (c < 128) {
                b += String.fromCharCode(c)
            } else if ((c > 127) && (c < 2048)) {
                b += String.fromCharCode((c >> 6) | 192);
                b += String.fromCharCode((c & 63) | 128)
            } else {
                b += String.fromCharCode((c >> 12) | 224);
                b += String.fromCharCode(((c >> 6) & 63) | 128);
                b += String.fromCharCode((c & 63) | 128)
            }
        }
        return b
    }
}

12.经过分析,可以发现其流程为:websocket连接服务端,向其发送flag字段,然后服务端向html发送明文flag,通过加密再次发送出去

 

加密流程:首先随机生成字符串补在flag字段后面,然后进行了异或的加密,最后进行了换表的base64操作。

 

至此我们可以同样写一串js代码来解密其字段

 

13.解密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<script>
var str1 ="待解密的字符串"
function Base64() {
    var _keyStr = "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC";
 
    this.decode = function(input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length) {
            enc1 = _keyStr.indexOf(input.charAt(i++));
            enc2 = _keyStr.indexOf(input.charAt(i++));
            enc3 = _keyStr.indexOf(input.charAt(i++));
            enc4 = _keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = _utf8_decode(output);
        return output;
    }
 
    var _utf8_decode = function (utftext) {
        var string = "";
        var i = 0;
        var c = 0;
        var c1 = 0;
        var c2 = 0;
        while (i < utftext.length) {   
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if ((c > 191) && (c < 224)) {
                c1 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c1 & 63));
                i += 2;
            } else {
                c1 = utftext.charCodeAt(i + 1);
                c2 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c1 & 63) << 6) | (c2 & 63));
                i += 3;
            }
        }
        return string;
    }
}
 
function decrypto( str, xor, hex ) {
  if ( typeof str !== 'string' || typeof xor !== 'number' || typeof hex !== 'number') {
    return;
  }
  let strCharList = [];
  let resultList = [];
  hex = hex <= 25 ? hex : hex % 25;
  let splitStr = String.fromCharCode(hex + 97);
  strCharList = str.split(splitStr);
 
  for ( let i=0; i<strCharList.length; i++ ) {
 
    let charCode = parseInt(strCharList[i], hex);
 
    charCode = (charCode * 1) ^ xor;
    let strChar = String.fromCharCode(charCode);
    resultList.push(strChar);
  }
  let resultStr = resultList.join('');
  return resultStr;
}
 
var base = new Base64()
b64 = base.decode(str1)
console.log(b64)
s1 = decrypto(b64,15,25)
console.log(s1[0])
 
 
</script>

image-20220724040332203

 

14.成功解密前半段,获得前半段flag:WMCTF{LOL_StR1ngs_1s_F@ke_BUT

 

15.流量还有quic流量需要解密,结合前面得知的sslkeylog.txt,我们同样可以通过strings来快速锁定

 

此处考察对sslkeylog关键字段知识点的了解,此处文章:NSS Key Log Format — Firefox Source Docs documentation (mozilla.org)

 

那么只需要一次通过strings来过滤如下

1
2
3
4
CLIENT_HANDSHAKE_TRAFFIC_SECRET
SERVER_HANDSHAKE_TRAFFIC_SECRET
CLIENT_TRAFFIC_SECRET_0
SERVER_TRAFFIC_SECRET_0

四个关键段即可

 

16.最终拼接的sslkeylog的内容为

1
2
3
4
CLIENT_HANDSHAKE_TRAFFIC_SECRET 1002eec63c7da0d66827ebc83af50e00550704d76420b1d039f9ef2222641dd2 48f1197d22ef93778c14f15ddbbf9a53df20cf74c9c68b9f3073fa9f405da995
SERVER_HANDSHAKE_TRAFFIC_SECRET 1002eec63c7da0d66827ebc83af50e00550704d76420b1d039f9ef2222641dd2 38b4671e9ded337c7066e3830563f4519f3bf4effb13d046c2e62847329f0787
CLIENT_TRAFFIC_SECRET_0 1002eec63c7da0d66827ebc83af50e00550704d76420b1d039f9ef2222641dd2 457d3990a971aad9a308ea0af62db5745d99a75e0c484487289f9e760b33a43f
SERVER_TRAFFIC_SECRET_0 1002eec63c7da0d66827ebc83af50e00550704d76420b1d039f9ef2222641dd2 dc730355e51308929f66eabb06458080459810bdd6b27de884a1c1fdc5385b1e

17.最后在wireshark 编辑 首选项 TLS设置一下即可

 

image-20220724040816839

 

便可以成功解开http3的流量

 

image-20220724040900702

 

18.最后获得后半段flag,

 

image-20220724040925014

 

19.最终flag为:WMCTF{LOL_StR1ngs_1s_F@ke_BUT_HTTP3_1s_C000L}

hilbert_wave

首先是一堆音频,au查看后可以看见有间隙的波纹点

 

直接用wave读一下数据可以发现其值都不大于255(ps:原始数据是49152的一维信息,但是通过声道可以知道是RGB三个颜色分别分到了三个音轨上面),易得其本来为图片,且可以发现49152=128*128*3

 

再根据题目名称hilbert_wave可以知道其通过了希尔伯特的处理,逆处理一波可以得到图像(ps:下面脚本为更好的可以图片ocr,进行了二值化处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import wave
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import time
from tqdm import tqdm
 
def wav_to_pic(wav,pic):
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
 
    f = wave.open(wav, "rb")
    params = f.getparams()
    nchannels, sampwidth, framerate, nframes = params[:4]
    str_data = f.readframes(nframes)
    # print(nchannels, sampwidth, framerate, nframes)
    f.close()
 
    wave_data = np.fromstring(str_data, dtype=np.short).reshape((16384, 3))
    def _hilbert(direction, rotation, order):
        if order == 0:
            return
 
        direction += rotation
        _hilbert(direction, -rotation, order - 1)
        step1(direction)
 
        direction -= rotation
        _hilbert(direction, rotation, order - 1)
        step1(direction)
        _hilbert(direction, rotation, order - 1)
 
        direction -= rotation
        step1(direction)
        _hilbert(direction, -rotation, order - 1)
 
    def step1(direction):
        next = {0: (1, 0), 1: (0, 1), 2: (-1, 0), 3: (0, -1)}[direction & 0x3]
 
        global x, y
        x.append(x[-1] + next[0])
        y.append(y[-1] + next[1])
 
    def hilbert(order):
        global x, y
        x = [0,]
        y = [0,]
        _hilbert(0, 1, order)
        return (x, y)
 
    x, y = hilbert(7)
    inx = []
    for i in range(len(x)):
        inx.append((x[i],y[i]))
    inx = np.array(inx)
    new_p = Image.new('RGB', (128,128))
    for i in range(len(inx)):
        if tuple(wave_data[i]) != (255,255,255):
            new_p.putpixel(inx[i], (0,0,0))
        else:
            new_p.putpixel(inx[i], (255,255,255))
 
    new_p.save(pic)
 
for i in tqdm(range(104)):
    wav_to_pic('wavs/'+str(i)+'.wav','res/'+str(i)+'.png')

然后可以发现上面有部分是缺省了一个数字而有的没有缺省,把没有缺省的代入0,缺省的代入缺省的数字这里用了百度的ocr(图片不多,也可以人工去查看),把所有数字凑起来以后long_to_bytes即可得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
res2 = []
import requests,base64,json
from urllib.parse import quote_from_bytes
import time
from tqdm import tqdm
 
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL'
url='https://aip.baidubce.com'
path = '/rest/2.0/ocr/v1/accurate_basic'
headers = {}
headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
headers['Host'] = 'aip.baidubce.com'
params = {}
params['access_token'] = '***********************************************************'
 
for ii in tqdm(range(104)):
    time.sleep(1)
    body = 'image='+quote_from_bytes(base64.b64encode(open('res/'+str(ii)+'.png','rb').read()))
    r = requests.Session()
    rr = r.post(url+path,data=body,headers=headers,params=params,verify=False)
    res = json.loads(rr.text)
 
    f_res = ""
    print(res)
    for i in range(len(res["words_result"])):
        f_res += res["words_result"][i]["words"]
    print(f_res)
    res2.append(str(f_res))
 
 
print(res2)
res3 = ''
for i in res2:
    for j in [1,2,3,4,5,6,7,8,9]:
        flag = 1
        if str(j) not in i:
            res3+=str(j)
            flag = 0
            break
    if flag:
        res3+='0'
 
print(res3)
from Crypto.Util import number
print(number.long_to_bytes(int(res3)))

Hacked_by_L1near

这里我们可以知道是tomcat的websocket,基本上都是默认开启了permessage-deflate,然后分析数据包我们也可以知道其中的permessage-deflate的开启情况,我们总的可以通过RFC 7692 - Compression Extensions for WebSocket (ietf.org)此处的协议来编写脚本,中间有些数据会失真,我们无法解出,但是使用cyberchef仍然可以看到部分数据,比如第4个流:

 

image-20220724164950340

 

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from Crypto.Util.number import *
import zlib
 
def unmark(masked_data,mask_key,payload_length):
    res = b''
    for i in range(len(masked_data)):
        res += long_to_bytes(masked_data[i] ^ mask_key[i % 4])
    payload = hex(bytes_to_long(res))[2:]
    fin_payload = _fill(payload,payload_length)
    return fin_payload
 
def _fill(payload,payload_length):
    if payload.__len__()!=payload_length*2:
        payload = payload.zfill(payload_length*2)
    payload = payload[0] + hex(int(payload[1],16)+1)[2:] + payload[2:]
    return payload
 
f = open('1.txt','r').read().split('\n')
# print(f)
for ff in f:
    try:
        websocket_info = bin(int(ff[:4],16))[2:]
        mode = websocket_info[1]
        if mode == '1':
            print('permessage-deflate')
        else:
            # print('no permessage-deflate')
            continue
        payload_length = int(websocket_info[-7:],2)
 
        mask = websocket_info[8]
 
        if mask == '1':
            print('marked')
            if payload_length != 0:
                if payload_length == 126:
                    payload_length = int(ff[4:8],16)
                    print('payload_length:',payload_length)
                    mask_key = long_to_bytes(int(ff[8:16],16))
                    # print(ff[8:16])
                    masked_data = long_to_bytes(int(ff[16:],16))
                    payload = unmark(masked_data,mask_key,payload_length)
                    print('payload:',payload)
                    data = long_to_bytes(int(payload,16))
                    fin = zlib.decompress(data,-15)
                    print(fin.decode())
 
                else:
                    print('payload_length:',payload_length)
                    mask_key = long_to_bytes(int(ff[4:12],16))
                    # print(mask_key)
                    masked_data = long_to_bytes(int(ff[12:],16))
                    payload = unmark(masked_data,mask_key,payload_length)
                    print('payload:',payload)
                    data = long_to_bytes(int(payload,16))
                    fin = zlib.decompress(data,-15)
                    print(fin.decode())
 
            else:
                print('payload_length:',payload_length)
            print()
        else:
            print('unmarked')
            if payload_length != 0:
                if payload_length == 126:
                    payload_length = int(ff[4:8],16)
                    print('payload_length:',payload_length)
                    payload = hex(int(ff[8:],16))[2:]
                    fin_payload = _fill(payload,payload_length)
                    print('payload:',fin_payload)
                    data = long_to_bytes(int(fin_payload,16))
                    fin = zlib.decompress(data,-15)
                    print(fin.decode())
 
                else:
                    print('payload_length:',payload_length)
                    payload = hex(int(ff[4:],16))[2:]
                    fin_payload = _fill(payload,payload_length)
                    print('payload:',fin_payload)
                    data = long_to_bytes(int(fin_payload,16))
                    fin = zlib.decompress(data,-12)
                    print(fin.decode())
 
            else:
                print('payload_length:',payload_length)
            print()
    except:
        print()
        pass

image-20220724165032181

nano

  1. PaxHeader以高度准确的方式记录了文件的创建时间,当使用tar解压原附件时,我们可以使用命令stat来显示每个图像的不同创建时间。
  2. 挑战的描述中说:"看看这些雪!"。哦,等一下......"。为了观看nanoTV(?),我们需要根据图像的创建时间来排序。
  3. 为了方便观看,我们可以制作一个每秒30帧的GIF,并观看它。然后我们就可以看到flag从右向左漂移了!(大致可以看到flag从右向左漂移。(大致可以看到中间的几秒钟)。
    https://cache.nan.pub/imgs/flag.gif

nanoStego

1、找到两个IEND PNG_CHUNK,分割得到两个png文件。

 

image-20220824173043993

 

2、检查IDAT PNG_CHUNK。根据PNG的结构,通过对IDAT内的数据进行连接和解压,可以得到原始图像数据。然而,zlib是用来解压的,它并不关心原始图像数据后面的额外数据。注意到这一点,你就可以解压这部分,得到一个Python脚本和一个.ttf字体。

 

例如:图像中被框住的部分是Python脚本的压缩位置。

 

image-20220824173147697

 

3、Python脚本在这里。实现了一个盲水印的功能,所以我们需要写一个盲水印的解码程序。

 

image-20220824173214576

 

4、有一个解码程序是不够的。盲水印的实现还涉及到阿诺德的猫图,我们需要A和B的值。水印的大小是150*150,所以0<=A,B<150。

 

试着列举出A和B的值来进行解码。当A和B的值都不对时,你会得到一个随机的图像。但当A或B的值正确时,你可以看到一些有图案的图像。

 

经过这一步,我们可以得到A和B的正确值。

 

5、最后你会得到这个水印。但是为什么我们看不到flag呢?这是因为L43的代码做了一个类型强制转换,导致水印中值为255的像素被保留,而其他低于255的像素值被平移为0。

 

image-20220824173240579

 

6、如何解决这个问题?我们可以从短到长不断地猜测flag。用相同的参数和相同的字体文件在图像上打印所有被猜中的flag,并选择像素匹配数较高的flag,然后继续进行。完成了!

 

image-20220824173309810


[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回