mirror of
https://github.com/mandiant/capa.git
synced 2025-12-12 07:40:38 -08:00
Add copyright and license information headers to the source code files inside the `web` directory and the `capa/render/proto/capa.proto` file. I have used addlicense to add the headers.
303 lines
11 KiB
JavaScript
303 lines
11 KiB
JavaScript
/**
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { parseRules, parseFunctionCapabilities } from "../utils/rdocParser";
|
|
|
|
describe("parseRules", () => {
|
|
it("should return an empty array for empty rules", () => {
|
|
const rules = {};
|
|
const flavor = "static";
|
|
const layout = {};
|
|
const result = parseRules(rules, flavor, layout);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("should correctly parse a simple rule with static scope", () => {
|
|
const rules = {
|
|
"test rule": {
|
|
meta: {
|
|
name: "test rule",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: {
|
|
static: "function",
|
|
dynamic: "process"
|
|
}
|
|
},
|
|
source: "test rule source",
|
|
matches: [
|
|
[
|
|
{ type: "absolute", value: 0x1000 },
|
|
{
|
|
success: true,
|
|
node: { type: "feature", feature: { type: "api", api: "TestAPI" } },
|
|
children: [],
|
|
locations: [{ type: "absolute", value: 0x1000 }],
|
|
captures: {}
|
|
}
|
|
]
|
|
]
|
|
}
|
|
};
|
|
const result = parseRules(rules, "static", {});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].key).toBe("0");
|
|
expect(result[0].data.type).toBe("rule");
|
|
expect(result[0].data.name).toBe("test rule");
|
|
expect(result[0].data.lib).toBe(false);
|
|
expect(result[0].data.namespace).toBe("test");
|
|
expect(result[0].data.source).toBe("test rule source");
|
|
expect(result[0].children).toHaveLength(1);
|
|
expect(result[0].children[0].key).toBe("0-0");
|
|
expect(result[0].children[0].data.type).toBe("match location");
|
|
expect(result[0].children[0].children[0].data.type).toBe("feature");
|
|
expect(result[0].children[0].children[0].data.typeValue).toBe("api");
|
|
expect(result[0].children[0].children[0].data.name).toBe("TestAPI");
|
|
});
|
|
|
|
it('should handle rule with "not" statements correctly', () => {
|
|
const rules = {
|
|
"test rule": {
|
|
meta: {
|
|
name: "test rule",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: {
|
|
static: "function",
|
|
dynamic: "process"
|
|
}
|
|
},
|
|
source: "test rule source",
|
|
matches: [
|
|
[
|
|
{ type: "absolute", value: 0x1000 },
|
|
{
|
|
success: true,
|
|
node: { type: "statement", statement: { type: "not" } },
|
|
children: [
|
|
{ success: false, node: { type: "feature", feature: { type: "api", api: "TestAPI" } } }
|
|
]
|
|
}
|
|
]
|
|
]
|
|
}
|
|
};
|
|
const result = parseRules(rules, "static", {});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].children[0].children[0].data.type).toBe("statement");
|
|
expect(result[0].children[0].children[0].data.name).toBe("not:");
|
|
expect(result[0].children[0].children[0].children[0].data.type).toBe("feature");
|
|
expect(result[0].children[0].children[0].children[0].data.typeValue).toBe("api");
|
|
expect(result[0].children[0].children[0].children[0].data.name).toBe("TestAPI");
|
|
});
|
|
});
|
|
|
|
describe("parseFunctionCapabilities", () => {
|
|
it("should return an empty array when no functions match", () => {
|
|
const mockData = {
|
|
meta: {
|
|
analysis: {
|
|
feature_counts: {
|
|
file: 0,
|
|
functions: []
|
|
},
|
|
layout: {
|
|
functions: []
|
|
}
|
|
}
|
|
},
|
|
rules: {}
|
|
};
|
|
const result = parseFunctionCapabilities(mockData, false);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("should parse a single function with one rule match", () => {
|
|
const mockDoc = {
|
|
meta: {
|
|
analysis: {
|
|
layout: {
|
|
functions: [
|
|
{
|
|
address: { type: "absolute", value: 0x1000 },
|
|
matched_basic_blocks: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
}
|
|
]
|
|
},
|
|
feature_counts: {
|
|
functions: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
}
|
|
}
|
|
},
|
|
rules: {
|
|
rule1: {
|
|
meta: {
|
|
name: "Test Rule",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: { static: "function" }
|
|
},
|
|
matches: [[{ type: "absolute", value: 0x1000 }]]
|
|
}
|
|
}
|
|
};
|
|
const result = parseFunctionCapabilities(mockDoc);
|
|
expect(result).toEqual([
|
|
{
|
|
address: "0x1000",
|
|
capabilities: [{ name: "Test Rule", namespace: "test", lib: false }]
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should handle multiple rules matching a single function", () => {
|
|
const mockDoc = {
|
|
meta: {
|
|
analysis: {
|
|
layout: {
|
|
functions: [
|
|
{
|
|
address: { type: "absolute", value: 0x1000 },
|
|
matched_basic_blocks: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
}
|
|
]
|
|
},
|
|
feature_counts: {
|
|
functions: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
}
|
|
}
|
|
},
|
|
rules: {
|
|
rule1: {
|
|
meta: {
|
|
name: "Test Rule 1",
|
|
lib: true,
|
|
scopes: { static: "function" }
|
|
},
|
|
matches: [[{ type: "absolute", value: 0x1000 }]]
|
|
},
|
|
rule2: {
|
|
meta: {
|
|
name: "Test Rule 2",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: { static: "function" }
|
|
},
|
|
matches: [[{ type: "absolute", value: 0x1000 }]]
|
|
}
|
|
}
|
|
};
|
|
const result = parseFunctionCapabilities(mockDoc);
|
|
expect(result).toEqual([
|
|
{
|
|
address: "0x1000",
|
|
capabilities: [
|
|
{ name: "Test Rule 1", lib: true },
|
|
{ name: "Test Rule 2", namespace: "test", lib: false }
|
|
]
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should handle basic block scoped rules", () => {
|
|
const mockDoc = {
|
|
meta: {
|
|
analysis: {
|
|
layout: {
|
|
functions: [
|
|
{
|
|
address: { type: "absolute", value: 0x1000 },
|
|
matched_basic_blocks: [{ address: { type: "absolute", value: 0x1100 } }]
|
|
}
|
|
]
|
|
},
|
|
feature_counts: {
|
|
functions: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
}
|
|
}
|
|
},
|
|
rules: {
|
|
rule1: {
|
|
meta: {
|
|
name: "Basic Block Rule",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: { static: "basic block" }
|
|
},
|
|
matches: [[{ type: "absolute", value: 0x1100 }]]
|
|
}
|
|
}
|
|
};
|
|
const result = parseFunctionCapabilities(mockDoc);
|
|
expect(result).toEqual([
|
|
{
|
|
address: "0x1000",
|
|
capabilities: [{ name: "Basic Block Rule", namespace: "test", lib: false }]
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should handle a single rule matching in multiple functions", () => {
|
|
const mockDoc = {
|
|
meta: {
|
|
analysis: {
|
|
layout: {
|
|
functions: [
|
|
{
|
|
address: { type: "absolute", value: 0x1000 },
|
|
matched_basic_blocks: [{ address: { type: "absolute", value: 0x1000 } }]
|
|
},
|
|
{
|
|
address: { type: "absolute", value: 0x2000 },
|
|
matched_basic_blocks: [{ address: { type: "absolute", value: 0x2000 } }]
|
|
}
|
|
]
|
|
},
|
|
feature_counts: {
|
|
functions: [
|
|
{ address: { type: "absolute", value: 0x1000 } },
|
|
{ address: { type: "absolute", value: 0x2000 } }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
rules: {
|
|
rule1: {
|
|
meta: {
|
|
name: "Test Rule",
|
|
namespace: "test",
|
|
lib: false,
|
|
scopes: { static: "function" }
|
|
},
|
|
matches: [[{ type: "absolute", value: 0x1000 }], [{ type: "absolute", value: 0x2000 }]]
|
|
}
|
|
}
|
|
};
|
|
const result = parseFunctionCapabilities(mockDoc);
|
|
expect(result).toEqual([
|
|
{
|
|
address: "0x1000",
|
|
capabilities: [{ name: "Test Rule", namespace: "test", lib: false }]
|
|
},
|
|
{
|
|
address: "0x2000",
|
|
capabilities: [{ name: "Test Rule", namespace: "test", lib: false }]
|
|
}
|
|
]);
|
|
});
|
|
});
|