狗儿

热爱的话就坚持吧~

0%

DASCTF 安恒七月赛 逆向

我就是一条懒狗,天天白天睡觉。

游戏辅助

本来应该是签到题,但是实际上是脑洞题。

出题人本意可能是考察远程注入,但是题目描述太过于模糊,显得太过于脑洞了。

给出两个程序,一个是游戏辅助(假的,仅含注册码的判断的相关代码),一个是破解这个游戏辅助的补丁。

运行游戏辅助时要求输入注册码,随便输入了一个,不正确。

然后打开补丁,等破解完成后,再次随便输入一个注册码,正确。

补丁的窗口输出:flag{md5(dec(What_you_found))}

游戏辅助的代码很简单,明文比较注册码是否和1_am_n0t_f1ag相等。

游戏复制窗口中注册码输入1_am_n0t_f1ag,可以成功注册,没看懂上面的dec()是啥意思,我以为是decrypt,解密的意思,但是比较过程是明文比较的,也没有加密的过程啊。后来题目描述更新了,说是dec是十进制的意思。我无语了。。。

1_am_n0t_f1ag取md5提交,并不正确。

正确的What_you_found不在游戏辅助里面,而是在补丁里面。

看一下游戏补丁的部分代码:

1595946414254

1
2
3
4
5
6
7
8
9
10
11
12
13
WriteProcessMemory(v4, (char *)&loc_401227 + 1, &Buffer, 2u, 0);
// 向目标进程的内存0x401228处写入
// &Buffer开始的2个字节的数据,0x90 0x90,即两个nop

WriteProcessMemory(v4, (char *)&loc_401014 + 2, &v9, 5u, 0);
// 向目标进程的内存0x401016处写入
// &v9开始的5个字节的数据,B8 A9 F1 00 00
// 即mov eax, 0x0000f1a9

WriteProcessMemory(v4, (char *)&loc_401019 + 2, &v7, 5u, 0);
// 向目标进程的内存0x40101b处写入
// &v7开始的5个字节的数据,BB 38 6E 0F 02
// 即mov ebx, 0x020f6e38

第2个和第3个WriteProcessMemory分别写入5个数据,拿第3个WriteProcessMemory举个例子:

1
2
3
4
v7 = 0x0F6E38BB;
v8 = 0x00000002;
v7是[bp-14],v8是[bp-10h]
依次写入BB 38 6E 0F 02

TIM图片20200728224437

上图来自本题一血的炜昊大佬:

为什么运行游戏补丁后,游戏辅助中随便输入一个注册码就能通过检测了呢?我们再来看一下游戏辅助中的0x401228附近。

1595946538781

上上图中,游戏补丁向游戏复制的内存的0x401228中写入了nop nop,正好件将jnz short loc_40125c覆盖,loc_40125C是注册失败的分支。覆盖后,无论输入的字符串和注册码的验证结果是否相同,都不会跳转至失败的分支,而是继续往下运行,即注册成功的分支。

flag是啥?是md5(20f6e38),20fe38是游戏补丁中向游戏辅助的内存0x40101b处写入的数据。别问我问啥偏偏是这个数据,我也不知道,要是知道的话,我当时就做出来了。

考察远程注入这个知识点倒是挺好的,但是你倒是说明白flag到底是啥呀,What you found,我found的数据可多了,我怎么知道是哪个。。。

simple

1595948326906

看起来似乎很简单,输入长度为16的字符串,进行32轮tea加密,比较密文。

虚假的解密脚本

v8, v9是明文->密文,v10每轮要减去0x61C8864661C88647或者加上0x9E3779B99E3779B9,注意需要手动维持溢出。v3和v4是中间变量,直接化简去掉即可。

轻松写出解密脚本:

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
def ull(n):
return n & 0xffffffffffffffff

def get_v10(i):
v10 = 0
for i in range(i+1):
#v10 = ull(v10 - 0x61C8864661C88647)
v10 = ull(v10 + 0x9E3779B99E3779B9)
return v10

def hex2bytes(h):
import struct
b = b''
for i in range(7,-1,-1):
b += struct.pack('b', int(h[2+i*2:2+i*2+2], 16))
print(b)
return b

def md5(b):
import hashlib
m = hashlib.md5()
m.update(b)
mm = m.hexdigest()
print(mm)

def solve():
v8 = 0xC3D3413C4B6381F9
v9 = 0x8A6047975A08CCBA
for i in range(31, -1, -1):
v10 = get_v10(i)
v9 = ull(v9 - ull(ull(v8+v10) ^ ull(16*v8) ^ ull(v8>>5)))
v8 = ull(v8 - ull(ull(v9+v10) ^ ull(16*v9) ^ ull(v9>>5)))
print(hex(v8), hex(v9), hex(v10))
b = hex2bytes(hex(v8)) + hex2bytes(hex(v9))
print(b)
md5(b)

solve()

解出的输入应该为flag_is_not_here,按照题目要求取md5提交,不正确。啊哈,怎么回事??

查看交叉引用,发现这个函数(sub_402436)并没有被call,只是mov而已:

1595949291230

先去call的sub_402880,这个函数贼复杂,直接动调吧。后来才知道,这个函数类似于脱壳,将代码解码出来。

动调

本想动调看看哪里有问题,对于这个ELF64,ida7.0不知道为啥动调失败,好在最后发现ida6.8可以动调它。

经过多次动调总结:在4110f0处按f4,输入长度16的字符串

1595949631903

1595949711343

图1是按f4前,图2是按f4并输入长度为16的字符串后。

按3次f8,即可来到解压出的新代码处,是个堆(heap):

1595949809381

其中有些奇奇怪怪的汇编指令:

1595949868541

dec eax频繁出现,而且这个ELF64的程序,汇编指令中没有出现任何一个rax之类的。出大问题。

我的猜测是:

1
2
3
4
dec     eax
mov eax, [eax+8]
的机器码同样可以反汇编成:
mov rax, [rax+8]

猜测的对不对我不清楚,反正我拿capstone反汇编的结果验证了我的猜测。

capstone 机器码->汇编代码

ida反汇编的结果太影响理解了。

idc脚本提取出这个堆里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
static main()
{
auto i,fp;
fp = fopen("d:\\dump","wb");
auto start = 0x1fd4b20;
auto size = 0x1fd4d00 - 0x1fd4b20;
for(i=start;i<start+size;i++)
{
fputc(Byte(i),fp);
}
fp.close();
}

使用capstone反汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from capstone import *

def dis(b):
shellcode = b
md = Cs(CS_ARCH_X86, CS_MODE_64)
disasm = ''
for i in md.disasm(shellcode, 0x00):
#print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
disasm += "\t%s\t%s\n" %(i.mnemonic, i.op_str)
return disasm

#with open(r'd:\dump', 'rb')as f:
# b = f.read()
b = b'UH\x89\xe5H\x83\xec`dH\x8b\x04%(\x00\x00\x00H\x89E\xf81\xc0H\x89u\xb8\xc6E\xeb\x84\xc6E\xec\xb2\xc6E\xed\xa8\xc6E\xee\xaf\xc6E\xef\xfd\xc6E\xf0\xb4\xc6E\xf1\xb3\xc6E\xf2\xad\xc6E\xf3\xa8\xc6E\xf4\xa9\xc6E\xf5\xfd\xc6E\xf6\xe7\xc6E\xf7\xfd\xc6E\xe1\x9c\xc6E\xe2\x87\xc6E\xe3\x89\xc6E\xe4\x86\xc6E\xe5\x9a\xc6E\xe6\x88\xc6E\xe7\x8d\xc6E\xe8\x90\xc6E\xe9\x91\xc6E\xea\x98\xc7E\xa8\x00\x00\x00\x00\x83}\xa8\x0c\x7f\x1d\x8bE\xa8H\x98\x0f\xb6D\x05\xeb\x83\xf0\xdd\x0f\xb6\xc0\x89\xc7\xe8\x85\xb4\xe6\xfe\x83E\xa8\x01\xeb\xddH\x8bE\xb8H\x89\xc7\xe8s\xb2\xe6\xfeH\x8bE\xb8H\x8b\x00H\x89E\xc0H\x8bE\xb8H\x8b@\x08H\x89E\xc8H\xc7E\xd0\x00\x00\x00\x00H\xb8\x8aJ\x04\xad\x8aJ\x04\xbdH\x89E\xd8H\xb83333333#H1E\xd8\xc7E\xac\x00\x00\x00\x00\x83}\xac\x1f\x7fXH\x8bE\xc8H\xc1\xe0\x04H\x89\xc2H\x8bE\xc8H\xc1\xe8\x05H1\xc2H\x8bE\xc8H\x01\xd0H3E\xd0H\x01E\xc0H\x8bE\xd8H\x01E\xd0H\x8bE\xc0H\xc1\xe0\x04H\x89\xc2H\x8bE\xc0H\xc1\xe8\x05H1\xc2H\x8bE\xc0H\x01\xd0H3E\xd0H\x01E\xc8\x83E\xac\x01\xeb\xa2H\xb8\xaa\x92\x06\x94Y\xb4K\xadH9E\xc0uFH\xb8\xc9\xc6G\xc4N\xfdeVH9E\xc8u6\xc7E\xb0\x00\x00\x00\x00\x83}\xb0\x04\x7f\x1d\x8bE\xb0H\x98\x0f\xb6D\x05\xe1\x83\xf0\xee\x0f\xb6\xc0\x89\xc7\xe8\x8f\xb3\xe6\xfe\x83E\xb0\x01\xeb\xdd\xbf\n\x00\x00\x00\xe8\x7f\xb3\xe6\xfe\xeb3\xc7E\xb4\x00\x00\x00\x00\x83}\xb4\x04\x7f\x1c\x8bE\xb4H\x98\x0f\xb6D\x05\xe6\xf7\xd0\x0f\xb6\xc0\x89\xc7\xe8Z\xb3\xe6\xfe\x83E\xb4\x01\xeb\xde\xbf\n\x00\x00\x00\xe8J\xb3\xe6\xfe\xbf\x00\x00\x00\x00\xe8\xd0\x97\xe6\xfe'
d = dis(b)
print(d)

输出的汇编代码为:

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
push    rbp
mov rbp, rsp
sub rsp, 0x60
mov rax, qword ptr fs:[0x28]
mov qword ptr [rbp - 8], rax
xor eax, eax
mov qword ptr [rbp - 0x48], rsi
mov byte ptr [rbp - 0x15], 0x84
mov byte ptr [rbp - 0x14], 0xb2
mov byte ptr [rbp - 0x13], 0xa8
mov byte ptr [rbp - 0x12], 0xaf
mov byte ptr [rbp - 0x11], 0xfd
mov byte ptr [rbp - 0x10], 0xb4
mov byte ptr [rbp - 0xf], 0xb3
mov byte ptr [rbp - 0xe], 0xad
mov byte ptr [rbp - 0xd], 0xa8
mov byte ptr [rbp - 0xc], 0xa9
mov byte ptr [rbp - 0xb], 0xfd
mov byte ptr [rbp - 0xa], 0xe7
mov byte ptr [rbp - 9], 0xfd
mov byte ptr [rbp - 0x1f], 0x9c
mov byte ptr [rbp - 0x1e], 0x87
mov byte ptr [rbp - 0x1d], 0x89
mov byte ptr [rbp - 0x1c], 0x86
mov byte ptr [rbp - 0x1b], 0x9a
mov byte ptr [rbp - 0x1a], 0x88
mov byte ptr [rbp - 0x19], 0x8d
mov byte ptr [rbp - 0x18], 0x90
mov byte ptr [rbp - 0x17], 0x91
mov byte ptr [rbp - 0x16], 0x98
mov dword ptr [rbp - 0x58], 0
cmp dword ptr [rbp - 0x58], 0xc
jg 0xa1
mov eax, dword ptr [rbp - 0x58]
cdqe
movzx eax, byte ptr [rbp + rax - 0x15]
xor eax, 0xffffffdd
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x58], 1
jmp 0x7e
mov rax, qword ptr [rbp - 0x48]
mov rdi, rax
call 0xfffffffffee6b320
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax]
mov qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax + 8]
mov qword ptr [rbp - 0x38], rax
mov qword ptr [rbp - 0x30], 0
movabs rax, 0xbd044a8aad044a8a
mov qword ptr [rbp - 0x28], rax
movabs rax, 0x2333333333333333
xor qword ptr [rbp - 0x28], rax
mov dword ptr [rbp - 0x54], 0
cmp dword ptr [rbp - 0x54], 0x1f
jg 0x14d
mov rax, qword ptr [rbp - 0x38]
shl rax, 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x38]
shr rax, 5
xor rdx, rax
mov rax, qword ptr [rbp - 0x38]
add rax, rdx
xor rax, qword ptr [rbp - 0x30]
add qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x28]
add qword ptr [rbp - 0x30], rax
mov rax, qword ptr [rbp - 0x40]
shl rax, 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x40]
shr rax, 5
xor rdx, rax
mov rax, qword ptr [rbp - 0x40]
add rax, rdx
xor rax, qword ptr [rbp - 0x30]
add qword ptr [rbp - 0x38], rax
add dword ptr [rbp - 0x54], 1
jmp 0xef
movabs rax, 0xad4bb459940692aa
cmp qword ptr [rbp - 0x40], rax
jne 0x1a3
movabs rax, 0x5665fd4ec447c6c9
cmp qword ptr [rbp - 0x38], rax
jne 0x1a3
mov dword ptr [rbp - 0x50], 0
cmp dword ptr [rbp - 0x50], 4
jg 0x197
mov eax, dword ptr [rbp - 0x50]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x50], 1
jmp 0x174
mov edi, 0xa
call 0xfffffffffee6b520
jmp 0x1d6
mov dword ptr [rbp - 0x4c], 0
cmp dword ptr [rbp - 0x4c], 4
jg 0x1cc
mov eax, dword ptr [rbp - 0x4c]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1a]
not eax
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x4c], 1
jmp 0x1aa
mov edi, 0xa
call 0xfffffffffee6b520
mov edi, 0
call 0xfffffffffee699b0

capstone反汇编的结果中,跳转分支没有明确地指示,call的函数也和ida中的表示不太一样,不过对照着看也不是不行。

汇编分析

分析结果大体如下:

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
push    rbp
mov rbp, rsp
sub rsp, 0x60
mov rax, qword ptr fs:[0x28]
mov qword ptr [rbp - 8], rax
xor eax, eax
mov qword ptr [rbp - 0x48], rsi
mov byte ptr [rbp - 0x15], 0x84 //Your input :
mov byte ptr [rbp - 0x14], 0xb2 //func: s[i] ^ 0xdd
mov byte ptr [rbp - 0x13], 0xa8
mov byte ptr [rbp - 0x12], 0xaf
mov byte ptr [rbp - 0x11], 0xfd
mov byte ptr [rbp - 0x10], 0xb4
mov byte ptr [rbp - 0xf], 0xb3
mov byte ptr [rbp - 0xe], 0xad
mov byte ptr [rbp - 0xd], 0xa8
mov byte ptr [rbp - 0xc], 0xa9
mov byte ptr [rbp - 0xb], 0xfd
mov byte ptr [rbp - 0xa], 0xe7
mov byte ptr [rbp - 9], 0xfd
mov byte ptr [rbp - 0x1f], 0x9c //right
mov byte ptr [rbp - 0x1e], 0x87 //func: s[i] ^ 0xee
mov byte ptr [rbp - 0x1d], 0x89
mov byte ptr [rbp - 0x1c], 0x86
mov byte ptr [rbp - 0x1b], 0x9a
mov byte ptr [rbp - 0x1a], 0x88 //wrong
mov byte ptr [rbp - 0x19], 0x8d //func: ~s[i]
mov byte ptr [rbp - 0x18], 0x90
mov byte ptr [rbp - 0x17], 0x91
mov byte ptr [rbp - 0x16], 0x98



mov dword ptr [rbp - 0x58], 0

cmp dword ptr [rbp - 0x58], 0xc
jg 0xa1 //跳出本段代码
mov eax, dword ptr [rbp - 0x58]
cdqe
movzx eax, byte ptr [rbp + rax - 0x15] //输出"Your input : %16c"
xor eax, 0xffffffdd
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520 //即ida中的sub_412040,输出一个字符
add dword ptr [rbp - 0x58], 1
jmp 0x7e
mov rax, qword ptr [rbp - 0x48]
mov rdi, rax
call 0xfffffffffee6b320 //goto 本段代码开始处



mov rax, qword ptr [rbp - 0x48] //输入字符串赋值给var_40, var_38
mov rax, qword ptr [rax] //即[rbp - 0x40] ~ [rbp - 0x31]
mov qword ptr [rbp - 0x40], rax
mov rax, qword ptr [rbp - 0x48]
mov rax, qword ptr [rax + 8]
mov qword ptr [rbp - 0x38], rax



mov qword ptr [rbp - 0x30], 0 //v28 = 0xbd044a8aad044a8a ^ 0x2333333333333333
movabs rax, 0xbd044a8aad044a8a
mov qword ptr [rbp - 0x28], rax
movabs rax, 0x2333333333333333
xor qword ptr [rbp - 0x28], rax



mov dword ptr [rbp - 0x54], 0

cmp dword ptr [rbp - 0x54], 0x1f //循环32次
jg 0x14d //跳出本段代码
mov rax, qword ptr [rbp - 0x38]
shl rax, 4 //v9 << 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x38]
shr rax, 5 //v9 >> 5
xor rdx, rax //(v9 << 4) ^ (v9 >>5)
mov rax, qword ptr [rbp - 0x38]
add rax, rdx //v9 + ((v9 << 4) ^ (v9 >>5))
xor rax, qword ptr [rbp - 0x30] //(v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30
add qword ptr [rbp - 0x40], rax //v8 = v8 + ((v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30)

mov rax, qword ptr [rbp - 0x28]
add qword ptr [rbp - 0x30], rax //v30 = v30 + v28

mov rax, qword ptr [rbp - 0x40]
shl rax, 4 //v8 << 4
mov rdx, rax
mov rax, qword ptr [rbp - 0x40]
shr rax, 5 //v8 >> 5
xor rdx, rax //(v8 << 4) ^ (v8 >> 5)
mov rax, qword ptr [rbp - 0x40]
add rax, rdx //v8 + ((v8 << 4) ^ (v8 >> 5))
xor rax, qword ptr [rbp - 0x30] //(v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30
add qword ptr [rbp - 0x38], rax //v9 = v9 + ((v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30)

add dword ptr [rbp - 0x54], 1
jmp 0xef //跳回本段代码开始处



movabs rax, 0xad4bb459940692aa //judge
cmp qword ptr [rbp - 0x40], rax
jne 0x1a3 //goto wrong
movabs rax, 0x5665fd4ec447c6c9
cmp qword ptr [rbp - 0x38], rax
jne 0x1a3 //goto wrong



mov dword ptr [rbp - 0x50], 0 //输出right
cmp dword ptr [rbp - 0x50], 4
jg 0x197
mov eax, dword ptr [rbp - 0x50]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x50], 1
jmp 0x174
mov edi, 0xa
call 0xfffffffffee6b520
jmp 0x1d6 //goto exit



mov dword ptr [rbp - 0x4c], 0 //输出wrong
cmp dword ptr [rbp - 0x4c], 4
jg 0x1cc
mov eax, dword ptr [rbp - 0x4c]
cdqe
movzx eax, byte ptr [rbp + rax - 0x1a]
not eax
movzx eax, al
mov edi, eax
call 0xfffffffffee6b520
add dword ptr [rbp - 0x4c], 1
jmp 0x1aa
mov edi, 0xa
call 0xfffffffffee6b520



mov edi, 0 //exit
call 0xfffffffffee699b0

Your input:,right,wrong是如何输出的呢?

以right为例:

1
2
3
movzx   eax, byte ptr [rbp + rax - 0x1f]
xor eax, 0xffffffee
movzx eax, al

rax是索引,rbp - 0x1f开始的5个数据对应right。

取出数据后与0xee异或,比如第一个数据[rbp - 0x1f] = 0x9c,chr(0x9c ^ 0xee) = 'r'

后面的4个数据也是这样处理,可得字符串right。

加密流程

下面看一下整体代码的流程,配合多次动调分析:

1.首先是Your Input : rightwrong这些字符所对应的加密数据入栈(数据经过解密处理后才可以得到明文)

2.输出Your Input :

3.将input传值到[rbp - 0x40] ~ [rbp - 0x31],我将var_40记为v8,var_38记为v9,以便和ida对应。

4.v28 = 0xbd044a8aad044a8a ^ 0x2333333333333333

5.tea加密,好像是变种,我不清楚。管它是原版还是变种,直接逆它的逻辑就行。共32次循环,每轮循环:

1
2
3
v8 = v8 + ((v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30)
v30 += 0x2333333333333333 ^ 0xbd044a8aad044a8a
v9 = v9 + ((v8 + ((v8 << 4) ^ (v8 >> 5))) ^ v30)

6.判断v8 == 0xad4bb459940692aa , v9 == 0x5665fd4ec447c6c9

7.输出right或是wrong

8.exit

真正的解密脚本

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
def ull(n):
return n & 0xffffffffffffffff

def hex2bytes(h):
import struct
b = b''
for i in range(7,-1,-1): #这里不是for i in range(8),也把我小坑了一把
b += struct.pack('b', int(h[2+i*2:2+i*2+2], 16))
print(b)
return b

def md5(b):
import hashlib
m = hashlib.md5()
m.update(b)
mm = m.hexdigest()
print(mm)

def get_v30(i):
v10 = 0
for i in range(i):
v10 = ull(v10 + ull(0x2333333333333333 ^ 0xbd044a8aad044a8a)) #尼玛+和^的运算优先级把我给坑死了,大哭
return v10

def solve():
v8 = 0xad4bb459940692aa
v9 = 0x5665fd4ec447c6c9
for i in range(31, -1, -1):
v30 = get_v30(i+1)
v9 = ull(v9 - ull(ull(ull(v8 + ull(ull(v8 << 4) ^ ull(v8 >> 5))) ^ v30)))
v30 = get_v30(i)
v8 = ull(v8 - ull(ull(v9 + ull(ull(v9 << 4) ^ ull(v9 >>5))) ^ v30))
print(hex(v8), hex(v9), hex(v30))
b = hex2bytes(hex(v8)) + hex2bytes(hex(v9))
print(b)
md5(b)

solve()

# 输入为:ae569d8654d7b54e
# 按照题目要求,flag为其md5:6efa713d07cd5bc15d70da8c6b120e5d

注意大小端,注意+^的运算优先级。注意v30的改变是在v8和v9中间。