highlight links, use monospace for feature values

This commit is contained in:
Soufiane Fariss
2024-07-24 11:31:39 +02:00
parent 106c31735e
commit 6da0e5d985
6 changed files with 97 additions and 58 deletions

View File

@@ -9,6 +9,11 @@ body {
a {
text-decoration: none;
color: inherit;
transition: color 0.15s ease-in-out;
}
a:hover {
color: var(--primary-color);
}
/* Disable the toggle button for statement and features */

View File

@@ -11,7 +11,6 @@
</h1>
<p class="text-xs mt-1 mb-0 text-white-alpha-70">SHA256:
<a :href="`https://www.virustotal.com/gui/file/${sha256}`" target="_blank">{{ sha256 }}
<i class="pi pi-external-link ml-1 text-xs"></i>
</a>
</p>
</div>

View File

@@ -58,46 +58,8 @@
placeholder="Filter by Rule or Feature"
/>
</template>
<template #body="slotProps">
<div
:style="{
color:
!slotProps.node.children || slotProps.node.children.length === 0
? 'green'
: 'black',
fontWeight:
slotProps.node.children &&
slotProps.node.children.length > 0 &&
slotProps.node.key.includes('-') &&
!slotProps.node.data.isMatchLocation
? 'bold'
: 'normal'
}"
>
{{ slotProps.node.data.name }}
<span
v-if="slotProps.node.data.description"
style="font-style: none; font-size: 90%; font-weight: normal; color: grey"
>
{{ ' ' + slotProps.node.data.description }}
</span>
<span v-if="slotProps.node.data.matchCount > 1" style="font-style: italic">{{
`(${slotProps.node.data.matchCount} matches)`
}}</span>
<Tag
v-if="slotProps.node.data.lib && slotProps.node.data.matchCount"
class="ml-2"
style="scale: 0.8"
v-tooltip.top="{
value: 'Library rules capture common logic',
showDelay: 100,
hideDelay: 100
}"
value="lib"
severity="info"
></Tag>
</div>
<template #body="{ node }">
<RuleColumn :node="node" />
</template>
</Column>
@@ -130,7 +92,7 @@
<div v-if="slotProps.node.data.attack">
<div v-for="(attack, index) in slotProps.node.data.attack" :key="index">
<a :href="createATTACKHref(attack)" target="_blank">
{{ attack.technique }} ({{ attack.id }})
{{ attack.technique }} <span class="text-500 text-sm font-normal ml-1">({{ attack.id }})</span>
</a>
<div
v-for="(technique, techIndex) in attack.techniques"
@@ -138,7 +100,7 @@
style="font-size: 0.8em; margin-left: 1em"
>
<a :href="createATTACKHref(technique)" target="_blank">
{{ technique.technique }} ({{ technique.id }})
{{ technique.technique }} <span class="text-500 text-xs font-normal ml-1">({{ technique.id }})</span>
</a>
</div>
</div>
@@ -150,7 +112,7 @@
<div v-if="slotProps.node.data.mbc">
<div v-for="(mbc, index) in slotProps.node.data.mbc" :key="index">
<a :href="createMBCHref(mbc)" target="_blank">
{{ formatMBC(mbc) }}
{{ mbc.parts.join('::') }} <span class="text-500 text-sm font-normal opacity-80 ml-1">[{{ mbc.id }}]</span>
</a>
</div>
</div>
@@ -188,7 +150,6 @@
import { ref, onMounted, computed } from 'vue'
import TreeTable from 'primevue/treetable'
import InputText from 'primevue/inputtext'
import Tag from 'primevue/tag'
import Dialog from 'primevue/dialog'
import Column from 'primevue/column'
import Button from 'primevue/button'
@@ -196,6 +157,8 @@ import IconField from 'primevue/iconfield'
import InputIcon from 'primevue/inputicon'
import MultiSelect from 'primevue/multiselect'
import RuleColumn from './columns/RuleColumn.vue';
import { parseRules } from '../utils/rdocParser'
const props = defineProps({

View File

@@ -0,0 +1,39 @@
<template>
<div>
<template v-if="node.data.type === 'rule'">
{{ node.data.name }}
</template>
<template v-else-if="node.data.type === 'match location'">
<span class="font-italic">{{ node.data.name }}</span>
</template>
<template v-else-if="node.data.type === 'statement'">-
<span :class="{ 'text-green-700': node.data.typeValue === 'range', 'font-semibold': node.data.typeValue !== 'range' }">
{{ node.data.name }}
</span>
</template>
<template v-else-if="node.data.type === 'feature'">
<!--<span v-if="node.data.typeValue === 'number' || node.data.typeValue === 'mnemonic' || node.data.typeValue === 'bytes' || node.data.typeValue === 'api' || node.data.typeValue === 'offset' || node.data.typeValue === 'operand offset'">- {{ node.data.typeValue }}: <span class="text-green-700" style="font-family: monospace;">{{ node.data.name }}</span></span>-->
<span>- {{ node.data.typeValue }}: <span class="text-green-700" style="font-family: monospace;">{{ node.data.name }}</span></span>
</template>
<span v-if="node.data.description" class="text-gray-500" style="font-size: 90%;">
= {{ node.data.description }}
</span>
<span v-if="node.data.matchCount > 1" class="font-italic">
({{ node.data.matchCount }} matches)
</span>
<LibraryTag v-if="node.data.lib && node.data.matchCount" />
</div>
</template>
<script setup>
import { defineProps } from 'vue';
import LibraryTag from '../misc/LibraryTag.vue';
defineProps({
node: {
type: Object,
required: true
}
});
</script>

View File

@@ -0,0 +1,13 @@
<template>
<Tag
class="ml-2"
style="scale: 0.8"
value="lib"
severity="info"
v-tooltip.right="'Library rules capture common logic'"
/>
</template>
<script setup>
import Tag from 'primevue/tag';
</script>

View File

@@ -8,6 +8,7 @@ export function parseRules(rules, flavor) {
const ruleNode = {
key: index.toString(),
data: {
type: 'rule',
name: rule.meta.name,
lib: rule.meta.lib,
matchCount: rule.matches.length,
@@ -45,15 +46,15 @@ export function parseRules(rules, flavor) {
const matchNode = {
key: matchKey,
data: {
type: 'match location',
name:
flavor === 'static'
? `${rule.meta.scopes.static} @ ${formatAddress(match[0].value)}`
? `${rule.meta.scopes.static} @ ${formatStaticAddress(match[0].value)}`
: `${formatDynamicAddress(match[0].value)}`,
address:
flavor === 'static'
? `${formatAddress(match[0].value)}`
? `${formatStaticAddress(match[0].value)}`
: formatDynamicAddress(match[0].value),
isMatchLocation: true
},
children: [parseNode(match[1], `${matchKey}-0`, rules, rule.meta.lib)]
}
@@ -101,11 +102,11 @@ export function parseFunctionCapabilities(data, showLibraryRules) {
matchingRules.push({
key: `${functionAddress}-${matchingRules.length}`, // Unique key for each rule
data: {
funcaddr: `${rule.meta.name}`, // Display rule name
lib: rule.meta.lib, // Indicate if it's a library rule
matchcount: null, // Matchcount is not used for individual rules
namespace: rule.meta.namespace, // Rule namespace
source: rule.source // Rule source code
funcaddr: `${rule.meta.name}`,
lib: rule.meta.lib,
matchcount: null,
namespace: rule.meta.namespace,
source: rule.source
}
})
}
@@ -116,7 +117,7 @@ export function parseFunctionCapabilities(data, showLibraryRules) {
result.push({
key: functionAddress, // Use function address as key
data: {
funcaddr: `function: 0x${functionAddress}`, // Display function address
funcaddr: `function: 0x${functionAddress}`,
lib: false, // Functions are not library rules
matchcount: matchingRules.length, // Number of matching rules for this function
namespace: null, // Functions don't have a namespace
@@ -228,6 +229,8 @@ function parseNode(node, key, rules, lib) {
const result = {
key: key,
data: {
type: processedNode.node.type, // statement or feature
typeValue: processedNode.node.statement ? processedNode.node.statement.type : processedNode.node.feature.type, // type value (eg. number, regex, api, or, and, optional ... etc)
success: processedNode.success,
name: getNodeName(processedNode),
lib: lib,
@@ -333,7 +336,8 @@ function getStatementName(statement) {
case 'some':
return `${statement.count} or more`
default:
return `${statement.type}: `
// statement (e.g. "and: ", "or: ", "optional:", ... etc)
return `${statement.type}:`
}
}
@@ -347,11 +351,14 @@ function getFeatureName(feature) {
case 'number':
case 'offset':
// example: "number: 0x1234", "offset: 0x3C"
return `${feature.type}: 0x${feature[feature.type].toString(16).toUpperCase()}`
// return `${feature.type}: 0x${feature[feature.type].toString(16).toUpperCase()}`
return `0x${feature[feature.type].toString(16).toUpperCase()}`
case 'bytes':
return formatBytes(feature.bytes)
case 'operand offset':
return `operand[${feature.index}].offset: 0x${feature.operand_offset.toString(16).toUpperCase()}`
default:
return `${feature.type}: ${feature[feature.type]}`
return `${feature[feature.type]}`
}
}
@@ -391,6 +398,19 @@ function getNodeAddress(node) {
return null
}
/**
* Formats bytes string for display
* @param {Array} value - The bytes string
* @returns {string} - Formatted bytes string
*/
function formatBytes(byteString) {
// Use a regular expression to insert a space after every two characters
const formattedString = byteString.replace(/(.{2})/g, '$1 ').trim();
// convert to uppercase
return formattedString.toUpperCase();
}
/**
* Formats the address for dynamic flavor
* @param {Array} value - The address value array
@@ -404,6 +424,6 @@ function formatDynamicAddress(value) {
.join(' ← ')
}
function formatAddress(address) {
function formatStaticAddress(address) {
return `0x${address.toString(16).toUpperCase()}`
}