mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-10-20 14:52:20 +02:00
feat: Add log sending functionality with device information and logcat capture, including a new utility and UI integration, and update the application version.
This commit is contained in:
@@ -70,6 +70,8 @@ import top.niunaijun.blackbox.utils.DexCrashPrevention;
|
||||
import top.niunaijun.blackbox.utils.NativeCrashPrevention;
|
||||
import top.niunaijun.blackbox.utils.CrashMonitor;
|
||||
import top.niunaijun.blackbox.utils.StoragePermissionHelper;
|
||||
import top.niunaijun.blackbox.utils.LogSender;
|
||||
|
||||
// just use it guys and i know you forgot to give credits so have it what ever 🤧
|
||||
/**
|
||||
* updated by alex5402 on 4/9/21.
|
||||
@@ -2175,4 +2177,162 @@ public class BlackBoxCore extends ClientConfiguration {
|
||||
tryAlternativeServerStartupMethods();
|
||||
}
|
||||
}
|
||||
public void sendLogs(String caption, boolean async) {
|
||||
String chatId = mClientConfiguration != null ? mClientConfiguration.getLogSenderChatId() : null;
|
||||
if (chatId == null || chatId.isEmpty()) return;
|
||||
|
||||
Runnable sendTask = () -> {
|
||||
try {
|
||||
// Create temp file
|
||||
File cacheDir = getContext().getCacheDir();
|
||||
File tempLog = File.createTempFile("crash_log_", ".txt", cacheDir);
|
||||
|
||||
// Construct full caption
|
||||
String baseCaption = caption != null ? caption : "Log Report";
|
||||
String deviceInfo = getDeviceInfoString();
|
||||
String fullCaption = baseCaption + "\n\n" + deviceInfo;
|
||||
|
||||
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tempLog)) {
|
||||
// Write Header
|
||||
String header = "Caption: " + baseCaption + "\n\n" + deviceInfo + "\n\n--- LOGCAT ---\n";
|
||||
fos.write(header.getBytes("UTF-8"));
|
||||
|
||||
// Dump Logcat
|
||||
java.lang.Process process = Runtime.getRuntime().exec("logcat -d -v threadtime");
|
||||
try (java.io.InputStream in = process.getInputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
fos.flush();
|
||||
}
|
||||
|
||||
// Send
|
||||
String error = LogSender.send(chatId, tempLog, fullCaption);
|
||||
if (error != null) {
|
||||
Slog.e(TAG, "Log upload failed: " + error);
|
||||
// Show Toast on Main Thread
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
try {
|
||||
android.widget.Toast.makeText(getContext(), "Log Upload Failed: " + error, android.widget.Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {}
|
||||
});
|
||||
|
||||
// Show Notification
|
||||
if (getContext() != null) {
|
||||
NotificationManager nm = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (nm != null) {
|
||||
String channelId = getContext().getPackageName() + ".blackbox_core";
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new Notification.Builder(getContext(), channelId);
|
||||
} else {
|
||||
builder = new Notification.Builder(getContext());
|
||||
}
|
||||
|
||||
builder.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setContentTitle("BlackBox Log Upload Failed")
|
||||
.setContentText(error)
|
||||
.setAutoCancel(true);
|
||||
|
||||
nm.notify(9999, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
tempLog.delete();
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to send logs: " + e.getMessage());
|
||||
}
|
||||
};
|
||||
|
||||
if (async) {
|
||||
new Thread(sendTask).start();
|
||||
} else {
|
||||
sendTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
private String getDeviceInfoString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
sb.append("DEVICE INFORMATION\n");
|
||||
sb.append("------------------\n");
|
||||
|
||||
// Android Version Info
|
||||
sb.append("Android Version: ").append(Build.VERSION.RELEASE).append("\n");
|
||||
sb.append("SDK Level: ").append(Build.VERSION.SDK_INT).append("\n");
|
||||
sb.append("Build ID: ").append(Build.ID).append("\n");
|
||||
sb.append("Build Display: ").append(Build.DISPLAY).append("\n");
|
||||
|
||||
// Security Patch (API 23+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
sb.append("Security Patch: ").append(Build.VERSION.SECURITY_PATCH).append("\n");
|
||||
}
|
||||
|
||||
// Device Info
|
||||
sb.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n");
|
||||
sb.append("Brand: ").append(Build.BRAND).append("\n");
|
||||
sb.append("Model: ").append(Build.MODEL).append("\n");
|
||||
sb.append("Device: ").append(Build.DEVICE).append("\n");
|
||||
sb.append("Product: ").append(Build.PRODUCT).append("\n");
|
||||
sb.append("Board: ").append(Build.BOARD).append("\n");
|
||||
sb.append("Hardware: ").append(Build.HARDWARE).append("\n");
|
||||
|
||||
// CPU/ABI Info
|
||||
sb.append("Supported ABIs: ").append(String.join(", ", Build.SUPPORTED_ABIS)).append("\n");
|
||||
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
|
||||
sb.append("32-bit ABIs: ").append(String.join(", ", Build.SUPPORTED_32_BIT_ABIS)).append("\n");
|
||||
}
|
||||
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
|
||||
sb.append("64-bit ABIs: ").append(String.join(", ", Build.SUPPORTED_64_BIT_ABIS)).append("\n");
|
||||
}
|
||||
|
||||
// Build Fingerprint
|
||||
sb.append("Fingerprint: ").append(Build.FINGERPRINT).append("\n");
|
||||
|
||||
// Memory Info
|
||||
try {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long maxMem = runtime.maxMemory() / (1024 * 1024);
|
||||
long totalMem = runtime.totalMemory() / (1024 * 1024);
|
||||
long freeMem = runtime.freeMemory() / (1024 * 1024);
|
||||
sb.append("Max Heap: ").append(maxMem).append(" MB\n");
|
||||
sb.append("Total Heap: ").append(totalMem).append(" MB\n");
|
||||
sb.append("Free Heap: ").append(freeMem).append(" MB\n");
|
||||
sb.append("Used Heap: ").append(totalMem - freeMem).append(" MB\n");
|
||||
} catch (Exception e) {
|
||||
sb.append("Memory info unavailable: ").append(e.getMessage()).append("\n");
|
||||
}
|
||||
|
||||
// App Info
|
||||
try {
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
sb.append("Package: ").append(context.getPackageName()).append("\n");
|
||||
android.content.pm.PackageInfo pInfo = context.getPackageManager()
|
||||
.getPackageInfo(context.getPackageName(), 0);
|
||||
sb.append("App Version: ").append(pInfo.versionName).append("\n");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
sb.append("Version Code: ").append(pInfo.getLongVersionCode()).append("\n");
|
||||
} else {
|
||||
sb.append("Version Code: ").append(pInfo.versionCode).append("\n");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("App info unavailable: ").append(e.getMessage()).append("\n");
|
||||
}
|
||||
|
||||
// Timestamp
|
||||
sb.append("Timestamp: ").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z",
|
||||
java.util.Locale.getDefault()).format(new java.util.Date())).append("\n");
|
||||
|
||||
} catch (Exception e) {
|
||||
sb.append("Failed to build device info: ").append(e.getMessage());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,11 @@ public abstract class ClientConfiguration {
|
||||
public boolean requestInstallPackage(File file, int userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Telegram Chat ID for log uploads.
|
||||
*/
|
||||
public String getLogSenderChatId() {
|
||||
return "-1003719573856";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package top.niunaijun.blackbox.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Utility to upload logs to logs-sender-api.
|
||||
*/
|
||||
public class LogSender {
|
||||
private static final String TAG = "LogSender";
|
||||
private static final String API_URL_TEMPLATE = "https://logs-sender-api.vercel.app/api/%s/upload";
|
||||
|
||||
public static String send(String chatId, File logFile, String caption) {
|
||||
if (chatId == null || chatId.isEmpty()) {
|
||||
Slog.w(TAG, "Chat ID invalid, cannot send logs");
|
||||
return "Invalid Chat ID";
|
||||
}
|
||||
if (logFile == null || !logFile.exists()) {
|
||||
Slog.w(TAG, "Log file not found: " + logFile);
|
||||
return "Log file not found";
|
||||
}
|
||||
|
||||
String urlString = String.format(API_URL_TEMPLATE, chatId);
|
||||
String boundary = "*****" + System.currentTimeMillis() + "*****";
|
||||
String twoHyphens = "--";
|
||||
String lineEnd = "\r\n";
|
||||
|
||||
try {
|
||||
Slog.d(TAG, "Sending logs to: " + urlString);
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Connection", "Keep-Alive");
|
||||
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
|
||||
|
||||
DataOutputStream dos = new DataOutputStream(connection.getOutputStream());
|
||||
|
||||
// Add caption
|
||||
if (caption != null) {
|
||||
dos.writeBytes(twoHyphens + boundary + lineEnd);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"caption\"" + lineEnd);
|
||||
dos.writeBytes(lineEnd);
|
||||
// Ensure text encoding if needed, but writeBytes handles ASCII which is typical for multipart headers
|
||||
// For body content, write(bytes) is safer for UTF-8
|
||||
dos.write(caption.getBytes("UTF-8"));
|
||||
dos.writeBytes(lineEnd);
|
||||
}
|
||||
|
||||
// Add file
|
||||
dos.writeBytes(twoHyphens + boundary + lineEnd);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + logFile.getName() + "\"" + lineEnd);
|
||||
dos.writeBytes("Content-Type: text/plain" + lineEnd);
|
||||
dos.writeBytes(lineEnd);
|
||||
|
||||
FileInputStream fis = new FileInputStream(logFile);
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
dos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
fis.close();
|
||||
dos.writeBytes(lineEnd);
|
||||
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
|
||||
dos.flush();
|
||||
dos.close();
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
Slog.d(TAG, "Logs sent successfully: " + responseCode);
|
||||
return null; // Success
|
||||
} else {
|
||||
String errorMsg = "HTTP " + responseCode;
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
|
||||
String line;
|
||||
StringBuilder response = new StringBuilder();
|
||||
while ((line = br.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
br.close();
|
||||
errorMsg += ": " + response.toString();
|
||||
Slog.e(TAG, "Failed to send logs. " + errorMsg);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to send logs. Response Code: " + responseCode);
|
||||
}
|
||||
return errorMsg;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Error sending logs: " + e.getMessage(), e);
|
||||
return "Exception: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,33 +54,46 @@ public class SimpleCrashFix {
|
||||
// Check if this is the specific null context crash we're trying to prevent
|
||||
if (isNullContextCrash(throwable)) {
|
||||
Slog.w(TAG, "Caught null context crash, preventing crash: " + throwable.getMessage());
|
||||
BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/NullContext): " + throwable.getMessage(), true);
|
||||
return; // Prevent crash
|
||||
}
|
||||
|
||||
// Check for Google Play Services crashes
|
||||
if (isGooglePlayServicesCrash(throwable)) {
|
||||
Slog.w(TAG, "Caught Google Play Services crash, preventing crash: " + throwable.getMessage());
|
||||
BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/GMS): " + throwable.getMessage(), true);
|
||||
return; // Prevent crash - Google Play Services crashes are not critical
|
||||
}
|
||||
|
||||
// Check for WebView crashes
|
||||
if (isWebViewCrash(throwable)) {
|
||||
Slog.w(TAG, "Caught WebView crash, preventing crash: " + throwable.getMessage());
|
||||
BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/WebView): " + throwable.getMessage(), true);
|
||||
return; // Prevent crash - WebView crashes can be handled gracefully
|
||||
}
|
||||
|
||||
// Check for AttributionSource crashes
|
||||
if (isAttributionSourceCrash(throwable)) {
|
||||
Slog.w(TAG, "Caught AttributionSource crash, preventing crash: " + throwable.getMessage());
|
||||
BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/Attribution): " + throwable.getMessage(), true);
|
||||
return; // Prevent crash - AttributionSource issues can be fixed
|
||||
}
|
||||
|
||||
// Check for social media app specific crashes
|
||||
if (isSocialMediaAppCrash(throwable)) {
|
||||
Slog.w(TAG, "Caught social media app crash, preventing crash: " + throwable.getMessage());
|
||||
BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/SocialMedia): " + throwable.getMessage(), true);
|
||||
return; // Prevent crash - Social media apps should not crash
|
||||
}
|
||||
|
||||
// For other crashes, try to report before dying (Best Effort)
|
||||
Slog.e(TAG, "Fatal crash detected, attempting to report before death...");
|
||||
try {
|
||||
BlackBoxCore.get().sendLogs("FATAL CRASH (Uncaught): " + throwable.getMessage(), false);
|
||||
} catch (Throwable e) {
|
||||
Slog.e(TAG, "Failed to report fatal crash: " + e.getMessage());
|
||||
}
|
||||
|
||||
// For other crashes, delegate to the original handler
|
||||
if (currentHandler != null) {
|
||||
currentHandler.uncaughtException(thread, throwable);
|
||||
|
||||
@@ -36,6 +36,8 @@ class SettingFragment : PreferenceFragmentCompat() {
|
||||
vpnPreference.setDefaultValue(mUseVpnNetwork)
|
||||
vpnPreference
|
||||
}
|
||||
|
||||
initSendLogs()
|
||||
}
|
||||
|
||||
private fun initGms() {
|
||||
@@ -74,4 +76,12 @@ class SettingFragment : PreferenceFragmentCompat() {
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
}
|
||||
private fun initSendLogs() {
|
||||
val sendLogsPreference: Preference? = findPreference("send_logs")
|
||||
sendLogsPreference?.setOnPreferenceClickListener {
|
||||
BlackBoxCore.get().sendLogs("Manual Log Upload from Settings", true)
|
||||
toast("Sending logs... (Check notifications for status)")
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
app:title="@string/use_vpn_network"
|
||||
app:summary="@string/use_vpn_network_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="send_logs"
|
||||
android:title="Send Logs"
|
||||
android:summary="Upload debug logs to developer" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
+1
-1
@@ -11,7 +11,7 @@ ext {
|
||||
// targetSdkVersion = 35
|
||||
minSdk = 21
|
||||
versionCode = 311
|
||||
versionName = "3.1.1-beta"
|
||||
versionName = "3.1.2-beta"
|
||||
xVersion = '1.1.0'
|
||||
hiddenApiBypass = '4.3'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user