终于要更新博客了,有点幽默了……

最近碰到了一个神奇的题目,记录一下感想

首先,我本来都是习惯用jeb分析apk的,但是后来听不记得哪位师傅说,其实jadx反编译更贴近源代码,而且我发现,原来jadx也是可以完成apk java层动态调试工作的,所以成功转手

我们可以看一下大致的代码逻辑

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
//Mainactivity
package com.example.WarmUp;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), new OnApplyWindowInsetsListener() { // from class: com.example.WarmUp.MainActivity$$ExternalSyntheticLambda0
@Override // androidx.core.view.OnApplyWindowInsetsListener
public final WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat windowInsetsCompat) {
return MainActivity.lambda$onCreate$0(view, windowInsetsCompat);
}
});
final EditText flagInput = (EditText) findViewById(R.id.flagInput);
Button confirmButton = (Button) findViewById(R.id.confirmButton);
final TextView validationResult = (TextView) findViewById(R.id.validationResult);
confirmButton.setOnClickListener(new View.OnClickListener() { // from class: com.example.WarmUp.MainActivity$$ExternalSyntheticLambda1
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.this.m221lambda$onCreate$1$comexampleWarmUpMainActivity(flagInput, validationResult, view);
}
});
}

/* JADX INFO: Access modifiers changed from: package-private */
public static /* synthetic */ WindowInsetsCompat lambda$onCreate$0(View v, WindowInsetsCompat insets) {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
}

/* JADX INFO: Access modifiers changed from: package-private */
/* renamed from: lambda$onCreate$1$com-example-WarmUp-MainActivity reason: not valid java name */
public /* synthetic */ void m221lambda$onCreate$1$comexampleWarmUpMainActivity(EditText flagInput, TextView validationResult, View v) {
String inputFlag = flagInput.getText().toString().trim();
try {
if (validateFlag(inputFlag)) {
validationResult.setText("Correct!");
validationResult.setTextColor(getResources().getColor(17170453));
} else {
validationResult.setText("Wrong! Try Again!");
validationResult.setTextColor(getResources().getColor(17170455));
}
validationResult.setVisibility(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private boolean validateFlag(String data) throws Exception {
RC4 rc4 = new RC4();
String ciphertext = rc4.encrypt(data, "咪吗学高手");
return Base64.encodeToString(ciphertext.getBytes(), 2).equals("w5dDcHZ9woslI8KBPTNrYjlAwpzDjHgwI8OqeTrCpsOiwoZVCG/DjMOHwp3Dn3BPd8KtDwHDhMOUbQ==");
}
}

题目不难,简单分析就知道是RC4魔改+base64了,

再看一下RC4

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
package com.example.WarmUp;

import org.bouncycastle.crypto.tls.CipherSuite;

/* loaded from: classes3.dex */
public class RC4 {
public String encrypt(String plainOrCipherText, String key) {
Integer[] S = new Integer[256];
Character[] keySchedul = new Character[plainOrCipherText.length()];
StringBuilder ciphertext = new StringBuilder();
ksa(S, key);
rpga(S, keySchedul, plainOrCipherText.length());
for (int i = 0; i < plainOrCipherText.length(); i++) {
ciphertext.append((char) (plainOrCipherText.charAt(i) ^ keySchedul[i].charValue()));
}
return ciphertext.toString();
}

public void ksa(Integer[] s, String key) {
for (int i = 0; i < 256; i++) {
s[i] = Integer.valueOf(i ^ CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA);
}
int j = 0;
for (int i2 = 0; i2 < 256; i2++) {
j = ((s[i2].intValue() + j) + key.charAt(i2 % key.length())) % 256;
swap(s, i2, j);
}
}

public void rpga(Integer[] s, Character[] keySchedul, int plaintextLength) {
int i = 0;
int j = 0;
for (int k = 0; k < plaintextLength; k++) {
i = (i + 1) % 256;
j = (s[i].intValue() + j) % 256;
swap(s, i, j);
keySchedul[k] = Character.valueOf((char) s[(s[i].intValue() + s[j].intValue()) % 256].intValue());
}
}

public void swap(Integer[] s, int i, int j) {
Integer mTemp = s[i];
s[i] = s[j];
s[j] = mTemp;
}
}

简单提及一下要用到的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
//控制台

adb devices

显示offline就
adb reconnect offline

adb shell
su
cd /data/local/tmp
ls
./fri......

frida的几种模式(彩蛋)

1
2
3
4
5
6
7
8
先frida启动
frida -U -f com.yyyiggod.cdmmmmmmm

文件
frida -U -f com.w3333333.sevedy -l .\jjkk.js

先运行,再启动Frida,用文件名
frida -U 'showmaker' -l .\linkhash.js

法一:直接抄,然后解就行

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
import base64

class RC4:
def __init__(self, key):
self.S = list(range(256))
self.key = [ord(char) for char in key]
self.ksa()

def ksa(self):
# Key Scheduling Algorithm (KSA)
for i in range(256):
self.S[i] = i ^ 149

j = 0
for i in range(256):
j = (j + self.S[i] + self.key[i % len(self.key)]) % 256
self.swap(self.S, i, j)

def rpga(self, data):
# Pseudo-Random Generation Algorithm (PRGA)
i = j = 0
keystream = []
for char in data:
i = (i + 1) % 256
j = (j + self.S[i]) % 256
self.swap(self.S, i, j)
keystream.append(self.S[(self.S[i] + self.S[j]) % 256])
return keystream

def swap(self, s, i, j):
s[i], s[j] = s[j], s[i]

def encrypt_decrypt(self, plain_or_cipher_text):
# Encrypt or decrypt the data using XOR operation
keystream = self.rpga(plain_or_cipher_text)
result = []
for i in range(len(plain_or_cipher_text)):
result.append(chr(ord(plain_or_cipher_text[i]) ^ keystream[i]))
return ''.join(result)

if __name__ == "__main__":
# 示例: 使用 Base64 编码的密钥进行加解密
encoded_key = "w5dDcHZ9woslI8KBPTNrYjlAwpzDjHgwI8OqeTrCpsOiwoZVCG/DjMOHwp3Dn3BPd8KtDwHDhMOUbQ=="
plain_text = base64.b64decode(encoded_key).decode('utf-8')
key = "咪吗学高手"

# 创建 RC4 加密解密实例
rc4 = RC4(key)

# 加密过程
cipher_text = rc4.encrypt_decrypt(plain_text)
print("Cipher Text:", cipher_text)

# 解密过程
decrypted_text = rc4.encrypt_decrypt(cipher_text)
print("Decrypted Text:", decrypted_text)

感谢cdm格格送来的脚本

法二:动态调试

因为rc4的加密特殊性我们采用输入 111111111111111111111111111111111来直接查看最后结果的方式反推

不过多赘述了,相同方法参见sctf sbox那题

1.4

贴个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main()
{
int flag[] = { 0xc2,0x80,0x1e,0x20,0x20,0x37,0xc3,0x9b,0x22,0x23,0xc2,0x87,0x3a,0x30,0x6e,0x65,0x25,0x48,0xc2,0x9b,0xc3,0x8e,0x2f,0x2c,0x26,0xc2,0xbd,0x7c,0x3c,0xc2,0xba,0xc3,0xaa,0xc3,0x93,0x06,0x00,0x73,0xc3,0x85,0xc3,0x83,0xc2,0x9d,0xc2,0x88,0x71,0x48,0x76,0xc3,0xbe,0x0a,0x06,0xc2,0x96,0xc3,0x95,0x21 };
int xxxor[] = { 0xc3,0x97,0x43,0x70,0x76,0x7d,0xc2,0x8b,0x25,0x23,0xc2,0x81,0x3d,0x33,0x6b,0x62,0x39,0x40,0xc2,0x9c,0xc3,0x8c,0x78,0x30,0x23,0xc3,0xaa,0x79,0x3a,0xc2,0xa6,0xc3,0xa2,0xc2,0x86,0x55,0x08,0x6f,0xc3,0x8c,0xc3,0x87,0xc2,0x9d,0xc3,0x9f,0x70,0x4f,0x77,0xc2,0xad,0x0f,0x01,0xc3,0x84,0xc3,0x94,0x6d };
for (int i = 0; i < 58; i++)
{
char a = flag[i] ^ xxxor[i] ^ 0x31;
printf("%c", a);
}

return 0;



}
//0&lag{0!61176246-91613f-40&471-190$b9-1815110&0600"460#10}

没错,确实有问题,所以我们将使用第二种方法去取更精准的值

法三: frida_hook取值

我们要hook rc4加密后的返回结果

1.4

这是很经典的一个hook模板了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hook2() {
var MainActivity = Java.use("com.example.WarmUp.RC4");
MainActivity.encrypt.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
console.log("Origin i and i2 = ", a, b);
let result = this.encrypt(a, b);
console.log("Origin i and i2 = ", result);
return this.check(a, b);
}
}

function main() {
Java.perform(function () {
hook2();
})
}

setImmediate(main);

//let RC4 = Java.use("com.example.WarmUp.RC4");

结果

1.5

那么,我们用java转16进制即可

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
function stringToByte(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i);
st = [];
do {
st.push(ch & 0xFF);
ch = ch >> 8;
} while (ch);
re = re.concat(st.reverse());
} // return an array of bytes
return re;
}



function hook2() {
var MainActivity = Java.use("com.example.WarmUp.RC4");
MainActivity.encrypt.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
console.log("Origin i and i2 = ", a, b);
let result = this.encrypt(a, b); //打印加密的值
console.log("Origin i and i2 = ", result);
let byteResult = stringToByte(result);
console.log("Result as byte array: ", byteResult);

// 打印字节数组(如果需要)
//console.log("Result as byte array: ", byteResult);
return result;
}
}

function main() {
Java.perform(function () {
hook2();
})
}

setImmediate(main);



结果

1
2
3
[Pixel 4::com.example.WarmUp ]-> Origin i and i2 =  1111111111111111111111111111111 咪吗学高手
Origin i and i2 = 7Û"#:0ne%HÎ/,&½|<ºêÓ
Result as byte array: 128,30,32,32,55,219,34,35,135,58,48,110,101,37,72,155,206,47,44,38,189,124,60,186,234,211,6

没错,成功了,但是转念一想,既然输出多少有点问题,为什么不直接取值然后顺便就异或回去呢,只要脚本写在一起就行

先看一下怎么用他的导入包函数

1.6

1.7

脚本

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
setImmediate(function () {
Java.perform(function () {
// 获取 RC4 类的引用
var RC4 = Java.use('com.example.WarmUp.RC4');
var keyIndeed
// hook encrypt 方法
RC4.encrypt.implementation = function (plainOrCipherText, key) {
console.log('Calling RC4.encrypt with plainOrCipherText: ' + plainOrCipherText + ', key: ' + key);
keyIndeed = key

var base64CipherText = "w5dDcHZ9woslI8KBPTNrYjlAwpzDjHgwI8OqeTrCpsOiwoZVCG/DjMOHwp3Dn3BPd8KtDwHDhMOUbQ==";


var decodedCipherText = Java.use('android.util.Base64').decode(base64CipherText, 2); // 2表示URL-safe模式

var decodedText = Java.use('java.lang.String').$new(decodedCipherText);

var result = this.encrypt(decodedText, key);

console.log('RC4.encrypt result: ' + result);

// 返回加密结果
return result;
};


});

});

1.8

也确实成功了

法四:暴力内存搜刮

1
https://www.52pojie.cn/thread-1838539-1-1.html

有点复杂,具体去看正己师傅的文章

主要是要换frida环境

1
2
3
4
5
6
pip install virtualenvwrapper-win -i https://pypi.tuna.tsinghua.edu.cn/simple
mkvirtualenv frida14


(frida14)pip install frida-tools==9.2.4
//虽然我没搞就是了,报错再说.....

命令

1
objection -g com.example.saga131 explore
1
android hooking list activities -查看内存中加载的activity   /android hooking list services -查看内存中加载的services
1
2
3
4
5
android intent launch_activity 类名 -启动activity或service(可以用于一些没有验证的activity,在一些简单的ctf中有时候可以出奇效)

//这个程序比较简单,如果复杂的话可以使用类的搜索
android hooking search classes +
//android hooking search classes example

首先我们需要一个hashcode

1
2
3
4
5
6
com.example.WarmUp on (google: 10) [usb] # android heap search instances com.example.WarmUp.MainActivity
Class instance enumeration complete for com.example.WarmUp.MainActivity
Hashcode Class toString()
-------- ------------------------------- ---------------------------------------
22710814 com.example.WarmUp.MainActivity com.example.WarmUp.MainActivity@15a8a1e
11745467 com.example.WarmUp.MainActivity com.example.WarmUp.MainActivity@b338bb

1.9

1
2
3
4
5
6
7
8
android heap execute <handle> getPublicInt(实例的hashcode+方法名)   //这里要求无参数,待会再找个无参数demo实验

如果是带参数的方法,则需要进入编辑器环境
android heap evaluate <handle>
console.log(clazz.a("吾爱破解"));

//console.log(clazz.validateFlag("11111111111111111111"))
按住esc+enter触发

2.0

2.1

当然,还能用方法

1
android hooking list class_methods com.example.WarmUp.MainActivity 类名 -内存漫游类中的所有方法

2.2

1
2
3
android hooking watch class_method 类名.方法名 --dump-args --dump-return --dump-backtrace
//android hooking watch class_method com.example.WarmUp.MainActivity.validateF
lag --dump-args --dump-return --dump-backtrace

2.3

开始正题

所实话,感觉有点像java层模拟执行

有个问题,就是你会发现他搜不到这个RC4类,可以给大家看一下我之前的内存寻找结果

2.5

所以,我们得多点几下check,让他抓到,再人为搜索

1
2
3
4
5
com.example.WarmUp on (google: 10) [usb] # android hooking search classes RC4
Note that Java classes are only loaded when they are used, so if the expected class has not been found, it might not have been loaded yet.
com.android.org.bouncycastle.jcajce.provider.symmetric.ARC4
com.android.org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings
com.example.WarmUp.RC4

找个hashcode先

1
2
3
4
5
com.example.WarmUp on (google: 10) [usb] # android heap search instances com.example.WarmUp.RC4
Class instance enumeration complete for com.example.WarmUp.RC4
Hashcode Class toString()
--------- ---------------------- ------------------------------
108025448 com.example.WarmUp.RC4 com.example.WarmUp.RC4@6705668

2.4

1
2
3
4
5
6
7
com.example.WarmUp on (google: 10) [usb] # android heap evaluate 108025448
(The hashcode at `108025448` will be available as the `clazz` variable.)
console.log(clazz.encrypt("11111111111111111111111111111111111", "咪吗学高手"))
JavaScript capture complete. Evaluating...
Handle 108025448 is to class
com.example.WarmUp.RC4
7Û"#:0ne%HÎ/,&½|<ºêÓ

额,没区别啊,…….,感觉得想个办法,最好使用16进制的格式输出

不过找到了objection其他好玩的功能

1
https://www.anquanke.com/post/id/197657

有个辅助写frida的

1
android hooking generate simple com.example.WarmUp.RC4

效果

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
Java.perform(function() {
var clazz = Java.use('com.example.WarmUp.RC4');
clazz.swap.implementation = function() {

//

return clazz.swap.apply(this, arguments);
}
});


Java.perform(function() {
var clazz = Java.use('com.example.WarmUp.RC4');
clazz.rpga.implementation = function() {

//

return clazz.rpga.apply(this, arguments);
}
});


Java.perform(function() {
var clazz = Java.use('com.example.WarmUp.RC4');
clazz.ksa.implementation = function() {

//

return clazz.ksa.apply(this, arguments);
}
});


Java.perform(function() {
var clazz = Java.use('com.example.WarmUp.RC4');
clazz.encrypt.implementation = function() {

//

return clazz.encrypt.apply(this, arguments);
}
});
//还行,自己在修改一下

必要的知识掌握

有个超级好看的文章,建议都去学一下

1
https://www.52pojie.cn/thread-742686-1-1.html

先加个debuggable再找一下入口函数

1.3

小插件

可以帮助分析so层的

1
https://github.com/Pr0214/trace_natives

大家真可以去下下这个,好有意思的小.py文件

frida检测绕过

GitHub - Ylarod/Florida: 基础反检测 frida-server / Basic anti-detection frida-server

download

1.1

下载16.5.6这个

1.2

然后用法和frida一样

1
frida -U -f com.jjkk.showmaker