Skip to content

Commit d0156fd

Browse files
authored
Syncing recent changes. (#1088)
* Syncing recent changes. Notable changes: * Parsers support fully removed from the artifacts collector. * Interrogate is no longer artifact-collector based. * RDFProtoStruct -> plain protos migration in progress. * Text type no longer used (replaced with 'str').
1 parent 02d09c4 commit d0156fd

File tree

387 files changed

+17146
-14259
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

387 files changed

+17146
-14259
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
https://github.com/google/grr/pkgs/container/grr
2828
* Docker compose configuration file to run all GRR/Fleetspeak components in
2929
separate Docker containers.
30+
* Python API was extended by a function (`DecodeCrowdStrikeQuarantineEncoding`)
31+
to decode a crowdstrike quarantine encoded file, given as a
32+
`BinaryChunkIterator`.
3033

3134

3235
### API removed
@@ -45,6 +48,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4548
OSReleaseBreakdown7ReportPlugin, OSReleaseBreakdown14ReportPlugin,
4649
OSReleaseBreakdown30ReportPlugin, SystemFlowsReportPlugin,
4750
UserFlowsReportPlugin, MostActiveUsersReportPlugin, UserActivityReportPlugin).
51+
* GetFileDecoders API method
52+
(`/api/clients/<client_id>/vfs-decoders/<path:file_path>`). Getting file
53+
decoders functionality was removed as it was not used before.
54+
* GetDecodedFileBlob API method (`/api/clients/<client_id>/vfs-decoded-blob/`).
55+
Get decoded file blob functionality was removed as it was unused before. Only
56+
one decoder for decoding crowdstrike quarantine encoded files was implemented,
57+
this functionality is now exposed via the Python API.
4858

4959
### Planned for removal
5060

api_client/python/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ grr_api_shell --basic_auth_username "user" --basic_auth_password "pwd" \
194194
http://localhost:1234
195195
```
196196

197+
Decode a Crowdstrike encoded file that was fetched via GetBlob:
198+
199+
```bash
200+
grr_api_shell --basic_auth_username "user" --basic_auth_password "pwd" \
201+
--exec_code 'grrapi.Client("C.1234567890ABCDEF").File("/fs/os/var/encrypted/file").GetBlob().DecodeCrowdStrikeQuarantineEncoding().WriteToFile("./decoded.file")' \
202+
http://localhost:1234
203+
```
204+
197205

198206
Download an archive of all files collected with OS-handler (not TSK/NTFS) from a
199207
GRR client:

api_client/python/grr_api_client/api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Any
55
from typing import Dict
66
from typing import Optional
7-
from typing import Text
87
from typing import Tuple
98

109
from google.protobuf import message
@@ -95,7 +94,7 @@ def GrrBinary(
9594
def GrrUser(self) -> user.GrrUser:
9695
return user.GrrUser(context=self._context)
9796

98-
def UploadYaraSignature(self, signature: Text) -> bytes:
97+
def UploadYaraSignature(self, signature: str) -> bytes:
9998
"""Uploads the specified YARA signature.
10099
101100
Args:

api_client/python/grr_api_client/hunt.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,7 @@ def CreateHunt(
449449

450450
request = hunt_pb2.ApiCreateHuntArgs(flow_name=flow_name)
451451
if flow_args:
452-
request.flow_args.value = flow_args.SerializeToString()
453-
request.flow_args.type_url = utils.GetTypeUrl(flow_args)
452+
request.flow_args.Pack(flow_args)
454453

455454
if hunt_runner_args:
456455
request.hunt_runner_args.CopyFrom(hunt_runner_args)

api_client/python/grr_api_client/utils.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ def MapItemsIterator(
8484
)
8585

8686

87-
class BinaryChunkIterator(object):
87+
class BinaryChunkIterator:
8888
"""Iterator object for binary streams."""
8989

90+
chunks: Iterator[bytes]
91+
9092
def __init__(self, chunks: Iterator[bytes]) -> None:
9193
super().__init__()
9294

93-
self.chunks = chunks # type: Iterator[bytes]
95+
self.chunks = chunks
9496

9597
def __iter__(self) -> Iterator[bytes]:
9698
for c in self.chunks:
@@ -107,6 +109,32 @@ def WriteToFile(self, file_name: str) -> None:
107109
with open(file_name, "wb") as fd:
108110
self.WriteToStream(fd)
109111

112+
def DecodeCrowdStrikeQuarantineEncoding(self) -> "BinaryChunkIterator":
113+
"""Decodes Crowdstrike quarantine file."""
114+
115+
def DecoderGenerator() -> Iterator[bytes]:
116+
for index, chunk in enumerate(self):
117+
if index == 0:
118+
if len(chunk) < 12:
119+
raise ValueError(
120+
"Unsupported chunk size, chunks need to be at least 12 bytes"
121+
)
122+
123+
if chunk[0:4] != b"CSQD":
124+
raise ValueError(
125+
"File does not start with Crowdstrike quarantine identifier"
126+
)
127+
128+
# TODO: Add a check if the actual file size matches the
129+
# value in chunk[4:12].
130+
131+
# The remainder of the first chunk belongs to the actual file.
132+
chunk = chunk[12:]
133+
134+
yield Xor(chunk, 0x7E)
135+
136+
return BinaryChunkIterator(DecoderGenerator())
137+
110138

111139
# Default poll interval in seconds.
112140
DEFAULT_POLL_INTERVAL: int = 15
@@ -274,6 +302,11 @@ def Recurse(msg: message.Message, prev: Tuple[str, ...]) -> None:
274302
return result
275303

276304

305+
def Xor(bytestr: bytes, key: int) -> bytes:
306+
"""Returns a `bytes` object where each byte has been xored with key."""
307+
return bytes([byte ^ key for byte in bytestr])
308+
309+
277310
def RegisterProtoDescriptors(
278311
db: symbol_database.SymbolDatabase,
279312
*additional_descriptors: descriptor.FileDescriptor,

api_client/python/grr_api_client/utils_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#!/usr/bin/env python
2+
import io
3+
import struct
4+
25
from absl.testing import absltest
36

47
from google.protobuf import empty_pb2
@@ -53,5 +56,45 @@ def testTransform(self):
5356
self.assertEqual(dct, {"value": 1337 * 2})
5457

5558

59+
class GetCrowdstrikeDecodedBlobTest(absltest.TestCase):
60+
61+
def _CrowdstrikeEncode(self, data) -> bytes:
62+
buf = io.BytesIO()
63+
buf.write(b"CSQD")
64+
buf.write(struct.pack("<Q", len(data)))
65+
buf.write(utils.Xor(data, 0x7E))
66+
return buf.getvalue()
67+
68+
def testDecodeSingleChunk(self):
69+
content = b"foobarbaz"
70+
iterator_content = iter((self._CrowdstrikeEncode(content),))
71+
encoded_content = utils.BinaryChunkIterator(iterator_content)
72+
73+
decoded = encoded_content.DecodeCrowdStrikeQuarantineEncoding()
74+
self.assertIsInstance(decoded, utils.BinaryChunkIterator)
75+
self.assertEqual(b"".join(decoded), content)
76+
77+
def testDecode_RaisesIfCrowdstrikeIdentifierBytesMissing(self):
78+
content = b"foobarbazbang"
79+
iterator_content = iter((content,))
80+
encoded_content = utils.BinaryChunkIterator(iterator_content)
81+
82+
with self.assertRaises(ValueError):
83+
list(encoded_content.DecodeCrowdStrikeQuarantineEncoding())
84+
85+
def testDecodeSeveralChunks(self):
86+
content = b"ABC" * 1024
87+
encoded_content = self._CrowdstrikeEncode(content)
88+
first_chunk = encoded_content[0:1024]
89+
second_chunk = encoded_content[1024:2048]
90+
third_chunk = encoded_content[2048:]
91+
encoded_content = utils.BinaryChunkIterator(
92+
iter((first_chunk, second_chunk, third_chunk))
93+
)
94+
95+
decoded = encoded_content.DecodeCrowdStrikeQuarantineEncoding()
96+
self.assertEqual(b"".join(decoded), content)
97+
98+
5699
if __name__ == "__main__":
57100
absltest.main()

api_client/python/grr_api_client/yara.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
#!/usr/bin/env python
22
"""A module with YARA convenience wrappers for the GRR API."""
33

4-
from typing import Text
5-
64
from grr_api_client import context as api_context
75
from grr_response_proto.api import yara_pb2
86

97

108
def UploadYaraSignature(
11-
signature: Text,
9+
signature: str,
1210
context: api_context.GrrApiContext,
1311
) -> bytes:
1412
"""Uploads the specified YARA signature.

0 commit comments

Comments
 (0)