Compare commits

...

12 Commits

Author SHA1 Message Date
FORCHA PEARL 85d7d96383
Merge 4b89887f4b into cbd41b2aff 2024-04-23 22:53:30 +02:00
Rory Flynn cbd41b2aff
Mark 2.5.3 release (#490) 2024-04-19 17:23:55 +02:00
Rory Flynn 0509eaa162
Use backwards-compatible datetime.timezone.utc (#488) 2024-04-19 17:22:10 +02:00
Rory Flynn 59e6dff1e1
Fail builds on test failure (#489)
* Fail builds on test failure

* Deliberately fail a build to test

* Revert "Deliberately fail a build to test"

This reverts commit 666140a954.
2024-04-19 17:18:27 +02:00
Rory Flynn f1821d1a02
Mark release 2.5.2 (#486) 2024-04-18 16:53:41 +02:00
Rory Flynn 6c7ad0ac95
Convert timezone-aware datetimes automatically to UTC (#485) 2024-04-18 16:49:30 +02:00
tek 3a997d30d2 Updates SMS module to highlight new text of Apple notifications 2024-04-15 23:28:36 +02:00
tek 6f56939dd7 Requires latest cryptography version 2024-04-15 22:41:01 +02:00
Donncha Ó Cearbhaill 7a4946e2c6
Mark release 2.5.1 (#481)
Signed-off-by: Donncha Ó Cearbhaill <donncha.ocearbhaill@amnesty.org>
2024-04-11 11:14:42 +02:00
r-tx e1c4f4eb7a
Add more short urls (#479)
Co-authored-by: r-tx <r-tx@users.noreply.github.com>
2024-04-11 11:08:15 +02:00
Donncha Ó Cearbhaill f9d7b550dc
Add docs explaining how to seek expert help for forensic analysis (#476)
* Update forensic support links in the documentation

* Add expert help message to MVT output

* Add warning to disable ADB after an Android acquisition

* Include Developer Options in the ADB warning text
2024-04-08 18:47:59 +02:00
FORCHA 4b89887f4b
Fixed docker warnings
Removed empty lines within RUN command
2023-08-04 18:29:46 +01:00
11 changed files with 146 additions and 49 deletions

View File

@ -40,7 +40,9 @@ jobs:
- name: Safety checks - name: Safety checks
run: safety check run: safety check
- name: Test with pytest and coverage - name: Test with pytest and coverage
run: pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt run: |
set -o pipefail
pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
- name: Pytest coverage comment - name: Pytest coverage comment
continue-on-error: true # Workflows running on a fork can't post comments continue-on-error: true # Workflows running on a fork can't post comments
uses: MishaKav/pytest-coverage-comment@main uses: MishaKav/pytest-coverage-comment@main

View File

@ -13,7 +13,6 @@ ENV DEBIAN_FRONTEND=noninteractive
# ---------------------------- # ----------------------------
RUN apt update \ RUN apt update \
&& apt install -y python3 python3-pip libusb-1.0-0-dev wget unzip default-jre-headless adb \ && apt install -y python3 python3-pip libusb-1.0-0-dev wget unzip default-jre-headless adb \
# Install build tools for libimobiledevice # Install build tools for libimobiledevice
# ---------------------------------------- # ----------------------------------------
build-essential \ build-essential \
@ -27,7 +26,6 @@ RUN apt update \
libssl-dev \ libssl-dev \
sqlite3 \ sqlite3 \
pkg-config \ pkg-config \
# Clean up # Clean up
# -------- # --------
&& apt-get clean \ && apt-get clean \
@ -41,17 +39,11 @@ RUN git clone https://github.com/libimobiledevice/libplist \
&& git clone https://github.com/libimobiledevice/libusbmuxd \ && git clone https://github.com/libimobiledevice/libusbmuxd \
&& git clone https://github.com/libimobiledevice/libimobiledevice \ && git clone https://github.com/libimobiledevice/libimobiledevice \
&& git clone https://github.com/libimobiledevice/usbmuxd \ && git clone https://github.com/libimobiledevice/usbmuxd \
&& cd libplist && ./autogen.sh && make && make install && ldconfig \ && cd libplist && ./autogen.sh && make && make install && ldconfig \
&& cd ../libimobiledevice-glue && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --prefix=/usr && make && make install && ldconfig \ && cd ../libimobiledevice-glue && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --prefix=/usr && make && make install && ldconfig \
&& cd ../libusbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh && make && make install && ldconfig \ && cd ../libusbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh && make && make install && ldconfig \
&& cd ../libimobiledevice && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --enable-debug && make && make install && ldconfig \ && cd ../libimobiledevice && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --enable-debug && make && make install && ldconfig \
&& cd ../usbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --runstatedir=/run && make && make install \ && cd ../usbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --runstatedir=/run && make && make install \
# Clean up. # Clean up.
&& cd .. && rm -rf libplist libimobiledevice-glue libusbmuxd libimobiledevice usbmuxd && cd .. && rm -rf libplist libimobiledevice-glue libusbmuxd libimobiledevice usbmuxd

View File

@ -26,7 +26,7 @@ MVT supports using public [indicators of compromise (IOCs)](https://github.com/m
> >
> Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence. > Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence.
> >
>Such support is available to civil society through [Amnesty International's Security Lab](https://www.amnesty.org/en/tech/) or through our forensic partnership with [Access Nows Digital Security Helpline](https://www.accessnow.org/help/). >Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/get-help/?c=mvt_docs) or through our forensic partnership with [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
More information about using indicators of compromise with MVT is available in the [documentation](https://docs.mvt.re/en/latest/iocs/). More information about using indicators of compromise with MVT is available in the [documentation](https://docs.mvt.re/en/latest/iocs/).

View File

@ -21,7 +21,7 @@ MVT supports using [indicators of compromise (IOCs)](https://github.com/mvt-proj
Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence. Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence.
Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/contact-us/) or [Access Nows Digital Security Helpline](https://www.accessnow.org/help/). Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/get-help/?c=mvt_docs) or [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
More information about using indicators of compromise with MVT is available in the [documentation](iocs.md). More information about using indicators of compromise with MVT is available in the [documentation](iocs.md).

View File

@ -160,6 +160,27 @@ class Command:
def finish(self) -> None: def finish(self) -> None:
raise NotImplementedError raise NotImplementedError
def _show_disable_adb_warning(self) -> None:
"""Warn if ADB is enabled"""
if type(self).__name__ in ["CmdAndroidCheckADB", "CmdAndroidCheckAndroidQF"]:
self.log.info(
"Please disable Developer Options and ADB (Android Debug Bridge) on the device once finished with the acquisition. "
"ADB is a powerful tool which can allow unauthorized access to the device."
)
def _show_support_message(self) -> None:
support_message = "Please seek reputable expert help if you have serious concerns about a possible spyware attack. Such support is available to human rights defenders and civil society through Amnesty International's Security Lab at https://securitylab.amnesty.org/get-help/?c=mvt"
if self.detected_count == 0:
self.log.info(
f"[bold]NOTE:[/bold] Using MVT with public indicators of compromise (IOCs) [bold]WILL NOT[/bold] automatically detect advanced attacks.\n\n{support_message}",
extra={"markup": True},
)
else:
self.log.warning(
f"[bold]NOTE: Detected indicators of compromise[/bold]. Only expert review can confirm if the detected indicators are signs of an attack.\n\n{support_message}",
extra={"markup": True},
)
def run(self) -> None: def run(self) -> None:
try: try:
self.init() self.init()
@ -208,3 +229,6 @@ class Command:
self._store_timeline() self._store_timeline()
self._store_info() self._store_info()
self._show_disable_adb_warning()
self._show_support_message()

View File

@ -9,47 +9,65 @@ import requests
from tld import get_tld from tld import get_tld
SHORTENER_DOMAINS = [ SHORTENER_DOMAINS = [
"0rz.tw",
"1drv.ms", "1drv.ms",
"1link.in", "1link.in",
"1url.com", "1url.com",
"2big.at", "2big.at",
"2.gp",
"2pl.us", "2pl.us",
"2tu.us", "2tu.us",
"2ya.com", "2ya.com",
"3.ly",
"4sq.com",
"4url.cc", "4url.cc",
"6url.com", "6url.com",
"a.gg", "7.ly",
"a.nf",
"a2a.me", "a2a.me",
"abbrr.com", "abbrr.com",
"adf.ly", "adf.ly",
"adjix.com", "adjix.com",
"a.gg",
"alturl.com", "alturl.com",
"a.nf",
"anon.to",
"apple.news",
"atu.ca", "atu.ca",
"b23.ru", "b23.ru",
"bacn.me", "bacn.me",
"bc.vc",
"bfy.tw",
"binged.it",
"bit.do", "bit.do",
"bit.ly", "bit.ly",
"bizj.us",
"bkite.com", "bkite.com",
"bloat.me", "bloat.me",
"budurl.com", "budurl.com",
"buff.ly", "buff.ly",
"buk.me", "buk.me",
"burnurl.com", "burnurl.com",
"c-o.in",
"chilp.it", "chilp.it",
"chn.ge",
"clck.ru", "clck.ru",
"cli.gs",
"clickmeter.com", "clickmeter.com",
"cli.gs",
"c-o.in",
"cort.as", "cort.as",
"cut.ly", "cut.ly",
"cutt.ly",
"cuturl.com", "cuturl.com",
"decenturl.com", "dai.ly",
"dailym.ai",
"db.tt",
"decenturl.com", "decenturl.com",
"dfl8.me", "dfl8.me",
"digbig.com", "digbig.com",
"digg.com", "digg.com",
"disq.us",
"dlvr.it",
"doiop.com", "doiop.com",
"do.my",
"dwarfurl.com", "dwarfurl.com",
"dy.fi", "dy.fi",
"easyuri.com", "easyuri.com",
@ -58,27 +76,35 @@ SHORTENER_DOMAINS = [
"esyurl.com", "esyurl.com",
"ewerl.com", "ewerl.com",
"fa.b", "fa.b",
"ff.im", "fa.by",
"fb.me",
"fff.to", "fff.to",
"ff.im",
"fhurl.com", "fhurl.com",
"fire.to", "fire.to",
"firsturl.de", "firsturl.de",
"firsturl.net",
"flic.kr", "flic.kr",
"flq.us",
"fly2.ws", "fly2.ws",
"fon.gs", "fon.gs",
"forms.gle", "forms.gle",
"fwd4.me", "fwd4.me",
"gdurl.com",
"gg.gg",
"gl.am", "gl.am",
"go.9nl.com",
"go2.me",
"go2cut.com", "go2cut.com",
"go2.me",
"go.9nl.com",
"goo.gl", "goo.gl",
"goshrink.com", "goshrink.com",
"got.by",
"gowat.ch", "gowat.ch",
"gri.ms", "gri.ms",
"gurl.es", "gurl.es",
"hellotxt.com", "hellotxt.com",
"hex.io", "hex.io",
"hongkiat.shorturl.com",
"hover.com", "hover.com",
"href.in", "href.in",
"ht.ly", "ht.ly",
@ -87,13 +113,15 @@ SHORTENER_DOMAINS = [
"hurl.it", "hurl.it",
"hurl.me", "hurl.me",
"hurl.ws", "hurl.ws",
"ibb.co",
"icanhaz.com", "icanhaz.com",
"idek.net", "idek.net",
"inreply.to", "inreply.to",
"is.gd",
"iscool.net", "iscool.net",
"is.gd",
"iterasi.net", "iterasi.net",
"jijr.com", "jijr.com",
"j.mp",
"jmp2.net", "jmp2.net",
"just.as", "just.as",
"kissa.be", "kissa.be",
@ -101,21 +129,23 @@ SHORTENER_DOMAINS = [
"klck.me", "klck.me",
"korta.nu", "korta.nu",
"krunchd.com", "krunchd.com",
"lat.ms",
"liip.to", "liip.to",
"liltext.com", "liltext.com",
"lin.cr", "lin.cr",
"linkbee.com", "linkbee.com",
"linkbun.ch", "linkbun.ch",
"liurl.cn", "liurl.cn",
"ln-s.net", "lnkd.in",
"ln-s.ru",
"lnk.gd", "lnk.gd",
"lnk.in", "lnk.in",
"lnkd.in", "ln-s.net",
"ln-s.ru",
"loopt.us", "loopt.us",
"lru.jp", "lru.jp",
"lt.tl", "lt.tl",
"lurl.no", "lurl.no",
"lyhyt.eu",
"metamark.net", "metamark.net",
"migre.me", "migre.me",
"minilien.com", "minilien.com",
@ -123,52 +153,71 @@ SHORTENER_DOMAINS = [
"minurl.fr", "minurl.fr",
"moourl.com", "moourl.com",
"myurl.in", "myurl.in",
"nbcnews.to",
"ne1.net", "ne1.net",
"njx.me", "njx.me",
"nn.nf", "nn.nf",
"notlong.com", "notlong.com",
"n.pr",
"nsfw.in", "nsfw.in",
"o-x.fr", "nyti.ms",
"om.ly", "om.ly",
"onforb.es",
"on.mktw.net",
"ow.ly", "ow.ly",
"o-x.fr",
"pca.st",
"pd.am", "pd.am",
"pic.gd", "pic.gd",
"ping.fm", "ping.fm",
"piurl.com", "piurl.com",
"pnt.me", "pnt.me",
"politi.co",
"poprl.com", "poprl.com",
"post.ly",
"posted.at", "posted.at",
"post.ly",
"profile.to", "profile.to",
"q.gs",
"qicute.com", "qicute.com",
"qlnk.net", "qlnk.net",
"qr.ae",
"qte.me",
"quip-art.com", "quip-art.com",
"rb6.me", "rb6.me",
"rb.gy",
"read.bi",
"redir.ec",
"redirx.com", "redirx.com",
"ri.ms", "redr.me",
"reut.rs",
"rickroll.it", "rickroll.it",
"r.im",
"ri.ms",
"riz.gd", "riz.gd",
"rsmonkey.com", "rsmonkey.com",
"ru.ly",
"rubyurl.com", "rubyurl.com",
"ru.ly",
"s7y.us", "s7y.us",
"safe.mn", "safe.mn",
"sharein.com", "sharein.com",
"sharetabs.com", "sharetabs.com",
"shorl.com", "shorl.com",
"short.ie", "short.ie",
"short.to",
"shortlinks.co.uk", "shortlinks.co.uk",
"shortna.me", "shortna.me",
"short.to",
"shorturl.at",
"shorturl.com", "shorturl.com",
"shoturl.us", "shoturl.us",
"shout.to",
"shrinkify.com", "shrinkify.com",
"shrinkster.com", "shrinkster.com",
"shrt.st",
"shrten.com", "shrten.com",
"shrt.st",
"shrunkin.com", "shrunkin.com",
"shw.me", "shw.me",
"simurl.com", "simurl.com",
"smsh.me",
"sn.im", "sn.im",
"snipr.com", "snipr.com",
"snipurl.com", "snipurl.com",
@ -179,24 +228,30 @@ SHORTENER_DOMAINS = [
"starturl.com", "starturl.com",
"sturly.com", "sturly.com",
"su.pr", "su.pr",
"t.cn",
"t.co", "t.co",
"tcrn.ch", "tcrn.ch",
"tgr.ph",
"thrdl.es", "thrdl.es",
"tighturl.com", "tighturl.com",
"tiny.cc",
"tiny.pl",
"tiny123.com", "tiny123.com",
"tinyarro.ws", "tinyarro.ws",
"tiny.cc",
"tinylink.in",
"tiny.pl",
"tiny.tw",
"tinytw.it", "tinytw.it",
"tinyuri.ca", "tinyuri.ca",
"tinyurl.com", "tinyurl.com",
"tinyvid.io", "tinyvid.io",
"t.me",
"tnij.org", "tnij.org",
"to.ly", "tnw.to",
"togoto.us", "togoto.us",
"to.ly",
"traceurl.com",
"tr.im", "tr.im",
"tr.my", "tr.my",
"traceurl.com",
"turo.us", "turo.us",
"tweetburner.com", "tweetburner.com",
"twirl.at", "twirl.at",
@ -206,49 +261,62 @@ SHORTENER_DOMAINS = [
"twiturl.de", "twiturl.de",
"twurl.cc", "twurl.cc",
"twurl.nl", "twurl.nl",
"u.mavrev.com",
"u.nu",
"u6e.de", "u6e.de",
"ub0.cc", "ub0.cc",
"ukl.me.uk",
"u.mavrev.com",
"u.nu",
"updating.me", "updating.me",
"ur1.ca", "ur1.ca",
"url.co.uk",
"url.ie",
"url4.eu", "url4.eu",
"urlao.com", "urlao.com",
"urlbrief.com", "urlbrief.com",
"url.co.uk",
"urlcover.com", "urlcover.com",
"urlcut.com", "urlcut.com",
"urlenco.de", "urlenco.de",
"urlhawk.com", "urlhawk.com",
"url.ie",
"urlkiss.com", "urlkiss.com",
"urlot.com", "urlot.com",
"urlpire.com", "urlpire.com",
"urlx.ie", "urlx.ie",
"urlx.org", "urlx.org",
"urlzen.com", "urlzen.com",
"use.my",
"u.to",
"v.gd",
"virl.com", "virl.com",
"vl.am", "vl.am",
"vurl.com",
"vzturl.com",
"w3t.org", "w3t.org",
"wapo.st",
"wapurl.co.uk", "wapurl.co.uk",
"wipi.es", "wipi.es",
"wp.me", "wp.me",
"x.co",
"x.se",
"xaddr.com", "xaddr.com",
"x.co",
"xeeurl.com", "xeeurl.com",
"xr.com", "xr.com",
"xrl.in", "xrl.in",
"xrl.us", "xrl.us",
"x.se",
"xurl.es",
"xurl.jp", "xurl.jp",
"xzb.cc", "xzb.cc",
"ye.pe",
"yep.it", "yep.it",
"yfrog.com", "yfrog.com",
"yhoo.it",
"ymlp.com", "ymlp.com",
"yuarel.com",
"yweb.com", "yweb.com",
"zi.ma", "zi.ma",
"zi.pe", "zi.pe",
"zipmyurl.com", "zipmyurl.com",
"zurl.to",
"zurl.ws",
"zz.gd", "zz.gd",
] ]

View File

@ -53,20 +53,20 @@ def convert_chrometime_to_datetime(timestamp: int) -> datetime.datetime:
def convert_datetime_to_iso(date_time: datetime.datetime) -> str: def convert_datetime_to_iso(date_time: datetime.datetime) -> str:
"""Converts datetime to ISO string. """Converts datetime to ISO string.
:param datetime: datetime. :param datetime: datetime, naive or timezone aware
:type datetime: datetime.datetime :type datetime: datetime.datetime
:returns: ISO datetime string in YYYY-mm-dd HH:MM:SS.ms format. :returns: ISO datetime string in YYYY-mm-dd HH:MM:SS.ms format.
:rtype: str :rtype: str
""" """
try: if date_time.tzinfo:
return date_time.strftime("%Y-%m-%d %H:%M:%S.%f") # Timezone aware object - convert to UTC
except Exception: date_time = date_time.astimezone(tz=datetime.timezone.utc)
return "" return date_time.strftime("%Y-%m-%d %H:%M:%S.%f")
def convert_unix_to_utc_datetime( def convert_unix_to_utc_datetime(
timestamp: Union[int, float, str] timestamp: Union[int, float, str],
) -> datetime.datetime: ) -> datetime.datetime:
"""Converts a unix epoch timestamp to UTC datetime. """Converts a unix epoch timestamp to UTC datetime.

View File

@ -3,4 +3,4 @@
# Use of this software is governed by the MVT License 1.1 that can be found at # Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
MVT_VERSION = "2.5.0" MVT_VERSION = "2.5.3"

View File

@ -66,8 +66,11 @@ class SMS(IOSExtraction):
def check_indicators(self) -> None: def check_indicators(self) -> None:
for message in self.results: for message in self.results:
alert = "ALERT: State-sponsored attackers may be targeting your iPhone" alert_old = "ALERT: State-sponsored attackers may be targeting your iPhone"
if message.get("text", "").startswith(alert): alert_new = "ALERT: Apple detected a targeted mercenary spyware attack against your iPhone"
if message.get("text", "").startswith(alert_old) or message.get(
"text", ""
).startswith(alert_new):
self.log.warning( self.log.warning(
"Apple warning about state-sponsored attack received on the %s", "Apple warning about state-sponsored attack received on the %s",
message["isodate"], message["isodate"],

View File

@ -31,7 +31,7 @@ install_requires =
iOSbackup >=0.9.923 iOSbackup >=0.9.923
adb-shell >=0.4.3 adb-shell >=0.4.3
libusb1 >=3.0.0 libusb1 >=3.0.0
cryptography >=38.0.1 cryptography >=42.0.5
pyyaml >=6.0 pyyaml >=6.0
pyahocorasick >= 2.0.0 pyahocorasick >= 2.0.0

View File

@ -42,6 +42,14 @@ class TestDateConversions:
converted = convert_unix_to_utc_datetime(TEST_DATE_EPOCH) converted = convert_unix_to_utc_datetime(TEST_DATE_EPOCH)
assert convert_datetime_to_iso(converted) == TEST_DATE_ISO assert convert_datetime_to_iso(converted) == TEST_DATE_ISO
def test_convert_timezone_aware_to_iso(self):
assert (
convert_datetime_to_iso(
datetime.strptime("2024-09-30 11:21:20+0200", "%Y-%m-%d %H:%M:%S%z")
)
== "2024-09-30 09:21:20.000000"
)
class TestHashes: class TestHashes:
def test_hash_from_file(self): def test_hash_from_file(self):