GGCTF的对称密码学

研究了GGCTF中的两题密码学,用到了SHA1 Length Extension Attack && CBC Padding Oracle Attack

最近中了密码学的毒,研究完非对称,又来看对称密码学了(数学虐我千百遍,我待数学如初恋),这两种对称密码的攻击方法在《白帽子讲web安全》中也都有详写

这两题也算了一个系列的了

Eucalypt Forest

Can you find any weaknesses in the use of the encryption keys?
Head over to eucalypt-forest.ctfcompetition.com

这题需要你构造出{"username": "admin"}, 只需要利用比特反转就行了,比如,我注册一个aamin, 最后进行加密的字符串是{"username": "aamin"}+\x0b * 11, 返回的cookie中包含了16位的IV,和32位的加密密文,因为使用的是CBC模式,所以我们可以通过修改IV来修改解密后前16位明文,通过修改最后一位IV, 我们能把a -> d, 导致最后解密cookie得到的明文是{"username": "admin"},GETFLAG

仅仅利用比特反转有局限性,只能任意修改前16位明文,不过再加上Padding Oracle Attack, 那么就可以伪造任意明文了,Talk is cheap, show you the code.

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from optparse import OptionParser
import requests
import struct

class POAttack():
"""
以GGCTF的一题为例写的Padding Oracle Attack
By Hcamael
"""
def __init__(self, options):
self.url = options.url
self.length = options.length
cookie = options.cookie.split("=")
assert len(cookie) == 2
self.c_name = cookie[0]
c = cookie[1].decode('hex')
if len(c) % self.length != 0 or len(c) == self.length:
raise "cookie error!"
self.sign = c[-self.length:].encode('hex')
self.result = self.sign
self.unenc = ""
if options.plain:
self.plain = options.plain
self.pad_plain = (self.length - len(self.plain) % self.length)
self.pad_iv = c[-self.length*2: -self.length]
else:
self.pad_plain = 0

def attack(self, msg):
self.a_str = msg
pad = (self.length - len(self.a_str) % self.length)
self.a_str += chr(pad) * pad
assert len(self.a_str) % self.length == 0
self.l_str = list(struct.unpack("16s"*(len(self.a_str)/self.length), self.a_str))

self.iv = "\x00" * self.length
for x in xrange(len(self.l_str)):
result = self.padding()
self.unenc = result + self.unenc
assert len(result) == len(self.l_str[-x-1]) == self.length
tmp = ""
for y in xrange(len(result)):
tmp += chr(ord(result[y])^ord(self.l_str[-x-1][y]))
self.sign = tmp.encode('hex')
self.result = self.sign + self.result
return (self.result, self.unenc.encode("hex"))

def padding(self):
tmp_iv = list(self.iv)
result = list("\x00" * self.length)
if not self.pad_plain:
n = 0
else:
n = self.pad_plain
for i in xrange(n):
result[-i-1] = chr(n ^ ord(self.pad_iv[-i-1]))
self.pad_plain = 0
for x in xrange(n, self.length):
if n != x:
raise "Padding Error!"
for i in xrange(x):
tmp_iv[-i-1] = chr((x+1) ^ ord(result[-i-1]))
for y in xrange(256):
tmp_iv[-x-1] = chr(y)
tmp = "".join(tmp_iv).encode('hex')
cookie = {self.c_name: tmp + self.sign}
try:
req = requests.get(self.url, cookies=cookie, verify=False, allow_redirects=False)
except:
print result
print self.result
exit()
if req.status_code != 500:
result[-x-1] = chr(y^(x+1))
n += 1
break
return "".join(result)

def add_parse():
parser = OptionParser()
parser.add_option(
"--url",
dest="url",
help="Please input the url")
parser.add_option(
"--l",
dest="length",
type="int",
help="Please input the iv's bytes length")
parser.add_option(
"--cookie",
dest="cookie",
help="Please input the url's cookie")
parser.add_option(
"--s",
dest="a_str",
help="Please input you want to construct a string")
parser.add_option(
"--p",
dest="plain",
help="Please input if you know cookie's pliantext")
return parser

def main():
parser = add_parse()
(options, args) = parser.parse_args()
if not (options.url and options.length and options.cookie and options.a_str):
parser.parse_args(['cbc-padding-oracle-attack.py', '-h'])
exit(-1)

cbca = POAttack(options)
r, s = cbca.attack(options.a_str)
print "+-------------------+"
print "| Result |"
print "+--------------------+"
print "Your want string's cookie: " + r
print "AES decrypt result: " + s

if __name__ == '__main__':
main()

要完成Padding Oracle Attack, 首先需要知道IV的长度(–l int型长度),还需要能对此进行攻击的服务(–url),还有就是对服务传递的参数(–cookie),由于该脚本是我对照了GGCTF的这题写的,所以是--url--cookie参数,然后还有你想构造的字符串(–s),最后一个--p参数为可选参数,可以加快Attack的速度.

Padding Oracle Attack 原理

看GGCTF中这题对解密后的密文第一个判断

1
2
3
4
5
6
7
8
if pad > CookieCutter.KEY_SIZE:
raise ValueError, "pad error - pad is %d" % (pad)

expected = chr(pad) * pad
piece = plaintext[-pad:]

if piece != expected:
raise ValueError, "padding is corrupted"

然后再看看加密时,对明文Padding的代码

1
2
pad = (16 - (len(s) % 16))
s += chr(pad) * pad

我们可以得到0 < pad <= 16

pad用来把明文s补全到16的倍数,补全pad个chr(pad)字符

我们利用的就是解密时,对padding的判断,服务器是否返回500来对解密后的信息来进行判断

比如我们随便找一串密文4da82e7d080d689d6fed5942671dde6f

初始化IV为00000000000000000000000000000000

CBC模式流程为:c=4da82e7d080d689d6fed5942671dde6f -> 解密 -> de=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> xor IV -> M[16]

先遍历IV的最后一位,从00遍历道ff,如果M[15] != 0x01时,服务器则会返回500,(不过也会有例外,比如M[14]==M[15]==0x02,类推有15种这样的特殊情况,不过出现的概率太小,所以默认排除这类情况)

假如当IV[15] = \xae时,服务器返回200或302(视具体情况而定)时,我们可以得到de[15] xor IV[15] = \x01 -> de[15] = \x01 xor \xae
这样解密后的de的最后一位我们就算出来了

接下来就是de的倒数第二位,也就是M[14]==M[15]==\x02,因为我们知道里de[15]所以我们可以求得IV[15] = de[15] xor \x02,这时IV[15]是确定的值,所以我们只要遍历IV[14],假如当IV[14] = \x63时,服务器返回非500,我们可以求得de[14] = \x63 xor \x02

以此类推我们最后可以求出de的全16位,按这题,我们需要构造的字符串是{"username": "admin"}+\x0b * 11,不过我们需要从最后算起,所以就是用min"}+\x0b * 11 xor de,这样可以求出IV,然后把这IV当做{"username": "ad的c,然后再跑出最终的IV

如果上面的代码能看懂,也就能理解Padding Oracle Attack了

Wolf Spider

https://wolf-spider.ctfcompetition.com/

和上面那题是一个系列的,也就是在上一题的基础上加上了消息认证,从而多了一步Length Extension Attack

在《白帽子讲WEB安全》上是以md5为例进行的讲解,本题用的是sha1,所以我还去研究了下sha1的流程,自己写了个python实现的脚本

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import ctypes
import struct

def ROTL32(x, r):
try:
a = (x << r) ^ (x >> (32 - r))
except:
print type(x)
print type(r)
exit(-1)
return a

class SHA1():
def __init__(self):
self.length_ = 0
self.unprocessed_ = 0
self.hash_ = [
0x67452301,
0xefcdab89,
0x98badcfe,
0x10325476,
0xc3d2e1f0
]

def sha1_process(self):
wblock = []
for x in xrange(80):
wblock.append(0)

for x in xrange(16):
wblock[x] = self.block[x]

for x in xrange(16, 80):
wblock[x] = ROTL32(wblock[x - 3] ^ wblock[x - 8] ^ wblock[x - 14] ^ wblock[x - 16], 1) & 0xFFFFFFFF

a = self.hash_[0]
b = self.hash_[1]
c = self.hash_[2]
d = self.hash_[3]
e = self.hash_[4]

for x in xrange(20):

temp = ROTL32(a, 5) + (((c ^ d) & b) ^ d) + e + wblock[x] + 0x5A827999
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(20, 40):
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[x] + 0x6ED9EBA1
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(40, 60):
temp = ROTL32(a, 5) + ((b & c) | (b & d) | (c & d)) + e + wblock[x] + 0x8F1BBCDC
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(60, 80):
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[x] + 0xCA62C1D6
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

self.hash_[0] += a
self.hash_[1] += b
self.hash_[2] += c
self.hash_[3] += d
self.hash_[4] += e
for x in xrange(5):
self.hash_[x] &= 0xFFFFFFFF

def str_to_block(self, x):
self.block = []
for i in xrange(x, x + 64, 4):
tmp = self.msg[i: i + 4]
tmp = int(tmp.encode('hex') or '0', 16)
self.block.append(tmp)

def sha1(self, msg, length):
self.msg = msg
self.length_ = length
self.msg += (64 - length % 64) * '\x00'
x = 0
while length >= 64:
self.str_to_block(x)
self.sha1_process()
x += 64
length -= 64
self.str_to_block(x)
self.unprocessed_ = length
self.block = self.padding()
self.sha1_process()
return self.final()

def padding(self):
message = []
for x in xrange(16):
message.append(0)
for x in xrange(16):
tmp = struct.pack("I", self.block[x])
message[x] = int(tmp.encode('hex'), 16)

index = (self.length_ & 63) >> 2
shift = (self.length_ & 3) * 8
message[index] &= ~(0xFFFFFFFF << shift)
message[index] ^= 0x80 << shift
index += 1

if index > 14:
while index < 16:
message[index] = 0
index += 1
# 进行大小端转换
for x in xrange(16):
tmp = struct.pack("I", message[x])
message[x] = int(tmp.encode('hex'), 16)
self.block = message
self.sha1_process()
index = 0

while index < 14:
message[index] = 0
index += 1

data_len = self.length_ << 3
data_len = int(struct.pack("L", data_len).encode("hex"), 16)
message[14] = data_len & 0x00000000FFFFFFFF
message[15] = (data_len & 0xFFFFFFFF00000000) >> 32
# 进行大小端转换
for x in xrange(16):
tmp = struct.pack("I", message[x])
message[x] = int(tmp.encode('hex'), 16)
return message

def final(self):
for x in xrange(5):
self.hash_[x] = ctypes.c_uint32(self.hash_[x])
result = ""
for x in self.hash_:
result += "{:0>8}".format(hex(x.value)[2:-1])
return result

if __name__ == '__main__':
import hashlib
test_str = [
"",
"a",
"abc",
"message digest",
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
]
# for x in test_str:
# s = SHA1()
# print s.sha1(x, len(x))
# print hashlib.sha1(x).hexdigest()
'''
已知 sha1("abcdefghijklmnopqrstuvwxyz")
可求 sha1("abcdefghijklmnopqrstuvwxyz\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a" + "test")
'''
s1 = hashlib.sha1("abcdefghijklmnopqrstuvwxyz").hexdigest()
#s = SHA1()
x1 = "abcdefghijklmnopqrstuvwxyz\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd0" + "test"
x2 = "abcdefghijklmnopqrstuvwxyz"
#print s.sha1(x1, len(x1))
# s = SHA1()
#s.sha1(x2, len(x2))
check_s2 = hashlib.sha1(x1).hexdigest()
#print s1
print check_s2
s = SHA1()
for x in xrange(0, 40, 8):
s.hash_[x/8] = int(s1[x: x+8], 16)
hash_str = "test"
block = hash_str + "\x80" + "\x00" * (64 - len(hash_str) - 1)
s.msg = block
s.str_to_block(0)
s.block[15] = (len(hash_str) + 64) * 8
s.sha1_process()
print s.final()

该代码写的太糟,仅是为了了解sha1的流程,没有任何实用价值,下面是C源码(还有md5的部分)

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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <iostream>
#include <assert.h>

//字节序的小头和大头的问题
#define ZEN_LITTLE_ENDIAN 0x0123
#define ZEN_BIG_ENDIAN 0x3210

//目前所有的代码都是为了小头党服务的,不知道有生之年这套代码是否还会为大头党服务一次?
#ifndef ZEN_BYTES_ORDER
#define ZEN_BYTES_ORDER ZEN_LITTLE_ENDIAN
#endif

#ifndef ZEN_SWAP_UINT16
#define ZEN_SWAP_UINT16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8))
#endif
#ifndef ZEN_SWAP_UINT32
#define ZEN_SWAP_UINT32(x) ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
(((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
#endif
#ifndef ZEN_SWAP_UINT64
#define ZEN_SWAP_UINT64(x) ((((x) & 0xff00000000000000) >> 56) | (((x) & 0x00ff000000000000) >> 40) | \
(((x) & 0x0000ff0000000000) >> 24) | (((x) & 0x000000ff00000000) >> 8) | \
(((x) & 0x00000000ff000000) << 8 ) | (((x) & 0x0000000000ff0000) << 24) | \
(((x) & 0x000000000000ff00) << 40 ) | (((x) & 0x00000000000000ff) << 56))
#endif

//将一个(字符串)数组,拷贝到另外一个uint32_t数组,同时每个uint32_t反字节序
void *swap_uint32_memcpy(void *to, const void *from, size_t length)
{
memcpy(to, from, length);
size_t remain_len = (4 - (length & 3)) & 3;

//数据不是4字节的倍数,补充0
if (remain_len)
{
for (size_t i = 0; i < remain_len; ++i)
{
*((char *)(to) + length + i) = 0;
}
//调整成4的倍数
length += remain_len;
}

//所有的数据反转
for (size_t i = 0; i < length / 4; ++i)
{
((uint32_t *)to)[i] = ZEN_SWAP_UINT32(((uint32_t *)to)[i]);
}

return to;
}

///MD5的结果数据长度
static const size_t ZEN_MD5_HASH_SIZE = 16;
///SHA1的结果数据长度
static const size_t ZEN_SHA1_HASH_SIZE = 20;



namespace ZEN_LIB
{


/*!
@brief 求某个内存块的MD5,
@return unsigned char* 返回的的结果,
@param[in] buf 求MD5的内存BUFFER指针
@param[in] size BUFFER长度
@param[out] result 结果
*/
unsigned char *md5(const unsigned char *buf,
size_t size,
unsigned char result[ZEN_MD5_HASH_SIZE]);


/*!
@brief 求内存块BUFFER的SHA1值
@return unsigned char* 返回的的结果
@param[in] buf 求SHA1的内存BUFFER指针
@param[in] size BUFFER长度
@param[out] result 结果
*/
unsigned char *sha1(const unsigned char *buf,
size_t size,
unsigned char result[ZEN_SHA1_HASH_SIZE]);
};


//================================================================================================
//MD5的算法

//每次处理的BLOCK的大小
static const size_t ZEN_MD5_BLOCK_SIZE = 64;

//md5算法的上下文,保存一些状态,中间数据,结果
typedef struct md5_ctx
{
//处理的数据的长度
uint64_t length_;
//还没有处理的数据长度
uint64_t unprocessed_;
//取得的HASH结果(中间数据)
uint32_t hash_[4];
} md5_ctx;


#define ROTL32(dword, n) ((dword) << (n) ^ ((dword) >> (32 - (n))))
#define ROTR32(dword, n) ((dword) >> (n) ^ ((dword) << (32 - (n))))
#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n))))
#define ROTR64(qword, n) ((qword) >> (n) ^ ((qword) << (64 - (n))))


/*!
@brief 内部函数,初始化MD5的context,内容
@param ctx
*/
static void zen_md5_init(md5_ctx *ctx)
{
ctx->length_ = 0;
ctx->unprocessed_ = 0;

/* initialize state */
ctx->hash_[0] = 0x67452301;
ctx->hash_[1] = 0xefcdab89;
ctx->hash_[2] = 0x98badcfe;
ctx->hash_[3] = 0x10325476;
}

/* First, define four auxiliary functions that each take as input
* three 32-bit words and returns a 32-bit word.*/

/* F(x,y,z) = ((y XOR z) AND x) XOR z - is faster then original version */
#define MD5_F(x, y, z) ((((y) ^ (z)) & (x)) ^ (z))
#define MD5_G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define MD5_H(x, y, z) ((x) ^ (y) ^ (z))
#define MD5_I(x, y, z) ((y) ^ ((x) | (~z)))

/* transformations for rounds 1, 2, 3, and 4. */
#define MD5_ROUND1(a, b, c, d, x, s, ac) { \
(a) += MD5_F((b), (c), (d)) + (x) + (ac); \
(a) = ROTL32((a), (s)); \
(a) += (b); \
}
#define MD5_ROUND2(a, b, c, d, x, s, ac) { \
(a) += MD5_G((b), (c), (d)) + (x) + (ac); \
(a) = ROTL32((a), (s)); \
(a) += (b); \
}
#define MD5_ROUND3(a, b, c, d, x, s, ac) { \
(a) += MD5_H((b), (c), (d)) + (x) + (ac); \
(a) = ROTL32((a), (s)); \
(a) += (b); \
}
#define MD5_ROUND4(a, b, c, d, x, s, ac) { \
(a) += MD5_I((b), (c), (d)) + (x) + (ac); \
(a) = ROTL32((a), (s)); \
(a) += (b); \
}


/*!
@brief 内部函数,将64个字节,16个uint32_t的数组进行摘要(杂凑)处理,处理的数据自己序是小头数据
@param state 存放处理的hash数据结果
@param block 要处理的block,64个字节,16个uint32_t的数组
*/
static void zen_md5_process_block(uint32_t state[4], const uint32_t block[ZEN_MD5_BLOCK_SIZE / 4])
{
register unsigned a, b, c, d;
a = state[0];
b = state[1];
c = state[2];
d = state[3];

const uint32_t *x = NULL;

//MD5里面计算的数据都是小头数据.大头党的数据要处理
#if ZEN_BYTES_ORDER == ZEN_LITTLE_ENDIAN
x = block;
#else
uint32_t swap_block[ZEN_MD5_BLOCK_SIZE / 4];
swap_uint32_memcpy(swap_block, block, 64);
x = swap_block;
#endif


MD5_ROUND1(a, b, c, d, x[ 0], 7, 0xd76aa478);
MD5_ROUND1(d, a, b, c, x[ 1], 12, 0xe8c7b756);
MD5_ROUND1(c, d, a, b, x[ 2], 17, 0x242070db);
MD5_ROUND1(b, c, d, a, x[ 3], 22, 0xc1bdceee);
MD5_ROUND1(a, b, c, d, x[ 4], 7, 0xf57c0faf);
MD5_ROUND1(d, a, b, c, x[ 5], 12, 0x4787c62a);
MD5_ROUND1(c, d, a, b, x[ 6], 17, 0xa8304613);
MD5_ROUND1(b, c, d, a, x[ 7], 22, 0xfd469501);
MD5_ROUND1(a, b, c, d, x[ 8], 7, 0x698098d8);
MD5_ROUND1(d, a, b, c, x[ 9], 12, 0x8b44f7af);
MD5_ROUND1(c, d, a, b, x[10], 17, 0xffff5bb1);
MD5_ROUND1(b, c, d, a, x[11], 22, 0x895cd7be);
MD5_ROUND1(a, b, c, d, x[12], 7, 0x6b901122);
MD5_ROUND1(d, a, b, c, x[13], 12, 0xfd987193);
MD5_ROUND1(c, d, a, b, x[14], 17, 0xa679438e);
MD5_ROUND1(b, c, d, a, x[15], 22, 0x49b40821);

MD5_ROUND2(a, b, c, d, x[ 1], 5, 0xf61e2562);
MD5_ROUND2(d, a, b, c, x[ 6], 9, 0xc040b340);
MD5_ROUND2(c, d, a, b, x[11], 14, 0x265e5a51);
MD5_ROUND2(b, c, d, a, x[ 0], 20, 0xe9b6c7aa);
MD5_ROUND2(a, b, c, d, x[ 5], 5, 0xd62f105d);
MD5_ROUND2(d, a, b, c, x[10], 9, 0x2441453);
MD5_ROUND2(c, d, a, b, x[15], 14, 0xd8a1e681);
MD5_ROUND2(b, c, d, a, x[ 4], 20, 0xe7d3fbc8);
MD5_ROUND2(a, b, c, d, x[ 9], 5, 0x21e1cde6);
MD5_ROUND2(d, a, b, c, x[14], 9, 0xc33707d6);
MD5_ROUND2(c, d, a, b, x[ 3], 14, 0xf4d50d87);
MD5_ROUND2(b, c, d, a, x[ 8], 20, 0x455a14ed);
MD5_ROUND2(a, b, c, d, x[13], 5, 0xa9e3e905);
MD5_ROUND2(d, a, b, c, x[ 2], 9, 0xfcefa3f8);
MD5_ROUND2(c, d, a, b, x[ 7], 14, 0x676f02d9);
MD5_ROUND2(b, c, d, a, x[12], 20, 0x8d2a4c8a);

MD5_ROUND3(a, b, c, d, x[ 5], 4, 0xfffa3942);
MD5_ROUND3(d, a, b, c, x[ 8], 11, 0x8771f681);
MD5_ROUND3(c, d, a, b, x[11], 16, 0x6d9d6122);
MD5_ROUND3(b, c, d, a, x[14], 23, 0xfde5380c);
MD5_ROUND3(a, b, c, d, x[ 1], 4, 0xa4beea44);
MD5_ROUND3(d, a, b, c, x[ 4], 11, 0x4bdecfa9);
MD5_ROUND3(c, d, a, b, x[ 7], 16, 0xf6bb4b60);
MD5_ROUND3(b, c, d, a, x[10], 23, 0xbebfbc70);
MD5_ROUND3(a, b, c, d, x[13], 4, 0x289b7ec6);
MD5_ROUND3(d, a, b, c, x[ 0], 11, 0xeaa127fa);
MD5_ROUND3(c, d, a, b, x[ 3], 16, 0xd4ef3085);
MD5_ROUND3(b, c, d, a, x[ 6], 23, 0x4881d05);
MD5_ROUND3(a, b, c, d, x[ 9], 4, 0xd9d4d039);
MD5_ROUND3(d, a, b, c, x[12], 11, 0xe6db99e5);
MD5_ROUND3(c, d, a, b, x[15], 16, 0x1fa27cf8);
MD5_ROUND3(b, c, d, a, x[ 2], 23, 0xc4ac5665);

MD5_ROUND4(a, b, c, d, x[ 0], 6, 0xf4292244);
MD5_ROUND4(d, a, b, c, x[ 7], 10, 0x432aff97);
MD5_ROUND4(c, d, a, b, x[14], 15, 0xab9423a7);
MD5_ROUND4(b, c, d, a, x[ 5], 21, 0xfc93a039);
MD5_ROUND4(a, b, c, d, x[12], 6, 0x655b59c3);
MD5_ROUND4(d, a, b, c, x[ 3], 10, 0x8f0ccc92);
MD5_ROUND4(c, d, a, b, x[10], 15, 0xffeff47d);
MD5_ROUND4(b, c, d, a, x[ 1], 21, 0x85845dd1);
MD5_ROUND4(a, b, c, d, x[ 8], 6, 0x6fa87e4f);
MD5_ROUND4(d, a, b, c, x[15], 10, 0xfe2ce6e0);
MD5_ROUND4(c, d, a, b, x[ 6], 15, 0xa3014314);
MD5_ROUND4(b, c, d, a, x[13], 21, 0x4e0811a1);
MD5_ROUND4(a, b, c, d, x[ 4], 6, 0xf7537e82);
MD5_ROUND4(d, a, b, c, x[11], 10, 0xbd3af235);
MD5_ROUND4(c, d, a, b, x[ 2], 15, 0x2ad7d2bb);
MD5_ROUND4(b, c, d, a, x[ 9], 21, 0xeb86d391);

state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}


/*!
@brief 内部函数,处理数据的前面部分(>64字节的部分),每次组成一个64字节的block就进行杂凑处理
@param[out] ctx 算法的context,用于记录一些处理的上下文和结果
@param[in] buf 处理的数据,
@param[in] size 处理的数据长度
*/
static void zen_md5_update(md5_ctx *ctx, const unsigned char *buf, size_t size)
{
//为什么不是=,因为在某些环境下,可以多次调用zen_md5_update,但这种情况,必须保证前面的调用,每次都没有unprocessed_
ctx->length_ += size;

//每个处理的块都是64字节
while (size >= ZEN_MD5_BLOCK_SIZE)
{
zen_md5_process_block(ctx->hash_, reinterpret_cast<const uint32_t *>(buf));
buf += ZEN_MD5_BLOCK_SIZE;
size -= ZEN_MD5_BLOCK_SIZE;
}

ctx->unprocessed_ = size;
}


/*!
@brief 内部函数,处理数据的末尾部分,我们要拼出最后1个(或者两个)要处理的BLOCK,加上0x80,加上长度进行处理
@param[in] ctx 算法的context,用于记录一些处理的上下文和结果
@param[in] buf 处理的数据
@param[in] size 处理buffer的长度
@param[out] result 返回的结果,
*/
static void zen_md5_final(md5_ctx *ctx, const unsigned char *buf, size_t size, unsigned char *result)
{
uint32_t message[ZEN_MD5_BLOCK_SIZE / 4];

//保存剩余的数据,我们要拼出最后1个(或者两个)要处理的块,前面的算法保证了,最后一个块肯定小于64个字节
if (ctx->unprocessed_)
{
memcpy(message, buf + size - ctx->unprocessed_, static_cast<size_t>( ctx->unprocessed_));
}

//得到0x80要添加在的位置(在uint32_t 数组中),
uint32_t index = ((uint32_t)ctx->length_ & 63) >> 2;
uint32_t shift = ((uint32_t)ctx->length_ & 3) * 8;

//添加0x80进去,并且把余下的空间补充0
message[index] &= ~(0xFFFFFFFF << shift);
message[index++] ^= 0x80 << shift;

//如果这个block还无法处理,其后面的长度无法容纳长度64bit,那么先处理这个block
if (index > 14)
{
while (index < 16)
{
message[index++] = 0;
}

zen_md5_process_block(ctx->hash_, message);
index = 0;
}

//补0
while (index < 14)
{
message[index++] = 0;
}

//保存长度,注意是bit位的长度,这个问题让我看着郁闷了半天,
uint64_t data_len = (ctx->length_) << 3;

//注意MD5算法要求的64bit的长度是小头LITTLE-ENDIAN编码,注意下面的比较是!=
#if ZEN_BYTES_ORDER != ZEN_LITTLE_ENDIAN
data_len = ZEN_SWAP_UINT64(data_len);
#endif

message[14] = (uint32_t) (data_len & 0x00000000FFFFFFFF);
message[15] = (uint32_t) ((data_len & 0xFFFFFFFF00000000ULL) >> 32);

zen_md5_process_block(ctx->hash_, message);

//注意结果是小头党的,在大头的世界要进行转换
#if ZEN_BYTES_ORDER == ZEN_LITTLE_ENDIAN
memcpy(result, &ctx->hash_, ZEN_MD5_HASH_SIZE);
#else
swap_uint32_memcpy(result, &ctx->hash_, ZEN_MD5_HASH_SIZE);
#endif

}


//计算一个内存数据的MD5值
unsigned char *ZEN_LIB::md5(const unsigned char *buf,
size_t size,
unsigned char result[ZEN_MD5_HASH_SIZE])
{
assert(result != NULL);

md5_ctx ctx;
zen_md5_init(&ctx);
zen_md5_update(&ctx, buf, size);
zen_md5_final(&ctx, buf, size, result);
return result;
}




//================================================================================================
//SHA1的算法

//每次处理的BLOCK的大小
static const size_t ZEN_SHA1_BLOCK_SIZE = 64;

//SHA1算法的上下文,保存一些状态,中间数据,结果
typedef struct sha1_ctx
{

//处理的数据的长度
uint64_t length_;
//还没有处理的数据长度
uint64_t unprocessed_;
/* 160-bit algorithm internal hashing state */
uint32_t hash_[5];
} sha1_ctx;

//内部函数,SHA1算法的上下文的初始化
static void zen_sha1_init(sha1_ctx *ctx)
{
ctx->length_ = 0;
ctx->unprocessed_ = 0;
// 初始化算法的几个常量,魔术数
ctx->hash_[0] = 0x67452301;
ctx->hash_[1] = 0xefcdab89;
ctx->hash_[2] = 0x98badcfe;
ctx->hash_[3] = 0x10325476;
ctx->hash_[4] = 0xc3d2e1f0;
}


/*!
@brief 内部函数,对一个64bit内存块进行摘要(杂凑)处理,
@param hash 存放计算hash结果的的数组
@param block 要计算的处理得内存块
*/
static void zen_sha1_process_block(uint32_t hash[5],
const uint32_t block[ZEN_SHA1_BLOCK_SIZE / 4])
{
size_t t;
uint32_t wblock[80];
register uint32_t a, b, c, d, e, temp;

//SHA1算法处理的内部数据要求是大头党的,在小头的环境转换
#if ZEN_BYTES_ORDER == ZEN_LITTLE_ENDIAN
swap_uint32_memcpy(wblock, block, ZEN_SHA1_BLOCK_SIZE);
#else
::memcpy(wblock, block, ZEN_SHA1_BLOCK_SIZE);
#endif
for (t = 0; t < 80; t++) {
printf("%u\n", wblock[t]);
}
//处理
for (t = 16; t < 80; t++)
{
wblock[t] = ROTL32(wblock[t - 3] ^ wblock[t - 8] ^ wblock[t - 14] ^ wblock[t - 16], 1);
}

a = hash[0];
b = hash[1];
c = hash[2];
d = hash[3];
e = hash[4];

for (t = 0; t < 20; t++)
{
/* the following is faster than ((B & C) | ((~B) & D)) */
temp = ROTL32(a, 5) + (((c ^ d) & b) ^ d)
+ e + wblock[t] + 0x5A827999;
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}
// printf("%u\n", a);
// printf("%u\n", b);
// printf("%u\n", c);
// printf("%u\n", d);
// printf("%u\n", e);
for (t = 20; t < 40; t++)
{
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[t] + 0x6ED9EBA1;
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}

for (t = 40; t < 60; t++)
{
temp = ROTL32(a, 5) + ((b & c) | (b & d) | (c & d))
+ e + wblock[t] + 0x8F1BBCDC;
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}

for (t = 60; t < 80; t++)
{
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[t] + 0xCA62C1D6;
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}

hash[0] += a;
hash[1] += b;
hash[2] += c;
hash[3] += d;
hash[4] += e;
}


/*!
@brief 内部函数,处理数据的前面部分(>64字节的部分),每次组成一个64字节的block就进行杂凑处理
@param ctx 算法的上下文,记录中间数据,结果等
@param msg 要进行计算的数据buffer
@param size 长度
*/
static void zen_sha1_update(sha1_ctx *ctx,
const unsigned char *buf,
size_t size)
{
//为了让zen_sha1_update可以多次进入,长度可以累计
ctx->length_ += size;

//每个处理的块都是64字节
while (size >= ZEN_SHA1_BLOCK_SIZE)
{
zen_sha1_process_block(ctx->hash_, reinterpret_cast<const uint32_t *>(buf));
buf += ZEN_SHA1_BLOCK_SIZE;
size -= ZEN_SHA1_BLOCK_SIZE;
}

ctx->unprocessed_ = size;
}


/*!
@brief 内部函数,处理数据的最后部分,添加0x80,补0,增加长度信息
@param ctx 算法的上下文,记录中间数据,结果等
@param msg 要进行计算的数据buffer
@param result 返回的结果
*/
static void zen_sha1_final(sha1_ctx *ctx,
const unsigned char *msg,
size_t size,
unsigned char *result)
{

uint32_t message[ZEN_SHA1_BLOCK_SIZE / 4];

//保存剩余的数据,我们要拼出最后1个(或者两个)要处理的块,前面的算法保证了,最后一个块肯定小于64个字节
if (ctx->unprocessed_)
{
memcpy(message, msg + size - ctx->unprocessed_, static_cast<size_t>( ctx->unprocessed_));
}

//得到0x80要添加在的位置(在uint32_t 数组中),
uint32_t index = ((uint32_t)ctx->length_ & 63) >> 2;
uint32_t shift = ((uint32_t)ctx->length_ & 3) * 8;

//添加0x80进去,并且把余下的空间补充0
message[index] &= ~(0xFFFFFFFF << shift);
message[index++] ^= 0x80 << shift;

//如果这个block还无法处理,其后面的长度无法容纳长度64bit,那么先处理这个block
if (index > 14)
{
while (index < 16)
{
message[index++] = 0;
}

zen_sha1_process_block(ctx->hash_, message);
index = 0;
}

//补0
while (index < 14)
{
message[index++] = 0;
}

//保存长度,注意是bit位的长度,这个问题让我看着郁闷了半天,
uint64_t data_len = (ctx->length_) << 3;

//注意SHA1算法要求的64bit的长度是大头BIG-ENDIAN,在小头的世界要进行转换
#if ZEN_BYTES_ORDER == ZEN_LITTLE_ENDIAN
data_len = ZEN_SWAP_UINT64(data_len);
#endif

message[14] = (uint32_t) (data_len & 0x00000000FFFFFFFF);
message[15] = (uint32_t) ((data_len & 0xFFFFFFFF00000000ULL) >> 32);

zen_sha1_process_block(ctx->hash_, message);

//注意结果是大头党的,在小头的世界要进行转换
#if ZEN_BYTES_ORDER == ZEN_LITTLE_ENDIAN
swap_uint32_memcpy(result, &ctx->hash_, ZEN_SHA1_HASH_SIZE);
#else
memcpy(result, &ctx->hash_, ZEN_SHA1_HASH_SIZE);
#endif
}



//计算一个内存数据的SHA1值
unsigned char *ZEN_LIB::sha1(const unsigned char *msg,
size_t size,
unsigned char result[ZEN_SHA1_HASH_SIZE])
{
assert(result != NULL);

sha1_ctx ctx;
zen_sha1_init(&ctx);
zen_sha1_update(&ctx, msg, size);
zen_sha1_final(&ctx, msg, size, result);
return result;
}

int main(int /*argc*/, char * /*argv*/[])
{

int ret = 0;
static unsigned char test_buf[7][81] =
{
{ "" },
{ "a" },
{ "abc" },
{ "message digest" },
{ "abcdefghijklmnopqrstuvwxyz" },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" },
{ "12345678901234567890123456789012345678901234567890123456789012345678901234567890" }
};

static const size_t test_buflen[7] =
{
0, 1, 3, 14, 26, 62, 80
};

// static const unsigned char md5_test_sum[7][16] =
// {
// { 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E },
// { 0x0C, 0xC1, 0x75, 0xB9, 0xC0, 0xF1, 0xB6, 0xA8, 0x31, 0xC3, 0x99, 0xE2, 0x69, 0x77, 0x26, 0x61 },
// { 0x90, 0x01, 0x50, 0x98, 0x3C, 0xD2, 0x4F, 0xB0, 0xD6, 0x96, 0x3F, 0x7D, 0x28, 0xE1, 0x7F, 0x72 },
// { 0xF9, 0x6B, 0x69, 0x7D, 0x7C, 0xB7, 0x93, 0x8D, 0x52, 0x5A, 0x2F, 0x31, 0xAA, 0xF1, 0x61, 0xD0 },
// { 0xC3, 0xFC, 0xD3, 0xD7, 0x61, 0x92, 0xE4, 0x00, 0x7D, 0xFB, 0x49, 0x6C, 0xCA, 0x67, 0xE1, 0x3B },
// { 0xD1, 0x74, 0xAB, 0x98, 0xD2, 0x77, 0xD9, 0xF5, 0xA5, 0x61, 0x1C, 0x2C, 0x9F, 0x41, 0x9D, 0x9F },
// { 0x57, 0xED, 0xF4, 0xA2, 0x2B, 0xE3, 0xC9, 0x55, 0xAC, 0x49, 0xDA, 0x2E, 0x21, 0x07, 0xB6, 0x7A }
// };
unsigned char result[32] ={0};

// for(size_t i=0;i<7;++i)
// {
// ZEN_LIB::md5(test_buf[i],test_buflen[i],result);
// ret = memcmp(result,md5_test_sum[i],16);
// if (ret != 0)
// {
// assert(false);
// }
// }

static const unsigned char sha1_test_sum[7][20] =
{
{ 0xda,0x39,0xa3,0xee,0x5e,0x6b,0x4b,0x0d,0x32,0x55,0xbf,0xef,0x95,0x60,0x18,0x90,0xaf,0xd8,0x07,0x09 },
{ 0x86,0xf7,0xe4,0x37,0xfa,0xa5,0xa7,0xfc,0xe1,0x5d,0x1d,0xdc,0xb9,0xea,0xea,0xea,0x37,0x76,0x67,0xb8 },
{ 0xa9,0x99,0x3e,0x36,0x47,0x06,0x81,0x6a,0xba,0x3e,0x25,0x71,0x78,0x50,0xc2,0x6c,0x9c,0xd0,0xd8,0x9d },
{ 0xc1,0x22,0x52,0xce,0xda,0x8b,0xe8,0x99,0x4d,0x5f,0xa0,0x29,0x0a,0x47,0x23,0x1c,0x1d,0x16,0xaa,0xe3 },
{ 0x32,0xd1,0x0c,0x7b,0x8c,0xf9,0x65,0x70,0xca,0x04,0xce,0x37,0xf2,0xa1,0x9d,0x84,0x24,0x0d,0x3a,0x89 },
{ 0x76,0x1c,0x45,0x7b,0xf7,0x3b,0x14,0xd2,0x7e,0x9e,0x92,0x65,0xc4,0x6f,0x4b,0x4d,0xda,0x11,0xf9,0x40 },
{ 0x50,0xab,0xf5,0x70,0x6a,0x15,0x09,0x90,0xa0,0x8b,0x2c,0x5e,0xa4,0x0f,0xa0,0xe5,0x85,0x55,0x47,0x32 },
};

ZEN_LIB::sha1(test_buf[0],test_buflen[0],result);
// for (int i=0; i < 20; i++)
// printf("0x%02x\n", result[i]);
return 0;
}

对于Length Extension Attack,由于我太菜了,难以叙说,还是直接上我写的一个利用脚本

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from optparse import OptionParser
import struct
import ctypes

def ROTL32(x, r):
x, r = int(x), int(r)
return (x << r) ^ (x >> (32 - r))

class SHA1Attack():
"""
SHA1 Length extend attack by Hcamael
s = sha1(mac+m)
if we know s, we can calculate sha1(mac+m+padding+msg)
"""
def __init__(self, options):
self.hash_ = [0, 0, 0, 0, 0]
for x in xrange(0, 40, 8):
self.hash_[x/8] = int(options.signal[x:x+8], 16)
self.length_ = len(options.extend) + ((options.macl + len(options.origin)) / 64 + 1) * 64
print self.length_
self.str_to_block(options.extend)
self.block = self.padding()

def padding(self):
message = []
for x in xrange(16):
message.append(0)
for x in xrange(16):
tmp = struct.pack("I", self.block[x])
message[x] = int(tmp.encode('hex'), 16)

index = (self.length_ & 63) >> 2
shift = (self.length_ & 3) * 8
message[index] &= ~(0xFFFFFFFF << shift)
message[index] ^= 0x80 << shift
index += 1

if index > 14:
while index < 16:
message[index] = 0
index += 1
# 进行大小端转换
for x in xrange(16):
tmp = struct.pack("I", message[x])
message[x] = int(tmp.encode('hex'), 16)
self.block = message
self.sha1_process()
index = 0

while index < 14:
message[index] = 0
index += 1

data_len = self.length_ << 3
data_len = int(struct.pack("L", data_len).encode("hex"), 16)
message[14] = data_len & 0x00000000FFFFFFFF
message[15] = (data_len & 0xFFFFFFFF00000000) >> 32
# 进行大小端转换
for x in xrange(16):
tmp = struct.pack("I", message[x])
message[x] = int(tmp.encode('hex'), 16)
return message

def str_to_block(self, msg):
self.block = []
msg += (64 - self.length_%64) * '\x00'
for i in xrange(0, 64, 4):
tmp = msg[i: i + 4]
tmp = int(tmp.encode('hex') or '0', 16)
self.block.append(tmp)

def calculate(self):
self.sha1_process()
for x in xrange(5):
self.hash_[x] = ctypes.c_uint32(self.hash_[x])
result = ""
for x in self.hash_:
result += "{:0>8}".format(hex(x.value)[2:-1])
return result

def sha1_process(self):
wblock = []
for x in xrange(80):
wblock.append(0)

for x in xrange(16):
wblock[x] = self.block[x]

for x in xrange(16, 80):
wblock[x] = ROTL32(wblock[x - 3] ^ wblock[x - 8] ^ wblock[x - 14] ^ wblock[x - 16], 1) & 0xFFFFFFFF

a = self.hash_[0]
b = self.hash_[1]
c = self.hash_[2]
d = self.hash_[3]
e = self.hash_[4]

for x in xrange(20):

temp = ROTL32(a, 5) + (((c ^ d) & b) ^ d) + e + wblock[x] + 0x5A827999
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(20, 40):
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[x] + 0x6ED9EBA1
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(40, 60):
temp = ROTL32(a, 5) + ((b & c) | (b & d) | (c & d)) + e + wblock[x] + 0x8F1BBCDC
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

for x in xrange(60, 80):
temp = ROTL32(a, 5) + (b ^ c ^ d) + e + wblock[x] + 0xCA62C1D6
temp &= 0xFFFFFFFF
e = d
d = c
c = ROTL32(b, 30) & 0xFFFFFFFF
b = a
a = temp

self.hash_[0] += a
self.hash_[1] += b
self.hash_[2] += c
self.hash_[3] += d
self.hash_[4] += e
for x in xrange(5):
self.hash_[x] &= 0xFFFFFFFF

def add_parse():
parser = OptionParser()
parser.add_option(
"--macl",
dest="macl",
type="int",
help="Please enter the length of mac")
parser.add_option(
"--o",
dest="origin",
help="sha1(mac+origin), Please enter the origin")
parser.add_option(
"--sign",
dest="signal",
help="signal=sha1(mac+origin), Please enter the signal")
parser.add_option(
"--e",
dest="extend",
help="Please enter the extend value")
return parser

def main():
parser = add_parse()
(options, args) = parser.parse_args()
if not (options.macl and options.origin and options.signal and options.extend):
parser.parse_args(['sha1-length-extend-attack.py', '-h'])
exit(-1)
o_data_length = options.macl + len(options.origin)
p = 64 - 8 - 1 - o_data_length
n = 2
while p < 0:
p = 64 * n - 8 - 1 - o_data_length
n += 1
o_data_length *= 8
o_data_length = "{:0>16}".format(hex(o_data_length)[2:])
data_l = ""
for x in xrange(0, 16, 2):
data_l += "\\x" + o_data_length[x:x+2]
cal = SHA1Attack(options)
result = cal.calculate()
print "+---------------------------+"
print "| Result |"
print "+---------------------------+"
print "Origin signal: " + options.signal
print "New signal: " + result
print "New msg: " + "(" + str(options.macl) + " bytes unknow MAC) + " + options.origin + "\\x80" + "\\x00" * p + data_l + options.extend

if __name__ == '__main__':
main()

本题的漏洞在于,源码中

1
2
3
4
5
kv = urlparse.parse_qsl(s)
ret = {}
for k, v in kv:
ret[k] = v
return ret

如果解密出来的s为username=xxx&username=admin
那么最终得到ret = {'username': 'admin'}

我们可以注册username=a用户,得到SHA1(MAC + username=a)的hash值,利用Length Extension Attack我们可以算出SHA1(MAC + username=a + pad + &username=admin) 的hash值,然后我们再利用Padding Oracle Attack,再构造出username=a+pad+&username=admin密文,根据本题的具体需求,稍微改了下Padding Oracle Attack的脚本

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from optparse import OptionParser
import requests
import struct

class POAttack():
"""
以GGCTF的一题为例写的Padding Oracle Attack
By Hcamael
"""
def __init__(self, options):
self.url = options.url
self.length = options.length
cookie = options.cookie.split("=")
assert len(cookie) == 2
self.c_name = cookie[0]
c = cookie[1].decode('hex')
if len(c) % self.length != 0 or len(c) == self.length:
raise "cookie error!"
self.sign = c[-self.length:].encode('hex')
self.result = self.sign
self.unenc = ""
if options.plain:
self.plain = options.plain
self.pad_plain = (self.length - len(self.plain) % self.length)
self.pad_iv = c[-self.length*2: -self.length]
else:
self.pad_plain = 0

def attack(self, msg):
self.a_str = msg
pad = (self.length - len(self.a_str) % self.length)
self.a_str += chr(pad) * pad
assert len(self.a_str) % self.length == 0
self.l_str = list(struct.unpack("16s"*(len(self.a_str)/self.length), self.a_str))

self.iv = "\x00" * self.length
for x in xrange(len(self.l_str)):
print "+===================================================+"
print "plaintext: %s" % self.l_str[-x-1].encode('hex')
result = self.padding()
self.unenc = result + self.unenc
assert len(result) == len(self.l_str[-x-1]) == self.length
tmp = ""
for y in xrange(len(result)):
tmp += chr(ord(result[y])^ord(self.l_str[-x-1][y]))
self.sign = tmp.encode('hex')
self.result = self.sign + self.result
return (self.result, self.unenc.encode("hex"))

def padding(self):
tmp_iv = list(self.iv)
result = list("\x00" * self.length)
if not self.pad_plain:
n = 0
else:
n = self.pad_plain
for i in xrange(n):
result[-i-1] = chr(n ^ ord(self.pad_iv[-i-1]))
self.pad_plain = 0
for x in xrange(n, self.length):
if n != x:
raise "Padding Error!"
for i in xrange(x):
tmp_iv[-i-1] = chr((x+1) ^ ord(result[-i-1]))
for y in xrange(256):
tmp_iv[-x-1] = chr(y)
tmp = "".join(tmp_iv).encode('hex')
cookie = {self.c_name: "d379b40e4da82e7d080d689d6fed5942671dde6f." + tmp + self.sign}
try:
req = requests.get(self.url, cookies=cookie, verify=False, allow_redirects=False)
except:
print result
print self.result
exit()
if req.status_code != 500:
result[-x-1] = chr(y^(x+1))
print "iv xor plaintext = %s" % "".join(result[-x-1:]).encode('hex')
n += 1
break
return "".join(result)

def add_parse():
parser = OptionParser()
parser.add_option(
"--url",
dest="url",
help="Please input the url")
parser.add_option(
"--l",
dest="length",
type="int",
help="Please input the iv's bytes length")
parser.add_option(
"--cookie",
dest="cookie",
help="Please input the url's cookie")
# parser.add_option(
# "--s",
# dest="a_str",
# help="Please input you want to construct a string")
parser.add_option(
"--p",
dest="plain",
help="Please input if you know cookie's pliantext")
return parser

def main():
parser = add_parse()
(options, args) = parser.parse_args()
if not (options.url and options.length and options.cookie):
parser.parse_args(['cbc-padding-oracle-attack.py', '-h'])
exit(-1)
options.a_str = "username=a\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x50&username=admin"
cbca = POAttack(options)
r, s = cbca.attack(options.a_str)
print "+-------------------+"
print "| Result |"
print "+--------------------+"
print "Your want string's cookie: " + r
print "AES decrypt result: " + s

if __name__ == '__main__':
main()

GGCTF的对称密码学

https://nobb.site/2016/05/03/0x1A/

Author

Hcamael

Posted on

2016-05-03

Updated on

2017-08-08

Licensed under