April 25, 2022

复现

记录一些比较折磨有趣的复现

hgame

感觉做hgame还是学到了挺多的
唯一难过的就是出题人的题解都写得有亿点简略,让我这个菜狗懵逼被折磨的痛不欲生……

hardened

脱壳完了看主类,有一个显眼的加密:

1
2
3
4
5
6
7
8
public void sendPwd(View view) {
Intent intent = new Intent(this, rightpage.class);
if (bbbbb(aesEncryption(((EditText) findViewById(2131165238)).getText().toString().getBytes())).equals("mXYxnHYp61u/5qksdDel6TgiKqcvUbBkX3xErlR4lO0aEAdU0acJY8PRSVXJxxsRR8Dq9MTJhkWLSbBvCG5gtm==")) {
startActivity(intent);
} else {
Toast.makeText(this, "fail >﹏<", 1).show();
}
}

里面那一坨很明显是密文。
有明显的base64特征,根据函数名可以看出来是aes,外面加一个bbbbb的函数,应该就是base64加密。java里没有加密函数,那么去so库找。
libenc里,直接去找函数名aesEncryptionbbbbb,找到:
Java_com_example_hardened_MainActivity_aesEncryption
Java_com_example_hardened_MainActivity_bbbbb

主要任务就是找aes的key和iv,以及base64加密用的码表。

在aes里找字符串,发现aes里调了一个EVP_EncryptInit_ex(v11, v12, 0LL, ooo0oO0O0O0Ooo, OO0o0o00OoOO00o0o);
点进这两个一串0和o的东西,发现分别指向31020和31050处的数据。
base64加密用的码表也是这一串东西,指向31070。
但是麻烦的地方在于,这三组数据都不是可见字符串……可以猜想做了一些改动。

去查交叉引用,发现除了aes和base64加密调用这三组数据,还有一个datadiv_decode982000934203588085函数也调用了这三组数据。
直接看反汇编的代码比较迷……三组数据跟三个数字做了异或,但改变的三组数据并不是之前查到的指向的数据,不过其实可以猜测是我们查到的三组数据依次跟这三个数字异或。
看官方题解说是做了一些混淆……
那么直接看汇编

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
.text:000000000000D924 ; __unwind {
.text:000000000000D924 ADRL X8, stru_31020
.text:000000000000D92C ADRL X9, xmmword_31050
.text:000000000000D934 LDP Q3, Q4, [X8]
.text:000000000000D938 LDRB W11, [X8,#(byte_31040 - 0x31020)]
.text:000000000000D93C LDR Q5, [X9]
.text:000000000000D940 MOVI V0.16B, #0x30 ; '0'
.text:000000000000D944 ADRP X10, #stru_31070@PAGE
.text:000000000000D948 LDRB W12, [X9,#(byte_31060 - 0x31050)]
.text:000000000000D94C MOVI V1.16B, #0x7F
.text:000000000000D950 ADD X10, X10, #stru_31070@PAGEOFF
.text:000000000000D954 EOR V3.16B, V3.16B, V0.16B
.text:000000000000D958 EOR V0.16B, V4.16B, V0.16B
.text:000000000000D95C EOR W11, W11, #0x30
.text:000000000000D960 LDP Q6, Q7, [X10]
.text:000000000000D964 EOR V1.16B, V5.16B, V1.16B
.text:000000000000D968 LDP Q4, Q5, [X10,#0x20]
.text:000000000000D96C STP Q3, Q0, [X8]
.text:000000000000D970 STRB W11, [X8,#(byte_31040 - 0x31020)]
.text:000000000000D974 LDRB W8, [X10,#(byte_310B0 - 0x31070)]
.text:000000000000D978 LDRB W11, [X10,#(byte_310B1 - 0x31070)]
.text:000000000000D97C EOR W12, W12, #0x7F
.text:000000000000D980 MOVI V2.16B, #0x49 ; 'I'
.text:000000000000D984 STR Q1, [X9]
.text:000000000000D988 STRB W12, [X9,#(byte_31060 - 0x31050)]
.text:000000000000D98C MOV W9, #0x49 ; 'I'
.text:000000000000D990 EOR V6.16B, V6.16B, V2.16B
.text:000000000000D994 EOR V7.16B, V7.16B, V2.16B
.text:000000000000D998 EOR V4.16B, V4.16B, V2.16B
.text:000000000000D99C EOR V2.16B, V5.16B, V2.16B
.text:000000000000D9A0 EOR W8, W8, W9
.text:000000000000D9A4 EOR W9, W11, W9
.text:000000000000D9A8 STP Q6, Q7, [X10]
.text:000000000000D9AC STP Q4, Q2, [X10,#0x20]
.text:000000000000D9B0 STRB W8, [X10,#(byte_310B0 - 0x31070)]
.text:000000000000D9B4 STRB W9, [X10,#(byte_310B1 - 0x31070)]
.text:000000000000D9B8 RET
.text:000000000000D9B8 ; } // starts at D924

能看出来逻辑,分别是三组数据跟0x30、0x7f、0x49异或,那么写脚本复原一下即可。

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
key_l=[0x7A, 0x65, 0x63, 0x64, 0x6F, 0x71, 0x6F, 0x7E, 0x7F, 0x62, 
0x7D, 0x71, 0x7C, 0x6F, 0x7B, 0x75, 0x69, 0x6F, 0x76, 0x7F,
0x62, 0x6F, 0x69, 0x7F, 0x65, 0x6F, 0x64, 0x7F, 0x6F, 0x74,
0x75, 0x73]
key=''
for i in key_l:
key+=chr(i^0x30)

iv_l=[0x06, 0x10, 0x0A, 0x20, 0x19, 0x16, 0x11, 0x1B, 0x20, 0x12,
0x1A, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E]
iv=''
for i in iv_l:
iv+=chr(i^0x7f)

base_l=[0x79, 0x78, 0x7B, 0x7A, 0x7D, 0x7C, 0x7F, 0x7E, 0x71, 0x70,
0x08, 0x0B, 0x0A, 0x0D, 0x0C, 0x0F, 0x0E, 0x01, 0x00, 0x03,
0x02, 0x05, 0x04, 0x07, 0x06, 0x19, 0x18, 0x1B, 0x1A, 0x1D,
0x1C, 0x1F, 0x1E, 0x11, 0x10, 0x13, 0x28, 0x2B, 0x2A, 0x2D,
0x2C, 0x2F, 0x2E, 0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27,
0x26, 0x39, 0x38, 0x3B, 0x3A, 0x3D, 0x3C, 0x3F, 0x3E, 0x31,
0x30, 0x33, 0x62, 0x66]
base=''
for i in base_l:
base+=chr(i^0x49)

from Crypto.Cipher import AES
import base64

key=key.encode()
iv=iv.encode()

enc="mXYxnHYp61u/5qksdDel6TgiKqcvUbBkX3xErlR4lO0aEAdU0acJY8PRSVXJxxsRR8Dq9MTJhkWLSbBvCG5gtm=="
model = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
cipher=base64.b64decode(enc.translate(str.maketrans(base,model)))

aes=AES.new(key,AES.MODE_CBC,iv)
print(aes.decrypt(cipher))

学到了frida……之后有空学一学(挖坑++

server

IDA7.6打开,能恢复符号表。
函数列表里找main_main,发现上面两个函数main_HttpHandleFunc和main_encrypt,后者是主要的加密逻辑。

本题为一个 http 服务器,在9090 端口上可以使用 get 方法提交 flag。
端口号在IDA里找,提交flag在浏览器上面输入localhost:9090/?flag=xxxx

看main_encrypt的伪代码,其中函数math_big___ptr_Int__SetString()参数都分析不出来,可以手动修改函数声明来获得更好的反编译结果。

__usercall关键字用来自定义函数声明,观察调用math_big___ptr_Int__SetString()的汇编代码:

1
2
3
4
5
.text:00000000004C5971                 mov     rax, [rsp+arg_0]
.text:00000000004C5976 mov rbx, [rsp+arg_8]
.text:00000000004C597B mov rcx, [rsp+arg_10]
.text:00000000004C5980 mov rdi, [rsp+arg_18]
.text:00000000004C5985 jmp math_big___ptr_Int__SetString

可以确定此函数有4个参数,分别存在rax、rbx、rcx、rdi中。观察多处对math_big___ptr_Int__SetString交叉引用的汇编代码(主要看在main_encrypto里的交叉引用),可以确定各个参数的类型以及顺序:

1
2
3
4
5
6
.text:00000000005E1C54                 mov     ecx, 4Dh ; 'M'
.text:00000000005E1C59 mov edi, 0Ah
.text:00000000005E1C5E lea rax, [rsp+5F0h+var_28]
.text:00000000005E1C66 lea rbx, a92582184765240 ; "925821847652406633647957676942622731050"...
.text:00000000005E1C6D call math_big___ptr_Int__SetString
.text:00000000005E1C72 mov [rsp+5F0h+var_E0], rax

确定参数顺序为rbx、rax、edi、ecx,其中rbx装入了一个字符串的地址,为chr*类型,rax为一个指针,类型为__int64,edi和ecx均为int类型。
返回参数在rax,类型为__int64
修改函数声明如下:
__int64 __usercall math_big___ptr_Int__SetString@<rax>(char *str@<rbx>, __int64 a2@<rax>, int a3@<edi>, int a4@<ecx>)

拿到main_encrypto的反编译结果:

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
void __golang main_encrypt(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rax
unsigned __int64 v3; // rbx
__int64 v4; // r14
__int128 v5; // xmm15
__int64 v6; // rax
__int64 v7; // rax
unsigned __int64 v8; // rbx
const char *v9; // rax
unsigned __int64 v10; // rax
const char *v11; // rdx
__int64 v12; // rax
__int64 v13; // rcx
__int64 v14; // r8
__int64 i; // rax
__int64 v16; // rdx
__int64 v17; // [rsp-28h] [rbp-5F0h]
__int64 v18; // [rsp-28h] [rbp-5F0h]
__int64 v19; // [rsp-28h] [rbp-5F0h]
__int64 v20; // [rsp-28h] [rbp-5F0h]
__int64 v21; // [rsp-20h] [rbp-5E8h]
__int64 v22; // [rsp-20h] [rbp-5E8h]
__int64 v23; // [rsp-20h] [rbp-5E8h]
__int64 v24; // [rsp-18h] [rbp-5E0h]
__int64 v25; // [rsp+20h] [rbp-5A8h]
__int64 v26[155]; // [rsp+28h] [rbp-5A0h] BYREF
char v27; // [rsp+500h] [rbp-C8h]
__int64 v28; // [rsp+508h] [rbp-C0h]
__int128 v29; // [rsp+510h] [rbp-B8h]
__int64 v30[2]; // [rsp+520h] [rbp-A8h] BYREF
__int128 v31; // [rsp+530h] [rbp-98h]
__int64 v32[2]; // [rsp+540h] [rbp-88h] BYREF
__int128 v33; // [rsp+550h] [rbp-78h]
char v34; // [rsp+560h] [rbp-68h]
__int64 v35; // [rsp+568h] [rbp-60h]
__int128 v36; // [rsp+570h] [rbp-58h]
__int64 v37[2]; // [rsp+580h] [rbp-48h] BYREF
__int128 v38; // [rsp+590h] [rbp-38h]
__int64 a2a[2]; // [rsp+5A0h] [rbp-28h] BYREF
__int128 v40; // [rsp+5B0h] [rbp-18h]

while ( (unsigned __int64)&v26[6] <= *(_QWORD *)(v4 + 16) )
{
STACK[0xA98] = v2;
STACK[0xAA0] = v3;
runtime_morestack_noctxt();
v2 = STACK[0xA98];
v3 = STACK[0xAA0];
}
STACK[0xAA0] = v3;
STACK[0xA98] = v2;
memset(&a2, 0, 0x4C0uLL);
LOBYTE(a2a[0]) = 0;
a2a[1] = 0LL;
v40 = v5;
v26[152] = math_big___ptr_Int__SetString(
"92582184765240663364795767694262273105045150785272129481762171937885924776597",
(__int64)a2a,
10,
77);
LOBYTE(v37[0]) = 0;
v37[1] = 0LL;
v38 = v5;
math_big___ptr_Int__SetString(
"107310528658039985708896636559112400334262005367649176746429531274300859498993",
(__int64)v37,
10,
78);
v34 = 0;
v35 = 0LL;
v36 = v5;
v21 = math_big___ptr_Int__Mul(v17);
v26[153] = v6;
LOBYTE(v32[0]) = 0;
v32[1] = 0LL;
v33 = v5;
v26[154] = math_big___ptr_Int__SetString("950501", (__int64)v32, 10, 6);
LOBYTE(v30[0]) = 0;
v30[1] = 0LL;
v31 = v5;
math_big___ptr_Int__SetString((char *)STACK[0xA98], (__int64)v30, 16, STACK[0xAA0]);
v27 = 0;
v28 = 0LL;
v29 = v5;
v24 = math_big___ptr_Int__Exp(v18, v21);
if ( v7 )
{
math_big_nat_itoa(v19, v22, v24);
v8 = v10;
runtime_slicebytetostring(v20, v23, v24);
}
else
{
v8 = 5LL;
v9 = "<nil>";
}
v25 = 0LL;
v11 = v9;
memset(v26, 0, 0x4C0uLL);
v12 = 0LL;
v13 = 102LL;
while ( v12 < 153 )
{
if ( v12 >= v8 )
runtime_panicIndex();
v14 = (unsigned __int8)v11[v12];
v26[v12++ - 1] = v14 ^ v13;
v13 = v14;
}
for ( i = 0LL; i < 153; ++i )
{
v16 = v26[i - 1];
v26[i - 1] = v16 ^ v13;
v13 = v16;
}
qmemcpy(&a2, v26, 0x4C0uLL);
}

我去,我自己写的时候IDA反编译出来的字符串巨长一串,为什么写复现题解的时候就正常了,离离原上谱……什么玄学
实际上,这个函数里有四个参数,第1个是字符串,第3个是要转换的进制,第4个是字符串长度,如果字符串莫名其妙多出来一大串,按这个长度切割就可以了。

可以分析,逻辑就是RSA,然后用了两次异或。异或逻辑如下:

1
2
3
4
for i in range(153):
t=box[i]
box[i]^=num
num=box[i]

用的初始值num给了是0x66,这个逻辑就是用一个初始值跟box里的值异或,然后把box里原来的值又赋给num,迭代到最后。
这样的话最后一次的num值实际上取决于初始值和明文序列,而我们需要这个值来恢复明文序列,但是我们不知道明文……(废话,知道了就不用做了,所以这个值是不可知的,因而只能爆破。
可能会有多组解满足恢复出来的明文序列可以拼接成数字,所以要跑完所有可能性。
至于怎么获得密文序列……题解里没说,咱也不敢问(什……
我是去找它报wrong的字符串,然后查交叉引用,再去找分支判断的跳转,然后看上面比较的值。

1
2
3
4
5
6
.text:00000000005E2072                 lea     rax, [rsp+1368h+var_4E8]
.text:00000000005E207A lea rbx, [rsp+1368h+var_9B0]
.text:00000000005E2082 mov ecx, 4C8h
.text:00000000005E2087 call runtime_memequal
.text:00000000005E208C test al, al
.text:00000000005E208E jz short loc_5E20C7

下断到mov处,然后调试去拿值……

至此拿到全部信息,可以写脚本了……
这里有个坑,直接box=global_box的话是浅复制,对box的任何改动都会影响global_box,所以用列表的copy()方法 别问我怎么知道的

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
from Crypto.Util.number import *
import gmpy2
q=107310528658039985708896636559112400334262005367649176746429531274300859498993
p=92582184765240663364795767694262273105045150785272129481762171937885924776597
e=950501
global_box=[0x63,0x55,0x04,0x03,0x05,0x05,0x05,0x03,0x07,0x07,0x02,0x08,0x08,0x0B,0x01,0x02,0x0A,0x04,0x02,0x0D,0x08,0x09,0x0C,0x09,0x04,0x0D,0x08,0x00,0x0E,0x00,0x0F,0x0D,0x0E,0x0A,0x02,0x02,0x01,0x07,0x03,0x05,0x06,0x04,0x06,0x07,0x06,0x02,0x02,0x05,0x03,0x03,0x09,0x06,0x00,0x0B,0x0D,0x0B,0x00,0x02,0x03,0x08,0x03,0x0B,0x07,0x01,0x0B,0x05,0x0E,0x05,0x00,0x0A,0x0E,0x0F,0x0D,0x07,0x0D,0x07,0x0E,0x01,0x0F,0x01,0x0B,0x05,0x06,0x02,0x0C,0x06,0x0A,0x04,0x01,0x07,0x04,0x02,0x06,0x03,0x06,0x0C,0x05,0x0C,0x03,0x0C,0x06,0x00,0x04,0x0F,0x02,0x0E,0x07,0x00,0x0E,0x0E,0x0C,0x04,0x03,0x04,0x02,0x00,0x00,0x02,0x06,0x02,0x03,0x06,0x04,0x04,0x04,0x07,0x01,0x02,0x03,0x09,0x02,0x0C,0x08,0x01,0x0C,0x03,0x0C,0x02,0x00,0x03,0x0E,0x03,0x0E,0x0C,0x09,0x01,0x07,0x0F,0x05,0x07,0x02,0x02,0x04]
for k in range(0xff):
num=k
box=global_box.copy()
try:
s=''
for i in range(152,-1,-1):
num^=box[i]
box[i]^=num
for i in range(152,-1,-1):
num^=box[i]
box[i]^=num
for i in range(153):
s+=chr(box[i])
s=int(s)
d=gmpy2.invert(e,(p-1)*(q-1))
m=pow(s,d,p*q)
print(long_to_bytes(m))
except:
continue

hardasm

写了用于爆破的脚本,AVX2指令集实在是太抽象了……
按照题解patch程序,感觉学好硬编码还是很重要的……

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

j=6
flag='hgame{'
while(j<33):
for i in range(0x20,0x7f):
tmp=flag
tmp+=(chr(i)*(32-j))
tmp=tmp.encode()
p = subprocess.Popen(["hardasm.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(tmp)
p.stdin.close()
out=p.stdout.read()
p.stdout.close()
ind=len(out)
if ind>j:
flag+=chr(i)*(ind-j)
print(flag)
j=len(flag)
break

DNUICTF

EasyRe

详细分析

虚拟机,主要分析出来esp和eip寄存器,其他可以靠猜。
(从小到大依次eax,ebx,ecx,edx 正常人都会这样写代码吧)
esp寄存器比较明显,即每次push、pop、call、ret指令都会对其进行修改。

然后是两部分:指令和操作数。
程序要完成两个功能:定义指令集,写入操作数。重点分析这两部分就行。

汇编指令特征:
对于本题:
qword_6030C8[19]:esp
qword_6030C8[20]:eip

push指令:esp++,对栈上数据赋值

1
2
3
4
5
6
7
8
9
10
11
int __fastcall sub_400E1D(__int64 a1, __int64 a2)
{
__int64 v2; // rsi
unsigned __int8 vm_edx; // dl

v2 = *(_QWORD *)qword_6030C8;
vm_edx = *(_BYTE *)(qword_6030C8 + 19);
*(_BYTE *)(qword_6030C8 + 19) = vm_edx + 1;
*(_BYTE *)(v2 + vm_edx) = **(_BYTE **)(a2 + 24);
return semop(semid, &stru_6030BE, 1uLL);
}

test指令:赋标志值给寄存器

1
2
3
4
5
int sub_40103A()
{
*(_BYTE *)(qword_6030C8 + 21) = *(_BYTE *)(qword_6030C8 + 16) == *(_BYTE *)(qword_6030C8 + 17);
return semop(semid, &stru_6030BE, 1uLL);
}

jmp指令:直接修改eip

1
2
3
4
5
int sub_40113A()
{
*(_BYTE *)(qword_6030C8 + 20) = s1[79];
return semop(semid, &stru_6030BE, 1uLL);
}

call指令:eip入栈,修改eip使其指向跳转地址值。

1
2
3
4
5
6
7
8
9
10
11
12
int sub_401089()
{
__int64 v0; // rsi
unsigned __int8 v1; // dl

v0 = *(_QWORD *)qword_6030C8;
v1 = *(_BYTE *)(qword_6030C8 + 19);
*(_BYTE *)(qword_6030C8 + 19) = v1 + 1;
*(_BYTE *)(v0 + v1) = *(_BYTE *)(qword_6030C8 + 20);
*(_BYTE *)(qword_6030C8 + 20) = s1[79];
return semop(semid, &stru_6030BE, 1uLL);
}

ret指令:出栈esp到eip

1
2
3
4
5
6
7
8
9
10
11
12
13
int sub_4010EA()
{
__int64 v0; // rdx
__int64 v1; // rcx
__int64 v2; // rax

v0 = qword_6030C8;
v1 = *(_QWORD *)qword_6030C8;
v2 = qword_6030C8;
--*(_BYTE *)(qword_6030C8 + 19);
*(_BYTE *)(v0 + 20) = *(_BYTE *)(v1 + *(unsigned __int8 *)(v2 + 19));
return semop(semid, &stru_6030BE, 1uLL);
}
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
opcode=[0x11,0x34,0x00,0x2A,0x05,0x10,0x14,0x09,0x17,0x00,0x24,0x05,0x03,0x11,0x1D,0x06,0x00,0x00,0x05,0x03,0x11,0x40,0x06,0x00,0x48,0x05,0x11,0x1D,0x17,0x0E,0x01,0x15,0x04,0x0F,0x01,0x16,0x02,0x00,0x00,0x04,0x03,0x05,0x10,0x14,0x32,0x05,0x09,0x02,0x13,0x1D,0x05,0x12,0x15,0x04,0x10,0x14,0x3D,0x0A,0x01,0x13,0x34,0x03,0x04,0x12,0x0E,0x01,0x15,0x04,0x07,0x01,0x16,0x02,0x00,0x00,0x04,0x03,0x05,0x10,0x14,0x55,0x05,0x09,0x01,0x13,0x40,0x05,0x12,0x59]

var=0
cnt=0
mem=[0]*1024
esp=0#q[19]
q16=0
q17=0
zf=0#q21
def handler(sigl,a1=None):
global cnt
s='unknown: {}'.format(a1)
if sigl==34:
s='push {}'.format(a1)
elif sigl==35:
s='pop {}'.format(a1)
elif sigl==36:
s='add q16,q17'
elif sigl==37:
s='add {},{}'.format(a1,var)
elif sigl==38:
s='sub q16,q17'
elif sigl==39:
s='sub {},{}'.format(a1,var)
elif sigl==40:
s='xor q16,q17'
elif sigl==41:
s='test q16,q17'
elif sigl==42:
s='call {}'.format(var)
elif sigl==43:
s='ret'
elif sigl==44:
s='jmp {}'.format(var)
elif sigl==45:
s='jz {}'.format(var)
elif sigl==46:
s='push mem[q18]'
elif sigl==47:
s='pop mem[q18]'
elif sigl==2:
s='break'
return s
rot=[0,8,9,10,12,13,14,17,19,20]
while cnt<len(opcode):
op=opcode[cnt]
last_cnt=cnt
s='nop'
cnt+=1
if op in rot:
var=opcode[cnt]
cnt+=1
if op==0:
s=handler(34,var)
elif op==1:
s=handler(34,'q16')
elif op==2:
s=handler(34,'q17')
elif op==3:
s=handler(34,'q18')
elif op==4:
s=handler(35,'q16')
elif op==5:
s=handler(35,'q17')
elif op==6:
s=handler(35,'q18')
elif op==7:
s=handler(36)
elif op==8:
s=handler(37,'q16')
elif op==9:
s=handler(37,'q17')
elif op==10:
s=handler(37,'q18')
elif op==11:
s=handler(38)
elif op==12:
s=handler(39,'q16')
elif op==13:
s=handler(39,'q17')
elif op==14:
s=handler(39,'q18')
elif op==15:
s=handler(40)
elif op==16:
s=handler(41)
elif op==17:
s=handler(42)
elif op==18:
s=handler(43)
elif op==19:
s=handler(44)
elif op==20:
s=handler(45)
elif op==21:
s=handler(46)
elif op==22:
s=handler(47)
else:
s=handler(2)
print("%02d:%s"%(last_cnt,s))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
box=[0xA3, 0xD8, 0xAC, 0xA9, 0xA8, 0xD6, 0xA6, 0xCD, 0xD0, 0xD5, 
0xF7, 0xB7, 0x9C, 0xB3, 0x31, 0x2D, 0x40, 0x5B, 0x4B, 0x3A,
0xFD, 0x57, 0x42, 0x5F, 0x58, 0x52, 0x54, 0x1B, 0x0C, 0x78,
0x39, 0x2D, 0xD9, 0x3D, 0x35, 0x1F, 0x09, 0x41, 0x40, 0x47,
0x42, 0x11]
x1=72
x2=36
for i in range(len(box)-1,-1,-1):
box[i]^=x1
box[i]&=0xff
x1+=2
for i in range(len(box)):
box[i]-=len(box)-i-1
for i in range(len(box)-1,-1,-1):
box[i]^=x2
box[i]&=0xff
x2+=2
for i in box:
print(chr(i),end='')

蓝帽杯

loader

好折磨,真的是好折磨,对我这种调试菜鸡来说真的是太痛苦了😭
不过经过这次折磨,总算是感觉摸到了一点调试的门槛,所以详细记录一下调试过程。

按照https://mp.weixin.qq.com/s/lGPtsd8hPJnltZ8OJqOCiw复现

主程序是64位,但是子程序用DIE去查会发现是32位,所以是PE文件有问题,文件头和可选头被改了,就是标识程序是32位还是64位的那两个标志位。
用stud_PE去看这两个头的偏移,然后去把这两位手动改成64位程序的值。emmm至于这个值是多少,随便拖个64位程序来看看不就好了(x


改成

这里挖个坑,我找了半天没发现主程序是怎么把子程序这两个地方改过来的,感觉很奇怪。

子程序放到IDA里分析,主函数非常的抽象,里面调了一大堆函数。直接查字符串flag,抽象的地方来了……字符串上面到unk_416DE0才有交叉引用,又被引用了一次,跳转到该函数反编译,就是主要的加密逻辑。

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
__int64 sub_412850()
{
FILE *v0; // rax
__int64 v1; // rax
__int64 v2; // rdx
_QWORD *v3; // rsi
__int64 v4; // rdi
__int64 v5; // rbx
_QWORD *v6; // rax
_QWORD *v7; // rdi
__int64 v8; // rdx
_QWORD *v9; // rax
unsigned __int64 *v11; // rsi
__int64 v12; // rbx
_QWORD *v13; // rbp
unsigned __int64 v14; // rdi
__int64 v15; // rbx
unsigned __int64 *v16; // rbp
__int64 v17; // rdx
_QWORD *v18; // rax
_QWORD *v19; // rdi
unsigned __int64 v20; // rsi
__int64 v21; // rdi
__int64 v22; // rdx
unsigned __int64 v23; // rcx
__m128i v24; // xmm4
__int64 v25; // rdi
__int64 v26; // rdx
unsigned __int64 v27; // rcx
__m128i v28; // [rsp+20h] [rbp-78h] BYREF
__m128i v29; // [rsp+30h] [rbp-68h] BYREF
__int64 v30; // [rsp+40h] [rbp-58h] BYREF
__int64 v31; // [rsp+48h] [rbp-50h]
__m128i v32; // [rsp+50h] [rbp-48h] BYREF
__int64 v33; // [rsp+60h] [rbp-38h] BYREF
__int64 v34; // [rsp+68h] [rbp-30h]

sub_40D4C0(sub_4127A0);
sub_40D4C0(sub_412790);
sub_40D4C0(sub_412780);
sub_40D4C0(sub_412770);
sub_40D4C0(sub_412760);
sub_40D4C0(sub_412750);
sub_40D4C0(sub_412740);
sub_40D4C0(sub_412730);
sub_40D4C0(sub_412720);
sub_4026B0(&off_416DC8, 1i64);
v0 = (FILE *)off_4150C0(0i64);
v1 = sub_4031E0(v0);
v3 = (_QWORD *)v1;
if ( v1 )
*(_QWORD *)(v1 - 16) += 8i64;
if ( qword_42C1C0 )
{
v4 = *(_QWORD *)(qword_42C1C0 - 16);
v2 = qword_42C1C0 - 16;
*(_QWORD *)(qword_42C1C0 - 16) = v4 - 8;
if ( (unsigned __int64)(v4 - 8) <= 7 )
sub_406540(&qword_421F80 + 3, v2);
}
qword_42C1C0 = (__int64)v3;
v5 = 0i64;
v6 = (_QWORD *)sub_408FC0(5i64, v2);
v7 = v6;
if ( !v6 )
{
if ( v3 )
sub_40C420(0i64, -1i64);
sub_40C420(0i64, -1i64);
}
if ( !v3 )
{
if ( !*v6 )
sub_40C420(0i64, -1i64);
sub_40C420(0i64, -1i64);
}
do
{
if ( *v6 <= (unsigned __int64)v5 )
sub_40C420(v5, *v6 - 1i64);
v8 = *v3;
if ( *v3 <= (unsigned __int64)v5 )
sub_40C420(v5, v8 - 1);
*((_BYTE *)v6 + v5 + 16) = *((_BYTE *)v3 + v5 + 16);
++v5;
}
while ( v5 <= 4 );
if ( *v6 != 5i64 )
goto LABEL_15;
v9 = v6 + 2;
if ( *((_DWORD *)v7 + 4) != 1734437990 )
goto LABEL_15;
if ( *((_BYTE *)v9 + 4) != 123 )
goto LABEL_15;
v11 = (unsigned __int64 *)qword_42C1C0;
if ( !qword_42C1C0 || *(_QWORD *)qword_42C1C0 != 42i64 || *(_BYTE *)(qword_42C1C0 + 57) != 125 )
goto LABEL_15;
v12 = 0i64;
v13 = (_QWORD *)sub_408FC0(18i64, v8);
if ( !v13 )
sub_40C420(0i64, -1i64);
do
{
if ( *v13 <= (unsigned __int64)v12 )
sub_40C420(v12, *v13 - 1i64);
v14 = v12 + 5;
if ( v12 + 5 < 0 || v14 < v12 )
sub_407F70();
if ( *v11 <= v14 )
sub_40C420(v12 + 5, *v11 - 1);
*((_BYTE *)v13 + v12++ + 16) = *((_BYTE *)v11 + v14 + 16);
}
while ( v12 <= 17 );
v15 = 0i64;
sub_411340(v13, 10i64, &xmmword_42C170);
v16 = (unsigned __int64 *)qword_42C1C0;
v18 = (_QWORD *)sub_408FC0(18i64, v17);
v19 = v18;
if ( !v18 )
{
if ( v16 )
sub_40C420(0i64, -1i64);
sub_40C420(0i64, -1i64);
}
if ( !v16 )
{
if ( !*v18 )
sub_40C420(0i64, -1i64);
sub_40C420(23i64, -1i64);
}
do
{
if ( *v19 <= (unsigned __int64)v15 )
sub_40C420(v15, *v19 - 1i64);
v20 = v15 + 23;
if ( v15 + 23 < 0 || v20 < v15 )
sub_407F70();
if ( *v16 <= v20 )
sub_40C420(v15 + 23, *v16 - 1);
*((_BYTE *)v19 + v15++ + 16) = *((_BYTE *)v16 + v20 + 16);
}
while ( v15 <= 17 );
sub_411340(v19, 10i64, &xmmword_42C130);
sub_411340(&unk_416D80, 10i64, &xmmword_42C150);
sub_411340(&unk_416D40, 10i64, &xmmword_42C160);
v29 = _mm_loadu_si128((const __m128i *)&xmmword_42C150);
v28 = _mm_loadu_si128((const __m128i *)&xmmword_42C170);
if ( !(unsigned __int8)sub_412080(&v29, &v28) )
goto LABEL_15;
v29 = _mm_loadu_si128((const __m128i *)&xmmword_42C170);
v28 = _mm_loadu_si128((const __m128i *)&xmmword_42C160);
if ( !(unsigned __int8)sub_412080(&v29, &v28) )
goto LABEL_15;
v30 = 0i64;
v31 = 0i64;
v29 = (__m128i)xmmword_42C170;
v28 = (__m128i)xmmword_42C170;
sub_411270(&v29, &v28, &v30);
v21 = v30;
if ( v30 )
*(_QWORD *)(v30 - 16) += 8i64;
if ( (_QWORD)xmmword_42C190 )
{
v22 = xmmword_42C190 - 16;
v23 = *(_QWORD *)(xmmword_42C190 - 16) - 8i64;
*(_QWORD *)(xmmword_42C190 - 16) = v23;
if ( v23 <= 7 )
sub_406540(&qword_421F80 + 3, v22);
}
*(_QWORD *)&xmmword_42C190 = v21;
v32 = 0ui64;
BYTE8(xmmword_42C190) = v31;
v29 = (__m128i)xmmword_42C130;
v28 = (__m128i)xmmword_42C130;
sub_411270(&v29, &v28, &v32);
v24 = _mm_load_si128(&v32);
v33 = 0i64;
v34 = 0i64;
v29 = v24;
sub_412260(&v29, 11i64, &v33);
v25 = v33;
if ( v33 )
*(_QWORD *)(v33 - 16) += 8i64;
if ( (_QWORD)xmmword_42C120 )
{
v26 = xmmword_42C120 - 16;
v27 = *(_QWORD *)(xmmword_42C120 - 16) - 8i64;
*(_QWORD *)(xmmword_42C120 - 16) = v27;
if ( v27 <= 7 )
sub_406540(&qword_421F80 + 3, v26);
}
*(_QWORD *)&xmmword_42C120 = v25;
v29 = _mm_loadu_si128((const __m128i *)&xmmword_42C190);
BYTE8(xmmword_42C120) = v34;
v28 = _mm_loadu_si128((const __m128i *)&xmmword_42C120);
sub_4123C0(&v29, &v28, &xmmword_42C1A0);
sub_411340(&unk_416D10, 10i64, &xmmword_42C180);
v29 = _mm_loadu_si128((const __m128i *)&xmmword_42C1A0);
v28 = _mm_loadu_si128((const __m128i *)&xmmword_42C180);
if ( (unsigned __int8)sub_412500(&v29, &v28) )
{
qword_41A660 = 1i64;
}
else
{
LABEL_15:
if ( qword_41A660 != 1 )
return sub_4026B0(&off_416CC0, 1i64);
}
return sub_4026B0(&off_416CE8, 1i64);
}

前面先是调了一堆sub_40D4C0,猜测是什么神必库函数,finger一下,得到函数名nimRegisterGlobalMarker,在网上搜了半天也只有几个很神必的代码例子,没人说这玩意是干什么的……

最后还是万能的RX神告诉我,这是nim lang,一种编程语言。

所以字符串的引用这么抽象,反编译出来的代码更抽象

47行sub_4026B0打印欢迎语,49行sub_4031E0接受输入,后面干什么不用管,91-97行验证是否为’flag{‘开头和’}’结尾,长度是否为42。
flag长度为42,那么数字长度为36,102和131行的两个while循环把flag分成两半(flag1和flag2),存到v13和v19里,然后调用sub_411340。这个函数被调用了4次,操作的对象都是数字字符,后两次是操作程序中的字符串(称为num1和num2),可以猜测是把字符转为数字的函数。
自己输flag{123456789012345678901234567890123456}测一下

150和154行调用sub_412080,操作对象分别是flag1、num1和flag1、num2。
函数很长,静态分析根本不能看,因为是其他语言写的,不是C……只能动调。

在150行下断点,跑到call sub_582080,看v30,双击跳转,数据转地址再跳一次,往下翻一点就能看到数据。在这个数据开头的地方下硬件断点然后跑程序

数据:

1
2
3
4
5
6
>>> hex(72057594037927936)
'0x100000000000000'
>>> hex(1152921504606846976)
'0x1000000000000000'
hex(123456789012345678)
'0x1b69b4ba630f34e'

用的寄存器是32位的,可以看到edx里存的是flag1的高位,与num1的高位sub了一下,如果等于0就跳转。这里执行的操作就是检查flag1的高位是否等于num1的高位,等于就跳转。
我们可以改一下ZF的值来控制跳转,看看flag1的高位如果等于num1的高位会执行什么。

可以看到edx里的值是flag1的低位,所以是比较了flag1和num1的低位,用的是跟上面操作一样的循环。

判断不等于0之后,程序正常执行,到sub_582080的结尾处。

此处是给rax右移了0x3f,即只保留最高位。eax里存的是num1,而edx存flag1,进行sub操作后,如果num1小于flag1,那么rax的最高位会变成1。
由此,如果num1小于flag1,函数返回1,否则返回0。

根据主函数里的判断,此处是要求flag1大于num1,下面的一次调用同理,要求flag1小于num2。

后面调试过程大致相同,跑到sub_581270,在flag1处下硬件断点

可以看到是一个相乘的操作,两个操作数均是flag1,即对flag1平方,结果保存在v32。

sub_582260:

对flag2平方的结果乘以b,结果保存在v35

sub_5823C0将两个操作数相减,sub_582500比较是否相等。

按照上述流程重命名一下各个函数:

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
__int64 sub_432850()
{
FILE *v0; // rax
__int64 v1; // rax
__int64 v2; // rdx
_QWORD *v3; // rsi
__int64 v4; // rdi
__int64 v5; // rbx
_QWORD *v6; // rax
_QWORD *v7; // rdi
__int64 v8; // rdx
_QWORD *v9; // rax
unsigned __int64 *v11; // rsi
__int64 v12; // rbx
__int64 v13; // rcx
_QWORD *flag1; // rbp
unsigned __int64 v15; // rdi
__int64 v16; // rbx
unsigned __int64 *v17; // rbp
__int64 v18; // rdx
_QWORD *v19; // rax
__int64 v20; // rcx
_QWORD *flag2; // rdi
unsigned __int64 v22; // rsi
__int64 v23; // rdi
__int64 v24; // rdx
unsigned __int64 v25; // rcx
__m128i v26; // xmm4
__int64 v27; // rdi
__int64 v28; // rdx
unsigned __int64 v29; // rcx
__m128i v30; // [rsp+20h] [rbp-78h] BYREF
__m128i v31; // [rsp+30h] [rbp-68h] BYREF
__int64 v32; // [rsp+40h] [rbp-58h] BYREF
__int64 v33; // [rsp+48h] [rbp-50h]
__m128i v34; // [rsp+50h] [rbp-48h] BYREF
__int64 v35; // [rsp+60h] [rbp-38h] BYREF
__int64 v36; // [rsp+68h] [rbp-30h]

nimRegisterGlobalMarker(sub_4327A0);
nimRegisterGlobalMarker(sub_432790);
nimRegisterGlobalMarker(sub_432780);
nimRegisterGlobalMarker(sub_432770);
nimRegisterGlobalMarker(sub_432760);
nimRegisterGlobalMarker(sub_432750);
nimRegisterGlobalMarker(sub_432740);
nimRegisterGlobalMarker(sub_432730);
nimRegisterGlobalMarker(sub_432720);
sub_4226B0(&off_436DC8, 1i64);
v0 = (FILE *)off_4350C0(0i64);
v1 = sub_4231E0(v0);
v3 = (_QWORD *)v1;
if ( v1 )
*(_QWORD *)(v1 - 16) += 8i64;
if ( qword_44C1C0 )
{
v4 = *(_QWORD *)(qword_44C1C0 - 16);
v2 = qword_44C1C0 - 16;
*(_QWORD *)(qword_44C1C0 - 16) = v4 - 8;
if ( (unsigned __int64)(v4 - 8) <= 7 )
sub_426540(&qword_441F80 + 3, v2);
}
qword_44C1C0 = (__int64)v3;
v5 = 0i64;
v6 = (_QWORD *)sub_428FC0(5i64, v2);
v7 = v6;
if ( !v6 )
{
if ( v3 )
sub_42C420(0i64, -1i64);
sub_42C420(0i64, -1i64);
}
if ( !v3 )
{
if ( !*v6 )
sub_42C420(0i64, -1i64);
sub_42C420(0i64, -1i64);
}
do
{
if ( *v6 <= (unsigned __int64)v5 )
sub_42C420(v5, *v6 - 1i64);
v8 = *v3;
if ( *v3 <= (unsigned __int64)v5 )
sub_42C420(v5, v8 - 1);
*((_BYTE *)v6 + v5 + 16) = *((_BYTE *)v3 + v5 + 16);
++v5;
}
while ( v5 <= 4 );
if ( *v6 != 5i64 )
goto LABEL_15;
v9 = v6 + 2;
if ( *((_DWORD *)v7 + 4) != 'galf' )
goto LABEL_15;
if ( *((_BYTE *)v9 + 4) != '{' )
goto LABEL_15;
v11 = (unsigned __int64 *)qword_44C1C0;
if ( !qword_44C1C0 || *(_QWORD *)qword_44C1C0 != 42i64 || *(_BYTE *)(qword_44C1C0 + 57) != '}' )
goto LABEL_15;
v12 = 0i64;
flag1 = (_QWORD *)sub_428FC0(18i64, v8);
if ( !flag1 )
sub_42C420(0i64, -1i64);
do
{
if ( *flag1 <= (unsigned __int64)v12 )
sub_42C420(v12, *flag1 - 1i64);
v15 = v12 + 5;
if ( v12 + 5 < 0 || v15 < v12 )
sub_427F70(v13);
if ( *v11 <= v15 )
sub_42C420(v12 + 5, *v11 - 1);
*((_BYTE *)flag1 + v12++ + 16) = *((_BYTE *)v11 + v15 + 16);
}
while ( v12 <= 17 );
v16 = 0i64;
chr_to_int(flag1, 10i64, &flag_1); // flag1 --> int
v17 = (unsigned __int64 *)qword_44C1C0;
v19 = (_QWORD *)sub_428FC0(18i64, v18);
flag2 = v19;
if ( !v19 )
{
if ( v17 )
sub_42C420(0i64, -1i64);
sub_42C420(0i64, -1i64);
}
if ( !v17 )
{
if ( !*v19 )
sub_42C420(0i64, -1i64);
sub_42C420(23i64, -1i64);
}
do
{
if ( *flag2 <= (unsigned __int64)v16 )
sub_42C420(v16, *flag2 - 1i64);
v22 = v16 + 23;
if ( v16 + 23 < 0 || v22 < v16 )
sub_427F70(v20);
if ( *v17 <= v22 )
sub_42C420(v16 + 23, *v17 - 1);
*((_BYTE *)flag2 + v16++ + 16) = *((_BYTE *)v17 + v22 + 16);
}
while ( v16 <= 17 );
chr_to_int(flag2, 10i64, &flag_2); // flag2 --> int
chr_to_int(&num1, 10i64, &num_1);
chr_to_int(&num2, 10i64, &num_2);
v31 = _mm_loadu_si128((const __m128i *)&num_1);
v30 = _mm_loadu_si128((const __m128i *)&flag_1);
if ( !(unsigned __int8)cmp_lower(&v31, &v30) )
goto LABEL_15;
v31 = _mm_loadu_si128((const __m128i *)&flag_1);
v30 = _mm_loadu_si128((const __m128i *)&num_2);
if ( !(unsigned __int8)cmp_lower(&v31, &v30) )
goto LABEL_15;
v32 = 0i64;
v33 = 0i64;
v31 = (__m128i)flag_1;
v30 = (__m128i)flag_1;
square((__int64)&v31, (__int64)&v30, &v32);
v23 = v32;
if ( v32 )
*(_QWORD *)(v32 - 16) += 8i64;
if ( (_QWORD)square_flag1 )
{
v24 = square_flag1 - 16;
v25 = *(_QWORD *)(square_flag1 - 16) - 8i64;
*(_QWORD *)(square_flag1 - 16) = v25;
if ( v25 <= 7 )
sub_426540(&qword_441F80 + 3, v24);
}
*(_QWORD *)&square_flag1 = v23;
v34 = 0ui64;
BYTE8(square_flag1) = v33;
v31 = (__m128i)flag_2;
v30 = (__m128i)flag_2;
square((__int64)&v31, (__int64)&v30, &v34);
v26 = _mm_load_si128(&v34);
v35 = 0i64;
v36 = 0i64;
v31 = v26;
mul(&v31, 0xBu, &v35);
v27 = v35;
if ( v35 )
*(_QWORD *)(v35 - 16) += 8i64;
if ( (_QWORD)square_flag2_mul_0xb )
{
v28 = square_flag2_mul_0xb - 16;
v29 = *(_QWORD *)(square_flag2_mul_0xb - 16) - 8i64;
*(_QWORD *)(square_flag2_mul_0xb - 16) = v29;
if ( v29 <= 7 )
sub_426540(&qword_441F80 + 3, v28);
}
*(_QWORD *)&square_flag2_mul_0xb = v27;
v31 = _mm_loadu_si128((const __m128i *)&square_flag1);
BYTE8(square_flag2_mul_0xb) = v36;
v30 = _mm_loadu_si128((const __m128i *)&square_flag2_mul_0xb);
sub(&v31, &v30, &xmmword_44C1A0);
chr_to_int(&unk_436D10, 10i64, &num_9);
v31 = _mm_loadu_si128((const __m128i *)&xmmword_44C1A0);
v30 = _mm_loadu_si128((const __m128i *)&num_9);
if ( if_equal(&v31, &v30) )
{
qword_43A660 = 1i64;
}
else
{
LABEL_15:
if ( qword_43A660 != 1 )
return sub_4226B0(&off_436CC0, 1i64);
}
return sub_4226B0(&off_436CE8, 1i64);
}

梳理一下逻辑就是
$flag_1^2-11flag_2^2 = 9,72057594037927936 < flag_1 < 1152921504606846976$

然后就没办法了

在线网站解密or摇密码👴来看
反正我8会整(逃

关于本文

本文作者 云之君, 许可由 CC BY-NC 4.0.