2019 中关村网络与信息安全领域专项赛

中关村网络与信息安全领域专项赛,i春秋主办。最后一道python字节码混淆挺牛逼的,比赛是没解出来,赛后好好学习了一下这方面的知识,成功复现了。

打这场比赛的时候正在xman,桂电的师傅也一起在打,师傅们牛逼。

比赛时间:8月15日

题目下载地址

Crypto

sm4

使用pysm4库。不知道为什么字符串的解密解不出结果,用整形加解密可以

1
2
3
4
5
6
7
8
9
10
11
12
13
from pysm4 import decrypt
key = [13, 204, 99, 177, 254, 41, 198, 163, 201, 226, 56, 214, 192, 194, 98, 104]
c = [46, 48, 220, 156, 184, 218, 57, 13, 246, 91, 1, 63, 60, 67, 105, 64, 149, 240, 217, 77, 107, 49, 222, 61, 155, 225, 231, 196, 167, 121, 9, 16, 60, 182, 65, 101, 39, 253, 250, 224, 9, 204, 154, 122, 206, 43, 97, 59]
key = "".join(map(chr,key))
c = "".join(map(chr,c))
plain = ""
for i in range(3):
t = c[i*16:i*16+16].encode("hex")
cipher_num = eval("0x"+t)
mk = 0x0dcc63b1fe29c6a3c9e238d6c0c26268
m = decrypt(cipher_num,mk)
plain+=hex(m)[2:-1].decode("hex")
print(plain)

dp

dp = d%(p-1)

知道dp很容易求得p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import gmpy2
import libnum

e=65537
n=9637571466652899741848142654451413405801976834328667418509217149503238513830870985353918314633160277580591819016181785300521866901536670666234046521697590230079161867282389124998093526637796571100147052430445089605759722456767679930869250538932528092292071024877213105462554819256136145385237821098127348787416199401770954567019811050508888349297579329222552491826770225583983899834347983888473219771888063393354348613119521862989609112706536794212028369088219375364362615622092005578099889045473175051574207130932430162265994221914833343534531743589037146933738549770365029230545884239551015472122598634133661853901
dp=81339405704902517676022188908547543689627829453799865550091494842725439570571310071337729038516525539158092247771184675844795891671744082925462138427070614848951224652874430072917346702280925974595608822751382808802457160317381440319175601623719969138918927272712366710634393379149593082774688540571485214097
c=5971372776574706905158546698157178098706187597204981662036310534369575915776950962893790809274833462545672702278129839887482283641996814437707885716134279091994238891294614019371247451378504745748882207694219990495603397913371579808848136183106703158532870472345648247817132700604598385677497138485776569096958910782582696229046024695529762572289705021673895852985396416704278321332667281973074372362761992335826576550161390158761314769544548809326036026461123102509831887999493584436939086255411387879202594399181211724444617225689922628790388129032022982596393215038044861544602046137258904612792518629229736324827

for i in range(1,65538):
if(dp*e-1)%i == 0:
if n%(((dp*e-1)/i)+1)==0:
p=((dp*e-1)/i)+1
q=n/(((dp*e-1)/i)+1)
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)%phi
m = pow(c,d,n)
print hex(m)[2:].decode("hex")

Reverse

flat

使用ollvm使控制流程平坦化 。不过由于内容不复杂,调试一下很容易知道加密过程。

前4步都是校验flag格式,flag{},中间包裹一串数+字母

但是由于校验的原因出现了多解:

1
2
3
4
5
6
7
8
9
10
11
12
13
res = "J2261C63-3I2I-EGE4-IBCC-IE41A5I5F4HB"
t = map(ord,res)
print(t)
s = []
for i in t:
if (i>=(ord('0')+0x11) )and (i<= (ord('9') +0x11)):
i-=0x11
elif (i>=(ord('a')-0x30)) and (i<=(ord('z')-0x30)):
i+=0x30

s.append(i)
s = "".join(map(chr,s))
print(s)

这里的到

1
9bbfa2fc-c8b8-464d-8122-84da0e8e5d71

如果换一下if的顺序,会得到:

1
zbbfasfc-cyby-uwud-yrss-yudaqeyevdxr

由于数字的加密在两个范围中间有重复,因此数字解回去会有多解的境况。

每一位不同的互相组合,能得到2**17种解。

都是符合条件的解,输入都能得到正确结果

猜测是uuid,第一条正确。

src_leak

给出了c++的源码,是一系列的模板元编程,分析一下每个函数的作用,以及给出的结果,按照注释中说的求出每部分的值

x1-x5进行如下运算:

  • func2求参数的二进制数中1的个数,func3求参数的奇偶。全部返回1说明他们的二进制数应该有奇数个1。
  • _func1求参数的平方根,向下取整,使用了牛顿法求根。
  • flag是满足这样的数最小的,因为最终加起来要最小

于是只需要从结果的平方爆到结果+1的平方,找到第一个二进制数有奇数个1的数。

最后x6计算了func4<1>到func4<10000>中返回1的个数

func4为判断是否为素数,是返回1

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
from math import *

def bin1(a):
s = bin(a)
s = s[2:]
num = 0
for i in s:
if i == '1':
num+=1
return num%2

def get_result(a):
mi = a*a
ma = (a+1)*(a+1)
for i in range(mi,ma):
if(bin1(i)):
return i

def is_prime(a):
if a == 0:
return 0
if a == 1:
return 0
if a == 2:
return 1
for i in range(2,(a/2)+1):
if a%i == 0:
return 0
return 1
res = [963,4396,6666,1999,3141]
flag = ""
for i in res:
flag+=str(get_result(i))
flag+='-'
s = 0
for i in range(1,10001):
if is_prime(i):
s+=1
flag+=str(s)
print(flag)

py

这题比较难,先贴脚本,有空慢慢写。

pyc去混淆:

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
196
197
198
# python2 disasm_anti.py py.pyc
import dis, marshal, struct, sys, time, types
from opcode import *

def ana_branch(code, i, hits):
if i > len(code):
return
if i in hits:
return
else:
hits.append(i)
c = code[i]
op = ord(c)
if op == 111 or op == 112 or op == 114 or op == 115 or op == 120 or op == 93:
oparg = ord(code[i+1]) + ord(code[i+2])*256
if op == 120 or op == 93:
oparg += i
oparg += 3
ana_branch(code, oparg, hits)
ana_branch(code, i+3, hits)
elif op == 110:
oparg = ord(code[i+1]) + ord(code[i+2])*256
ana_branch(code, i + oparg + 3, hits)
elif op == 113:
oparg = ord(code[i+1]) + ord(code[i+2])*256
ana_branch(code, oparg, hits)
else:
if op>=HAVE_ARGUMENT:
ana_branch(code, i+3, hits)
else:
ana_branch(code, i+1, hits)

def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.

Generate pairs (offset, lineno) as described in Python/compile.c.

"""
byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
line_increments = [ord(c) for c in code.co_lnotab[1::2]]

lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)

def findhits(code):
hits = []
n = len(code)
i = 0
ana_branch(code, i, hits)
hits.sort()
return hits

def anti_findlabels(code):
"""Detect all offsets in a byte code which are jump targets.
Return the list of offsets.
"""
hits = findhits(code)
labels = []
n = len(code)
i = 0
while i < n:
if i not in hits:
i+=1
continue
c = code[i]
op = ord(c)
i = i+1
if op >= HAVE_ARGUMENT:
oparg = ord(code[i]) + ord(code[i+1])*256
i = i+2
label = -1
if op in hasjrel:
label = i+oparg
elif op in hasjabs:
label = oparg
if label >= 0:
if label not in labels:
labels.append(label)
return labels

def dis_anti_obf(co, lasti = -1):
"""Disassemble a code object, anti obf"""
anti_code = ""
code = co.co_code
hits = findhits(code)
labels = anti_findlabels(code)
linestarts = dict(findlinestarts(co))
n = len(code)
i = 0
i = 0
extended_arg = 0
free = None
while i < n:
if i not in hits:
i+=1
anti_code+="\x09"
continue
c = code[i]
op = ord(c)
if i in linestarts:
if i > 0:
print
print "%3d" % linestarts[i],
else:
print ' ',

if i == lasti: print '-->',
else: print ' ',
if i in labels: print '>>',
else: print ' ',
print repr(i).rjust(4),
print opname[op].ljust(20),
anti_code += code[i]
i = i+1
if op >= HAVE_ARGUMENT:
oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg
extended_arg = 0
anti_code+=code[i]
anti_code+=code[i+1]
i = i+2
if op == EXTENDED_ARG:
extended_arg = oparg*65536L
print repr(oparg).rjust(5),
if op in hasconst:
print '(' + repr(co.co_consts[oparg]) + ')',
elif op in hasname:
print '(' + co.co_names[oparg] + ')',
elif op in hasjrel:
print '(to ' + repr(i + oparg) + ')',
elif op in haslocal:
print '(' + co.co_varnames[oparg] + ')',
elif op in hascompare:
print '(' + cmp_op[oparg] + ')',
elif op in hasfree:
if free is None:
free = co.co_cellvars + co.co_freevars
print '(' + free[oparg] + ')',
print
print "patch code:"
print(anti_code.encode("hex"))




def show_file(fname):
f = open(fname, "rb")
magic = f.read(4)
moddate = f.read(4)
modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
print "magic %s" % (magic.encode('hex'))
print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
code = marshal.load(f)
show_code(code)

def show_code(code, indent=''):
print "%scode" % indent
indent += ' '
print "%sargcount %d" % (indent, code.co_argcount)
print "%snlocals %d" % (indent, code.co_nlocals)
print "%sstacksize %d" % (indent, code.co_stacksize)
print "%sflags %04x" % (indent, code.co_flags)
show_hex("code", code.co_code, indent=indent)
dis_anti_obf(code)
print "%sconsts" % indent
for const in code.co_consts:
if type(const) == types.CodeType:
show_code(const, indent+' ')
else:
print " %s%r" % (indent, const)
print "%snames %r" % (indent, code.co_names)
print "%svarnames %r" % (indent, code.co_varnames)
print "%sfreevars %r" % (indent, code.co_freevars)
print "%scellvars %r" % (indent, code.co_cellvars)
print "%sfilename %r" % (indent, code.co_filename)
print "%sname %r" % (indent, code.co_name)
print "%sfirstlineno %d" % (indent, code.co_firstlineno)
show_hex("lnotab", code.co_lnotab, indent=indent)

def show_hex(label, h, indent):
h = h.encode('hex')
if len(h) < 60:
print "%s%s %s" % (indent, label, h)
else:
print "%s%s" % (indent, label)
for i in range(0, len(h), 60):
print "%s %s" % (indent, h[i:i+60])

show_file(sys.argv[1])

得到去了混淆的python字节码,然后静态分析。没办法直接拿到py脚本。这里是ollvm,不知道怎么实现的,出题人牛逼。

稍微看一下不难发现是要解一元二次方程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from math import sqrt
def solve(a,b,c):
delta = b*b-4*a*c
assert delta >= 0
x1 = (-b + sqrt(delta))/(2*a)
x2 = (-b - sqrt(delta))/(2*a)
x1 = int(round(x1))
x2 = int(round(x2))
if x1>=0x20 and x1 < 0x7f:
return x1
else:
return x2

a = [13433, 4747, 17752, 33060, 31051, 48809, 29988, 6421, 20021, 38888, 24844, 20706, 11713, 34938, 12865, 6085, 37391, 32840, 31964, 27194, 8701, 48142, 27066, 28626, 37431, 39142, 46795, 21771, 44280, 40628, 35013, 18583, 5418, 4347, 43929, 9934, 46892, 19868]
b = [13711, 7074, 79833, 42654, 23241, 41412, 61795, 6373, 19304, 1363, 1682, 66279, 76134, 60748, 10355, 63484, 30491, 34005, 51393, 38029, 7241, 4998, 18562, 16935, 66677, 51321, 13771, 49108, 52166, 8851, 16900, 31682, 16684, 12046, 16764, 64315, 76742, 14022]
c = [832832835, -924053193, -307134635, -527578092, 998625960, -715102211, 3572182, -963194083, -475718185, -361574731, -678171563, 107566155, 608670527, 254218946, -81206308, -284228457, 373369420, 659110852, 165298084, -389004184, 893094421, -868933443, 44838205, -98551062, -59800920, -575871298, -748337118, 696390966, 427210246, -266607884, -555200820, -594235119, -233255094, 229291711, 711922719, 14476464, -783373820, 892608580]
d = [973988289, -867920193, -132362266, -172451190, 1471255182, -242282199, 321870424, -897049789, -428663209, -256350703, -613466537, 321254055, 641759727, 344601346, -40281788, -217030057, 476060216, 767746297, 503093626, -102198850, 984358207, -415480559, 322813233, 178032672, 48876640, -467362638, -260077296, 923436845, 536082660, -138702820, -210365307, -397666023, -215329942, 274852104, 818217684, 41479433, -632022956, 1204798830]
flag = ""
for i in range(38):
flag += chr(solve(a[i],b[i],c[i]-d[i]))
print(flag)

Misc

yasaxi

010 editor打开,压缩包结尾能看到密码为loli

解压出图片,末尾多了一串字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..... ..... ..... ..... !?!!. ?.... ..... ..... ..... .?.?! .?... .!...
..... ..... !.?.. ..... !?!!. ?!!!! !!?.? !.?!! !!!.. ..... ..... .!.?.
..... ...!? !!.?. ..... ..?.? !.?.. ..... .!.?. ..... ...!? !!.?! !!!!!
!!?.? !.?!! !!!!! !!!!. ?.... ..... ....! ?!!.? !!!!! !!!!! !!?.? !.?!!
!!!!! !!!!! !!!!! !!!!! !.!!! !!!!! !!!!! .?... ..... ..... ..!?! !.?..
..... ..... ..?.? !.?.. ..!.? ..... ..... ...!? !!.?! !!!!! !!!!! !?.?!
.?!!! !!!!! !!!!! !!!!! !!!.? ..... ..... ...!? !!.?. ..... ..... .?.?!
.?... ..... ..... ...!. ..!.! !!!!. ?.... ..... ..... .!?!! .?!!! !!!!!
!!!!! !?.?! .?!!! !!!!. ..... ..... ..!.! !!.!! !.!!! .!!!! !!!.. .....
..... ...!. ?.... ..... ....! ?!!.? ..... ..... ..?.? !.?.. ..... .....
..... .!.?. ..... ..... ..!?! !.?!! !!!!! !!!!! ?.?!. ?!!!! !!!!! !!!!!
!!!!! !!.!! !!!.! !!!!! !!!.? ..... ..!?! !.?.. ....? .?!.? ..... .!.?.
..... ..... ..!?! !.?.. ..... ..... ?.?!. ?.... ..... ..... ....! .!!!.
!!!!! !!.?. ..... ..... ....! ?!!.? !!!!! !!!!! !!!!? .?!.? !!!!! !!!!.
..... ...!. ..... ..... ..!.! !!... ..!.? ..... ..... ...!? !!.?. .....
..... .?.?! .?... ..... ..!.? ..... ..... ...!? !!.?! !!!!! !!!!! !?.?!
.?!!! !!!!! !!!!! !!.!! !.?.. ..... ..... .!?!! .?... ..... ....? .?!.?
..... ..... ..... .!.?. ..... ..... ..!?! !.?!! !!!!! !!!!! ?.?!. ?!!!!
!!!!! !!!!! !!!!! !!.!. ?.... ..... ..... .!?!! .?... ..... ..... .?.?!
.?!.! !!!!. ?.... ..... ..!?! !.?.. ..... ...?. ?!.?. ...!. ?.

查找发现是Ook!编码

http://tool.bugku.com/brainfuck/?wafcloud=3

解码一下得到flag

1
flag{f71d6bca-3210-4a31-9feb-1768a65a33db}