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

Add unary operator defined #178

Merged
merged 1 commit into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions include/yaramod/builder/yara_expression_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class YaraExpressionBuilder

YaraExpressionBuilder& contains(const YaraExpressionBuilder& other);
YaraExpressionBuilder& matches(const YaraExpressionBuilder& other);
YaraExpressionBuilder& defined();

YaraExpressionBuilder& access(const std::string& attr);
YaraExpressionBuilder& operator[](const YaraExpressionBuilder& other);
Expand Down
22 changes: 21 additions & 1 deletion include/yaramod/types/expressions.h
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ class UnaryOpExpression : public Expression
public:
virtual std::string getText(const std::string& indent = std::string{}) const override
{
if (_op->getType() == TokenType::NOT)
if (_op->getType() == TokenType::NOT || _op->getType() == TokenType::DEFINED)
return _op->getString() + " " + _expr->getText(indent);
else
return _op->getString() + _expr->getText(indent);
Expand Down Expand Up @@ -439,6 +439,26 @@ class NotExpression : public UnaryOpExpression
}
};

/**
* Class representing defined operation.
*
* For example:
* @code
* defined @str
* @endcode
*/
class DefinedExpression : public UnaryOpExpression
{
public:
template<typename ExpPtr>
DefinedExpression(TokenIt op, ExpPtr &&expr) : UnaryOpExpression(op, std::forward<ExpPtr>(expr)) {}

virtual VisitResult accept(Visitor *v) override
{
return v->visit(this);
}
};

/**
* Class representing unary minus operation.
*
Expand Down
1 change: 1 addition & 0 deletions include/yaramod/types/token_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ enum class TokenType
IMPORT_MODULE,
IMPORT_KEYWORD,
NOT,
DEFINED,
AND,
OR,
ALL,
Expand Down
5 changes: 5 additions & 0 deletions include/yaramod/utils/modifying_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ class ModifyingVisitor : public Visitor
return _handleUnaryOperation(expr);
}

virtual VisitResult visit(DefinedExpression *expr) override
{
return _handleUnaryOperation(expr);
}

virtual VisitResult visit(UnaryMinusExpression* expr) override
{
return _handleUnaryOperation(expr);
Expand Down
6 changes: 6 additions & 0 deletions include/yaramod/utils/observing_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ class ObservingVisitor : public Visitor
return {};
}

virtual VisitResult visit(DefinedExpression *expr) override
{
expr->getOperand()->accept(this);
return {};
}

virtual VisitResult visit(UnaryMinusExpression* expr) override
{
expr->getOperand()->accept(this);
Expand Down
2 changes: 2 additions & 0 deletions include/yaramod/utils/visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class StringCountExpression;
class StringOffsetExpression;
class StringLengthExpression;
class NotExpression;
class DefinedExpression;
class UnaryMinusExpression;
class BitwiseNotExpression;
class AndExpression;
Expand Down Expand Up @@ -84,6 +85,7 @@ class Visitor
virtual VisitResult visit(StringOffsetExpression* expr) = 0;
virtual VisitResult visit(StringLengthExpression* expr) = 0;
virtual VisitResult visit(NotExpression* expr) = 0;
virtual VisitResult visit(DefinedExpression* expr) = 0;
virtual VisitResult visit(UnaryMinusExpression* expr) = 0;
virtual VisitResult visit(BitwiseNotExpression* expr) = 0;
virtual VisitResult visit(AndExpression* expr) = 0;
Expand Down
12 changes: 12 additions & 0 deletions src/builder/yara_expression_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,18 @@ YaraExpressionBuilder& YaraExpressionBuilder::matches(const YaraExpressionBuilde
return *this;
}

/**
* Applies operation defined on two expression.
*
* @return Builder.
*/
YaraExpressionBuilder& YaraExpressionBuilder::defined() {
auto token = _tokenStream->emplace(_tokenStream->begin(), TokenType::DEFINED, "defined");
_expr = std::make_shared<DefinedExpression>(token, std::move(_expr));
setType(Expression::Type::Int);
return *this;
}

/**
* Accesses the attribute of a structure expression.
*
Expand Down
15 changes: 15 additions & 0 deletions src/parser/parser_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ void ParserDriver::defineTokens()
_parser.token("import").symbol("IMPORT_KEYWORD").description("import").action([&](std::string_view str) -> Value { return emplace_back(TokenType::IMPORT_KEYWORD, std::string{str}); });
_parser.token("not").symbol("NOT").description("not").action([&](std::string_view str) -> Value { return emplace_back(TokenType::NOT, std::string{str}); })
.precedence(14, pog::Associativity::Right);
if (_features & Features::AvastOnly) {
_parser.token("defined").symbol("DEFINED").description("defined").action(
[&](std::string_view str) -> Value {
return emplace_back(TokenType::DEFINED, std::string{str});
})
.precedence(15, pog::Associativity::Right);
}
_parser.token("and").symbol("AND").description("and").action([&](std::string_view str) -> Value { return emplace_back(TokenType::AND, std::string{str}); })
.precedence(5, pog::Associativity::Left);
_parser.token("or").symbol("OR").description("or").action([&](std::string_view str) -> Value { return emplace_back(TokenType::OR, std::string{str}); })
Expand Down Expand Up @@ -1227,6 +1234,14 @@ void ParserDriver::defineGrammar()
output->setTokenStream(currentTokenStream());
return output;
})
.production("DEFINED", "expression", [&](auto&& args) -> Value {
TokenIt not_token = args[0].getTokenIt();
auto expr = std::move(args[1].getExpression());
auto output = std::make_shared<DefinedExpression>(not_token, std::move(expr));
output->setType(Expression::Type::Bool);
output->setTokenStream(currentTokenStream());
return output;
})
.production("expression", "AND", "expression", [&](auto&& args) -> Value {
auto left = std::move(args[0].getExpression());
TokenIt and_token = args[1].getTokenIt();
Expand Down
4 changes: 4 additions & 0 deletions src/python/py_visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ void addVisitorClasses(py::module& module)
.def("visit_StringOffsetExpression", py::overload_cast<StringOffsetExpression*>(&Visitor::visit))
.def("visit_StringLengthExpression", py::overload_cast<StringLengthExpression*>(&Visitor::visit))
.def("visit_NotExpression", py::overload_cast<NotExpression*>(&Visitor::visit))
.def("visit_DefinedExpression", py::overload_cast<DefinedExpression*>(&Visitor::visit))
.def("visit_UnaryMinusExpression", py::overload_cast<UnaryMinusExpression*>(&Visitor::visit))
.def("visit_BitwiseNotExpression", py::overload_cast<BitwiseNotExpression*>(&Visitor::visit))
.def("visit_AndExpression", py::overload_cast<AndExpression*>(&Visitor::visit))
Expand Down Expand Up @@ -84,6 +85,7 @@ void addVisitorClasses(py::module& module)
.def("visit_StringOffsetExpression", py::overload_cast<StringOffsetExpression*>(&ObservingVisitor::visit))
.def("visit_StringLengthExpression", py::overload_cast<StringLengthExpression*>(&ObservingVisitor::visit))
.def("visit_NotExpression", py::overload_cast<NotExpression*>(&ObservingVisitor::visit))
.def("visit_DefinedExpression", py::overload_cast<DefinedExpression*>(&ObservingVisitor::visit))
.def("visit_UnaryMinusExpression", py::overload_cast<UnaryMinusExpression*>(&ObservingVisitor::visit))
.def("visit_BitwiseNotExpression", py::overload_cast<BitwiseNotExpression*>(&ObservingVisitor::visit))
.def("visit_AndExpression", py::overload_cast<AndExpression*>(&ObservingVisitor::visit))
Expand Down Expand Up @@ -142,6 +144,7 @@ void addVisitorClasses(py::module& module)
.def("visit_StringOffsetExpression", py::overload_cast<StringOffsetExpression*>(&ModifyingVisitor::visit))
.def("visit_StringLengthExpression", py::overload_cast<StringLengthExpression*>(&ModifyingVisitor::visit))
.def("visit_NotExpression", py::overload_cast<NotExpression*>(&ModifyingVisitor::visit))
.def("visit_DefinedExpression", py::overload_cast<DefinedExpression*>(&ModifyingVisitor::visit))
.def("visit_UnaryMinusExpression", py::overload_cast<UnaryMinusExpression*>(&ModifyingVisitor::visit))
.def("visit_BitwiseNotExpression", py::overload_cast<BitwiseNotExpression*>(&ModifyingVisitor::visit))
.def("visit_AndExpression", py::overload_cast<AndExpression*>(&ModifyingVisitor::visit))
Expand Down Expand Up @@ -192,6 +195,7 @@ void addVisitorClasses(py::module& module)
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, StringOffsetExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, StringLengthExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, NotExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, DefinedExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, UnaryMinusExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, BitwiseNotExpression*, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, AndExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
Expand Down
3 changes: 3 additions & 0 deletions src/python/py_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PyVisitor : public yaramod::Visitor
PURE_VISIT(StringOffsetExpression)
PURE_VISIT(StringLengthExpression)
PURE_VISIT(NotExpression)
PURE_VISIT(DefinedExpression)
PURE_VISIT(UnaryMinusExpression)
PURE_VISIT(BitwiseNotExpression)
PURE_VISIT(AndExpression)
Expand Down Expand Up @@ -107,6 +108,7 @@ class PyObservingVisitor : public yaramod::ObservingVisitor
VISIT(ObservingVisitor, StringOffsetExpression)
VISIT(ObservingVisitor, StringLengthExpression)
VISIT(ObservingVisitor, NotExpression)
VISIT(ObservingVisitor, DefinedExpression)
VISIT(ObservingVisitor, UnaryMinusExpression)
VISIT(ObservingVisitor, BitwiseNotExpression)
VISIT(ObservingVisitor, AndExpression)
Expand Down Expand Up @@ -167,6 +169,7 @@ class PyModifyingVisitor : public yaramod::ModifyingVisitor
VISIT(ModifyingVisitor, StringOffsetExpression)
VISIT(ModifyingVisitor, StringLengthExpression)
VISIT(ModifyingVisitor, NotExpression)
VISIT(ModifyingVisitor, DefinedExpression)
VISIT(ModifyingVisitor, UnaryMinusExpression)
VISIT(ModifyingVisitor, BitwiseNotExpression)
VISIT(ModifyingVisitor, AndExpression)
Expand Down
3 changes: 3 additions & 0 deletions src/python/yaramod_python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ void addEnums(py::module& module)
.value("ImportModule", TokenType::IMPORT_MODULE)
.value("ImportKeyword", TokenType::IMPORT_KEYWORD)
.value("Not", TokenType::NOT)
.value("Defined", TokenType::DEFINED)
.value("And", TokenType::AND)
.value("Or", TokenType::OR)
.value("All", TokenType::ALL)
Expand Down Expand Up @@ -582,6 +583,7 @@ void addExpressionClasses(py::module& module)
&UnaryOpExpression::getOperand,
py::overload_cast<const Expression::Ptr&>(&UnaryOpExpression::setOperand));
unaryOpClass<NotExpression>(module, "NotExpression");
unaryOpClass<DefinedExpression>(module, "DefinedExpression");
unaryOpClass<UnaryMinusExpression>(module, "UnaryMinusExpression");
unaryOpClass<BitwiseNotExpression>(module, "BitwiseNotExpression");

Expand Down Expand Up @@ -816,6 +818,7 @@ void addBuilderClasses(py::module& module)
.def("comment", &YaraExpressionBuilder::comment, py::arg("message"), py::arg("multiline") = false, py::arg("indent") = "")
.def("contains", &YaraExpressionBuilder::contains)
.def("matches", &YaraExpressionBuilder::matches)
.def("defined", &YaraExpressionBuilder::defined)
.def("read_int8", &YaraExpressionBuilder::readInt8)
.def("read_int16", &YaraExpressionBuilder::readInt16)
.def("read_int32", &YaraExpressionBuilder::readInt32)
Expand Down
29 changes: 29 additions & 0 deletions tests/cpp/builder_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1898,5 +1898,34 @@ ConjunctionWithSingleTerm) {
)", yaraFile->getTextFormatted());
}

TEST_F(BuilderTests,
DefinedTerm) {
auto cond = boolVal(false).defined().get();

YaraRuleBuilder newRule;
auto rule = newRule
.withName("defined_rule")
.withCondition(cond)
.get();

YaraFileBuilder newFile;
auto yaraFile = newFile
.withRule(std::move(rule))
.get(true);

ASSERT_NE(nullptr, yaraFile);
EXPECT_EQ(R"(rule defined_rule {
condition:
defined false
})", yaraFile->getText());

EXPECT_EQ(R"(rule defined_rule
{
condition:
defined false
}
)", yaraFile->getTextFormatted());
}

}
}
22 changes: 22 additions & 0 deletions tests/cpp/parser_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7110,5 +7110,27 @@ rule empty_rule
EXPECT_EQ(input_text, driver.getParsedFile().getTextFormatted());
}

TEST_F(ParserTests,
DefinedExpresion) {
prepareInput(
R"(
rule defined_expr
{
condition:
defined 1
}
)");

EXPECT_TRUE(driver.parse(input));
ASSERT_EQ(1u, driver.getParsedFile().getRules().size());
const auto& rule = driver.getParsedFile().getRules()[0];

EXPECT_EQ("defined 1", rule->getCondition()->getText());
EXPECT_EQ("\"defined\"", rule->getCondition()->getFirstTokenIt()->getText());
EXPECT_EQ("1", rule->getCondition()->getLastTokenIt()->getPureText());

EXPECT_EQ(input_text, driver.getParsedFile().getTextFormatted());
}

}
}
17 changes: 17 additions & 0 deletions tests/python/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1319,3 +1319,20 @@ def test_rule_with_custom_modules(self):
condition:
module_test.structure_test.function_test(/abc/) and cuckoo.sync.mutex(/abc/)
}''')

def test_rule_with_defined_condition(self):
cond = yaramod.int_val(200).defined()
rule = self.new_rule \
.with_name('rule_with_defined_condition') \
.with_condition(cond.get()) \
.get()
yara_file = self.new_file \
.with_rule(rule) \
.get(True)

self.assertEqual(yara_file.text_formatted, '''rule rule_with_defined_condition
{
condition:
defined 200
}
''')