Skip to content

Commit 4379581

Browse files
authored
Add console_callback to match call. (VirusTotal#194)
* Add console_callback to match call. This provides the user with an interface to handle console.log() messages. If not provided the log message is printed to stdout (limited to 1000 bytes based upon https://docs.python.org/3/c-api/sys.html?highlight=stdout#c.PySys_WriteStdout). Tested with the following (along with the updated tests): wxs@wxs-mbp yara-python % cat test.py import yara r = """ import "console" rule a { condition: console.log("Hello from Python!") } """ def console(message): print(f"Callback: {message}") rules = yara.compile(source=r) rules.match("/bin/ls", console_callback=console) rules.match("/bin/ls") wxs@wxs-mbp yara-python % PYTHONPATH=build/lib.macosx-10.14-arm64-3.8 python3 test.py Callback: Hello from Python! Hello from Python! wxs@wxs-mbp yara-python % * Add comment.
1 parent 08372e1 commit 4379581

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

tests.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,19 @@ def callback(requested_filename, filename, namespace):
940940
r = yara.compile(source='include "foo" rule r { condition: included }', include_callback=callback)
941941
self.assertTrue(r.match(data='dummy'))
942942

943+
def testConsoleCallback(self):
944+
global called
945+
called = False
946+
947+
def callback(message):
948+
global called
949+
called = True
950+
return yara.CALLBACK_CONTINUE
951+
952+
r = yara.compile(source='import "console" rule r { condition: console.log("AXSERS") }')
953+
r.match(data='dummy', console_callback=callback)
954+
self.assertTrue(called)
955+
943956
def testCompare(self):
944957

945958
r = yara.compile(sources={

yara-python.c

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ typedef struct _CALLBACK_DATA
418418
PyObject* modules_data;
419419
PyObject* modules_callback;
420420
PyObject* warnings_callback;
421+
PyObject* console_callback;
421422
int which;
422423

423424
} CALLBACK_DATA;
@@ -691,6 +692,58 @@ static int handle_module_imported(
691692
}
692693

693694

695+
static int handle_console_log(
696+
void* message_data,
697+
CALLBACK_DATA* data)
698+
{
699+
PyGILState_STATE gil_state = PyGILState_Ensure();
700+
int result = CALLBACK_CONTINUE;
701+
702+
if (data->console_callback == NULL)
703+
{
704+
// If the user does not specify a console callback we dump to stdout.
705+
// If we want to support 3.2 and newer only we can use
706+
// https://docs.python.org/3/c-api/sys.html?highlight=stdout#c.PySys_FormatStdout
707+
// instead of this call with the limit.
708+
PySys_WriteStdout("%.1000s\n", (char*) message_data);
709+
}
710+
else
711+
{
712+
PyObject* log_string = PY_STRING((char*) message_data);
713+
Py_INCREF(data->console_callback);
714+
715+
PyObject* callback_result = PyObject_CallFunctionObjArgs(
716+
data->console_callback,
717+
log_string,
718+
NULL);
719+
720+
if (callback_result != NULL)
721+
{
722+
#if PY_MAJOR_VERSION >= 3
723+
if (PyLong_Check(callback_result))
724+
#else
725+
if (PyLong_Check(callback_result) || PyInt_Check(callback_result))
726+
#endif
727+
{
728+
result = (int) PyLong_AsLong(callback_result);
729+
}
730+
}
731+
else
732+
{
733+
result = CALLBACK_ERROR;
734+
}
735+
736+
Py_DECREF(log_string);
737+
Py_XDECREF(callback_result);
738+
Py_DECREF(data->console_callback);
739+
}
740+
741+
PyGILState_Release(gil_state);
742+
743+
return result;
744+
}
745+
746+
694747
static int handle_too_many_matches(
695748
YR_SCAN_CONTEXT* context,
696749
YR_STRING* string,
@@ -870,6 +923,10 @@ int yara_callback(
870923
if (callback == NULL ||
871924
(which & CALLBACK_NON_MATCHES) != CALLBACK_NON_MATCHES)
872925
return CALLBACK_CONTINUE;
926+
break;
927+
928+
case CALLBACK_MSG_CONSOLE_LOG:
929+
return handle_console_log(message_data, user_data);
873930
}
874931

875932
// At this point we have handled all the other cases of when this callback
@@ -1551,7 +1608,8 @@ static PyObject* Rules_match(
15511608
static char* kwlist[] = {
15521609
"filepath", "pid", "data", "externals",
15531610
"callback", "fast", "timeout", "modules_data",
1554-
"modules_callback", "which_callbacks", "warnings_callback", NULL
1611+
"modules_callback", "which_callbacks", "warnings_callback",
1612+
"console_callback", NULL
15551613
};
15561614

15571615
char* filepath = NULL;
@@ -1574,12 +1632,13 @@ static PyObject* Rules_match(
15741632
callback_data.modules_data = NULL;
15751633
callback_data.modules_callback = NULL;
15761634
callback_data.warnings_callback = NULL;
1635+
callback_data.console_callback = NULL;
15771636
callback_data.which = CALLBACK_ALL;
15781637

15791638
if (PyArg_ParseTupleAndKeywords(
15801639
args,
15811640
keywords,
1582-
"|sis*OOOiOOiO",
1641+
"|sis*OOOiOOiOO",
15831642
kwlist,
15841643
&filepath,
15851644
&pid,
@@ -1591,7 +1650,8 @@ static PyObject* Rules_match(
15911650
&callback_data.modules_data,
15921651
&callback_data.modules_callback,
15931652
&callback_data.which,
1594-
&callback_data.warnings_callback))
1653+
&callback_data.warnings_callback,
1654+
&callback_data.console_callback))
15951655
{
15961656
if (filepath == NULL && data.buf == NULL && pid == -1)
15971657
{
@@ -1633,6 +1693,17 @@ static PyObject* Rules_match(
16331693
}
16341694
}
16351695

1696+
if (callback_data.console_callback != NULL)
1697+
{
1698+
if (!PyCallable_Check(callback_data.console_callback))
1699+
{
1700+
PyBuffer_Release(&data);
1701+
return PyErr_Format(
1702+
PyExc_TypeError,
1703+
"'console_callback' must be callable");
1704+
}
1705+
}
1706+
16361707
if (callback_data.modules_data != NULL)
16371708
{
16381709
if (!PyDict_Check(callback_data.modules_data))

0 commit comments

Comments
 (0)