Skip to content
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

web: don't include preview data and options in release mode #2301

Merged
merged 20 commits into from
Aug 21, 2024
Merged
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion web/explorer/src/components/NavBar.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup>
import Menubar from "primevue/menubar";
import { RouterLink } from "vue-router";

const isBundle = import.meta.env.MODE === "bundle";
</script>

<template>
@@ -13,8 +15,9 @@ import { RouterLink } from "vue-router";
<template #end>
<div class="flex align-items-center gap-3">
<a
v-if="!isBundle"
v-ripple
v-tooltip.right="'Download capa Explorer Web for offline usage'"
v-tooltip.bottom="'Download capa Explorer Web for offline usage'"
fariss marked this conversation as resolved.
Show resolved Hide resolved
href="./capa-explorer-web.zip"
download="capa-explorer-web.zip"
aria-label="Download capa Explorer Web release"
8 changes: 4 additions & 4 deletions web/explorer/src/components/RuleMatchesTable.vue
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
:value="filteredTreeData"
v-model:expandedKeys="expandedKeys"
size="small"
scrollable
:scrollable="true"
:filters="filters"
:filterMode="filterMode"
sortField="namespace"
@@ -49,10 +49,11 @@
</template>
</Column>

<!-- Address/Process column -->
<!-- Address column (only shown in static flavors) -->
fariss marked this conversation as resolved.
Show resolved Hide resolved
<Column
v-if="props.data.meta.flavor === 'static'"
field="address"
:header="props.data.meta.flavor === 'dynamic' ? 'Process' : 'Address'"
header="Address"
filterMatchMode="contains"
style="width: 8.5%"
class="cursor-default"
@@ -252,7 +253,6 @@ const onRightClick = (event, instance) => {
selectedNode.value = instance.node;

// show the context menu
console.log(menu);
menu.value.show(event);
}
};
56 changes: 35 additions & 21 deletions web/explorer/src/components/UploadOptions.vue
Original file line number Diff line number Diff line change
@@ -29,28 +29,34 @@
</FloatLabel>
<Button icon="pi pi-arrow-right" @click="$emit('load-from-url', loadURL)" :disabled="!loadURL" />
</div>
<template v-if="!isBundle">
<Divider layout="vertical" class="hidden-mobile">
<b>OR</b>
</Divider>
<Divider layout="horizontal" class="visible-mobile" align="center">
<b>OR</b>
</Divider>

<Divider layout="vertical" class="hidden-mobile">
<b>OR</b>
</Divider>
<Divider layout="horizontal" class="visible-mobile" align="center">
<b>OR</b>
</Divider>

<div class="flex-grow-1 flex align-items-center justify-content-center">
<Button label="Preview Static" @click="$emit('load-demo-static')" class="p-button" />
</div>

<Divider layout="vertical" class="hidden-mobile">
<b>OR</b>
</Divider>
<Divider layout="horizontal" class="visible-mobile" align="center">
<b>OR</b>
</Divider>
<div class="flex-grow-1 flex align-items-center justify-content-center">
<Button
label="Preview Static"
@click="router.push({ path: '/', query: { rdoc: staticURL } })"
/>
</div>

<div class="flex-grow-1 flex align-items-center justify-content-center">
<Button label="Preview Dynamic" @click="$emit('load-demo-dynamic')" class="p-button" />
</div>
<Divider layout="vertical" class="hidden-mobile">
<b>OR</b>
</Divider>
<Divider layout="horizontal" class="visible-mobile" align="center">
<b>OR</b>
</Divider>
<div class="flex-grow-1 flex align-items-center justify-content-center">
<Button
label="Preview Dynamic"
@click="router.push({ path: '/', query: { rdoc: dynamicURL } })"
/>
</div>
</template>
</div>
</template>
</Card>
@@ -65,9 +71,17 @@ import FloatLabel from "primevue/floatlabel";
import InputText from "primevue/inputtext";
import Button from "primevue/button";

import { useRouter } from "vue-router";
const router = useRouter();

const loadURL = ref("");
const isBundle = import.meta.env.MODE === "bundle";

defineEmits(["load-from-local", "load-from-url"]);

defineEmits(["load-from-local", "load-from-url", "load-demo-static", "load-demo-dynamic"]);
const dynamicURL =
"https://raw.githubusercontent.com/mandiant/capa-testfiles/master/rd/0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json.gz";
const staticURL = "https://raw.githubusercontent.com/mandiant/capa-testfiles/master/rd/al-khaser_x64.exe_.json";
</script>

<style scoped>
4 changes: 2 additions & 2 deletions web/explorer/src/components/columns/RuleColumn.vue
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@

<!-- example node: "basic block @ 0x401000" or "explorer.exe" -->
<template v-else-if="node.data.type === 'match location'">
<span class="text-sm font-italic">{{ node.data.name }}</span>
<span class="text-sm font-monospace text-xs">{{ node.data.name }}</span>
</template>

<!-- example node: "- or", "- and" -->
@@ -52,7 +52,7 @@

<!-- example node: "exit(0) -> 0" (if the node type is call-info, we highlight node.data.name.callInfo) -->
<template v-else-if="node.data.type === 'call-info'">
<highlightjs lang="c" :code="node.data.name.callInfo" />
<highlightjs lang="c" :code="node.data.name.callInfo" class="text-xs" />
</template>

<!-- example node: " = IMAGE_NT_SIGNATURE (PE)" -->
98 changes: 52 additions & 46 deletions web/explorer/src/composables/useRdocLoader.js
Original file line number Diff line number Diff line change
@@ -6,83 +6,89 @@ export function useRdocLoader() {
const MIN_SUPPORTED_VERSION = "7.0.0";

/**
* Checks if the loaded rdoc version is supported
* @param {Object} rdoc - The loaded JSON rdoc data
* @returns {boolean} - True if version is supported, false otherwise
* Displays a toast notification.
* @param {string} severity - The severity level of the notification
* @param {string} summary - The title of the notification.
* @param {string} detail - The detailed message of the notification.
* @returns {void}
*/
const showToast = (severity, summary, detail) => {
toast.add({ severity, summary, detail, life: 3000, group: "bc" }); // bc: bottom-center
};

/**
* Checks if the version of the loaded data is supported.
* @param {Object} rdoc - The loaded JSON data containing version information.
* @returns {boolean} True if the version is supported, false otherwise.
*/
const checkVersion = (rdoc) => {
const version = rdoc.meta.version;
if (version < MIN_SUPPORTED_VERSION) {
console.error(
showToast(
"error",
"Unsupported Version",
`Version ${version} is not supported. Please use version ${MIN_SUPPORTED_VERSION} or higher.`
);
toast.add({
severity: "error",
summary: "Unsupported Version",
detail: `Version ${version} is not supported. Please use version ${MIN_SUPPORTED_VERSION} or higher.`,
life: 5000,
group: "bc" // bottom-center
});
return false;
}
return true;
};

/**
* Loads JSON rdoc data from various sources
* @param {File|string|Object} source - File object, URL string, or JSON object
* @returns {Promise<void>}
* Processes the content of a file or blob.
* @param {File|Blob} blob - The file or blob to process.
* @returns {Promise<Object>} A promise that resolves to the parsed JSON data.
* @throws {Error} If the content cannot be parsed as JSON.
*/
const processBlob = async (blob) => {
const content = (await isGzipped(blob)) ? await decompressGzip(blob) : await readFileAsText(blob);
return JSON.parse(content);
};

/**
* Fetches data from a URL.
* @param {string} url - The URL to fetch data from.
* @returns {Promise<Blob>} A promise that resolves to the fetched data as a Blob.
* @throws {Error} If the fetch request fails.
*/
const fetchFromUrl = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
};

/**
* Loads and processes RDOC data from various sources.
* @param {string|File|Object} source - The source of the RDOC data. Can be a URL string, a File object, or a JSON object.
* @returns {Promise<Object|null>} A promise that resolves to the processed RDOC data, or null if processing fails.
*/
const loadRdoc = async (source) => {
try {
let data;

if (typeof source === "string") {
// Load from URL
const response = await fetch(source);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data = await response.json();
const blob = await fetchFromUrl(source);
data = await processBlob(blob);
} else if (source instanceof File) {
let fileContent;
if (await isGzipped(source)) {
fileContent = await decompressGzip(source);
} else {
fileContent = await readFileAsText(source);
}
data = JSON.parse(fileContent);
} else if (typeof source === "object") {
// Direct JSON object (Preview options)
data = source;
// Load from local
data = await processBlob(source);
} else {
throw new Error("Invalid source type");
}

if (checkVersion(data)) {
toast.add({
severity: "success",
summary: "Success",
detail: "JSON data loaded successfully",
life: 3000,
group: "bc" // bottom-center
});
showToast("success", "Success", "JSON data loaded successfully");
return data;
}
} catch (error) {
console.error("Error loading JSON:", error);
toast.add({
severity: "error",
summary: "Error",
detail: "Failed to process the file. Please ensure it's a valid JSON or gzipped JSON file.",
life: 3000,
group: "bc" // bottom-center
});
showToast("error", "Failed to process the file", error.message);
}
return null;
};

return {
loadRdoc
};
return { loadRdoc };
}
485 changes: 210 additions & 275 deletions web/explorer/src/utils/rdocParser.js

Large diffs are not rendered by default.

27 changes: 1 addition & 26 deletions web/explorer/src/views/ImportView.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<template>
<DescriptionPanel />
<UploadOptions
@load-from-local="loadFromLocal"
@load-from-url="loadFromURL"
@load-demo-static="loadDemoDataStatic"
@load-demo-dynamic="loadDemoDataDynamic"
/>
<UploadOptions @load-from-local="loadFromLocal" @load-from-url="loadFromURL" />
</template>

<script setup>
@@ -15,10 +10,6 @@ import { watch } from "vue";
import DescriptionPanel from "@/components/DescriptionPanel.vue";
import UploadOptions from "@/components/UploadOptions.vue";
// import demo data
import demoRdocStatic from "@testfiles/rd/al-khaser_x64.exe_.json";
import demoRdocDynamic from "@testfiles/rd/0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json";
// import router utils
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
@@ -47,22 +38,6 @@ const loadFromURL = async (url) => {
}
};
const loadDemoDataStatic = async () => {
const result = await loadRdoc(demoRdocStatic);
if (result) {
rdocStore.setData(demoRdocStatic);
router.push("/analysis");
}
};
const loadDemoDataDynamic = async () => {
const result = await loadRdoc(demoRdocDynamic);
if (result) {
rdocStore.setData(demoRdocDynamic);
router.push("/analysis");
}
};
// Watch for changes in the rdoc query parameter
watch(
() => route.query.rdoc,
3 changes: 2 additions & 1 deletion web/explorer/vite.config.js
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ export default defineConfig(({ mode }) => {
"@": fileURLToPath(new URL("src", import.meta.url)),
"@testfiles": fileURLToPath(new URL("../../tests/data", import.meta.url))
}
}
},
assetsInclude: ["**/*.gz"]
};
});