diff --git a/page/package-lock.json b/page/package-lock.json index 9066a71a..509f7fd6 100644 --- a/page/package-lock.json +++ b/page/package-lock.json @@ -15,6 +15,7 @@ "@radix-ui/react-scroll-area": "^1.2.5", "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.8", "@radix-ui/react-tooltip": "^1.2.3", "@tailwindcss/vite": "^4.1.4", "@types/react-window": "^1.8.8", @@ -25,6 +26,7 @@ "react": "^19.0.0", "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", + "react-router-dom": "^7.5.1", "react-window": "^1.8.11", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4", @@ -1275,6 +1277,32 @@ } } }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.4.tgz", + "integrity": "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1604,6 +1632,37 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz", + "integrity": "sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-scroll-area": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.5.tgz", @@ -1676,6 +1735,36 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.8.tgz", + "integrity": "sha512-4iUaN9SYtG+/E+hJ7jRks/Nv90f+uAsRHbLYA6BcA9EsR6GNWgsvtS4iwU2SP0tOZfDGAyqIT0yz7ckgohEIFA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.3", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-roving-focus": "1.1.7", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.3.tgz", @@ -3020,6 +3109,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -4458,6 +4556,45 @@ } } }, + "node_modules/react-router": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz", + "integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz", + "integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -4624,6 +4761,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -4796,6 +4939,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/tw-animate-css": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.8.tgz", diff --git a/page/package.json b/page/package.json index a5e23636..c9c07fc1 100644 --- a/page/package.json +++ b/page/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-scroll-area": "^1.2.5", "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.8", "@radix-ui/react-tooltip": "^1.2.3", "@tailwindcss/vite": "^4.1.4", "@types/react-window": "^1.8.8", @@ -27,6 +28,7 @@ "react": "^19.0.0", "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", + "react-router-dom": "^7.5.1", "react-window": "^1.8.11", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4", diff --git a/page/src/App.css b/page/src/App.css index 74c34ef5..494f7978 100644 --- a/page/src/App.css +++ b/page/src/App.css @@ -21,10 +21,6 @@ } } -html { - overflow: hidden; -} - .terminal-output { line-height: 1.5; font-weight: 600; diff --git a/page/src/App.tsx b/page/src/App.tsx index 30d00283..55b818ce 100644 --- a/page/src/App.tsx +++ b/page/src/App.tsx @@ -1,126 +1,21 @@ -import { useState, useRef } from 'react' -import { Output } from '@/components/output' +import { ThemeProvider } from "@/components/theme-provider"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { Playground } from "./Playground"; +import { LandingPage } from "./LandingPage"; -import { AppSidebar } from "@/components/app-sidebar" -import { ThemeProvider } from "@/components/theme-provider" -import { Separator } from "@/components/ui/separator" -import { - SidebarInset, - SidebarProvider, - SidebarTrigger, -} from "@/components/ui/sidebar" -import { Button } from './components/ui/button' - -import { Emulator, UserFile } from './emulator'; -import { getFilesystem } from './filesystem'; - -import './App.css' -import { Popover, PopoverContent, PopoverTrigger } from './components/ui/popover' - -import { createDefaultSettings } from './settings'; -import { SettingsMenu } from './components/settings-menu'; - -import { PlayFill, StopFill, GearFill } from 'react-bootstrap-icons'; -import { StatusIndicator } from './components/status-indicator' - -function selectAndReadFile(): Promise { - return new Promise((resolve, reject) => { - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.accept = '.exe'; - - fileInput.addEventListener('change', function (event) { - const file = (event as any).target.files[0]; - if (file) { - const reader = new FileReader(); - - reader.onload = function (e: ProgressEvent) { - const arrayBuffer = e.target?.result; - resolve({ - name: file.name, - data: arrayBuffer as ArrayBuffer - }); - }; - - reader.onerror = function (e: ProgressEvent) { - reject(new Error('Error reading file: ' + e.target?.error)); - }; - - reader.readAsArrayBuffer(file); - } else { - reject(new Error('No file selected')); - } - }); - - fileInput.click(); - }); -} +import "./App.css"; function App() { - const output = useRef(null); - const [settings, setSettings] = useState(createDefaultSettings()); - const [emulator, setEmulator] = useState(null); - - function logLine(line: string) { - output.current?.logLine(line); - } - - function logLines(lines: string[]) { - output.current?.logLines(lines); - } - - async function createEmulator(userFile: UserFile | null = null) { - emulator?.stop(); - output.current?.clear(); - - logLine("Starting emulation..."); - - const fs = await getFilesystem((current, total, file) => { - logLine(`Processing filesystem (${current}/${total}): ${file}`); - }); - - const new_emulator = new Emulator(fs, logLines); - new_emulator.onTerminate().then(() => setEmulator(null)); - setEmulator(new_emulator); - - new_emulator.start(settings, userFile); - } - - async function loadAndRunUserFile() { - const fileBuffer = await selectAndReadFile(); - await createEmulator(fileBuffer); - } - return ( - - - -
- - - - - - - - - - - - - - -
- -
-
-
- -
-
-
-
) + + + } /> + } /> + + + + ); } -export default App +export default App; diff --git a/page/src/LandingPage.tsx b/page/src/LandingPage.tsx new file mode 100644 index 00000000..96f6dcb0 --- /dev/null +++ b/page/src/LandingPage.tsx @@ -0,0 +1,189 @@ +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Shield, + FileCode, + Layers, + Cpu, + Database, + Terminal, + ExternalLink, + Github, + Play +} from "lucide-react"; + +export function LandingPage() { + const features = [ + { + icon: , + title: "Syscall-Level Emulation", + description: + "Operates at syscall level, leveraging existing system DLLs instead of reimplementing Windows APIs", + }, + { + icon: , + title: "Advanced Memory Management", + description: + "Supports Windows-specific memory types including reserved, committed, built on top of Unicorn's memory management", + }, + { + icon: , + title: "Complete PE Loading", + description: + "Handles executable and DLL loading with proper memory mapping, relocations, and TLS", + }, + { + icon: , + title: "Exception Handling", + description: + "Implements Windows structured exception handling (SEH) with proper exception dispatcher and unwinding support", + }, + { + icon: , + title: "Threading Support", + description: "Provides a scheduled (round-robin) threading model", + }, + { + icon: , + title: "Debugging Interface", + description: + "Implements GDB serial protocol for integration with common debugging tools", + }, + ]; + + return ( +
+ {/* Hero Section */} +
+
+
+
+

+ Sogen +

+

+ High-performance Windows user space emulator operating at + syscall level +

+ +
+ {/*
+
+
+ Sogen Emulator +
+
*/} +
+
+
+ + {/* Key Features */} +
+
+
+

+ Key Features +

+
+ +
+ {features.map((feature, index) => ( + + +
{feature.icon}
+ {feature.title} +
+ +

+ {feature.description} +

+
+
+ ))} +
+
+
+ + {/* Video Section */} +
+
+
+

+ See Sogen in Action +

+

+ Watch an overview of the emulator's capabilities and see how it + can help with your research. +

+
+ +
+
+ +
+
+
+
+
+ + {/* Footer */} +
+
+
+
+

Sogen

+

Windows User Space Emulator

+
+ +
+
+
+
+ ); +} diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx new file mode 100644 index 00000000..8051d860 --- /dev/null +++ b/page/src/Playground.tsx @@ -0,0 +1,123 @@ +import { useState, useRef } from 'react' +import { Output } from '@/components/output' + +import { AppSidebar } from "@/components/app-sidebar" +import { Separator } from "@/components/ui/separator" +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar" +import { Button } from './components/ui/button' + +import { Emulator, UserFile } from './emulator'; +import { getFilesystem } from './filesystem'; + +import './App.css' +import { Popover, PopoverContent, PopoverTrigger } from './components/ui/popover' + +import { createDefaultSettings } from './settings'; +import { SettingsMenu } from './components/settings-menu'; + +import { PlayFill, StopFill, GearFill } from 'react-bootstrap-icons'; +import { StatusIndicator } from './components/status-indicator' + +function selectAndReadFile(): Promise { + return new Promise((resolve, reject) => { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.exe'; + + fileInput.addEventListener('change', function (event) { + const file = (event as any).target.files[0]; + if (file) { + const reader = new FileReader(); + + reader.onload = function (e: ProgressEvent) { + const arrayBuffer = e.target?.result; + resolve({ + name: file.name, + data: arrayBuffer as ArrayBuffer + }); + }; + + reader.onerror = function (e: ProgressEvent) { + reject(new Error('Error reading file: ' + e.target?.error)); + }; + + reader.readAsArrayBuffer(file); + } else { + reject(new Error('No file selected')); + } + }); + + fileInput.click(); + }); +} + +export function Playground() { + const output = useRef(null); + const [settings, setSettings] = useState(createDefaultSettings()); + const [emulator, setEmulator] = useState(null); + + function logLine(line: string) { + output.current?.logLine(line); + } + + function logLines(lines: string[]) { + output.current?.logLines(lines); + } + + async function createEmulator(userFile: UserFile | null = null) { + emulator?.stop(); + output.current?.clear(); + + logLine("Starting emulation..."); + + const fs = await getFilesystem((current, total, file) => { + logLine(`Processing filesystem (${current}/${total}): ${file}`); + }); + + const new_emulator = new Emulator(fs, logLines); + new_emulator.onTerminate().then(() => setEmulator(null)); + setEmulator(new_emulator); + + new_emulator.start(settings, userFile); + } + + async function loadAndRunUserFile() { + const fileBuffer = await selectAndReadFile(); + await createEmulator(fileBuffer); + } + + return ( + + + +
+ + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+
+ ) +} + diff --git a/page/src/components/ui/tabs.tsx b/page/src/components/ui/tabs.tsx new file mode 100644 index 00000000..3d6f3acf --- /dev/null +++ b/page/src/components/ui/tabs.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent }