diff --git a/iped-app/resources/config/conf/CategoriesConfig.json b/iped-app/resources/config/conf/CategoriesConfig.json index bdbd5eb63c..c4a8ca502e 100644 --- a/iped-app/resources/config/conf/CategoriesConfig.json +++ b/iped-app/resources/config/conf/CategoriesConfig.json @@ -89,7 +89,11 @@ {"name": "Others Chats", "mimes":["application/x-ufed-html-chats", "application/x-ufed-chats-txt", "application/x-ufed-chat", "application/x-ufed-chat-preview"]} ]}, {"name": "USN Journal", "mimes": ["application/x-usnjournal-$J", "application/x-usnjournal-report-html", "application/x-usnjournal-report-csv", "application/x-usnjournal-registry"]}, - {"name": "Programs and Libraries", "mimes": ["application/java-archive", "application/x-dosexec", "application/x-msdownload", "application/x-bat", "application/vnd.ms-cab-compressed", "application/x-font-ttf", "application/pkcs7-signature", "application/vnd.ms-htmlhelp", "application/java-vm", "application/vnd.ms-pki.seccat", "application/x-ms-installer", "application/x-ufed-html-apps", "application/x-ufed-installedapplication"]}, + {"name": "Programs and Libraries", + "mimes": ["application/java-archive", "application/x-dosexec", "application/x-msdownload", "application/x-bat", "application/vnd.ms-cab-compressed", "application/x-font-ttf", "application/pkcs7-signature", "application/vnd.ms-htmlhelp", "application/java-vm", "application/vnd.ms-pki.seccat", "application/x-ms-installer", "application/x-ufed-html-apps", "application/x-ufed-installedapplication"], + "categories":[{"name": "Android Applications", "mimes": ["application/vnd.android.package-archive"]}] + }, + {"name": "Unallocated", "mimes": ["application/x-unallocated"]}, {"name": "File Slacks", "mimes": ["application/x-fileslack"]}, {"name": "Plain Texts", "mimes": ["text"], "categories":[ diff --git a/iped-app/resources/config/conf/MakePreviewConfig.txt b/iped-app/resources/config/conf/MakePreviewConfig.txt index 271edf63b4..ac285a1553 100644 --- a/iped-app/resources/config/conf/MakePreviewConfig.txt +++ b/iped-app/resources/config/conf/MakePreviewConfig.txt @@ -7,7 +7,7 @@ supportedMimes = application/x-msaccess; application/x-lnk; application/x-firefox-savedsession supportedMimes = application/x-sqlite3; application/sqlite-skype; application/x-win10-timeline; application/x-gdrive-cloud-graph; application/x-gdrive-snapshot supportedMimes = application/x-whatsapp-db; application/x-whatsapp-db-f; application/x-whatsapp-chatstorage; application/x-whatsapp-chatstorage-f; application/x-shareaza-searches-dat; application/x-msie-cache -supportedMimes = application/x-prefetch; text/x-vcard; application/x-bittorrent-resume-dat; application/x-bittorrent; application/x-emule-preferences-dat +supportedMimes = application/x-prefetch; text/x-vcard; application/x-bittorrent-resume-dat; application/x-bittorrent; application/x-emule-preferences-dat; application/vnd.android.package-archive # List of mimetypes which parsers insert links to other case items into preview supportedMimesWithLinks = application/x-emule; application/x-emule-part-met; application/x-ares-galaxy; application/x-shareaza-library-dat \ No newline at end of file diff --git a/iped-app/resources/config/conf/ParserConfig.xml b/iped-app/resources/config/conf/ParserConfig.xml index e613913f76..28a97f5cb2 100644 --- a/iped-app/resources/config/conf/ParserConfig.xml +++ b/iped-app/resources/config/conf/ParserConfig.xml @@ -361,6 +361,7 @@ + diff --git a/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml b/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml index 1c80e087ea..c532fb0842 100644 --- a/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml +++ b/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml @@ -348,6 +348,7 @@ + diff --git a/iped-app/resources/localization/iped-categories.properties b/iped-app/resources/localization/iped-categories.properties index f2c6123d11..8e94dac9a1 100644 --- a/iped-app/resources/localization/iped-categories.properties +++ b/iped-app/resources/localization/iped-categories.properties @@ -56,6 +56,7 @@ Registry\ User\ Communication=Registry\ User\ Communication Windows\ Recycle=Windows\ Recycle USN\ Journal=USN\ Journal Programs\ and\ Libraries=Programs\ and\ Libraries +Android\ Applications=Android\ Applications Unallocated=Unallocated File\ Slacks=File\ Slacks Multimedia=Multimedia diff --git a/iped-app/resources/localization/iped-categories_de_DE.properties b/iped-app/resources/localization/iped-categories_de_DE.properties index cc006869f2..35f48aa332 100644 --- a/iped-app/resources/localization/iped-categories_de_DE.properties +++ b/iped-app/resources/localization/iped-categories_de_DE.properties @@ -56,6 +56,7 @@ Registry\ User\ Communication=Registry\ Benutzerkommunikation Windows\ Recycle=Windows\ Papierkorb USN\ Journal=USN\ Journal Programs\ and\ Libraries=Programme\ und\ Bibliotheken +Android\ Applications=Android\ Applications[TBT] Unallocated=nicht\ zugeordnet File\ Slacks=Datei-Slack Multimedia=Multimedia diff --git a/iped-app/resources/localization/iped-categories_es_AR.properties b/iped-app/resources/localization/iped-categories_es_AR.properties index e375df0e33..38741b754a 100644 --- a/iped-app/resources/localization/iped-categories_es_AR.properties +++ b/iped-app/resources/localization/iped-categories_es_AR.properties @@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ Comunicaciones\ Usuario Windows\ Recycle=Windows\ Papalera de Reciclaje USN\ Journal=USN\ Diario Programs\ and\ Libraries=Programas\ y\ Bibliotecas +Android\ Applications=Android\ Applications[TBT] Unallocated=Sin Asignar File\ Slacks=Archivos\ Slacks Multimedia=Multimedia diff --git a/iped-app/resources/localization/iped-categories_it_IT.properties b/iped-app/resources/localization/iped-categories_it_IT.properties index 77466831c0..4e26f01ea1 100644 --- a/iped-app/resources/localization/iped-categories_it_IT.properties +++ b/iped-app/resources/localization/iped-categories_it_IT.properties @@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ Comunicazioni\ Utente Windows\ Recycle=Cestino\ di\ Windows USN\ Journal=USN\ Journal Programs\ and\ Libraries=Software\ e\ Librerie +Android\ Applications=Android\ Applications[TBT] Unallocated=Spazio\ non\ allocato File\ Slacks=File\ Slack Multimedia=Multimedia diff --git a/iped-app/resources/localization/iped-categories_pt_BR.properties b/iped-app/resources/localization/iped-categories_pt_BR.properties index 9ea30539bb..959d25913b 100644 --- a/iped-app/resources/localization/iped-categories_pt_BR.properties +++ b/iped-app/resources/localization/iped-categories_pt_BR.properties @@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ -\ Comunicação\ de\ Usuário Windows\ Recycle=Lixeira\ do\ Windows USN\ Journal=Journal\ USN Programs\ and\ Libraries=Programas\ e\ Bibliotecas +Android\ Applications=Aplicativos\ Android Unallocated=Não\ Alocado File\ Slacks=File\ Slacks Multimedia=Multimídia diff --git a/iped-app/resources/localization/iped-parsers-messages.properties b/iped-app/resources/localization/iped-parsers-messages.properties index 388bc9b039..5f67d0b566 100644 --- a/iped-app/resources/localization/iped-parsers-messages.properties +++ b/iped-app/resources/localization/iped-parsers-messages.properties @@ -318,4 +318,19 @@ TelegramReport.joinedByRequest=User joined by Request TelegramReport.ChannelMigratedFromGroup=This channel migrated from a group TelegramReport.RecoveredGroup=Recovered deleted group P2P.FoundInPedoHashDB=* Red lines mean the hashes were found in child porn alert hash databases. -Win10Mail.NotFound=Not Found \ No newline at end of file +Win10Mail.NotFound=Not Found +APKParser.Permissions=Permissions Required +APKParser.Manifest=Manifest XML +APKParser.Package=Package +APKParser.Version=Version +APKParser.SDKVersion=SDK Version +APKParser.Features=Features +APKParser.Signers=Signers +APKParser.SignersV2=Signers(V2) +APKParser.Path=Path +APKParser.Certificate=Certificate +APKParser.Algorithm=Algorithm +APKParser.MD5=MD5 +APKParser.OID=OID +APKParser.StartDate=Start Date +APKParser.EndDate=End Date diff --git a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties index c75b7e1e40..3db261a758 100644 --- a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties +++ b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties @@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Dieser Kanal ist aus einer Gruppe hervor TelegramReport.RecoveredGroup=wiederhergestellte gelöschte Gruppe P2P.FoundInPedoHashDB=* Rote Zeile bedeutet, dass der Hash in der KiPo Hash-Datenbank gefunden wurde. Win10Mail.NotFound=Nicht gefunden +APKParser.Permissions=Permissions Required(TBT) +APKParser.Manifest=Manifest XML(TBT) +APKParser.Package=Package(TBT) +APKParser.Version=Version(TBT) +APKParser.SDKVersion=SDK Version(TBT) +APKParser.Features=Features(TBT) +APKParser.Signers=Signers(TBT) +APKParser.SignersV2=Signers(V2)(TBT) +APKParser.Path=Path(TBT) +APKParser.Certificate=Certificate(TBT) +APKParser.Algorithm=Algorithm(TBT) +APKParser.MD5=MD5(TBT) +APKParser.OID=OID(TBT) +APKParser.StartDate=Start Date(TBT) +APKParser.EndDate=End Date(TBT) \ No newline at end of file diff --git a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties index c0596fe8cb..7525d9843e 100644 --- a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties +++ b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties @@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Este canal ha migrado desde un grupo TelegramReport.RecoveredGroup=Grupo borrado recuperado P2P.FoundInPedoHashDB=* Las líneas rojas significan que los hashtags se encontraron en bases de datos de hashtags de alertas de pornografía infantil. Win10Mail.NotFound=No encontrado +APKParser.Permissions=Permissions Required(TBT) +APKParser.Manifest=Manifest XML(TBT) +APKParser.Package=Package(TBT) +APKParser.Version=Version(TBT) +APKParser.SDKVersion=SDK Version(TBT) +APKParser.Features=Features(TBT) +APKParser.Signers=Signers(TBT) +APKParser.SignersV2=Signers(V2)(TBT) +APKParser.Path=Path(TBT) +APKParser.Certificate=Certificate(TBT) +APKParser.Algorithm=Algorithm(TBT) +APKParser.MD5=MD5(TBT) +APKParser.OID=OID(TBT) +APKParser.StartDate=Start Date(TBT) +APKParser.EndDate=End Date(TBT) \ No newline at end of file diff --git a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties index db8a50e075..65b14bcddc 100644 --- a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties +++ b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties @@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Questo canale è migrato da un gruppo TelegramReport.RecoveredGroup=Gruppo cancellato recuperato P2P.FoundInPedoHashDB=* Le linee rosse indicano che gli hash sono stati trovati nel child porn alert hash databases. Win10Mail.NotFound=Non trovato +APKParser.Permissions=Permissions Required(TBT) +APKParser.Manifest=Manifest XML(TBT) +APKParser.Package=Package(TBT) +APKParser.Version=Version(TBT) +APKParser.SDKVersion=SDK Version(TBT) +APKParser.Features=Features(TBT) +APKParser.Signers=Signers(TBT) +APKParser.SignersV2=Signers(V2)(TBT) +APKParser.Path=Path(TBT) +APKParser.Certificate=Certificate(TBT) +APKParser.Algorithm=Algorithm(TBT) +APKParser.MD5=MD5(TBT) +APKParser.OID=OID(TBT) +APKParser.StartDate=Start Date(TBT) +APKParser.EndDate=End Date(TBT) diff --git a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties index 7826525f39..68a0cb587e 100644 --- a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties +++ b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties @@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Este canal migrou de um grupo TelegramReport.RecoveredGroup=Grupo apagado recuperado P2P.FoundInPedoHashDB=* Linhas em vermelho indicam que os hashes foram encontrados em bases de hashes de alerta de pornografia infantil Win10Mail.NotFound=Não Encontrado +APKParser.Permissions=Permissões requeridas +APKParser.Manifest=Manifest XML +APKParser.Package=Pacote +APKParser.Version=Versão +APKParser.SDKVersion=Versão do SDK +APKParser.Features=Características +APKParser.Signers=Assinaturas +APKParser.SignersV2=Assinaturas(V2) +APKParser.Path=Caminho +APKParser.Certificate=Certificado +APKParser.Algorithm=Algorítmo +APKParser.MD5=MD5 +APKParser.OID=OID +APKParser.StartDate=Início +APKParser.EndDate=Válido até diff --git a/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png b/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png new file mode 100644 index 0000000000..79011b0445 Binary files /dev/null and b/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png differ diff --git a/iped-parsers/iped-parsers-impl/pom.xml b/iped-parsers/iped-parsers-impl/pom.xml index 278c7d4f45..a7ede2343f 100644 --- a/iped-parsers/iped-parsers-impl/pom.xml +++ b/iped-parsers/iped-parsers-impl/pom.xml @@ -232,6 +232,11 @@ RagingMoose c92dba11561cb1423dd81e3bb9dea8ae92a392d3 + + net.dongliu + apk-parser + 2.6.10 + diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java new file mode 100644 index 0000000000..fd94bf6f6a --- /dev/null +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java @@ -0,0 +1,304 @@ +package iped.parsers.apk; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; + +import javax.imageio.ImageIO; + +import org.apache.tika.exception.TikaException; +import org.apache.tika.extractor.EmbeddedDocumentExtractor; +import org.apache.tika.extractor.ParsingEmbeddedDocumentExtractor; +import org.apache.tika.io.TemporaryResources; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.HttpHeaders; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; +import org.apache.tika.mime.MediaType; +import org.apache.tika.parser.AbstractParser; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.sax.XHTMLContentHandler; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +import iped.parsers.util.Messages; +import iped.parsers.whatsapp.Util; +import iped.utils.IOUtil; +import iped.utils.ImageUtil; +import net.dongliu.apk.parser.bean.ApkMeta; +import net.dongliu.apk.parser.bean.ApkSigner; +import net.dongliu.apk.parser.bean.ApkV2Signer; +import net.dongliu.apk.parser.bean.CertificateMeta; +import net.dongliu.apk.parser.bean.Icon; +import net.dongliu.apk.parser.bean.IconFace; +import net.dongliu.apk.parser.bean.UseFeature; + +public class APKParser extends AbstractParser { + private static final long serialVersionUID = 8308661247390527209L; + private static final MediaType apkMimeType = MediaType.application("vnd.android.package-archive"); + public static final Set SUPPORTED_TYPES = Collections.singleton(apkMimeType); + + @Override + public Set getSupportedTypes(ParseContext context) { + return SUPPORTED_TYPES; + } + + @Override + public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context) + throws IOException, SAXException, TikaException { + + TemporaryResources tmp = new TemporaryResources(); + CustomApkFile apkFile = null; + XHTMLContentHandler xhtml = null; + File tmpFile = null; + EmbeddedDocumentExtractor extractor = context.get(EmbeddedDocumentExtractor.class, + new ParsingEmbeddedDocumentExtractor(context)); + + try { + TikaInputStream tis = TikaInputStream.get(stream, tmp); + tmpFile = tis.getFile(); + + apkFile = new CustomApkFile(tmpFile); + ApkMeta apkMeta = apkFile.getApkMeta(); + + metadata.set(HttpHeaders.CONTENT_TYPE, apkMimeType.toString()); + metadata.remove(TikaCoreProperties.RESOURCE_NAME_KEY); + + xhtml = new XHTMLContentHandler(handler, metadata); + xhtml.startDocument(); + + xhtml.startElement("head"); + xhtml.startElement("meta charset='UTF-8'"); + xhtml.endElement("meta"); + xhtml.endElement("head"); + + xhtml.startElement("style"); + xhtml.characters( + ".tab {border-collapse: collapse; font-family: Arial, sans-serif; margin-right: 32px; margin-bottom: 32px; } " + + "img { width: 64px; } " + + ".prop { border: solid; border-width: thin; padding: 3px; text-align: left; background-color:#EEEEEE; vertical-align: middle; white-space: nowrap; } " + + ".ico { border: solid; border-width: thin; padding: 2px; text-align: center; background-color:#BBBBBB; vertical-align: middle; } " + + ".val { border: solid; border-width: thin; padding: 3px; text-align: left; vertical-align: middle; } "); + xhtml.endElement("style"); + xhtml.newline(); + xhtml.startElement("table", "class", "tab"); + + String name = apkMeta.getName(); + if (name == null || name.isBlank()) { + name = apkMeta.getLabel(); + } + + Icon icon = null; + List iconFaces = apkFile.getAllIcons(); + for (IconFace f : iconFaces) { + if (f instanceof Icon && f.isFile() && f.getData() != null) { + if (icon == null || icon.getData().length < f.getData().length) { + icon = (Icon) f; + } + } + } + + add(xhtml, icon, name); + add(xhtml, Messages.getString("APKParser.Package"), apkMeta.getPackageName(), false); + add(xhtml, Messages.getString("APKParser.Version"), apkMeta.getVersionName(), false); + add(xhtml, Messages.getString("APKParser.SDKVersion"), apkMeta.getCompileSdkVersion(), false); + + StringBuilder sb = new StringBuilder(); + Set seenCertificates = new HashSet(); + List signers = null; + try { + signers = apkFile.getApkSingers(); + } catch (Exception e) { + } + if (signers != null && !signers.isEmpty()) { + for (ApkSigner s : signers) { + sb.append(Messages.getString("APKParser.Path") + ": ").append(s.getPath()).append("\n"); + + for (CertificateMeta m : s.getCertificateMetas()) { + if (seenCertificates.add(m.toString())) { + sb.append(formatCertificate(m)); + parseEmbeddedCertificate(m, extractor, xhtml); + } + } + } + add(xhtml, Messages.getString("APKParser.Signers"), sb.toString(), true); + } + + List signers2 = null; + try { + signers2 = apkFile.getApkV2Singers(); + } catch (Exception e) { + } + if (signers2 != null && !signers2.isEmpty()) { + List certificates = new ArrayList(); + for (ApkV2Signer s : signers2) { + for (CertificateMeta m : s.getCertificateMetas()) { + if (seenCertificates.add(m.toString())) { + certificates.add(m); + parseEmbeddedCertificate(m, extractor, xhtml); + } + } + } + if (!certificates.isEmpty()) { + sb.delete(0, sb.length()); + for (CertificateMeta m : certificates) { + sb.append(formatCertificate(m)); + } + add(xhtml, Messages.getString("APKParser.SignersV2"), sb.toString(), true); + } + } + + sb.delete(0, sb.length()); + List features = apkMeta.getUsesFeatures(); + Collections.sort(features, new Comparator() { + public int compare(UseFeature o1, UseFeature o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + for (UseFeature feature : features) { + sb.append(feature.getName()).append("\n"); + } + add(xhtml, Messages.getString("APKParser.Features"), sb.toString(), true); + + sb.delete(0, sb.length()); + List permissions = apkMeta.getUsesPermissions(); + Collections.sort(permissions); + for (String permission : permissions) { + sb.append(permission).append("\n"); + } + add(xhtml, Messages.getString("APKParser.Permissions"), sb.toString(), true); + + String manifestXml = apkFile.getManifestXml(); + add(xhtml, Messages.getString("APKParser.Manifest"), manifestXml, true); + } finally { + IOUtil.closeQuietly(apkFile); + tmp.close(); + + if (xhtml != null) { + xhtml.endElement("table"); + xhtml.endDocument(); + } + } + } + + private void parseEmbeddedCertificate(CertificateMeta m, EmbeddedDocumentExtractor extractor, ContentHandler xhtml) + throws SAXException, IOException { + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(m.getData())); + Metadata certMetadata = new Metadata(); + certMetadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, cert.getSubjectX500Principal().getName()); + extractor.parseEmbedded(new ByteArrayInputStream(m.getData()), xhtml, certMetadata, true); + } catch (CertificateException e) { + // LOGGER.warn("Invalid certificate data for APK:"+apkFile.get); + } + } + + private String iconToBase64(Icon icon) throws Exception { + BufferedImage img = ImageIO.read(new ByteArrayInputStream(icon.getData())); + int size = 128; + img = ImageUtil.resizeImage(img, size, size); + ByteArrayOutputStream out = new ByteArrayOutputStream(4096); + ImageIO.write(img, "png", out); + return Util.encodeBase64(out.toByteArray()); + } + + private void add(XHTMLContentHandler xhtml, Icon icon, String val) throws SAXException { + xhtml.startElement("tr"); + + xhtml.startElement("td", "class", "ico"); + String imgBase64 = null; + if (icon != null) { + try { + imgBase64 = iconToBase64(icon); + } catch (Exception e) { + } + } + if (imgBase64 != null) { + xhtml.startElement("img", "src", "data:image/png;base64," + imgBase64); + xhtml.endElement("img"); + } + xhtml.endElement("td"); + + xhtml.startElement("td", "class", "val"); + if (val != null) { + val = val.trim(); + } + if (val == null || val.isBlank()) { + val = "-"; + } + xhtml.startElement("b"); + xhtml.characters(val); + xhtml.endElement("b"); + xhtml.endElement("td"); + + xhtml.endElement("tr"); + xhtml.newline(); + } + + private void add(XHTMLContentHandler xhtml, String prop, String val, boolean isPre) throws SAXException { + xhtml.startElement("tr"); + xhtml.startElement("td", "class", "prop"); + xhtml.characters(prop); + xhtml.endElement("td"); + xhtml.startElement("td", "class", "val"); + if (val != null) { + val = val.trim(); + } + if (val == null || val.isBlank()) { + val = "-"; + } + if (isPre) { + xhtml.startElement("pre"); + xhtml.characters(val); + xhtml.endElement("pre"); + } else { + xhtml.characters(val); + } + xhtml.endElement("td"); + xhtml.endElement("tr"); + xhtml.newline(); + } + + private String formatCertificate(CertificateMeta m) { + String[] titles = new String[5]; + titles[0] = Messages.getString("APKParser.Algorithm"); + titles[1] = Messages.getString("APKParser.MD5"); + titles[2] = Messages.getString("APKParser.OID"); + titles[3] = Messages.getString("APKParser.StartDate"); + titles[4] = Messages.getString("APKParser.EndDate"); + int w = 0; + for (String t : titles) { + w = Math.max(w, t.length()); + } + for (int i = 0; i < titles.length; i++) { + titles[i] = String.format(" %-" + w + "s : ", titles[i]); + } + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + StringBuilder sb = new StringBuilder(); + sb.append(" ").append(Messages.getString("APKParser.Certificate")).append("\n"); + sb.append(titles[0]).append(m.getSignAlgorithm()).append("\n"); + sb.append(titles[1]).append(m.getCertMd5().toUpperCase()).append("\n"); + sb.append(titles[2]).append(m.getSignAlgorithmOID()).append("\n"); + sb.append(titles[3]).append(df.format(m.getStartDate())).append("\n"); + sb.append(titles[4]).append(df.format(m.getEndDate())).append("\n"); + return sb.toString(); + } +} diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java new file mode 100644 index 0000000000..1c25589442 --- /dev/null +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java @@ -0,0 +1,272 @@ +package iped.parsers.apk; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import iped.utils.IOUtil; +import net.dongliu.apk.parser.AbstractApkFile; +import net.dongliu.apk.parser.bean.ApkSignStatus; +import net.dongliu.apk.parser.bean.ApkV2Signer; +import net.dongliu.apk.parser.bean.CertificateMeta; +import net.dongliu.apk.parser.parser.CertificateMetas; +import net.dongliu.apk.parser.struct.signingv2.ApkSigningBlock; +import net.dongliu.apk.parser.struct.signingv2.Digest; +import net.dongliu.apk.parser.struct.signingv2.Signature; +import net.dongliu.apk.parser.struct.signingv2.SignerBlock; +import net.dongliu.apk.parser.struct.zip.EOCD; +import net.dongliu.apk.parser.utils.Buffers; +import net.dongliu.apk.parser.utils.Inputs; +import net.dongliu.apk.parser.utils.Unsigned; + +/** + * This class replaces the library net.dongliu.apk.parser.ApkFile, that uses + * MappedByteBuffer which may hold a reference to the file being parsed, until + * it is garbage collected, preventing the temporary file to be deleted. + */ +public class CustomApkFile extends AbstractApkFile implements Closeable { + + private final ZipFile zf; + private final File apkFile; + private List apkV2Signers; + + public CustomApkFile(File apkFile) throws IOException { + this.apkFile = apkFile; + this.zf = new ZipFile(apkFile); + } + + @Override + protected List getAllCertificateData() throws IOException { + Enumeration enu = zf.entries(); + List list = new ArrayList<>(); + while (enu.hasMoreElements()) { + ZipEntry ne = enu.nextElement(); + if (ne.isDirectory()) { + continue; + } + String name = ne.getName().toUpperCase(); + if (name.endsWith(".RSA") || name.endsWith(".DSA")) { + list.add(new CertificateFile(name, Inputs.readAllAndClose(zf.getInputStream(ne)))); + } + } + return list; + } + + @Override + public byte[] getFileData(String path) throws IOException { + ZipEntry entry = zf.getEntry(path); + if (entry == null) { + return null; + } + + InputStream inputStream = zf.getInputStream(entry); + return Inputs.readAllAndClose(inputStream); + } + + @Override + protected ByteBuffer fileData() throws IOException { + return null; + } + + @Override + @Deprecated + public ApkSignStatus verifyApk() throws IOException { + return null; + } + + @Override + public void close() throws IOException { + super.close(); + IOUtil.closeQuietly(zf); + } + + /** + * Create ApkSignBlockParser for this apk file. + * + * @return null if do not have sign block + */ + protected ByteBuffer findApkSignBlock() throws IOException { + long len = apkFile.length(); + if (len < 22) { + throw new RuntimeException("Not zip file"); + } + int maxEOCDSize = 1 << 18; + byte[] buf = new byte[(int) Math.min(len, maxEOCDSize)]; + try (RandomAccessFile raf = new RandomAccessFile(apkFile, "r")) { + raf.seek(len - buf.length); + raf.readFully(buf); + ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + long cdStart = -1; + for (int i = buf.length - 22; i >= 0; i--) { + int v = buffer.getInt(i); + if (v == EOCD.SIGNATURE) { + Buffers.position(buffer, i + 16); + cdStart = Buffers.readUInt(buffer); + break; + } + } + if (cdStart == -1) { + return null; + } + int magicStrLen = 16; + buf = new byte[magicStrLen]; + raf.seek(cdStart - magicStrLen); + raf.readFully(buf); + buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + String magic = Buffers.readAsciiString(buffer, magicStrLen); + if (!magic.equals(ApkSigningBlock.MAGIC)) { + return null; + } + buf = new byte[8]; + raf.seek(cdStart - 24); + raf.readFully(buf); + buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + int blockSize = Unsigned.ensureUInt(buffer.getLong()); + raf.seek(cdStart - blockSize - 8); + raf.readFully(buf); + buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + int blockSize2 = Unsigned.ensureUInt(buffer.getLong()); + if (blockSize != blockSize2) { + return null; + } + buf = new byte[blockSize - magicStrLen]; + raf.readFully(buf); + return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + } + } + + public List getApkV2Singers() throws IOException, CertificateException { + if (apkV2Signers == null) { + parseApkSigningBlock(); + } + return this.apkV2Signers; + } + + private void parseApkSigningBlock() throws IOException, CertificateException { + List list = new ArrayList<>(); + ByteBuffer apkSignBlockBuf = findApkSignBlock(); + if (apkSignBlockBuf != null) { + CustomApkSignBlockParser parser = new CustomApkSignBlockParser(apkSignBlockBuf); + ApkSigningBlock apkSigningBlock = parser.parse(); + for (SignerBlock signerBlock : apkSigningBlock.getSignerBlocks()) { + List certificates = signerBlock.getCertificates(); + List certificateMetas = CertificateMetas.from(certificates); + ApkV2Signer apkV2Signer = new ApkV2Signer(certificateMetas); + list.add(apkV2Signer); + } + } + this.apkV2Signers = list; + } +} + +/** + * This class replace ApkSignBlockParser to read more V2 signers. + */ +class CustomApkSignBlockParser { + private ByteBuffer data; + + public CustomApkSignBlockParser(ByteBuffer data) { + this.data = data.order(ByteOrder.LITTLE_ENDIAN); + } + + public ApkSigningBlock parse() throws CertificateException { + // sign block found, read pairs + List signerBlocks = new ArrayList<>(); + while (data.remaining() >= 8) { + int id = data.getInt(); + int size = Unsigned.ensureUInt(data.getInt()); + if (id == ApkSigningBlock.SIGNING_V2_ID) { + ByteBuffer signingV2Buffer = Buffers.sliceAndSkip(data, size); + // now only care about apk signing v2 entry + while (signingV2Buffer.hasRemaining()) { + SignerBlock signerBlock = readSigningV2(signingV2Buffer); + signerBlocks.add(signerBlock); + } + } else { + if (data.position() + size >= data.limit()) { + break; + } + Buffers.position(data, data.position() + size); + } + } + return new ApkSigningBlock(signerBlocks); + } + + private SignerBlock readSigningV2(ByteBuffer buffer) throws CertificateException { + buffer = readLenPrefixData(buffer); + + ByteBuffer signedData = readLenPrefixData(buffer); + ByteBuffer digestsData = readLenPrefixData(signedData); + List digests = readDigests(digestsData); + ByteBuffer certificateData = readLenPrefixData(signedData); + List certificates = readCertificates(certificateData); + ByteBuffer attributesData = readLenPrefixData(signedData); + readAttributes(attributesData); + + ByteBuffer signaturesData = readLenPrefixData(buffer); + List signatures = readSignatures(signaturesData); + + readLenPrefixData(buffer); + return new SignerBlock(digests, certificates, signatures); + } + + private List readDigests(ByteBuffer buffer) { + List list = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer digestData = readLenPrefixData(buffer); + int algorithmID = digestData.getInt(); + byte[] digest = Buffers.readBytes(digestData); + list.add(new Digest(algorithmID, digest)); + } + return list; + } + + private List readCertificates(ByteBuffer buffer) throws CertificateException { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + List certificates = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer certificateData = readLenPrefixData(buffer); + Certificate certificate = certificateFactory + .generateCertificate(new ByteArrayInputStream(Buffers.readBytes(certificateData))); + certificates.add((X509Certificate) certificate); + } + return certificates; + } + + private void readAttributes(ByteBuffer buffer) { + while (buffer.hasRemaining()) { + readLenPrefixData(buffer); + } + } + + private List readSignatures(ByteBuffer buffer) { + List signatures = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer signatureData = readLenPrefixData(buffer); + int algorithmID = signatureData.getInt(); + int signatureDataLen = Unsigned.ensureUInt(signatureData.getInt()); + byte[] signature = Buffers.readBytes(signatureData, signatureDataLen); + signatures.add(new Signature(algorithmID, signature)); + } + return signatures; + } + + private ByteBuffer readLenPrefixData(ByteBuffer buffer) { + int len = Unsigned.ensureUInt(buffer.getInt()); + return Buffers.sliceAndSkip(buffer, len); + } +}