Skip to content

Commit 769df56

Browse files
authored
Merge pull request #182 from avast/iequals
Added iequals operator
2 parents 8719af2 + 144df89 commit 769df56

15 files changed

+141
-4
lines changed

docs/rtd/creating_rulesets.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ basic expressions and find the most suitable one.
253253
* ``call(args)`` - represents call to function (``id("func").call({int_val(100), int_val(200)})``)
254254
* ``contains(rhs)`` - represents operator ``contains`` (``id("signature").contains(string_val("hello"))``)
255255
* ``matches(rhs)`` - represents operator ``matches`` (``id("signature").matches(regexp("^a.*b$", "i"))``)
256+
* ``iequals(rhs)`` - represents operator ``iequals`` (``id("signature").iequals(string_val("hello"))``)
256257
* ``access(rhs)`` - represents operator ``.`` as access to structure (``id("pe").access("numer_of_sections")``)
257258
* ``__getitem__`` - represents operator ``[]`` as access to array (``id("pe").access("sections")[int_val(0)]``)
258259
* ``read_int8(be)`` - represents call to special function ``int8(be)`` (``int_val(100).read_int8()``)
@@ -364,6 +365,7 @@ basic expressions and find the most suitable one.
364365
* ``call(args)`` - represents call to function (``id("func").call({intVal(100), intVal(200)})``)
365366
* ``contains(rhs)`` - represents operator ``contains`` (``id("signature").contains(stringVal("hello"))``)
366367
* ``matches(rhs)`` - represents operator ``matches`` (``id("signature").matches(regexp("^a.*b$", "i"))``)
368+
* ``iequals(rhs)`` - represents operator ``iequals`` (``id("signature").iequals(stringVal("hello"))``)
367369
* ``access(rhs)`` - represents operator ``.`` as access to structure (``id("pe").access("numer_of_sections")``)
368370
* ``operator[]`` - represents operator ``[]`` as access to array (``id("pe").access("sections")[intVal(0)]``)
369371
* ``readInt8(be)`` - represents call to special function ``int8(be)`` (``intVal(100).readInt8()``)

docs/rtd/parsing_rulesets.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ All of these provide methods ``getLeftOperand()`` and ``getRightOperand()`` (``l
336336
* ``NeqExpression`` - refers to ``!=`` operator (``!str1 != !str2``)
337337
* ``ContainsExpression`` - refers to ``contains`` operator (``pe.sections[0].name contains "text"``)
338338
* ``MatchesExpression`` - refers to ``matches`` operator (``pe.sections[0].name matches /(text|data)/``)
339+
* ``IequalsExpression`` - refers to ``iequals`` operator (``pe.sections[0].name iequals "text"``)
339340
* ``PlusExpression`` - refers to ``+`` operator (``@str1 + 0x100``)
340341
* ``MinusExpression`` - refers to ``-`` operator (``@str1 - 0x100``)
341342
* ``MultiplyExpression`` - refers to ``*`` operator (``@str1 * 0x100``)

include/yaramod/builder/yara_expression_builder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ class YaraExpressionBuilder
185185

186186
YaraExpressionBuilder& contains(const YaraExpressionBuilder& other);
187187
YaraExpressionBuilder& matches(const YaraExpressionBuilder& other);
188+
YaraExpressionBuilder& iequals(const YaraExpressionBuilder& other);
188189
YaraExpressionBuilder& defined();
189190

190191
YaraExpressionBuilder& access(const std::string& attr);

include/yaramod/types/expressions.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ class ContainsExpression : public BinaryOpExpression
733733
*
734734
* For example:
735735
* @code
736-
* pe.sections[0] matches /(text|data)/
736+
* pe.sections[0].name matches /(text|data)/
737737
* @endcode
738738
*/
739739
class MatchesExpression : public BinaryOpExpression
@@ -748,6 +748,26 @@ class MatchesExpression : public BinaryOpExpression
748748
}
749749
};
750750

751+
/**
752+
* Class representing iequals operation for case-insensitive string compare.
753+
*
754+
* For example:
755+
* @code
756+
* pe.sections[0].name iequals ".TEXT"
757+
* @endcode
758+
*/
759+
class IequalsExpression : public BinaryOpExpression
760+
{
761+
public:
762+
template <typename ExpPtr1, typename ExpPtr2>
763+
IequalsExpression(ExpPtr1&& left, TokenIt op, ExpPtr2&& right) : BinaryOpExpression(std::forward<ExpPtr1>(left), op, std::forward<ExpPtr2>(right)) {}
764+
765+
virtual VisitResult accept(Visitor* v) override
766+
{
767+
return v->visit(this);
768+
}
769+
};
770+
751771
/**
752772
* Class representing arithmetic plus operation.
753773
*

include/yaramod/types/token_type.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ enum class TokenType
9494
FILESIZE,
9595
CONTAINS,
9696
MATCHES,
97+
IEQUALS,
9798
SLASH,
9899
STRING_LITERAL,
99100
INTEGER,

include/yaramod/utils/modifying_visitor.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ class ModifyingVisitor : public Visitor
215215
return _handleBinaryOperation(expr);
216216
}
217217

218+
virtual VisitResult visit(IequalsExpression* expr) override
219+
{
220+
return _handleBinaryOperation(expr);
221+
}
222+
218223
virtual VisitResult visit(PlusExpression* expr) override
219224
{
220225
return _handleBinaryOperation(expr);

include/yaramod/utils/observing_visitor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ class ObservingVisitor : public Visitor
160160
return {};
161161
}
162162

163+
virtual VisitResult visit(IequalsExpression* expr) override
164+
{
165+
expr->getLeftOperand()->accept(this);
166+
expr->getRightOperand()->accept(this);
167+
return {};
168+
}
169+
163170
virtual VisitResult visit(PlusExpression* expr) override
164171
{
165172
expr->getLeftOperand()->accept(this);

include/yaramod/utils/visitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class EqExpression;
3333
class NeqExpression;
3434
class ContainsExpression;
3535
class MatchesExpression;
36+
class IequalsExpression;
3637
class PlusExpression;
3738
class MinusExpression;
3839
class MultiplyExpression;
@@ -98,6 +99,7 @@ class Visitor
9899
virtual VisitResult visit(NeqExpression* expr) = 0;
99100
virtual VisitResult visit(ContainsExpression* expr) = 0;
100101
virtual VisitResult visit(MatchesExpression* expr) = 0;
102+
virtual VisitResult visit(IequalsExpression* expr) = 0;
101103
virtual VisitResult visit(PlusExpression* expr) = 0;
102104
virtual VisitResult visit(MinusExpression* expr) = 0;
103105
virtual VisitResult visit(MultiplyExpression* expr) = 0;

src/builder/yara_expression_builder.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,23 @@ YaraExpressionBuilder& YaraExpressionBuilder::matches(const YaraExpressionBuilde
529529
return *this;
530530
}
531531

532+
/**
533+
* Applies operation iequals on two expression.
534+
*
535+
* @param other The other expression.
536+
*
537+
* @return Builder.
538+
*/
539+
YaraExpressionBuilder& YaraExpressionBuilder::iequals(const YaraExpressionBuilder& other)
540+
{
541+
TokenIt token = _tokenStream->emplace_back(TokenType::IEQUALS, "iequals");
542+
_tokenStream->moveAppend(other.getTokenStream());
543+
544+
_expr = std::make_shared<IequalsExpression>(std::move(_expr), token, other.get());
545+
setType(Expression::Type::Bool);
546+
return *this;
547+
}
548+
532549
/**
533550
* Applies operation defined on two expression.
534551
*

src/parser/parser_driver.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ void ParserDriver::defineTokens()
156156
_parser.token("filesize").symbol("FILESIZE").description("filesize").action([&](std::string_view str) -> Value { return emplace_back(TokenType::FILESIZE, std::string{str}); });
157157
_parser.token("contains").symbol("CONTAINS").description("contains").action([&](std::string_view str) -> Value { return emplace_back(TokenType::CONTAINS, std::string{str}); });
158158
_parser.token("matches").symbol("MATCHES").description("matches").action([&](std::string_view str) -> Value { return emplace_back(TokenType::MATCHES, std::string{str}); });
159+
_parser.token("iequals").symbol("IEQUALS").description("iequals").action([&](std::string_view str) -> Value { return emplace_back(TokenType::IEQUALS, std::string{str}); });
159160

160161
// $include
161162
_parser.token("include").symbol("INCLUDE_DIRECTIVE").description("include").enter_state("$include").action([&](std::string_view str) -> Value {
@@ -1339,6 +1340,19 @@ void ParserDriver::defineGrammar()
13391340
output->setTokenStream(currentTokenStream());
13401341
return output;
13411342
})
1343+
.production("primary_expression", "IEQUALS", "primary_expression", [&](auto&& args) -> Value {
1344+
auto left = std::move(args[0].getExpression());
1345+
TokenIt op_token = args[1].getTokenIt();
1346+
auto right = std::move(args[2].getExpression());
1347+
if (!left->isString())
1348+
error_handle(op_token->getLocation(), "operator 'iequals' expects string on the left-hand side of the expression");
1349+
if (!right->isString())
1350+
error_handle(op_token->getLocation(), "operator 'iequals' expects string on the right-hand side of the expression");
1351+
auto output = std::make_shared<IequalsExpression>(std::move(left), op_token, std::move(right));
1352+
output->setType(Expression::Type::Bool);
1353+
output->setTokenStream(currentTokenStream());
1354+
return output;
1355+
})
13421356
.production("primary_expression", [](auto&& args) -> Value {
13431357
return std::move(args[0]);
13441358
}).precedence(0, pog::Associativity::Left)

src/python/py_visitor.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ void addVisitorClasses(py::module& module)
4040
.def("visit_NeqExpression", py::overload_cast<NeqExpression*>(&Visitor::visit))
4141
.def("visit_ContainsExpression", py::overload_cast<ContainsExpression*>(&Visitor::visit))
4242
.def("visit_MatchesExpression", py::overload_cast<MatchesExpression*>(&Visitor::visit))
43+
.def("visit_IequalsExpression", py::overload_cast<IequalsExpression*>(&Visitor::visit))
4344
.def("visit_PlusExpression", py::overload_cast<PlusExpression*>(&Visitor::visit))
4445
.def("visit_MinusExpression", py::overload_cast<MinusExpression*>(&Visitor::visit))
4546
.def("visit_MultiplyExpression", py::overload_cast<MultiplyExpression*>(&Visitor::visit))
@@ -98,6 +99,7 @@ void addVisitorClasses(py::module& module)
9899
.def("visit_NeqExpression", py::overload_cast<NeqExpression*>(&ObservingVisitor::visit))
99100
.def("visit_ContainsExpression", py::overload_cast<ContainsExpression*>(&ObservingVisitor::visit))
100101
.def("visit_MatchesExpression", py::overload_cast<MatchesExpression*>(&ObservingVisitor::visit))
102+
.def("visit_IequalsExpression", py::overload_cast<IequalsExpression*>(&ObservingVisitor::visit))
101103
.def("visit_PlusExpression", py::overload_cast<PlusExpression*>(&ObservingVisitor::visit))
102104
.def("visit_MinusExpression", py::overload_cast<MinusExpression*>(&ObservingVisitor::visit))
103105
.def("visit_MultiplyExpression", py::overload_cast<MultiplyExpression*>(&ObservingVisitor::visit))
@@ -157,6 +159,7 @@ void addVisitorClasses(py::module& module)
157159
.def("visit_NeqExpression", py::overload_cast<NeqExpression*>(&ModifyingVisitor::visit))
158160
.def("visit_ContainsExpression", py::overload_cast<ContainsExpression*>(&ModifyingVisitor::visit))
159161
.def("visit_MatchesExpression", py::overload_cast<MatchesExpression*>(&ModifyingVisitor::visit))
162+
.def("visit_IequalsExpression", py::overload_cast<IequalsExpression*>(&ModifyingVisitor::visit))
160163
.def("visit_PlusExpression", py::overload_cast<PlusExpression*>(&ModifyingVisitor::visit))
161164
.def("visit_MinusExpression", py::overload_cast<MinusExpression*>(&ModifyingVisitor::visit))
162165
.def("visit_MultiplyExpression", py::overload_cast<MultiplyExpression*>(&ModifyingVisitor::visit))
@@ -208,6 +211,7 @@ void addVisitorClasses(py::module& module)
208211
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, NeqExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
209212
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, ContainsExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
210213
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, MatchesExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
214+
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, IequalsExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
211215
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, PlusExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
212216
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, MinusExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))
213217
.def("default_handler", static_cast<VisitResult(ModifyingVisitor::*)(const TokenStreamContext&, MultiplyExpression*, const VisitResult&, const VisitResult&)>(&ModifyingVisitor::defaultHandler))

src/python/py_visitor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class PyVisitor : public yaramod::Visitor
4949
PURE_VISIT(NeqExpression)
5050
PURE_VISIT(ContainsExpression)
5151
PURE_VISIT(MatchesExpression)
52+
PURE_VISIT(IequalsExpression)
5253
PURE_VISIT(PlusExpression)
5354
PURE_VISIT(MinusExpression)
5455
PURE_VISIT(MultiplyExpression)
@@ -121,6 +122,7 @@ class PyObservingVisitor : public yaramod::ObservingVisitor
121122
VISIT(ObservingVisitor, NeqExpression)
122123
VISIT(ObservingVisitor, ContainsExpression)
123124
VISIT(ObservingVisitor, MatchesExpression)
125+
VISIT(ObservingVisitor, IequalsExpression)
124126
VISIT(ObservingVisitor, PlusExpression)
125127
VISIT(ObservingVisitor, MinusExpression)
126128
VISIT(ObservingVisitor, MultiplyExpression)
@@ -182,6 +184,7 @@ class PyModifyingVisitor : public yaramod::ModifyingVisitor
182184
VISIT(ModifyingVisitor, NeqExpression)
183185
VISIT(ModifyingVisitor, ContainsExpression)
184186
VISIT(ModifyingVisitor, MatchesExpression)
187+
VISIT(ModifyingVisitor, IequalsExpression)
185188
VISIT(ModifyingVisitor, PlusExpression)
186189
VISIT(ModifyingVisitor, MinusExpression)
187190
VISIT(ModifyingVisitor, MultiplyExpression)

src/python/yaramod_python.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ void addEnums(py::module& module)
198198
.value("Filesize", TokenType::FILESIZE)
199199
.value("Contains", TokenType::CONTAINS)
200200
.value("Matches", TokenType::MATCHES)
201+
.value("Iequals", TokenType::IEQUALS)
201202
.value("Slash", TokenType::SLASH)
202203
.value("StringLiteral", TokenType::STRING_LITERAL)
203204
.value("Integer", TokenType::INTEGER)
@@ -608,6 +609,7 @@ void addExpressionClasses(py::module& module)
608609
binaryOpClass<NeqExpression>(module, "NeqExpression");
609610
binaryOpClass<ContainsExpression>(module, "ContainsExpression");
610611
binaryOpClass<MatchesExpression>(module, "MatchesExpression");
612+
binaryOpClass<IequalsExpression>(module, "IequalsExpression");
611613
binaryOpClass<PlusExpression>(module, "PlusExpression");
612614
binaryOpClass<MinusExpression>(module, "MinusExpression");
613615
binaryOpClass<MultiplyExpression>(module, "MultiplyExpression");
@@ -822,6 +824,7 @@ void addBuilderClasses(py::module& module)
822824
.def("comment", &YaraExpressionBuilder::comment, py::arg("message"), py::arg("multiline") = false, py::arg("indent") = "")
823825
.def("contains", &YaraExpressionBuilder::contains)
824826
.def("matches", &YaraExpressionBuilder::matches)
827+
.def("iequals", &YaraExpressionBuilder::iequals)
825828
.def("defined", &YaraExpressionBuilder::defined)
826829
.def("read_int8", &YaraExpressionBuilder::readInt8)
827830
.def("read_int16", &YaraExpressionBuilder::readInt16)

tests/cpp/builder_tests.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1900,7 +1900,7 @@ ConjunctionWithSingleTerm) {
19001900

19011901
TEST_F(BuilderTests,
19021902
DefinedTerm) {
1903-
auto cond = boolVal(false).defined().get();
1903+
auto cond = boolVal(false).defined().get();
19041904

19051905
YaraRuleBuilder newRule;
19061906
auto rule = newRule
@@ -1957,5 +1957,39 @@ FloatValueWorks) {
19571957
)", yaraFile->getTextFormatted());
19581958
}
19591959

1960+
TEST_F(BuilderTests,
1961+
IequalsWorks) {
1962+
auto cond = id("pe").access("pdb_path").iequals(stringVal("C:\\PATH")).get();
1963+
1964+
YaraRuleBuilder newRule;
1965+
auto rule = newRule
1966+
.withName("iequals_builder")
1967+
.withCondition(cond)
1968+
.get();
1969+
1970+
YaraFileBuilder newFile;
1971+
auto yaraFile = newFile
1972+
.withModule("pe")
1973+
.withRule(std::move(rule))
1974+
.get(true);
1975+
1976+
ASSERT_NE(nullptr, yaraFile);
1977+
EXPECT_EQ(R"(import "pe"
1978+
1979+
rule iequals_builder {
1980+
condition:
1981+
pe.pdb_path iequals "C:\\PATH"
1982+
})", yaraFile->getText());
1983+
1984+
EXPECT_EQ(R"(import "pe"
1985+
1986+
rule iequals_builder
1987+
{
1988+
condition:
1989+
pe.pdb_path iequals "C:\\PATH"
1990+
}
1991+
)", yaraFile->getTextFormatted());
1992+
}
1993+
19601994
}
19611995
}

tests/cpp/parser_tests.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7151,7 +7151,7 @@ rule empty_rule
71517151
}
71527152

71537153
TEST_F(ParserTests,
7154-
DefinedExpresion) {
7154+
DefinedExpression) {
71557155
prepareInput(
71567156
R"(
71577157
rule defined_expr
@@ -7166,11 +7166,34 @@ rule defined_expr
71667166
const auto& rule = driver.getParsedFile().getRules()[0];
71677167

71687168
EXPECT_EQ("defined 1", rule->getCondition()->getText());
7169-
EXPECT_EQ("\"defined\"", rule->getCondition()->getFirstTokenIt()->getText());
7169+
EXPECT_EQ("defined", rule->getCondition()->getFirstTokenIt()->getPureText());
71707170
EXPECT_EQ("1", rule->getCondition()->getLastTokenIt()->getPureText());
71717171

71727172
EXPECT_EQ(input_text, driver.getParsedFile().getTextFormatted());
71737173
}
71747174

7175+
TEST_F(ParserTests,
7176+
IequalsExpression) {
7177+
prepareInput(
7178+
R"(import "pe"
7179+
7180+
rule iequals_expr
7181+
{
7182+
condition:
7183+
pe.sections[0].name iequals ".TEXT"
7184+
}
7185+
)");
7186+
7187+
EXPECT_TRUE(driver.parse(input));
7188+
ASSERT_EQ(1u, driver.getParsedFile().getRules().size());
7189+
const auto& rule = driver.getParsedFile().getRules()[0];
7190+
7191+
EXPECT_EQ("pe.sections[0].name iequals \".TEXT\"", rule->getCondition()->getText());
7192+
EXPECT_EQ("pe", rule->getCondition()->getFirstTokenIt()->getPureText());
7193+
EXPECT_EQ("\".TEXT\"", rule->getCondition()->getLastTokenIt()->getText());
7194+
7195+
EXPECT_EQ(input_text, driver.getParsedFile().getTextFormatted());
7196+
}
7197+
71757198
}
71767199
}

0 commit comments

Comments
 (0)