菜逼!!啥都记录一点

ISCC-2025

整好借着打ISCC,顺便写两道WP

ISCC2025-whereisflag

APK直接打开看一眼,直接看activity里面的MainActivity

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

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.whereisflag.databinding.ActivityMainBinding;

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private EditText flagEditText;
private Button submitButton;

static {
System.loadLibrary("whereisflag");
}///加载本地的动态链接库,在windows就是dll,在Linux就是so

@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle bundle) {
super.onCreate(bundle); //初始化activity

ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
this.binding = inflate;//使用View Binding功能将XML中的控件绑定到Java中
setContentView(inflate.getRoot());//加载UI界面

this.flagEditText = this.binding.editTextFlag;
Button button = this.binding.button;
this.submitButton = button;//把EditText(输入flag的地方)和Button(提交按钮)分别绑定到flagEditText和submitButton成员变量。

button.setOnClickListener(new View.OnClickListener() { // from class: com.example.whereisflag.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String obj = MainActivity.this.flagEditText.getText().toString();
//给按钮设置点击事件
//当点击按钮时,取出用户在EditText中输入的文本obj

if (obj.length() != 16) {
Toast.makeText(MainActivity.this, "flag长度错误,请继续寻找", 0).show();
//设置判断长度
} else if (new a().b(obj)) {
Toast.makeText(MainActivity.this, "恭喜你找到了正确的flag", 1).show();
//调用a的方法b(obj)来校验flag是否正确

} else {
Toast.makeText(MainActivity.this, "flag错误,请继续寻找", 0).show();
//如果都不满足就输出失败
}
}
});
}
}

很明显,最简单的,看一下调用a的方法b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.whereisflag;  //表明这个类属于com.example.whereisflag包

/* loaded from: classes.dex */
public class a {
public native String compute(String str);
//compute是一个native方法,它的实现不在java中,而是在C/C++的本地代码中,通常是.so的动态链接库

public boolean b(String str) {
if (str.startsWith("ISCC{") && str.endsWith("}")) {
return compute(str.substring(5, 15)).equals("iB3A7kSISR");
}
//下面就是一个校验逻辑了,我们最后的形式必须是iB3A7kSISR!
return false;
}
}

显而易见,这就是我们需要的flag了

flag的包装头ISCC{xxxxxx}

肯定是有加密的,让我们简单分析一下

分析完毕,我们拆包去lib中找我们需要的加密逻辑。

image-20250522200206677

简单逆向分析一下咯,直接看encrypt函数

喂给ai

image-20250522201623078

没有ai我怎么活呢?,很明显了首先有个字符串反转,~~~然后有个换表,这里说错了,只是进行了凯撒加密,只不过对应的表换了,并不是我们常见的A-C这样~~,然后进行了凯撒加密,偏移是3

先找一下表吧

image-20250522202232842

找到了捏,WHEReISFLAGBCDJKMNOPQTUVXYZabcdfghijklmnopqrstuvwxyz0123456789

逆向的话,仔细看你就会发现它其实是对着表进行偏移的,就是上面的表

也就是我们只需要偏移回去在倒转即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 加密映射表(来自 C++ 中的 qword_4ED70)
ABC = "WHEReISFLAGBCDJKMNOPQTUVXYZabcdefghijklmnopqrstuvwxyz0123456789"

# 解密函数(偏移 -2 后再反转)
def decode(encrypted: str) -> str:
return ''.join([ABC[(ABC.index(ch) - 2) % len(ABC)] for ch in encrypted])[::-1]

if __name__ == "__main__":
# 将 compute() 返回的密文粘贴到这里:
secret = "iB3A7kSISR" # 示例密文,来自你的 Java 代码 b(str) 中的比较值

# 解密出 flag 中间部分
content = decode(secret)

# 拼出完整 flag
result = f"ISCC{{{content}}}"
print("解密结果:", result)
##ISCC{HeRei5F1Ag}

之后得到了flag。

然后就完事了,唉,怎么说呢!

还是比较简单的

image-20250522203409814

怎么说呢?现在准备还是一天一道CTF吧。(感觉做不到)

攻防世界

Android2.0

2025.5.25

怎么说,JADX打开,简单来说是直接看Activity

image-20250525125615163

基本来说,加密逻辑就在MainActivity里了

直接看Activity的生命周期

onCreate()–>onStart()–>onResume()–>onPause()–>onStop()–>onDestory()

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

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
Button button;
EditText pwd;
TextView textView;

@Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityGingerbread, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//设置布局文件activity_main.xml
init();//简单来说就是初始化的
this.button.setOnClickListener(new View.OnClickListener() { // from class: com.example.test.ctf03.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {

///主要的逻辑在这
String str = MainActivity.this.pwd.getText().toString();
///获取用户输入的字符串str
int result = JNI.getResult(str);
///调用本地JNI方法JNI.getResult(str),传入输入进行验证

MainActivity.this.Show(result); //显示结果的没啥说的
}
});
}

public void init() {
this.pwd = (EditText) findViewById(R.id.pwd); //输入框
this.button = (Button) findViewById(R.id.button); //按钮
this.textView = (TextView) findViewById(R.id.result); //文本显示区域
}

public void Show(int type) {
switch (type) {
case 0:
this.textView.setText("Wrong");
break;
case 1:
this.textView.setText("Great");
break;
}
}
}

所以找到核心的验证加密逻辑JNI.getResult(str),点进去看看

image-20250525131214322

加密逻辑一看就在Native层了,拆包看一下。正好lib中也只有一个.so文件(动态链接库)

点进去看见是ARM-32位的。

我点进去是先看的函数栏,找加密逻辑喽

image-20250525131849300

看着像分成了三段

其实感觉就是一个简单的拼接

image-20250525132430026

这个第二段和第三段在一块

image-20250525132537627

感觉不太对,转不成ASCLL码

先拼接一下试试,不太对

第一部分:LN^dl

第二部分: 5-

第三部分:AFBo}

想太多

我们直接看Java_com_example_test_ctf03_JNI_getResult这个函数,直接喂给ai看反汇编代码

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
int Java_com_example_test_ctf03_JNI_getResult(JNIEnv *env, jobject thiz, jstring input) {
const char *user_input = (*env)->GetStringUTFChars(env, input, 0);
int result = 0;

if (strlen(user_input) != 15) {
return 0;
}

void Init(char *out1, char *out2, char *out3, const char *in, int len) {
if (len < 1) {
out1[0] = 0;
out2[0] = 0;
out3[0] = 0;
return;
}

// 定义用于模拟取模操作的魔数
uint32_t div3_magic = 0x55555556; // 1/3的乘法倒数(右移可得余数)
uint32_t div3_magic_2 = 0xAAAAAAAB; // 类似用于除法操作

for (int i = 0; i < len; i++) {
int mod = i % 3;

switch (mod) {
case 0: { // out1
uint32_t idx = (i * div3_magic_2) >> 1; // 等效于除以2
out1[idx] = in[i];
break;
}
case 1: { // out2
uint32_t idx = (i * div3_magic_2) >> 1;
out2[idx] = in[i];
break;
}
case 2: { // out3
uint32_t idx = (i * div3_magic_2) >> 1;
out3[idx] = in[i];
break;
}
}
}

// 添加结束符
out1[len / 3] = 0;
out2[len / 3] = 0;
out3[len / 3] = 0;
}

int First(char *buf) {
int i = 0;
unsigned char xor_key = 0x80;

// 解码前4个字节
while (i < 4) {
buf[i] = (xor_key ^ (buf[i] << 1)) & 0xFF; // 等价于 EOR.W R3, R2, R3,LSL#1
i++;
}

// 比较处理后的 buf 是否等于 "LN^dl"
if (strcmp(buf, "LN^dl") == 0)
return 1;
return 0;
}



// 分配内存
char *buf1 = malloc(1);
char *buf2 = malloc(1);
char *buf3 = malloc(1);

int len = 15;
Init(buf1, buf2, buf3, user_input, len); // 模糊处理,具体实现未知




// 如果 First(buf1) 返回非 0
if (First(buf1)) {
// buf2[i] ^= buf1[i] for i in 0..3
for (int i = 0; i < 4; i++) {
buf2[i] ^= buf1[i];
}

// 比较 buf2 和 " 5-"
if (strcmp(buf2, " 5-") == 0) {
for (int i = 0; i < 4; i++) {
buf3[i] ^= buf2[i];
}

// 比较 buf3 和 "AFBo}"
if (strcmp(buf3, "AFBo}") == 0) {
result = 1;
}
}
}

return result;
}



怎么说,ai还是强大的

这里直接建议用ida9打开就好了

image-20250527201725689

image-20250527201737145

首先知道我们要的flag长度是15,init函数把我们的字符串分成三段。first函数的加密逻辑(xor_key ^ (buf[i] << 1)) & 0xFF;,异或0x80并且左移一位,然后和LN^dl比较。buf2是异或buf1,buf3是异或buf2,分别与 5-AFBo}比较。

所以直接进行逆向解密

part1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src=list("LN^dl")
for i in range(5):
print(chr(0x80^(ord(src[i])>>1),end='')



dst = list("LN^dl")
for i in range(4):
after = ord(dst[i])
for c in range(256):
if (0x80 ^ (c << 1) & 0xFF) == after:
print(chr(c), end='')
break
###fgor
###注意这里只对前4个数进行了操作,所以第五个数不用变
1
fgorl

这里笔者进行逆向是发现不对,只能暴力破解为好

image-20250525142434958

part2

1
2
3
4
5
6
7
buf1="LN^dl"
buf2=[0x20, 0x35, 0x2D, 0x16, 0x61]
for i in range(4):
print(chr(buf2[i]^ord(buf1[i])),end='')
#l{sr

###我去,这里是对加密后的buf1进行操作的

看反编译代码只对前四个进行了操作,则第五个不动吗????

所以第五个还是a

1
l{sra

注意buf2肯定也还是五字节的

image-20250525150821692

只不过有些无法转成ASCLL而已

part3

1
2
3
4
5
buf3="AFBo}"
buf2=[0x20, 0x35, 0x2D, 0x16, 0x61]
for i in range(4):
print(chr(buf2[i]^ord(buf3[i])),end='')
#asoy

加上第五个字符

1
asoy}

最终

1
2
3
4
5
6
7
8
buf1="fgorl"
buf2="l{sra"
buf3="asoy}"

for i in range(5):
print(buf1[i]+buf2[i]+buf3[i],end='')

#flag{sosorryla}

总结:

我去我去,注意笔者在part1的时候没有注意只对前4个字节进行了操作,所以踩坑了。part2的时候没注意是对加密后的buf1进行操作的又踩坑了,最终的拼接是要flag形状的。

怎么说呢!题出的还是非常好的

APK逆向

—-2025.5.27

老样子直接拖进JADX里面看看,直接看activity里的MainActivity

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
package com.example.crackme; //包名

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//导入的模块


/* loaded from: classes.dex */
public class MainActivity extends Activity {
private Button btn_register; //注册按钮
private EditText edit_sn; //输入框
String edit_userName;//用户名

@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //调用父类的onCreate方法,必不可少
setContentView(R.layout.activity_main); //设置页面布局的
setTitle(R.string.unregister); //设置窗口标题为"未注册"
this.edit_userName = "Tenshine";//写死用户名"Tenshine"
this.edit_sn = (EditText) findViewById(R.id.edit_sn);//通过ID找到界面的输入框
this.btn_register = (Button) findViewById(R.id.button_register);//通过ID找到界面的注册按钮
this.btn_register.setOnClickListener(new View.OnClickListener() { // from class: 监听的 com.example.crackme.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) { //点击按钮执行函数
if (!MainActivity.this.checkSN(MainActivity.this.edit_userName.trim(), MainActivity.this.edit_sn.getText().toString().trim()))
//checkSN()方法验证用户名和序列号是否匹配
//trim()是去除空格
{
Toast.makeText(MainActivity.this, R.string.unsuccessed, 0).show();
//注册不成功
return;
}
Toast.makeText(MainActivity.this, R.string.successed, 0).show();
//注册成功
MainActivity.this.btn_register.setEnabled(false);
//禁用按钮
MainActivity.this.setTitle(R.string.registered);
//更改注册表为已注册
}
});
}

@Override // android.app.Activity
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
//创建菜单的

然后我们可以雷电模拟运行一下这个APP

image-20250527182215452

看主要的校验逻辑吧

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
    /* JADX INFO: Access modifiers changed from: private */
public boolean checkSN(String userName, String sn) {
if (userName == null) {
return false;
}
try {
if (userName.length() == 0 || sn == null || sn.length() != 22) {
return false;
//限制长度和和序列号
}
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest();
//创建MD5摘要器
//将用户名转为字节数组进行MD5哈希计算
//bytes是MD5结果

String hexstr = toHexString(bytes, "");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
//每个一个字符取一个,拼接出新的字符串userSN
String userSN = sb.toString();
//得到最终部分
return new StringBuilder().append("flag{").append(userSN).append("}").toString().equalsIgnoreCase(sn);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false; //拼接flag{userSN},并和用户输入的sn比较
}
}

private static String toHexString(byte[] bytes, String separator) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 255);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex).append(separator);
}
return hexString.toString();
}
}
//把一个字节数组转换为十六进制字符串。
//例如 0x1f 会变成 "1f",0x03 会变成 "03"。
//separator 参数用来控制是否在每两个字符中间插入分隔符(此程序传入 "",即不加分隔)。
//没啥用辅助功能

现在我们知道的用户名就是Tenshine,我们简单的MD5加密一下B9C77224FF234F27AC6BADF83B855C76

1
2
3
4
5
6
7
src="B9C77224FF234F27AC6BADF83B855C76"

for i in range(0, len(src), 2):
print(src[i],end="")

#BC72F242A6AF3857
flag{BC72F242A6AF3857}

然后试一下

image-20250527184249386

额,行吧,交的时候注意换成32小写即可

1
bc72f242a6af3857

像这道题就是直接在Java层中找加密逻辑

APK中的结构

层级 说明 示例
💡 应用层(App Layer) 你写的代码,比如 MainActivity.java com.example.crackme.MainActivity
🔧 Framework 层(Java Framework) Android 提供的类库,比如 Activity, Button, Toast android.app.Activity
⚙️ Native 层(Native Libraries) 用 C/C++ 编写的库,NDK、libc、libcrypto 等 可能用来做更隐蔽的加密
🧩 HAL / Kernel 层 驱动层,硬件访问层 与 CrackMe 无关

人民的名义-抓捕赵德汉1-200

2025.5.28

这次下载完是jar后缀

不到是啥意思,直接使用JADX打开

image-20250528133009826

感觉主要的加密逻辑就在这了

喂给ai,这是一个动态加载类并验证密码的实列过程,它把真正验证密码的类加密后嵌入资源中,运行时再动态加载并调用。

怎么说?frida hook一下吗?

image-20250528134629882

我先看看idea能进行打个断点调试吗?

没源码吗?这是打不开反正

这里也是直接看WP了

第一次发现jar包还能直接运行呢

1
Java -jar jar包名

然后就能运行了

java版本太高运行不起来是正常的

换个低版本即可

没啥说的

1
2
3
fa3733c647dca53a66cf8df953c2d539
md5解密得到flag
monkey99

这题出的不好

XCTF_MOBILE15_人民的名义-抓捕赵德汉1-200-CSDN博客

ill-intentions

2025.5.31

JADX打开,老样子直接进行看MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.application;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.TextView;
import com.example.hellojni.Manifest;

/* loaded from: classes.dex */
public class MainActivity extends Activity {
@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(getApplicationContext());
tv.setText("Select the activity you wish to interact with.To-Do: Add buttons to select activity, for now use Send_to_Activity");
setContentView(tv);
IntentFilter filter = new IntentFilter();
filter.addAction("com.ctf.INCOMING_INTENT");
BroadcastReceiver receiver = new Send_to_Activity();
registerReceiver(receiver, filter, Manifest.permission._MSG, null);
}
}

简单翻译一下这句英文

image-20250531131524639

简单模拟一下这个APK看一下

image-20250531131709886

简单ai了一下MainActivity主要是显示一段引导文字,提醒用户暂时通过广播与指定的Activity交互,注册了一个广播接收器Send_to_Activity,监听 com.ctf.INCOMING_INTENT

然后我们主要看Send_to_Activity

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

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

/* loaded from: classes.dex */
public class Send_to_Activity extends BroadcastReceiver {
@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String msgText = intent.getStringExtra("msg");
if (msgText.equalsIgnoreCase("ThisIsTheRealOne")) {
Intent outIntent = new Intent(context, (Class<?>) ThisIsTheRealOne.class);
context.startActivity(outIntent);
} else if (msgText.equalsIgnoreCase("IsThisTheRealOne")) {
Intent outIntent2 = new Intent(context, (Class<?>) IsThisTheRealOne.class);
context.startActivity(outIntent2);
} else if (msgText.equalsIgnoreCase("DefinitelyNotThisOne")) {
Intent outIntent3 = new Intent(context, (Class<?>) DefinitelyNotThisOne.class);
context.startActivity(outIntent3);
} else {
Toast.makeText(context, "Which Activity do you wish to interact with?", 1).show();
}
}
}

监听”msg”字段的广播,根据其值启动不同的Activity

  • "ThisIsTheRealOne" → 启动 ThisIsTheRealOne 活动

  • "IsThisTheRealOne" → 启动 IsThisTheRealOne 活动

  • "DefinitelyNotThisOne" → 启动 DefinitelyNotThisOne 活动

看一下这三个Activity会发现它的每个类都加载了一个名为hello-jni的本地库。每个类都定义了若干native方法,每个类在界面中提供了一个按钮,点击后会发送一个广播com.ctf.OUTGOING_INTENT,其中附带了一段加密/处理过的msg

这下简单了,直接进行拆包,看动态加载库即可

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
__int64 __fastcall Java_com_example_application_IsThisTheRealOne_perhapsThis(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5)
{
__int64 v5; // r15
const char *v8; // r12
const char *v9; // rbp
const char *v10; // rbx
char *v11; // rax
char v12; // si
char v14[80]; // [rsp+0h] [rbp-188h] BYREF
char v15[80]; // [rsp+50h] [rbp-138h] BYREF
char dest[80]; // [rsp+A0h] [rbp-E8h] BYREF
char v17[88]; // [rsp+F0h] [rbp-98h] BYREF

v5 = 0LL;
v8 = (*(*a1 + 1352LL))(a1, a3, 0LL);
v9 = (*(*a1 + 1352LL))(a1, a4, 0LL);
v10 = (*(*a1 + 1352LL))(a1, a5, 0LL);
v11 = &v8[strlen(v8)];
*v11 = '{barGwnw';
strcpy(v11 + 16, "tfqm}");
*(v11 + 1) = 'Cr\x7FhbtuO';
strncpy(dest, v8, 'L');
strncpy(v14, v9, 'L');
strncpy(v15, v10, 'L');
dest[76] = 0;
v15[76] = 0;
v14[76] = 0;
do
{
v12 = dest[v5] ^ v14[v5] ^ v15[v5];
v17[v5++] = v12;
printf("%c\n", v12);
}
while ( v5 != 76 );
v17[76] = 0;
printf("Here is your Reply: %s", v17);
return (*(*a1 + 1336LL))(a1, v17);
}



__int64 __fastcall Java_com_example_application_ThisIsTheRealOne_orThat(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5)
{
__int64 v5; // r15
const char *v8; // r12
const char *v9; // rbp
const char *v10; // rbx
char *v11; // rax
char v12; // si
char v14[80]; // [rsp+0h] [rbp-188h] BYREF
char v15[80]; // [rsp+50h] [rbp-138h] BYREF
char dest[80]; // [rsp+A0h] [rbp-E8h] BYREF
char v17[88]; // [rsp+F0h] [rbp-98h] BYREF

v5 = 0LL;
v8 = (*(*a1 + 1352LL))(a1, a3, 0LL);
v9 = (*(*a1 + 1352LL))(a1, a4, 0LL);
v10 = (*(*a1 + 1352LL))(a1, a5, 0LL);
v11 = &v8[strlen(v8)];
*v11 = '[\x7FLElYIJ';
*(v11 + 1) = 'n`xuz`jx';
*(v11 + 2) = 'AOfz\x7FvO\x7F';
strcpy(v11 + 24, "RkiPkYmbFftKqusfn}flhQfsqxr\\TeaSgnmdc!");
strncpy(dest, v8, 'L');
strncpy(v14, v9, 'L');
strncpy(v15, v10, 'L');
dest[76] = 0;
v15[76] = 0;
v14[76] = 0;
do
{
v12 = dest[v5] ^ v14[v5] ^ v15[v5];
v17[v5++] = v12;
printf("%c\n", v12);
}
while ( v5 != 76 );
v17[76] = 0;
printf("Here is your Reply: %s", v17);
return (*(*a1 + 1336LL))(a1, v17);
}




__int64 __fastcall Java_com_example_application_DefinitelyNotThisOne_definitelyNotThis(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4)
{
(*(*a1 + 1352LL))(a1, a3, 0LL);
(*(*a1 + 1352LL))(a1, a4, 0LL);
return (*(*a1 + 1336LL))(a1, "Told you so!");
}

这三段代码里ThisIsTheRealOne是我们真正想要的,本来是想要动态调试一下这个.so文件的,有报错没调试起来。上网找了找

image-20250531150045938

照着文章整了整,但是把你会发现报错,我们调试android_server64时他会报错Segmentation fault,然后呢我们换成android x64 server进行调试的时候,你又会发现我们ida又会报错

image-20250531150404086

SO,笔者的建议还是使用frida进行hook好了,但是笔者这里不会搓脚本,然后也是使用ai编写的了

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
Interceptor.attach(Module.getExportByName(null, "dlopen"), {
onEnter: function (args) {
this.path = Memory.readCString(args[0]);
},
onLeave: function (retval) {
if (this.path.indexOf("libhello-jni.so") !== -1) {
console.log("[*] libhello-jni.so loaded");

var base = Module.findBaseAddress("libhello-jni.so");
console.log("[*] Base address:", base);

// 这里根据你 readelf 得到的 offset 修改
var target = base.add(0x6c0);
console.log("[*] Hook target function at", target);

Interceptor.attach(target, {
onEnter: function (args) {
console.log("[*] Function called");
console.log("Arg0: " + args[0]);
console.log("Arg1: " + args[1]);
},
onLeave: function (retval) {
console.log("[*] Return value:", retval);
}
});
}
}
});

不咋会用frida

image-20250531151801026

hook之后应该做什么哇?这里直接也是看WP了

下面直接贴hook脚本了

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


function main() {
Java.perform(function() {
var DefinitelyNotThisOneHandler = Java.use('com.example.application.DefinitelyNotThisOne')
DefinitelyNotThisOneHandler.definitelyNotThis.implementation = function(arg0, arg1) {
console.log('DefinitelyNotThisOneHandler called: ' + arg0 + " \n" + arg1)
var ret = this.definitelyNotThis(arg0, arg1)
console.log('DefinitelyNotThisOneHandler ret: ' + ret )
return ret
}

var ThisIsTheRealOneHandler = Java.use('com.example.application.ThisIsTheRealOne')
ThisIsTheRealOneHandler.orThat.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(arg0, arg1, arg2) {
console.log('ThisIsTheRealOneHandler called: ' + arg0 + " \n" + arg1 + " \n" + arg2)
var ret = this.orThat(arg0, arg1, arg2)
console.log('ThisIsTheRealOneHandler ret: ' + ret )
return ret
}

var IsThisTheRealOneHandler = Java.use('com.example.application.IsThisTheRealOne')
IsThisTheRealOneHandler.perhapsThis.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(arg0, arg1, arg2) {
console.log('IsThisTheRealOneHandler called: ' + arg0 + " \n" + arg1 + " \n" + arg2)
var ret = this.perhapsThis(arg0, arg1, arg2)
console.log('IsThisTheRealOneHandler ret: ' + ret )
return ret
}
})
}

setImmediate(main)

//firda-ps -U
//frida -U -n "CTF Application" -l hook.js

步骤就不说了,不会hook的,简单去学一下吧,或者看一下笔者的另一篇文章。

image-20250531161101809

hook上之后,要在模拟器那边简单触发一下。

这里讲一下objection

它是基于frida的命令工具,主要用于对 Android 和 iOS 应用的动态分析,尤其适合逆向工程、安全测试和 CTF 挖洞。

常见的功能如下:

功能 说明
explore 进入应用交互控制台
android hooking Hook Java 方法、类和 native 函数
android intent 启动 Activity,查看 intent 内容
android memory 查看内存中的字符串、对象等
android keystore 与系统 Keystore 交互
android file download 下载应用内部的文件
android sslpinning disable 绕过 SSL Pinning(HTTPS 拦截)
android misc 常用的辅助操作,如截图、剪贴板查看等

命令

1
2
3
4
5
6
>objection -g com.example.hellojni explore


>>>CLI中输入
>android intent launch_activity com.example.application.IsThisTheRealOne
>android hooking watch class_method android.content.Intent.putExtra --dump-return --dump-args --dump-backtrace

然后我们hook的时候就可以看见把拦截的信息打印下来了

image-20250531160841428

bananaship师傅说还是很好用的,简单了解一下吧

总结:

学的东西还是挺多的,至少ida的对安卓设备的调试会了,虽说没有用上。hook也是学上了,对frida的了解又加深了。对hook脚本的编写希望能在进一些。