-
Notifications
You must be signed in to change notification settings - Fork 2
feat(core): Patch latest sprays exploit #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| #define PLUGIN_VERSION "2.26" | ||||||||||||||||
| #define PLUGIN_VERSION "2.28" | ||||||||||||||||
|
|
||||||||||||||||
| /*======================================================================================= | ||||||||||||||||
| Plugin Info: | ||||||||||||||||
|
|
@@ -32,6 +32,18 @@ | |||||||||||||||
| ======================================================================================== | ||||||||||||||||
| Change Log: | ||||||||||||||||
|
|
||||||||||||||||
| 2.28 (02-Dec-2025) | ||||||||||||||||
| - Added file size validation to detect malformed sprays by comparing header size with actual file size. | ||||||||||||||||
| - Extended g_iVal array to cover offset 62 for better VTF header validation. | ||||||||||||||||
| - Added VTF format constants and helper functions (GetFormatInfo, CalcSize, MaxValC). | ||||||||||||||||
| - Added 5% tolerance threshold for file size comparison to minimize false positives. | ||||||||||||||||
| - Enhanced logging to include size mismatch details (actual size, expected size, difference percentage). | ||||||||||||||||
| - Forward "OnSprayExploit" now uses special code (-2) for size mismatch errors. | ||||||||||||||||
| - Thanks to null138 for reporting and testing. | ||||||||||||||||
|
|
||||||||||||||||
| 2.27 (24-Jun-2025) | ||||||||||||||||
| - Added a check for the latest spray exploit. Thanks to ".Rushaway" for fixing and reporting. | ||||||||||||||||
|
|
||||||||||||||||
| 2.26 (21-May-2025) | ||||||||||||||||
| - Added native "SprayExploitFixer_LogCustom" to log custom messages. Requested by ".Rushaway". | ||||||||||||||||
| - Added RegPluginLibrary "spray_exploit_fixer". | ||||||||||||||||
|
|
@@ -195,7 +207,26 @@ | |||||||||||||||
| #define TIMEOUT_LOG 10.0 | ||||||||||||||||
| #define PATH_BACKUP "backup_sprays" | ||||||||||||||||
|
|
||||||||||||||||
| int g_iVal[] = {86,84,70,0,7,0,0,0,42,0,0,0,42,0,0,0,42,42,42,42,42,42,42,42,42,42,42,0,0,0,0,0,0,0,0,0}; | ||||||||||||||||
| // VTF formats | ||||||||||||||||
| #define FVTF_RGBA8888 0 | ||||||||||||||||
| #define FVTF_ABGR8888 1 | ||||||||||||||||
| #define FVTF_RGB888 2 | ||||||||||||||||
| #define FVTF_BGR888 3 | ||||||||||||||||
| #define FVTF_RGB565 4 | ||||||||||||||||
| #define FVTF_I8 5 | ||||||||||||||||
| #define FVTF_IA88 6 | ||||||||||||||||
| #define FVTF_DXT1 7 | ||||||||||||||||
| #define FVTF_DXT5 9 | ||||||||||||||||
| #define FVTF_BGRA8888 12 | ||||||||||||||||
| #define FVTF_DXT1_ALT 13 | ||||||||||||||||
| #define FVTF_DXT3 14 | ||||||||||||||||
| #define FVTF_DXT5_ALT 15 | ||||||||||||||||
| #define FVTF_BGRX8888 16 | ||||||||||||||||
|
|
||||||||||||||||
| #define SIZE_TOLERANCE 0.05 // 5% tolerance | ||||||||||||||||
|
|
||||||||||||||||
| int g_iVal[] = {86,84,70,0,7,0,0,0,42,0,0,0,42,0,0,0,42,42,42,42,42,42,42,42,42,42,42,0,0,0,0,0,0,0,0,0,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42}; | ||||||||||||||||
| bool g_bIsVTFExploit[MAXPLAYERS+1] = {false, ...}; | ||||||||||||||||
| char g_sFilename[PLATFORM_MAX_PATH]; | ||||||||||||||||
| char g_sMoveFiles[PLATFORM_MAX_PATH]; | ||||||||||||||||
| char g_sDownloads[PLATFORM_MAX_PATH]; | ||||||||||||||||
|
|
@@ -355,6 +386,7 @@ public void OnClientPutInServer(int client) | |||||||||||||||
|
|
||||||||||||||||
| public void OnClientConnected(int client) | ||||||||||||||||
| { | ||||||||||||||||
| g_bIsVTFExploit[client] = false; | ||||||||||||||||
| g_fSprayed[client] = 0.0; | ||||||||||||||||
| g_sPath1[client][0] = 0; | ||||||||||||||||
| g_sPath2[client][0] = 0; | ||||||||||||||||
|
|
@@ -373,6 +405,7 @@ public void OnClientDisconnect(int client) | |||||||||||||||
| g_sAuth[client][0] = 0; | ||||||||||||||||
| g_sAuth[client][6] = 0; | ||||||||||||||||
| g_sAuthUnverified[client][0] = 0; | ||||||||||||||||
| g_bIsVTFExploit[client] = false; | ||||||||||||||||
|
|
||||||||||||||||
| /* | ||||||||||||||||
| static char sPath[PLATFORM_MAX_PATH]; | ||||||||||||||||
|
|
@@ -797,6 +830,7 @@ Action PlayerDecal(const char[] te_name, const int[] Players, int numClients, fl | |||||||||||||||
| g_fSprayed[client] = GetGameTime(); | ||||||||||||||||
| if( g_hCvarLog.IntValue ) LogCustom("Blocked invalid spray: %s from (%N) [%s]", g_sFilename, client, auth); | ||||||||||||||||
| if( g_hCvarMsg.IntValue ) PrintToServer("[Spray Exploit] Blocked invalid spray: %s from (%N) [%s]", g_sFilename, client, auth); | ||||||||||||||||
| g_bIsVTFExploit[client] = true; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if( g_hCvarPunish.IntValue == 1 || g_hCvarPunish.IntValue >= 3) | ||||||||||||||||
|
|
@@ -811,6 +845,7 @@ Action PlayerDecal(const char[] te_name, const int[] Players, int numClients, fl | |||||||||||||||
|
|
||||||||||||||||
| if( g_hCvarLog.IntValue ) LogCustom("Blocked unchecked spray - missing file: %s from (%N) [%s]", g_sFilename, client, auth); | ||||||||||||||||
| if( g_hCvarMsg.IntValue == 1 ) PrintToServer("[Spray Exploit] Blocked unchecked spray - missing file: %s from (%N) [%s]", g_sFilename, client, auth); | ||||||||||||||||
| g_bIsVTFExploit[client] = true; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -832,7 +867,7 @@ void ReqTempEnt(DataPack hPack) | |||||||||||||||
|
|
||||||||||||||||
| int client = hPack.ReadCell(); | ||||||||||||||||
| client = GetClientOfUserId(client); | ||||||||||||||||
| if( client ) | ||||||||||||||||
| if( client && !g_bIsVTFExploit[client] ) | ||||||||||||||||
| { | ||||||||||||||||
| float vPos[3]; | ||||||||||||||||
| vPos[0] = hPack.ReadFloat(); | ||||||||||||||||
|
|
@@ -1003,6 +1038,10 @@ void FileCheck() | |||||||||||||||
| if( hFile ) | ||||||||||||||||
| { | ||||||||||||||||
| hFile.Read(iRead, sizeof(iRead), 1); | ||||||||||||||||
|
|
||||||||||||||||
| // Get actual file size | ||||||||||||||||
| hFile.Seek(0, SEEK_END); | ||||||||||||||||
| int actualSize = hFile.Position; | ||||||||||||||||
| delete hFile; | ||||||||||||||||
|
|
||||||||||||||||
| int i = ValFile(iRead); | ||||||||||||||||
|
|
@@ -1026,6 +1065,8 @@ void FileCheck() | |||||||||||||||
| if( g_hCvarMsg.IntValue ) PrintToServer("[Spray Exploit] Invalid spray: %s: %02d (%02X <> %02X)", g_sFilename, i, iRead[i], g_iVal[i]); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| g_bIsVTFExploit[client] = true; | ||||||||||||||||
|
||||||||||||||||
|
|
||||||||||||||||
| Call_StartForward(g_hExploit); | ||||||||||||||||
| Call_PushCell(client); | ||||||||||||||||
| Call_PushCell(i); | ||||||||||||||||
|
|
@@ -1039,6 +1080,46 @@ void FileCheck() | |||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Check file size | ||||||||||||||||
| int calculatedSize = CalcSize(iRead); | ||||||||||||||||
| if( calculatedSize > 0 ) | ||||||||||||||||
| { | ||||||||||||||||
| float sizeDiff = FloatAbs(float(actualSize - calculatedSize)) / float(calculatedSize); | ||||||||||||||||
| if( sizeDiff > SIZE_TOLERANCE ) | ||||||||||||||||
| { | ||||||||||||||||
| int client = GetClientFromSpray(); | ||||||||||||||||
| if( !client ) client = GetClientFromJingle(); | ||||||||||||||||
| if( client ) | ||||||||||||||||
| { | ||||||||||||||||
| static char auth[64]; | ||||||||||||||||
| if ( g_sAuth[client][6] == 'I' ) | ||||||||||||||||
| FormatEx(auth, sizeof(auth), "Unverified: %s", g_sAuthUnverified[client]); | ||||||||||||||||
| else | ||||||||||||||||
| FormatEx(auth, sizeof(auth), "%s", g_sAuth[client]); | ||||||||||||||||
|
|
||||||||||||||||
| if( g_hCvarLog.IntValue ) LogCustom("Invalid spray (size mismatch): %s from (%N) [%s] - Actual: %d, Expected: %d, Diff: %.1f%%", g_sFilename, client, auth, actualSize, calculatedSize, sizeDiff * 100.0); | ||||||||||||||||
| if( g_hCvarMsg.IntValue ) PrintToServer("[Spray Exploit] Invalid spray (size mismatch): %s from (%N) [%s] - Actual: %d, Expected: %d, Diff: %.1f%%", g_sFilename, client, auth, actualSize, calculatedSize, sizeDiff * 100.0); | ||||||||||||||||
| } else { | ||||||||||||||||
| if( g_hCvarLog.IntValue ) LogCustom("Invalid spray (size mismatch): %s - Actual: %d, Expected: %d, Diff: %.1f%%", g_sFilename, actualSize, calculatedSize, sizeDiff * 100.0); | ||||||||||||||||
| if( g_hCvarMsg.IntValue ) PrintToServer("[Spray Exploit] Invalid spray (size mismatch): %s - Actual: %d, Expected: %d, Diff: %.1f%%", g_sFilename, actualSize, calculatedSize, sizeDiff * 100.0); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| g_bIsVTFExploit[client] = true; | ||||||||||||||||
|
||||||||||||||||
| g_bIsVTFExploit[client] = true; | |
| if (client) | |
| g_bIsVTFExploit[client] = true; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stock keyword is deprecated in modern SourcePawn with #pragma newdecls required. This function should be declared as static int MaxValC(int a, int b) instead to follow current SourcePawn best practices.
| stock int MaxValC(int a, int b) | |
| static int MaxValC(int a, int b) |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VTF format constant definitions have gaps (e.g., format 8, 10-11 are missing between DXT1=7, DXT5=9, and BGRA8888=12). While this may match the VTF specification, GetFormatInfo defaults unknown formats to 32bpp uncompressed (line 1266), which could lead to incorrect size calculations for these formats. Consider explicitly handling or documenting which formats are intentionally unsupported to avoid false positives or security bypasses.
| bpp = 32; | |
| compressed = false; | |
| return true; | |
| // Unknown/unsupported format. Do not accept as valid. | |
| bpp = 0; | |
| compressed = false; | |
| return false; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new function GetFormatInfo lacks documentation explaining its purpose, parameters, and return value. Consider adding a docstring that explains: 1) Maps VTF format codes to compression type and bits-per-pixel, 2) Returns false if format is unrecognized (though current implementation always returns true), 3) Uses output parameters for compressed and bpp.
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential integer overflow in size calculations. When calculating highresSize += bw*bh*bpp (line 1307) or highresSize += mw*mh*bpp/8 (line 1311), large dimensions could cause integer overflow before the result is added to highresSize. Similarly, multiplying by frames (line 1314) could overflow. Consider using 64-bit integers or adding overflow checks to prevent incorrect size validation that could allow malicious files to bypass detection.
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CalcSize function calculates only the image data size (mipmaps + thumbnail) but doesn't account for the VTF file header. VTF files have an 80-byte header (version 7.0+), so the actual file size should be headerSize + highresSize + thumbSize. This will cause false positives where valid spray files are flagged as having size mismatches. Consider returning 80 + highresSize + thumbSize instead.
| return highresSize + thumbSize; | |
| return 80 + highresSize + thumbSize; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new function CalcSize lacks documentation explaining its purpose, parameters, and return value. Consider adding a docstring that explains: 1) It calculates the expected VTF file size based on header data, 2) Returns -1 on invalid format/version, 3) Returns calculated size in bytes for valid files. This is especially important given the complexity of the VTF format calculations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The 5% size tolerance threshold (SIZE_TOLERANCE = 0.05) is defined but lacks explanation for why this specific value was chosen. Consider adding a comment explaining the rationale (e.g., "allows for file format overhead, compression variations, or minor header extensions") to help future maintainers understand if this value needs adjustment based on false positive rates.