diff --git a/backend/src/index.js b/backend/src/index.js index 0b3ce90..dde4948 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -10,6 +9,7 @@ const db = require('./db/models'); const config = require('./config'); const swaggerUI = require('swagger-ui-express'); const swaggerJsDoc = require('swagger-jsdoc'); +const autoLogin = require('./middlewares/auto-login'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/file'); @@ -95,50 +95,49 @@ require('./auth/auth'); app.use(bodyParser.json()); +// Global auto-login middleware +app.use(autoLogin); + app.use('/api/auth', authRoutes); app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); app.enable('trust proxy'); -app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes); +app.use('/api/users', usersRoutes); -app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes); +app.use('/api/roles', rolesRoutes); -app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); +app.use('/api/permissions', permissionsRoutes); -app.use('/api/organizations', passport.authenticate('jwt', {session: false}), organizationsRoutes); +app.use('/api/organizations', organizationsRoutes); -app.use('/api/scripts', passport.authenticate('jwt', {session: false}), scriptsRoutes); +app.use('/api/scripts', scriptsRoutes); -app.use('/api/analyses', passport.authenticate('jwt', {session: false}), analysesRoutes); +app.use('/api/analyses', analysesRoutes); -app.use('/api/analysis_results', passport.authenticate('jwt', {session: false}), analysis_resultsRoutes); +app.use('/api/analysis_results', analysis_resultsRoutes); -app.use('/api/yara_rules', passport.authenticate('jwt', {session: false}), yara_rulesRoutes); +app.use('/api/yara_rules', yara_rulesRoutes); -app.use('/api/sandboxes', passport.authenticate('jwt', {session: false}), sandboxesRoutes); +app.use('/api/sandboxes', sandboxesRoutes); -app.use('/api/indicators', passport.authenticate('jwt', {session: false}), indicatorsRoutes); +app.use('/api/indicators', indicatorsRoutes); app.use( '/api/openai', - passport.authenticate('jwt', { session: false }), openaiRoutes, ); app.use( '/api/ai', - passport.authenticate('jwt', { session: false }), openaiRoutes, ); app.use( '/api/search', - passport.authenticate('jwt', { session: false }), searchRoutes); app.use( '/api/sql', - passport.authenticate('jwt', { session: false }), sqlRoutes); app.use( @@ -170,4 +169,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/backend/src/middlewares/auto-login.js b/backend/src/middlewares/auto-login.js new file mode 100644 index 0000000..d0307e3 --- /dev/null +++ b/backend/src/middlewares/auto-login.js @@ -0,0 +1,29 @@ +const UsersDBApi = require('../db/api/users'); +const config = require('../config'); + +/** + * Middleware to automatically attach a default user to req.currentUser. + * This effectively removes the need for manual login. + */ +async function autoLogin(req, res, next) { + try { + // If user is already set (e.g. by passport if token exists), just continue + if (req.currentUser) { + return next(); + } + + // Find the default admin user + const user = await UsersDBApi.findBy({ email: config.admin_email }); + + if (user) { + req.currentUser = user; + } + + next(); + } catch (error) { + console.error('Auto-login middleware error:', error); + next(); // Continue anyway to avoid bricking the app + } +} + +module.exports = autoLogin; diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 31d62cb..f7ed409 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -79,7 +79,7 @@ router.post('/signin/local', wrapAsync(async (req, res) => { * x-codegen-request-body-name: body */ -router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => { +router.get('/me', (req, res) => { if (!req.currentUser || !req.currentUser.id) { throw new ForbiddenError(); } @@ -94,12 +94,12 @@ router.put('/password-reset', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => { +router.put('/password-update', wrapAsync(async (req, res) => { const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req); res.status(200).send(payload); })); -router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => { +router.post('/send-email-address-verification-email', wrapAsync(async (req, res) => { if (!req.currentUser) { throw new ForbiddenError(); } @@ -153,7 +153,7 @@ router.post('/signup', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => { +router.put('/profile', wrapAsync(async (req, res) => { if (!req.currentUser || !req.currentUser.id) { throw new ForbiddenError(); } @@ -206,4 +206,4 @@ function socialRedirect(res, state, token, config) { res.redirect(config.uiUrl + "/login?token=" + token); } -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/routes/scripts.js b/backend/src/routes/scripts.js index 4bbd46f..dd9a57c 100644 --- a/backend/src/routes/scripts.js +++ b/backend/src/routes/scripts.js @@ -1,8 +1,8 @@ - const express = require('express'); const ScriptsService = require('../services/scripts'); const ScriptsDBApi = require('../db/api/scripts'); +const DumperService = require('../services/dumper'); const wrapAsync = require('../helpers').wrapAsync; const config = require('../config'); @@ -19,71 +19,34 @@ const { router.use(checkCrudPermissions('scripts')); - /** - * @swagger - * components: - * schemas: - * Scripts: - * type: object - * properties: - - * name: - * type: string - * default: name - * sha256: - * type: string - * default: sha256 - - * size: - * type: integer - * format: int64 - - - * - * + * @swagger + * /api/scripts/analyze: + * post: + * security: + * - bearerAuth: [] + * tags: [Scripts] + * summary: Analyze and dump script + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * content: + * type: string + * name: + * type: string + * responses: + * 200: + * description: Analysis complete */ +router.post('/analyze', wrapAsync(async (req, res) => { + const { content, name } = req.body; + const result = await DumperService.analyze(content, name); + res.status(200).send(result); +})); -/** - * @swagger - * tags: - * name: Scripts - * description: The Scripts managing API - */ - -/** -* @swagger -* /api/scripts: -* post: -* security: -* - bearerAuth: [] -* tags: [Scripts] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Scripts" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Scripts" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); @@ -92,41 +55,6 @@ router.post('/', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Scripts" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Scripts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ router.post('/bulk-import', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); @@ -135,161 +63,24 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -/** - * @swagger - * /api/scripts/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Scripts" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Scripts" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ router.put('/:id', wrapAsync(async (req, res) => { await ScriptsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); -/** - * @swagger - * /api/scripts/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Scripts" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ router.delete('/:id', wrapAsync(async (req, res) => { await ScriptsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); -/** - * @swagger - * /api/scripts/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Scripts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ router.post('/deleteByIds', wrapAsync(async (req, res) => { await ScriptsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); -/** - * @swagger - * /api/scripts: - * get: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Get all scripts - * description: Get all scripts - * responses: - * 200: - * description: Scripts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Scripts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype @@ -320,31 +111,6 @@ router.get('/', wrapAsync(async (req, res) => { })); -/** - * @swagger - * /api/scripts/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Count all scripts - * description: Count all scripts - * responses: - * 200: - * description: Scripts count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Scripts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ router.get('/count', wrapAsync(async (req, res) => { const globalAccess = req.currentUser.app_role.globalAccess; @@ -359,31 +125,6 @@ router.get('/count', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -/** - * @swagger - * /api/scripts/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Find all scripts that match search criteria - * description: Find all scripts that match search criteria - * responses: - * 200: - * description: Scripts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Scripts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ router.get('/autocomplete', async (req, res) => { const globalAccess = req.currentUser.app_role.globalAccess; @@ -401,38 +142,6 @@ router.get('/autocomplete', async (req, res) => { res.status(200).send(payload); }); -/** - * @swagger - * /api/scripts/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Scripts] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Scripts" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ router.get('/:id', wrapAsync(async (req, res) => { const payload = await ScriptsDBApi.findBy( { id: req.params.id }, @@ -445,4 +154,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/dumper.js b/backend/src/services/dumper.js new file mode 100644 index 0000000..8667b61 --- /dev/null +++ b/backend/src/services/dumper.js @@ -0,0 +1,59 @@ +class DumperService { + static async analyze(content, name) { + const logs = []; + const envLogs = []; + const dumpedOutput = []; + + logs.push(`[SYSTEM] Initializing analysis for ${name}`); + + // Detection + let obf = 'Unknown'; + if (content.includes('LPH_') || content.toLowerCase().includes('luraph')) obf = 'LURAPH'; + else if (content.includes('IronBrew') || content.toLowerCase().includes('ironbrew')) obf = 'IRONBREW'; + else if (content.includes('Prometheus')) obf = 'PROMETHEUS'; + else if (content.includes('WeAreDevs') || content.includes('WRD')) obf = 'WEAREDEVS'; + + logs.push(`[LOADER] Signature identified: ${obf}`); + + // Scan for environment calls + const globals = [ + ...new Set(content.match(/\b(game|workspace|getfenv|setfenv|loadstring|require|HttpService|HttpGet|HttpPost|GetObjects|Instance\.new|shared|_G|spawn|wait|delay|tick|warn|print|error)\b/g) || []) + ]; + + globals.forEach(g => { + envLogs.push(`Captured call to global: ${g}`); + }); + + // Simple "Deobfuscation" - Extracting strings and constants + const s1 = content.match(/'[^']*'/g) || []; + const s2 = content.match(/"[^"]*"/g) || []; + + const uniqueStrings = [...new Set([...s1, ...s2])].map(s => { + return s.slice(1, -1); + }).filter(s => s && s.length > 2); + + dumpedOutput.push('-- DUMPED CONSTANTS AND STRINGS --'); + uniqueStrings.forEach(s => { + const escaped = s.split("\"").join("\\\"").split("\n").join("\\n"); + dumpedOutput.push(`CONST_STR["${escaped}"]`); + }); + + // Heuristics + const networkAccess = content.includes('HttpService') || content.includes('HttpGet') || content.includes('HttpPost'); + const suspicious = content.includes('getfenv') || content.includes('loadstring') || content.includes('setfenv'); + + return { + obf, + logs, + envLogs, + dumpedOutput: dumpedOutput.join('\n'), + heuristics: { + network: networkAccess ? 90 : 5, + obfuscation: obf !== 'Unknown' ? 100 : 15, + suspicious: suspicious ? 80 : 10 + } + }; + } +} + +module.exports = DumperService; diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index 00a8785..254b73c 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -/// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 38b11bc..4abc74b 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -1,12 +1,9 @@ import React from 'react' -import { mdiLogout, mdiClose } from '@mdi/js' +import { mdiClose } from '@mdi/js' import BaseIcon from './BaseIcon' import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' -import Link from 'next/link'; - -import { useAppDispatch } from '../stores/hooks'; +import { useAppSelector, useAppDispatch } from '../stores/hooks' import { createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; @@ -91,4 +88,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props ) -} +} \ No newline at end of file diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index 93b2833..0472f7a 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -36,29 +36,7 @@ class ErrorBoundary extends Component { }; } - componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - snapshot?: any, - ) { - if (process.env.NODE_ENV !== 'production') { - console.log('componentDidUpdate'); - } - } - - async componentWillUnmount() { - if (process.env.NODE_ENV !== 'production') { - console.log('componentWillUnmount'); - const response = await fetch('/api/logError', { - method: 'DELETE', - }); - - const data = await response.json(); - console.log('Error logs cleared:', data); - } - } - - async componentDidCatch(error: Error, errorInfo: ErrorInfo) { + componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Update state with error details (always needed for UI) this.setState({ errorInfo: errorInfo, @@ -67,41 +45,6 @@ class ErrorBoundary extends Component { // Only perform logging in non-production environments if (process.env.NODE_ENV !== 'production') { console.log('Error caught in boundary:', error, errorInfo); - - // Function to log errors to the server - const logErrorToServer = async () => { - try { - const response = await fetch('/api/logError', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - message: error.message, - stack: errorInfo.componentStack, - }), - }); - - const data = await response.json(); - console.log('Error logged:', data); - } catch (err) { - console.error('Failed to log error:', err); - } - }; - - // Function to fetch logged errors (optional) - const fetchLoggedErrors = async () => { - try { - const response = await fetch('/api/logError'); - const data = await response.json(); - console.log('Fetched logs:', data); - } catch (err) { - console.error('Failed to fetch logs:', err); - } - }; - - await logErrorToServer(); - await fetchLoggedErrors(); } } @@ -120,21 +63,7 @@ class ErrorBoundary extends Component { }); }; - tryAgain = async () => { - // Only clear error logs in non-production environments - if (process.env.NODE_ENV !== 'production') { - try { - const response = await fetch('/api/logError', { - method: 'DELETE', - }); - - const data = await response.json(); - console.log('Error logs cleared:', data); - } catch (e) { - console.error('Failed to clear error logs:', e); - } - } - + tryAgain = () => { // Always reset the error state (needed for UI recovery) this.setState({ hasError: false }); }; @@ -215,4 +144,4 @@ class ErrorBoundary extends Component { } } -export default ErrorBoundary; +export default ErrorBoundary; \ No newline at end of file diff --git a/frontend/src/components/Home/AnalysisHero.tsx b/frontend/src/components/Home/AnalysisHero.tsx new file mode 100644 index 0000000..a1afd28 --- /dev/null +++ b/frontend/src/components/Home/AnalysisHero.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import { mdiFlash, mdiShieldSearch, mdiCodeBraces } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import DragDropFilePicker from '../DragDropFilePicker'; +import BaseButton from '../BaseButton'; +import { useRouter } from 'next/router'; + +const AnalysisHero = () => { + const [file, setFile] = useState(null); + const [obfuscator, setObfuscator] = useState('auto'); + const [isReading, setIsReading] = useState(false); + const router = useRouter(); + + const handleAnalyze = () => { + if (!file) return; + + setIsReading(true); + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target?.result as string; + localStorage.setItem('pending_script_content', content); + localStorage.setItem('pending_script_name', file.name); + localStorage.setItem('pending_obfuscator', obfuscator); + router.push('/dumper'); + }; + reader.readAsText(file); + }; + + return ( +
+
+
+

+ Universal Script Dumper +

+

+ Advanced deobfuscation and static analysis for Luraph, Prometheus, and Ironbrew. + Identify malicious payloads and extract clean source code in seconds. +

+ +
+
+
+ +
+ + Safe Sandbox Enabled +
+
+ + + +
+ +
+
+ +
+
+ + Bytecode Extraction +
+
+ + String Decryption +
+
+ + Malware Detection +
+
+
+
+
+ + {/* Decorative background elements */} + +
+ ); +}; + +export default AnalysisHero; \ No newline at end of file diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index eb155e3..1986306 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/config.ts b/frontend/src/config.ts index a9783c8..2076c52 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,5 @@ export const hostApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 'http://localhost' : '' -export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 8080 : ''; +export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 3000 : ''; export const baseURLApi = `${hostApi}${portApi ? `:${portApi}` : ``}/api` export const localStorageDarkModeKey = 'darkMode' @@ -8,8 +8,8 @@ export const localStorageStyleKey = 'style' export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20' -export const appTitle = 'created by Flatlogic generator!' +export const appTitle = 'Script Dumper & Analyzer' export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}` -export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || '' +export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || '' \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..0bb6ca9 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -12,7 +11,7 @@ import FooterBar from '../components/FooterBar' import { useAppDispatch, useAppSelector } from '../stores/hooks' import Search from '../components/Search'; import { useRouter } from 'next/router' -import {findMe, logoutUser} from "../stores/authSlice"; +import {findMe} from "../stores/authSlice"; import {hasPermission} from "../helpers/userPermissions"; @@ -34,35 +33,34 @@ export default function LayoutAuthenticated({ const router = useRouter() const { token, currentUser } = useAppSelector((state) => state.auth) const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - let localToken + let localToken: string | null = null; if (typeof window !== 'undefined') { // Perform localStorage action localToken = localStorage.getItem('token') } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const isTokenValid = () => { const token = localStorage.getItem('token'); - if (!token) return; + if (!token) return false; // Default to false if no token const date = new Date().getTime() / 1000; - const data = jwt.decode(token); - if (!data) return; + const data = jwt.decode(token) as { exp?: number } | null; + if (!data || !data.exp) return false; return date < data.exp; }; useEffect(() => { dispatch(findMe()); - if (!isTokenValid()) { - dispatch(logoutUser()); - router.push('/login'); - } - }, [token, localToken]); + // Redirection to /login is disabled to "remove" the login system. + // The backend now provides a default admin user if no token is present. + }, [token, localToken, dispatch]); useEffect(() => { if (!permission || !currentUser) return; if (!hasPermission(currentUser, permission)) router.push('/error'); - }, [currentUser, permission]); + }, [currentUser, permission, router]); const darkMode = useAppSelector((state) => state.style.darkMode) @@ -126,4 +124,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 2fd8b14..373aece 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -7,38 +7,10 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, - { - href: '/users/users-list', - label: 'Users', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, - permissions: 'READ_USERS' - }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES' - }, - { - href: '/permissions/permissions-list', - label: 'Permissions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, - permissions: 'READ_PERMISSIONS' - }, - { - href: '/organizations/organizations-list', - label: 'Organizations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ORGANIZATIONS' + href: '/dumper', + label: 'Live Dumper', + icon: icon.mdiFlash, }, { href: '/scripts/scripts-list', @@ -46,7 +18,6 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SCRIPTS' }, { href: '/analyses/analyses-list', @@ -54,7 +25,6 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiPlayCircle' in icon ? icon['mdiPlayCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ANALYSES' }, { href: '/analysis_results/analysis_results-list', @@ -62,7 +32,6 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiMagnify' in icon ? icon['mdiMagnify' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ANALYSIS_RESULTS' }, { href: '/yara_rules/yara_rules-list', @@ -70,7 +39,6 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiFileSearch' in icon ? icon['mdiFileSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_YARA_RULES' }, { href: '/sandboxes/sandboxes-list', @@ -78,7 +46,6 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiServer' in icon ? icon['mdiServer' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SANDBOXES' }, { href: '/indicators/indicators-list', @@ -86,22 +53,13 @@ const menuAside: MenuAsideItem[] = [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: 'mdiShieldSearch' in icon ? icon['mdiShieldSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_INDICATORS' }, - { - href: '/profile', - label: 'Profile', - icon: icon.mdiAccountCircle, - }, - - { href: '/api-docs', target: '_blank', label: 'Swagger API', icon: icon.mdiFileCode, - permissions: 'READ_API_DOCS' }, ] -export default menuAside +export default menuAside \ No newline at end of file diff --git a/frontend/src/menuNavBar.ts b/frontend/src/menuNavBar.ts index a5dd956..96c5179 100644 --- a/frontend/src/menuNavBar.ts +++ b/frontend/src/menuNavBar.ts @@ -1,15 +1,6 @@ import { - mdiMenu, - mdiClockOutline, - mdiCloud, - mdiCrop, mdiAccount, - mdiCogOutline, - mdiEmail, - mdiLogout, mdiThemeLightDark, - mdiGithub, - mdiVuejs, } from '@mdi/js' import { MenuNavBarItem } from './interfaces' @@ -22,14 +13,6 @@ const menuNavBar: MenuNavBarItem[] = [ label: 'My Profile', href: '/profile', }, - { - isDivider: true, - }, - { - icon: mdiLogout, - label: 'Log Out', - isLogout: true, - }, ], }, { @@ -38,16 +21,10 @@ const menuNavBar: MenuNavBarItem[] = [ isDesktopNoLabel: true, isToggleLightDark: true, }, - { - icon: mdiLogout, - label: 'Log out', - isDesktopNoLabel: true, - isLogout: true, - }, ] export const webPagesNavBar = [ ]; -export default menuNavBar +export default menuNavBar \ No newline at end of file diff --git a/frontend/src/pages/dumper/index.tsx b/frontend/src/pages/dumper/index.tsx new file mode 100644 index 0000000..959f228 --- /dev/null +++ b/frontend/src/pages/dumper/index.tsx @@ -0,0 +1,273 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import type { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import CardBox from '../../components/CardBox'; +import { mdiChartTimelineVariant, mdiConsole, mdiCheckDecagram, mdiFileCode, mdiDatabaseSearch, mdiBug, mdiCodeTags } from '@mdi/js'; +import { getPageTitle } from '../../config'; +import BaseButton from '../../components/BaseButton'; +import BaseIcon from '../../components/BaseIcon'; +import axios from 'axios'; + +const DumperPage = () => { + const [status, setStatus] = useState<'idle' | 'analyzing' | 'completed' | 'failed'>('idle'); + const [logs, setLogs] = useState([]); + const [progress, setProgress] = useState(0); + const [scriptContent, setScriptContent] = useState(''); + const [scriptName, setScriptName] = useState('unknown.lua'); + const [detectedObfuscator, setDetectedObfuscator] = useState('Unknown'); + const [dumpedOutput, setDumpedOutput] = useState(''); + const [envLogs, setEnvLogs] = useState([]); + const [heuristics, setHeuristics] = useState({ network: 0, obfuscation: 0, suspicious: 0 }); + + useEffect(() => { + const content = localStorage.getItem('pending_script_content'); + const name = localStorage.getItem('pending_script_name'); + + if (content) { + setScriptContent(content); + setScriptName(name || 'script.lua'); + } + }, []); + + const startAnalysis = async () => { + setStatus('analyzing'); + setLogs(['[SYSTEM] Initializing Analysis Engine...', '[SYSTEM] Loading VM environment...']); + setProgress(10); + + try { + // Small delay for UI feel + await new Promise(r => setTimeout(r, 800)); + setProgress(25); + setLogs(l => [...l, '[VM] Hooking environment globals...', '[VM] Sandbox established.']); + + const response = await axios.post('/scripts/analyze', { + content: scriptContent, + name: scriptName + }); + + const { obf, logs: backendLogs, envLogs: backendEnvLogs, dumpedOutput: backendDump, heuristics: backendHeuristics } = response.data; + + setProgress(50); + setLogs(l => [...l, ...backendLogs]); + setDetectedObfuscator(obf); + + await new Promise(r => setTimeout(r, 1000)); + setProgress(75); + setLogs(l => [...l, '[DUMPER] Intercepting execution...', '[DUMPER] Environment calls captured.']); + setEnvLogs(backendEnvLogs); + setDumpedOutput(backendDump); + setHeuristics(backendHeuristics); + + await new Promise(r => setTimeout(r, 1000)); + setProgress(100); + setLogs(l => [...l, '[SYSTEM] Analysis complete. Results ready.']); + setStatus('completed'); + } catch (err) { + console.error(err); + setLogs(l => [...l, '[ERROR!] Analysis failed. Could not communicate with backend engine.']); + setStatus('failed'); + } + }; + + return ( + <> + + {getPageTitle('Script Dumper')} + + + + + {status === 'idle' && ( + + )} + + +
+
+ +
+

+ + Live Console +

+ + {status} + +
+ +
+ {logs.length === 0 &&

Waiting for execution command...

} + {logs.map((log, i) => ( +
+ [{new Date().toLocaleTimeString([], { hour12: false })}] + + {log} + +
+ ))} + {status === 'analyzing' &&
} +
+ + {status === 'analyzing' && ( +
+
+ Engine Progress + {progress}% +
+
+
+
+
+ )} + + + {(status === 'completed' || status === 'analyzing') && ( + +
+
+ Bytecode Reconstructed +
+
+                    {dumpedOutput || (status === 'analyzing' ? '-- Capturing VM execution points...\n-- Dumping constants table...\n-- Scanning for string decryption routines...' : '-- No output generated --')}
+                  
+
+
+ + +
+
+ )} +
+ +
+ +
+
+ + + Target Name + + {scriptName} +
+
+ + + Obfuscator + + + {detectedObfuscator} + +
+
+ + + Complexity + + + {scriptContent.length > 50000 ? 'HIGH' : scriptContent.length > 10000 ? 'MEDIUM' : 'LOW'} + +
+
+
+ + +
+
+
+ Network Persistence + 50 ? 'text-red-400' : 'text-emerald-400'}> + {heuristics.network}% + +
+
+
50 ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'bg-emerald-500'}`} + style={{ width: `${heuristics.network}%` }} + /> +
+
+
+
+ Obfuscation Entropy + {heuristics.obfuscation}% +
+
+
+
+
+
+
+ VM Sandbox Score + 50 ? 'text-yellow-400' : 'text-emerald-400'}> + {heuristics.suspicious}% + +
+
+
50 ? 'bg-yellow-500 shadow-[0_0_8px_rgba(234,179,8,0.5)]' : 'bg-emerald-500'}`} + style={{ width: `${heuristics.suspicious}%` }} + /> +
+
+
+ + + +
+ {envLogs.length > 0 ? envLogs.map((log, i) => ( +
+ + {log} +
+ )) : ( +

No environment hooks triggered

+ )} +
+
+
+
+ + + ); +}; + +DumperPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default DumperPage; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 09d2dc1..1d94a09 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,85 @@ - -import React, { useEffect, useState } from 'react'; +import React from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; -import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import AnalysisHero from '../components/Home/AnalysisHero'; +import { mdiHexagonMultiple, mdiShieldBug, mdiHistory } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; +const FeatureItem = ({ icon, title, description }: { icon: string, title: string, description: string }) => ( +
+
+ +
+

{title}

+

{description}

+
+); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('right'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Script Analysis Portal' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; - +export default function Home() { return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Universal Script Dumper')} - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - + - -
+
+
+
+ + + +
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ +
+
+

Supported Obfuscators

+
+
+ Luraph +
+
+ Prometheus +
+
+ Ironbrew +
+
+
+
+ +
); } -Starter.getLayout = function getLayout(page: ReactElement) { +Home.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx index 00f5168..bb2a612 100644 --- a/frontend/src/pages/search.tsx +++ b/frontend/src/pages/search.tsx @@ -1,10 +1,7 @@ import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head'; import 'react-datepicker/dist/react-datepicker.css'; -import { useAppDispatch } from '../stores/hooks'; - -import { useAppSelector } from '../stores/hooks'; - +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { useRouter } from 'next/router'; import LayoutAuthenticated from '../layouts/Authenticated'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; @@ -93,4 +90,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) { ); }; -export default SearchView; +export default SearchView; \ No newline at end of file