a_game

坑,我找了好久胜利的时候的条件,没想到是在退出的时候检查的QAQ,不过学到了注册表的相关函数,

image-20241101003113092

把这里解密的脚本直接dump然后每次把最前面的函数名改成Write-Out输出没有混淆的版本,

比如:

1
2
Write-Output( ( [RuntIme.INteroPsErviCEs.maRSHal]::PTrtostriNGAUTo( [rUNtIme.inteRopServIces.MarshAl]::seCuREsTrinGtobStr( $('76492d1116743f0423413b160省略base64内容' | conveRTTo-SECureSTrinG  -K (94..117)) ) ))) 

这时可以直接在pwsh里执行,然后得到输出的代码,继续解密

然后继续直到解密出来代码

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
function enenenenene {
param(
$plaintextBytes,
$keyBytes
)
# Initialize S and KSA
$S = 0..255
$j = 0
for ($i = 0; $i -lt 256; $i++) {
$j = ($j + $S[$i] + $keyBytes[$i % $keyBytes.Length]) % 256
$temp = $S[$i]
$S[$i] = $S[$j]
$S[$j] = $temp
}

# PRGA and encryption
$i = 0
$j = 0
$ciphertextBytes = @()
for ($k = 0; $k -lt $plaintextBytes.Length; $k++) {
$i = ($i + 1) % 256
$j = ($j + $S[$i]) % 256
$temp = $S[$i]
$S[$i] = $S[$j]
$S[$j] = $temp
$t = ($S[$i] + $S[$j]) % 256
$ciphertextBytes += ($plaintextBytes[$k] -bxor $S[$t])
}

# Return ciphertext as a string
return $ciphertextBytes
}
function enenenenene1 {
param(
$inputbyte
)
$key = @(0x70, 0x6f, 0x77, 0x65, 0x72)
$encryptedText = @();
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$encryptedText = enenenenene -plaintextBytes $inputbyte -keyBytes $key;
$key = enenenenene -plaintextBytes $key -keyBytes $encryptedText;
}
return $encryptedText + $key;
}
function enenenenene2 {
param(
$inputbyte
)
$key = @(0x70, 0x30, 0x77, 0x65, 0x72)
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$inputbyte[$k] = $inputbyte[$k] + $key[$k % $key.Length]
}
return $inputbyte;
}
function enenenenene3 {
param(
$inputbyte
)
$key = @(0x70, 0x30, 0x77, 0x33, 0x72)
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$inputbyte[$k] = $inputbyte[$k] * $key[$k % $key.Length]
}
return $inputbyte;
}
$registryPath = 'HKCU:\Software\PacManX'
$valueName = 'MYFLAG'
$value = Get-ItemPropertyValue $registryPath $valueName
$plaintext = @($value) | ForEach-Object {
$input = $_
$plaintext = @()
for ($i = 0; $i -lt $input.Length; $i++) {
$plaintext += [int][char]$input[$i]
}
$plaintext
}
if ($plaintext.Length -ne 36) {
Set-Content -Path "log.txt" -Value "ERROR"
exit
}
$encrypted1Text = enENenenene2 -inputbyte (enenenENene2 -inputbyte (enenenenene3 -inputbyte (Enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene1 -input $plaintext))))))
$result = @(38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616)
for ($k = 0; $k -lt $result.Length; $k++) {
if ($encrypted1Text[$k] -ne $result[$k]) {
Set-Content -Path "log.txt" -Value "ERROR"
exit

}
Set-Content -Path "log.txt" -Value "RIGHT"

然后直接逆

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
from prism import *
def rc4_encrypt(plaintext_bytes, key_bytes):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key_bytes[i % len(key_bytes)]) % 256
S[i], S[j] = S[j], S[i]
i = 0
j = 0
ciphertext_bytes = []
for k in range(len(plaintext_bytes)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
ciphertext_bytes.append(plaintext_bytes[k] ^ S[t])
return ciphertext_bytes

def multi_layer_rc4_encrypt(input_bytes):
key = [0x70, 0x6f, 0x77, 0x65, 0x72]
encrypted_text = []
for _ in range(len(input_bytes)):
encrypted_text = rc4_encrypt(input_bytes, key)
key = rc4_encrypt(key, encrypted_text)
return encrypted_text + key

def additive_encrypt(input_bytes):
key = [0x70, 0x30, 0x77, 0x65, 0x72]
for k in range(len(input_bytes)):
input_bytes[k] = (input_bytes[k] + key[k % len(key)]) & 0xFF # Ensure bytes are in range 0-255
return input_bytes

def multiplicative_encrypt(input_bytes):
key = [0x70, 0x30, 0x77, 0x33, 0x72]
for k in range(len(input_bytes)):
input_bytes[k] = (input_bytes[k] * key[k % len(key)]) & 0xFF # Ensure bytes are in range 0-255
return input_bytes

result = [38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616]

def multi_layer_rc4_decrypt(input_bytes):
# key = [0x70, 0x6f, 0x77, 0x65, 0x72]
# input_bytes = input_bytes[:-5]
input_bytes,key_last = input_bytes[:-5],input_bytes[-5:]
for _ in range(len(input_bytes)):
key_last = rc4_encrypt(key_last, input_bytes)
input_bytes = rc4_encrypt(input_bytes, key_last)
# print(input_bytes)
try:
if all(input_byte<=127 and input_byte>=32 for input_byte in input_bytes):
pl(input_bytes,'flag')
except:
pass
return input_bytes

def additive_decrypt(input_bytes):
key = [0x70, 0x30, 0x77, 0x65, 0x72]
for k in range(len(input_bytes)):
input_bytes[k] = (input_bytes[k] - key[k % len(key)]) # Ensure bytes are in range 0-255
return input_bytes

def multiplicative_decrypt(input_bytes):
key = [0x70, 0x30, 0x77, 0x33, 0x72]
# inv_key = [mod_inverse(key[i], 0xff) for i in range(5)]
for k in range(len(input_bytes)):
assert input_bytes[k] % key[k % len(key)] == 0
input_bytes[k] = (input_bytes[k] // key[k % len(key)]) # Ensure bytes are in range 0-255
return input_bytes


res = additive_decrypt(additive_decrypt(result))
res = multiplicative_decrypt(res)
res = additive_decrypt(additive_decrypt(res))
res = additive_decrypt(res)
res = multi_layer_rc4_decrypt(res)
# print(res)

servers

打开应用,发现有两个按钮input和check,对应了两个onClick函数

image-20241101004406675

很明显用了动态加载类。尝试先对decode函数进行hook

得到

1
2
3
4
5
6
7
8
myclass.decode is called: str=RtHTxaKcRXIdES5ktXugN2ww1d91EMp/QOxAh8bV
myclass.decode result=android.content.ContextWrapper
myclass.decode is called: str=VMvWxbmmRC4IFyN1
myclass.decode result=startService
myclass.decode is called: str=RtHTxaKcRXIdES5ktXugN2ww1d91EMp/QOxAh8bV
myclass.decode result=android.content.ContextWrapper
myclass.decode is called: str=VMvYx56QUyoXHSU=
myclass.decode result=stopService
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
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), new OnApplyWindowInsetsListener() { // from class: com.nobody.Serv1ce.MainActivity$$ExternalSyntheticLambda0
@Override // androidx.core.view.OnApplyWindowInsetsListener
public final WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat windowInsetsCompat) {
return MainActivity.lambda$onCreate$0(view, windowInsetsCompat);
}
});
Button button = (Button) findViewById(R.id.ClickMe);
Button button2 = (Button) findViewById(R.id.ClickMe1);
final Intent intent = new Intent(this, (Class<?>) MyService.class);
button.setOnClickListener(new View.OnClickListener() { // from class: com.nobody.Serv1ce.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
myclass myclassVar = new myclass();
try {
Class<?> mycall = myclassVar.mycall(myclassVar.decode("RtHTxaKcRXIdES5ktXugN2ww1d91EMp/QOxAh8bV"));
Method method = mycall.getMethod(myclassVar.decode("VMvWxbmmRC4IFyN1"), Intent.class);
Object newInstance = mycall.getConstructor(Context.class).newInstance(this);
intent.putExtra("input", ((TextView) MainActivity.this.findViewById(R.id.Input)).getText().toString());
method.invoke(newInstance, intent);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});

1
2
3
4
5
final Intent intent = new Intent(this, (Class<?>) MyService.class);

Method method = mycall.getMethod(myclassVar.decode("VMvWxbmmRC4IFyN1"), Intent.class);
method.invoke(newInstance, intent);

说明它用自己的service调用了startService

查看这个类

1
2
private native boolean check(String str, byte[] bArr, int i);

native函数

继续hook检查函数

得到

1
bArr=-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59, i=11

得到最后检查值

然后看so文件,直接就有加密逻辑

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
__int64 __fastcall Java_com_nobody_Serv1ce_MyService_check(JNIEnv *a1, jclass *a2, void *a3, void *a4, char a5)
{
const char *v8; // x20
jbyte *v9; // x21
__int64 v10; // x8
unsigned __int8 v11; // w10
char v13[4]; // [xsp+4h] [xbp-Ch] BYREF
__int64 v14; // [xsp+8h] [xbp-8h]

v14 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v8 = (*a1)->GetStringUTFChars(a1, a3, v13);
v9 = (*a1)->GetByteArrayElements(a1, a4, v13);
if ( strlen(v8) == 36LL )
{
v10 = 0LL;
while ( 1 )
{
v11 = (v8[v10] ^ v9[v10]) * a5;
v8[v10] = v11;
if ( v[v10] != v11 )
break;
if ( ++v10 == 36 )
return 1LL;
}
}
return 0LL;
}

非常明显,直接爆破即可

1
2
3
4
5
6
7
8
9
10
11
12
13
from prism import *

last = [0xB9, 0x32, 0xC2, 0xD4, 0x69, 0xD5, 0xCA, 0xFB, 0xF8, 0xFB, 0x80, 0x7C, 0xD4, 0xE5, 0x93, 0xD5, 0x1C, 0x8B, 0xF8, 0xDF, 0xDA, 0xA1, 0x11, 0xF8, 0xA1, 0x93, 0x93, 0xC2, 0x7C, 0x8B, 0x1C, 0x66, 0x01, 0x3D, 0xA3, 0x67]
# print(len(last))
key = [-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59,-30,-83,-83,-30,-59,-30,-30,-59,-59,-30,-83,-83,-30,-30,-59,-59,-83,-30,-27,-59]
num = 11
flag = []
for i in range(len(last)):
for ch in range(32,128):
if ((key[i]^ch)*num)&0xff == last[i]:
flag.append(ch)
break
pl(flag)

baby_re

打开一看,第一个函数有很明显的AES特征,可以使用自己的值测试一下,发现确实是AES

image-20241101145041414

第二段是一个将加密后每个输入切为bit,然后附上4位index值

image-20241101144424639

最后一段的检查函数是bit的一堆运算,本来想用z3,但是z3类型一直报错,然后一想发现只有8+4位,直接开爆

image-20241101145100006

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
from prism import *
def generate_combinations(n):
result = []
def backtrack(current:list, length):
if length == n:
result.append(current[:])
return
backtrack(current+[0], length + 1)
backtrack(current+[1], length + 1)
backtrack([], 0)
return result
datab = generate_combinations(8)
dataf = generate_combinations(4)
res = []
# print(datab)
# print(dataf)
for j in range(len(dataf)):
turn_choice = []
for i in range(len(datab)):
data = datab[i] + dataf[j]
v1 = data[9] & data[8] & data[7] & (data[5] == 0) & (data[4] == 0) & data[3] & ((data[2] | data[1] | data[0]) == 0) & (data[6] == 0) & (data[10] == 0) | data[9] & data[7] & data[5] & (data[3] == 0) & (data[2] == 0) & data[0] & (data[1] == 0) & (data[4] == 0) & (data[6] == 0) & (data[8] == 0) & (data[10] == 0) | data[10] & (data[8] == 0) & (data[7] == 0) & (data[6] == 0) & data[5] & data[4] & (data[2] & data[1] & data[0]) & (data[3] == 0) & (data[9] == 0)
v2 = data[11] & data[10] & data[8] & data[7] & data[6] & data[5] & data[4] & (data[2] == 0) & (data[1] & data[0]) & (data[3] == 0) & (data[9] == 0) | (data[10] == 0) & (data[9] == 0) & data[8] & data[7] & data[6] & data[4] & data[3] & data[1] & (data[0] == 0) & (data[2] == 0) & (data[5] == 0) & (data[11] == 0) | data[11] & (data[9] == 0) & data[8] & data[6] & data[4] & data[3] & data[2] & data[0] & (data[1] == 0) & (data[5] == 0) & (data[7] == 0) & (data[10] == 0) | data[11] & data[9] & data[8] & data[6] & data[5] & data[3] & data[2] & (data[1] == 0) & (data[4] == 0) & (data[7] == 0) & (data[10] == 0) | (v1 | data[10] & data[9] & (data[7] == 0) & data[6] & (data[4] == 0) & data[3] & data[2] & data[0] & (data[1] == 0) & (data[5] == 0) & (data[8] == 0)) & (data[11] == 0)
v3 = data[11] & data[10] & (data[8] == 0) & (data[7] == 0) & data[6] & (data[4] == 0) & (data[3] == 0) & (data[2] == 0) & (data[1] & data[0]) & (data[5] == 0) & (data[9] == 0) | (data[10] == 0) & (data[9] == 0) & (data[8] == 0) & (data[7] == 0) & data[6] & (data[4] == 0) & data[3] & ((data[2] | data[1] | data[0]) == 0) & (data[5] == 0) & (data[11] == 0) | data[11] & data[10] & data[9] & (data[7] == 0) & (data[6] == 0) & data[5] & data[4] & (data[2] == 0) & data[1] & (data[0] == 0) & (data[3] == 0) & (data[8] == 0) | data[11] & data[9] & (data[7] == 0) & (data[6] == 0) & data[5] & ((data[4] | data[3] | data[2] | data[1] | data[0]) == 0) & (data[8] == 0) & (data[10] == 0) | data[11] & (data[9] == 0) & (data[8] == 0) & data[7] & data[6] & data[5] & data[4] & (data[2] == 0) & data[0] & (data[1] == 0) & (data[3] == 0) & (data[10] == 0) | v2
if ((data[10] & data[8] & data[6] & data[4] & (data[2] == 0) & data[1] & (data[0] == 0) & (data[3] == 0) & (data[5] == 0) & (data[7] == 0) & (data[9] == 0) & (data[11] == 0) | data[11] & data[10] & data[9] & data[8] & (data[6] == 0) & (data[5] == 0) & data[4] & (data[2] == 0) & data[1] & (data[0] == 0) & (data[3] == 0) & (data[7] == 0) | v3 | data[10] & data[9] & data[8] & data[6] & data[4] & ((data[3] | data[2] | data[1] | data[0]) == 0) & (data[5] == 0) & (data[7] == 0) & (data[11] == 0)) ):
turn_choice.append(data)
res.append(turn_choice)
# print((turn_choice))

fi = []
for b in res:
qu = []
for ie in b:
t = ie[:8]
t = int("".join(str(i)for i in t),2)
qu.append(t)
fi.append(qu)
# for f in fi:
# phex(f)

def list_com(lists):
if not lists:
return [[]]

result = []
for item in lists[0]:
for rest in list_com(lists[1:]):
result.append([item] + rest)

return result

reallylast = list_com(fi)
print(reallylast)

from Crypto.Cipher import AES
key = bytes.fromhex('3577402ECCA44A3F9AB72182F9B01F35')

cipher = AES.new(key, AES.MODE_ECB)
dec = [cipher.decrypt(bytes(i)).hex() for i in reallylast]
print(dec[0])
print(dec[1])
"""
4d87ef03-77bb-491a-80f5-4620245807c4
0461d177-8471-23e0-dd55-0c471123c3f1
"""

最后有两个值,但是只有第一个是对的,不知道为什么。

ez_re

这个题一打开人就傻了,全是花,我该怎么分析流程呢

那就硬调吧QAQ

image-20241101153119187

输入完之后步出,到这里,说明这之前在输入之前

然后转到data段开头,有一个56长度的数据,后面有一个0x12345678

猜测长度是56,对上面两个数据下断点,然后run

在某个地方断下来了(触发了0x12345678的断点),发现某个寄存器保存的输入被异或了

输入

1
flag{11111111111111111111111111111111111111111111111111}

输出

1
2
3
4
0x19, 0x13, 0x1E, 0x18, 0x04, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 
0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E,
0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E,
0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x02

继续往后调,7FF7FBA45F18位置发现RAX变成了9E3779B9,有可能是Tea

后面会发现它把eax的值放到栈里面,接着就是这一次的加密数据

image-20241101193839970

猜是个tea,然后不会了