0x00 题目分析

VM,重点在于解释器,把opcode整出来

0x01 解

无壳

直接拖IDA看看

main函数内容还算清晰,可以知道的信息是flag长度为32,下面的for循环应该是用于判断

sub_41103C跟进去可以发现有个scanf

sub_41145B将参数与0x66异或,测试了一下byte_41F5C4,应该只是用于解密输出

1
2
3
4
s = [0x01, 0x03, 0x12, 0x46, 0x12, 0x0e, 0x03, 0x46, 0x0A, 0x07, 0x01, 0x47, 0x66]
for i in s:
print(chr(i ^ 0x66), end="")
# get the lag!

那么关键的函数就应该是sub_4112D0,sub_41128A和sub_41118B了

先跟进sub_41118B看看

给v9附了很多值,然后调了一个sub_4112DF

是VM,而且case不少

可以回收站了

先不碰这个了,回到main看看另外两个函数sub_4112D0和sub_41128A

先看看sub_4112D0,进去可以发现有很多赋值为0的,对比可以发现这些值都在vm的函数中出现

所以合理猜测这些是寄存器

改下名比较好认

这样虚拟机也就比较好看了

往下翻翻可以看到函数sub_41141A,将dword_41E000[r0]给r1或将dowrd_41E080[r2]中的值给r1

之后是三个函数sub_411307,sub_411334和sub_41127B

这三个函数除了寄存器外,参数里还有数组

这些函数跟进去可以发现又回到了VM这里,应该是属于子函数一样的东西

这些数组里面都没有东西,运行起来才知道里面是什么

这题有几点不一样的地方,题目文件是32位的,但这里虚拟机却是64位的。跟进sub_41141A,就可以发现这里把64位拆成两个32位,后续所有的操作中都是如此。即通过32位数组的形式实现64位的操作。

还有一点在于这里的虚拟机更像是虚拟函数,因为这里面嵌套了虚拟机,根据opcode的不同实现不同的功能。

写写解释器吧

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
def vm(opcode, eip):
if opcode[eip] == 81:
exit()
code = {
1: ["mov r0,r1", 1],
2: ["mov r0,r2", 1],
3: ["mov r0,r3", 1],
4: ["mov r1,r0", 1],
5: ["mov r1,r2", 1],
6: ["mov r1,r3", 1],
7: ["mov r2,r0", 1],
8: ["mov r2,r1", 1],
9: ["mov r2,r3", 1],
10: ["mov r3,r0", 1],
11: ["mov r3,r1", 1],
12: ["mov r3,r2", 1],
13: [f"mov r3,{(opcode[eip + 1])}", 2],
14: ["nop", 1],
15: ["nop", 1],
16: ["push r1", 1],
17: [f"shl r0, {(opcode[eip+1])}", 2],
18: ["pop r2", 1],
19: ["nop", 1],
20: [f"shr r1, {(opcode[eip+1])}", 2],
21: ["pop r3", 1],
22: ["nop", 1],
23: ["nop", 1],
24: ["add r0,r2", 1],
25: ["nop", 1],
28: ["push r0", 1],
26: ["nop", 1],
30: [f"shr r0, {(opcode[eip+1])}", 2],
27: ["nop", 1],
29: ["nop", 1],
31: ["nop", 1],
32: ["nop", 1],
33: ["nop", 1],
34: ["__CheckForDebuggerJustMyCode", 1],
35: ["pop r1", 1],
36: ["add r1,r0", 1],
37: ["pop r0", 1],
38: [f"xor r0,{(opcode[eip + 1])}", 2],
39: ["push r2", 1],
40: ["xor r0,r1", 1],
41: [f"xor r1,{(opcode[eip + 1])}", 2],
42: ["add r1,r2", 1],
43: ["xor r1,r2", 1],
44: ["nop", 2],
45: ["add r0,r1", 1],
47: ["nop", 2],
48: ["unknow", 0],
49: ["nop", 2],
50: ["nop", 1],
51: ["nop", 2],
52: ["nop", 1],
53: ["nop", 1],
54: ["nop", 1],
55: [f"and r0,{(opcode[eip + 1])}", 2],
56: ["nop", 1],
57: [f"mov r2,{(opcode[eip + 1])}", 2],
58: [f"mov r1,{(opcode[eip + 1])}", 2],
59: ["nop", 1],
60: ["nop", 1],
61: ["shl r2,r0", 1],
62: ["nop", 2],
63: ["nop", 1],
64: ["nop", 1],
65: ["nop", 2],
66: [f"mov r5,{(opcode[eip + 1])}", 2],
67: ["mov r1,r4", 1],
68: ["shr r1,r0", 1],
69: ["mov r1,dword_47E000[r0]", 1],
70: [f"shl r3,{(opcode[eip + 1])}", 2],
71: ["shl r1,r3", 1],
72: ["add r5,r1", 1],
73: ["add r2,1", 1],
74: ["shr r1,r2", 1],
75: ["mov r1,dword_47E080[r2]", 1],
76: ["shl r0,r1", 1],
77: ["add r5,r0", 1],
78: ["r0 = 0,r1 = 0,r3 = 0,r5 = 0\n\t push r4\n\t push dword_4203A8[index]\n\t index++\n\t call vm(sub_func1,5)\n\t mov r4,r0", 1],
79: ["r2 = 0\n\t call vm(sub_func2,243)\n\t mov r4,r5", 1],
80: ["r5 = 0,r2 = 0\n\t call vm(sub_func3,579)\n\t mov r4,r5\n\t check index == 31", 1]
}
print(opcode[eip], '\t', code[opcode[eip]][0])
eip += code[opcode[eip]][1]
return eip

重点说明一下那个push,在push函数里面可以看到这个

和pop函数是相对应的

解释器写完了就先去看看主函数

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
from vm import vm
v9 = [81]*129
v9[128] = 32
v9[0] = 57
v9[1] = 0
v9[2] = 78
v9[3] = 79
v9[4] = 80
v9[5] = 73
v9[6] = 78
v9[7] = 79
v9[8] = 80
v9[9] = 73
v9[10] = 78
v9[11] = 79
v9[12] = 80
v9[13] = 73
v9[14] = 78
v9[15] = 79
v9[16] = 80
v9[17] = 73
v9[18] = 78
v9[19] = 79
v9[20] = 80
v9[21] = 73
v9[22] = 78
v9[23] = 79
v9[24] = 80
v9[25] = 73
v9[26] = 78
v9[27] = 79
v9[28] = 80
v9[29] = 73
v9[30] = 78
v9[31] = 79
v9[32] = 80
v9[33] = 73
v9[34] = 78
v9[35] = 79
v9[36] = 80
v9[37] = 73
v9[38] = 78
v9[39] = 79
v9[40] = 80
v9[41] = 73
v9[42] = 78
v9[43] = 79
v9[44] = 80
v9[45] = 73
v9[46] = 78
v9[47] = 79
v9[48] = 80
v9[49] = 73
v9[50] = 78
v9[51] = 79
v9[52] = 80
v9[53] = 73
v9[54] = 78
v9[55] = 79
v9[56] = 80
v9[57] = 73
v9[58] = 78
v9[59] = 79
v9[60] = 80
v9[61] = 73
v9[62] = 78
v9[63] = 79
v9[64] = 80
v9[65] = 73
v9[66] = 78
v9[67] = 79
v9[68] = 80
v9[69] = 73
v9[70] = 78
v9[71] = 79
v9[72] = 80
v9[73] = 73
v9[74] = 78
v9[75] = 79
v9[76] = 80
v9[77] = 73
v9[78] = 78
v9[79] = 79
v9[80] = 80
v9[81] = 73
v9[82] = 78
v9[83] = 79
v9[84] = 80
v9[85] = 73
v9[86] = 78
v9[87] = 79
v9[88] = 80
v9[89] = 73
v9[90] = 78
v9[91] = 79
v9[92] = 80
v9[93] = 73
v9[94] = 78
v9[95] = 79
v9[96] = 80
v9[97] = 73
v9[98] = 78
v9[99] = 79
v9[100] = 80
v9[101] = 73
v9[102] = 78
v9[103] = 79
v9[104] = 80
v9[105] = 73
v9[106] = 78
v9[107] = 79
v9[108] = 80
v9[109] = 73
v9[110] = 78
v9[111] = 79
v9[112] = 80
v9[113] = 73
v9[114] = 78
v9[115] = 79
v9[116] = 80
v9[117] = 73
v9[118] = 78
v9[119] = 79
v9[120] = 80
v9[121] = 73
v9[122] = 78
v9[123] = 79
v9[124] = 80
v9[125] = 73
print(v9)

r = 0
while 1:
r = vm(v9, r)
if r >= len(v9):
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
26
27
28
29
30
"""
57 mov r2,0

78 r0 = 0,r1 = 0,r3 = 0,r5 = 0
push dword_4203A8[index]
call vm(sub_func1,5)
mov r4,r0
79 r2 = 0
call vm(sub_func2,243)
mov r4,r5
80 r5 = 0,r2 = 0
call vm(sub_func3,579)
mov r4,r5
check dword_4205C8 == 31
73 add r2,1

78 r0 = 0,r1 = 0,r3 = 0,r5 = 0
push dword_4203A8[index]
call vm(sub_func1,5)
mov r4,r0
79 r2 = 0
call vm(sub_func2,243)
mov r4,r5
80 r5 = 0,r2 = 0
call vm(sub_func3,579)
mov r4,r5
check dword_4205C8 == 31
73 add r2,1
......
"""

一个pattern被重复31(127-1)/4=31次,这个pattern为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""
78 r0 = 0,r1 = 0,r3 = 0,r5 = 0
push dword_4203A8[index]
index++
call vm(sub_func1,5)
mov r4,r0
79 r2 = 0
call vm(sub_func2,243)
mov r4,r5
80 r5 = 0,r2 = 0
call vm(sub_func3,579)
mov r4,r5
check index == 31
73 add r2,1
"""

看得出来这个r2应该被用于判断轮数了,然后pattern内调用了三个子函数,该看看子函数是什么了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import struct
from vm import vm

def read_int_array(file_name):
with open(file_name, 'rb') as f:
data = f.read()
return struct.unpack('i' * (len(data) // 4), data)

a = read_int_array('./sub_func1.txt')
r = 0
while 1:
r = vm(a, r)
if r >= len(a):
break

子函数一:

1
2
3
4
5
6
"""
37 pop r0
35 pop r1
40 xor r0,r1
38 xor r0,51
"""

结合一下主函数的内容,不难判断这是把dword_4203A8[index]进行了一个异或的操作

子函数二:

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
"""
66 mov r5,0 r5: sum = 0
57 mov r2,0 r2: rounds = 0

2 mov r0,r2
17 shl r0, 2
67 mov r1,r4
68 shr r1,r0
1 mov r0,r1
55 and r0,15
69 mov r1,dword_47E000[r0]
12 mov r3,r2
70 shl r3,2
71 shl r1,r3
72 add r5,r1
73 add r2,1

2 mov r0,r2
17 shl r0, 2
67 mov r1,r4
68 shr r1,r0
1 mov r0,r1
55 and r0,15
69 mov r1,dword_47E000[r0]
12 mov r3,r2
70 shl r3,2
71 shl r1,r3
72 add r5,r1
73 add r2,1
......
"""

这也是一个pattern不断循环,循环的pattern为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
2 mov r0,r2
17 shl r0, 2 r0: rounds << 2
67 mov r1,r4 r1: enc = input ^ key
68 shr r1,r0 r1: enc >> (rounds << 2)
1 mov r0,r1 r0: enc >> (rounds << 2)
55 and r0,15 r0: (enc >> (rounds << 2)) & 15
69 mov r1,dword_47E000[r0] r1: dword_47E000[((enc >> (rounds << 2)) & 15)]
12 mov r3,r2 r3: rounds
70 shl r3,2 r3: rounds << 2
71 shl r1,r3 r1: (arr[((enc >> (rounds << 2)) & 15)]) << (rounds << 2)
72 add r5,r1 r5: sum += ((arr[((enc >> (rounds << 2)) & 15)]) << (rounds << 2))
73 add r2,1 r2: rounds += 1
"""

结合一下主函数,这里是把dword_4203A8[index]进行了更进一步的操作

其中需要知道dword_47E000

动调可得

1
dword_47E000 = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12]

由于这个dword_47E000只有16个值,因此上面的pattern只会循环16次

结合一下子函数一,将汇编转成python

1
2
3
4
5
6
7
8
def calc_sum_1(state,key_index):
sum_ = 0
rounds = 0
state = state ^ dword_4203A8[index] ^ 51
for i in range(16):
sum_ += (arr1[(state >> (rounds << 2)) & 15]) << (rounds << 2)
rounds += 1
return sum_

子函数三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
66 mov r5,0 r5: sum = 0
57 mov r2,0 r2: rounds = 0

67 mov r1,r4
74 shr r1,r2
1 mov r0,r1
55 and r0,1
75 mov r1,dword_47E080[r2]
76 shl r0,r1
77 add r5,r0
73 add r2,1

67 mov r1,r4
74 shr r1,r2
1 mov r0,r1
55 and r0,1
75 mov r1,dword_47E080[r2]
76 shl r0,r1
77 add r5,r0
73 add r2,1
....
"""

同样也是存在一个pattern不断循环

1
2
3
4
5
6
7
8
9
10
"""
67 mov r1,r4 r1: sum
74 shr r1,r2 r1: sum >> rounds
1 mov r0,r1 r0: sum >> rounds
55 and r0,1 r0: (sum >> rounds) & 1
75 mov r1,dword_47E080[r2] r1: dword_47E080[rounds]
76 shl r0,r1 r0: ((sum >> rounds) & 1) << dword_47E080[rounds]
77 add r5,r0 r5: sum += ((sum >> rounds) & 1) << dword_47E080[rounds]
73 add r2,1 r2: round += 1
"""

同样需要知道dword_47E080,同理动调

1
dword_47E080 = [63, 47, 31, 15, 62, 46, 30, 14, 61, 45, 29, 13, 60, 44, 28, 12, 59, 43, 27, 11, 58, 42, 26, 10, 57, 41, 25, 9, 56, 40, 24, 8, 55, 39, 23, 7, 54, 38, 22, 6, 53, 37, 21, 5, 52, 36, 20, 4, 51, 35, 19, 3, 50, 34, 18, 2, 49, 33, 17, 1, 48, 32, 16, 0]

同理,这个pattern只会循环64次

转成python可得

1
2
3
4
5
6
7
def calc_sum_2(sum_1):
total = 0
rounds = 0
for i in range(64):
total += ((sum_1 >> rounds) & 1) << p_box[rounds]
rounds += 1
return total

再配合动调得出dword_4203A8

1
dword_4203A8 = [7596569225765413736, 14892793705278438672, 14897626102458855899, 15127138055849948981, 17864553494577085546, 1138107802150484470, 8911548945514081943, 3235953879216500034, 11010657345162096808, 14479365304648704483, 12150232467342833339, 12487849248796427385, 13416156134946011488, 5920119153271060313, 8053781650739871525, 9558394794771259844, 4339208423645057876, 16476906405380659427, 11912452293618884519, 1406977800464934583, 2088792745124906531, 12099790537768180319, 2503949348962038751, 1807992343561743431, 1910441527462264642, 10680376501786139932, 3656171727515946310, 8846275897411559763, 15016467168181810528, 14129026293696237890, 127218696325272760, 14650765194520711557]

结合上面全部,可以得到

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
dword_47E000 = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12]
dword_47E080 = [63, 47, 31, 15, 62, 46, 30, 14, 61, 45, 29, 13, 60, 44, 28, 12, 59, 43, 27, 11, 58, 42, 26, 10, 57, 41, 25, 9,
56, 40, 24, 8, 55, 39, 23, 7, 54, 38, 22, 6, 53, 37, 21, 5, 52, 36, 20, 4, 51, 35, 19, 3, 50, 34, 18, 2, 49, 33,
17, 1, 48, 32, 16, 0]

dword_4203A8 = [7596569225765413736, 14892793705278438672, 14897626102458855899, 15127138055849948981, 17864553494577085546,
1138107802150484470, 8911548945514081943, 3235953879216500034, 11010657345162096808, 14479365304648704483,
12150232467342833339, 12487849248796427385, 13416156134946011488, 5920119153271060313, 8053781650739871525,
9558394794771259844, 4339208423645057876, 16476906405380659427, 11912452293618884519, 1406977800464934583,
2088792745124906531, 12099790537768180319, 2503949348962038751, 1807992343561743431, 1910441527462264642,
10680376501786139932, 3656171727515946310, 8846275897411559763, 15016467168181810528, 14129026293696237890,
127218696325272760, 14650765194520711557]


def calc_sum_1(state):
sum_ = 0
rounds = 0
for i in range(16):
sum_ += (dword_47E000[(state >> (rounds << 2)) & 15]) << (rounds << 2)
rounds += 1
return sum_


def calc_sum_2(sum_1):
total = 0
rounds = 0
for i in range(64):
total += ((sum_1 >> rounds) & 1) << dword_47E080[rounds]
rounds += 1
return total


def encrypt(input_):
key_index = 0
s = 0
r5 = input_
for i in range(31):
s = r5 ^ dword_4203A8[key_index] ^ 51
s = calc_sum_1(s)
s = calc_sum_2(s)
r5 = s
key_index += 1

return s ^ 14650765194520711557 ^ 51


a = 0x6161616161616161
print(hex(encrypt(a)))

经过对比,发现是present魔改加密

不过还是先获取密文吧,此程序的密文需要先动调再从表中获取

dword_420578是存放密文索引的地方(不过只使用0~3四个索引)

而dword_41E280和dword_41E284则是存放密文表的地方

需要注意:

1.所获取的密文为8个字节(64bit),比较时被分为高4字节(32bit)和低4字节(32bit)进行比较

2.dword_420578需要动调才能被填充

3.dword_41E280和dword_41E284实际上是连着的,高32位从dword_41E280和dword_41E284的结合中取,而低32位仅从dword_41E284中取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import struct


def read_int_array(file_name):
with open(file_name, 'rb') as f:
data = f.read()
return struct.unpack('i' * (len(data) // 4), data)


a = read_int_array('./enc_index.txt')
b = read_int_array('./enc_table_1.txt')
b += read_int_array('./enc_table_2.txt')
for i in range(4):
tmp = ""
tmp += hex(b[2*(a[i])+1]+2**32)[2:]
tmp += hex(b[2*a[i]]+2**32)[2:]
print(int(tmp, 16))
# 11246785761149773209
# 16090349977178840068
# 13524222093275577792
# 10243890929873528779

在github上有原始present解密(https://github.com/wuhanstudio/present/tree/master/present-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
Sbox = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12]
Sbox_inv = [Sbox.index(x) for x in range(16)]

PBox = [63, 47, 31, 15, 62, 46, 30, 14, 61, 45, 29, 13, 60, 44, 28, 12, 59, 43, 27, 11, 58, 42, 26, 10, 57, 41, 25, 9,
56, 40, 24, 8, 55, 39, 23, 7, 54, 38, 22, 6, 53, 37, 21, 5, 52, 36, 20, 4, 51, 35, 19, 3, 50, 34, 18, 2, 49, 33,
17, 1, 48, 32, 16, 0]
PBox_inv = [PBox.index(x) for x in range(64)]
def addRoundKey(state, roundkey):
return state ^ roundkey ^ 51

def sBoxLayer(state):
"""SBox function for encryption

Input: 64-bit integer
Output: 64-bit integer"""

output = 0
for i in range(16):
output += Sbox[(state >> (i * 4)) & 0xF] << (i * 4)
return output


def sBoxLayer_dec(state):
"""Inverse SBox function for decryption

Input: 64-bit integer
Output: 64-bit integer"""
output = 0
for i in range(16):
output += Sbox_inv[(state >> (i * 4)) & 0xF] << (i * 4)
return output


def pLayer(state):
"""Permutation layer for encryption

Input: 64-bit integer
Output: 64-bit integer"""
output = 0
for i in range(64):
output += ((state >> i) & 0x01) << PBox[i]
return output


def pLayer_dec(state):
"""Permutation layer for decryption

Input: 64-bit integer
Output: 64-bit integer"""
output = 0
for i in range(64):
output += ((state >> i) & 0x01) << PBox_inv[i]
return output


def string2number(i):
""" Convert a string to a number

Input: string (big-endian)
Output: long or integer
"""
return int(i, 16)


def number2string_N(i, N):
"""Convert a number to a string of fixed size

i: long or integer
N: length of string
Output: string (big-endian)
"""
s = '%0*x' % (N * 2, i)
return s


class Present:

def __init__(self, rounds=32):
self.rounds = rounds
self.roundkeys = [7596569225765413736, 14892793705278438672, 14897626102458855899, 15127138055849948981,
17864553494577085546,
1138107802150484470, 8911548945514081943, 3235953879216500034, 11010657345162096808,
14479365304648704483,
12150232467342833339, 12487849248796427385, 13416156134946011488, 5920119153271060313,
8053781650739871525,
9558394794771259844, 4339208423645057876, 16476906405380659427, 11912452293618884519,
1406977800464934583,
2088792745124906531, 12099790537768180319, 2503949348962038751, 1807992343561743431,
1910441527462264642,
10680376501786139932, 3656171727515946310, 8846275897411559763, 15016467168181810528,
14129026293696237890,
127218696325272760, 14650765194520711557]


def decrypt(self, state):
"""Decrypt 1 block (8 bytes)

Input: ciphertext block as raw string
Output: plaintext block as raw string
"""
for i in range(self.rounds - 1):
state = addRoundKey(state, self.roundkeys[-i - 1])
state = pLayer_dec(state)
state = sBoxLayer_dec(state)
decipher = addRoundKey(state, self.roundkeys[0])
return decipher

def get_block_size(self):
return 8


test = Present()
enc = [11246785761149773209, 16090349977178840068, 13524222093275577792, 10243890929873528779]
for k in range(4):
src = test.decrypt(enc[k])
for i in range(8):
print(chr((src >> (8 * i)) & 0xff), end='')

# a9d99caef9ae999a299129c91299fc95